Following this topic, I am trying to generate a 3D curved triangle as a NURBS surface, but I don't understand how to set up my 3D points to do that.
Here is the current implementation :
var edges = this.getEdges(), // An edge is a line following 4 dots as a bezier curve.
dots = self.getDotsFromEdges(edges), // Get all dots in order for building the surface.
ctrlPoints = [ // Is generated only once before, but copy-pasted here for this sample code.
[
new THREE.Vector4(0, 0, 0, 1),
new THREE.Vector4(0, 0, 0, 1),
new THREE.Vector4(0, 0, 0, 1),
new THREE.Vector4(0, 0, 0, 1)
],
[
new THREE.Vector4(0, 0, 0, 1),
new THREE.Vector4(0, 0, 0, 1),
new THREE.Vector4(0, 0, 0, 1),
new THREE.Vector4(0, 0, 0, 1)
],
[
new THREE.Vector4(0, 0, 0, 1),
new THREE.Vector4(0, 0, 0, 1),
new THREE.Vector4(0, 0, 0, 1),
new THREE.Vector4(0, 0, 0, 1)
]
],
nc,
deg1 = ctrlPoints.length - 1,
knots1 = [],
deg2 = 3, // Cubic bezier
knots2 = [0, 0, 0, 0, 1, 1, 1, 1], // <-
cpts,
nurbs ;
nc = ctrlPoints.length ;
while (nc-- > 0) knots1.push(0) ;
nc = ctrlPoints.length ;
while (nc-- > 0) knots1.push(1) ;
// The following seems to be the problem... :
cpts = ctrlPoints[0] ;
cpts[0].set(dots[0].x, dots[0].y, dots[0].z, 1) ;
cpts[1].set(dots[1].x, dots[1].y, dots[1].z, 1) ;
cpts[2].set(dots[2].x, dots[2].y, dots[2].z, 1) ;
cpts[3].set(dots[3].x, dots[3].y, dots[3].z, 1) ;
cpts = ctrlPoints[1] ;
cpts[0].set(dots[6].x, dots[6].y, dots[6].z, 1) ;
cpts[1].set(dots[5].x, dots[5].y, dots[5].z, 1) ;
cpts[2].set(dots[4].x, dots[4].y, dots[4].z, 1) ;
cpts[3].set(dots[3].x, dots[3].y, dots[3].z, 1) ;
cpts = ctrlPoints[2] ;
cpts[0].set(dots[6].x, dots[6].y, dots[6].z, 1) ;
cpts[1].set(dots[7].x, dots[7].y, dots[7].z, 1) ;
cpts[2].set(dots[8].x, dots[8].y, dots[8].z, 1) ;
cpts[3].set(dots[0].x, dots[0].y, dots[0].z, 1) ;
nurbs = new THREE.NURBSSurface(deg1, deg2, knots1, knots2, ctrlPoints) ;
this.mesh.geometry.dispose() ;
this.mesh.geometry = new THREE.ParametricBufferGeometry(function(u, v, target) {
return nurbs.getPoint(u, v, target) ;
}, 10, 10) ;
And here is the result:
I tried many different settings but can't find any working well.
Note: The white points are the edges ends ; The red points are the bezier curve middle points.
Note 2: dots[0]
refers to the point 0
in the sample picture, and so on.
Here is working snippet (and fiddle version here)
const
PI = Math.PI,
sin = Math.sin,
cos = Math.cos,
W = 480,
H = 400,
log = console.log,
DISTANCE = 100 ;
let renderer = new THREE.WebGLRenderer({
canvas : document.querySelector('canvas'),
antialias : true,
alpha : true
}),
camera = new THREE.PerspectiveCamera(25, W/H),
scene = new THREE.Scene(),
center = new THREE.Vector3(0, 0, 0),
pts = [] ;
renderer.setClearColor(0x000000, 0) ;
renderer.setSize(W, H) ;
// camera.position.set(-48, 32, 80) ;
camera.position.set(0, 0, DISTANCE) ;
camera.lookAt(center) ;
function createPoint(x, y, z, color) {
let pt = new THREE.Mesh(
new THREE.SphereGeometry(1, 10, 10),
new THREE.MeshBasicMaterial({ color })
) ;
pt.position.set(x, y, z) ;
pt.x = x ;
pt.y = y ;
pt.z = z ;
pts.push(pt) ;
scene.add(pt) ;
}
function createEdge(pt1, pt2, pt3, pt4) {
let curve = new THREE.CubicBezierCurve3(
pt1.position,
pt2.position,
pt3.position,
pt4.position
),
mesh = new THREE.Mesh(
new THREE.TubeGeometry(curve, 8, 0.5, 8, false),
new THREE.MeshBasicMaterial({
color : 0x203040
})
) ;
scene.add(mesh) ;
}
///////////////////////////////////////////////
// POINTS //
createPoint(-16, -8, 0, 0xcc0000) ; // RED
createPoint(-8, -12, 0, 0x999999) ;
createPoint(8, -12, 0, 0x888888) ;
createPoint(16, -8, 0, 0x00cc00) ; // GREEN
createPoint(12, -6, -8, 0x777777) ;
createPoint(8, 6, -8, 0x666666) ;
createPoint(0, 12, 0, 0x0000cc) ; // BLUE
createPoint(-8, 6, -8, 0x555555) ;
createPoint(-12, -6, -8, 0x444444) ;
// EDGES //
createEdge(pts[0], pts[1], pts[2], pts[3]) ;
createEdge(pts[3], pts[4], pts[5], pts[6]) ;
createEdge(pts[6], pts[7], pts[8], pts[0]) ;
// SURFACE //
let ctrlPoints = [
[
new THREE.Vector4(pts[0].x, pts[0].y, pts[0].z, 1),
new THREE.Vector4(pts[1].x, pts[1].y, pts[1].z, 1),
new THREE.Vector4(pts[2].x, pts[2].y, pts[2].z, 1),
new THREE.Vector4(pts[3].x, pts[3].y, pts[3].z, 1)
],
[
new THREE.Vector4(pts[6].x, pts[6].y, pts[6].z, 1),
new THREE.Vector4(pts[5].x, pts[5].y, pts[5].z, 1),
new THREE.Vector4(pts[4].x, pts[4].y, pts[4].z, 1),
new THREE.Vector4(pts[3].x, pts[3].y, pts[3].z, 1)
],
[
new THREE.Vector4(pts[6].x, pts[6].y, pts[6].z, 1),
new THREE.Vector4(pts[7].x, pts[7].y, pts[7].z, 1),
new THREE.Vector4(pts[8].x, pts[8].y, pts[8].z, 1),
new THREE.Vector4(pts[0].x, pts[0].y, pts[0].z, 1)
]
],
nc,
deg1 = ctrlPoints.length - 1,
knots1 = [],
deg2 = 3, // Cubic bezier
knots2 = [0, 0, 0, 0, 1, 1, 1, 1], // <-
cpts,
nurbs ;
nc = ctrlPoints.length ;
while (nc-- > 0) knots1.push(0) ;
nc = ctrlPoints.length ;
while (nc-- > 0) knots1.push(1) ;
nurbs = new THREE.NURBSSurface(deg1, deg2, knots1, knots2, ctrlPoints) ;
let surfaceMesh = new THREE.Mesh(
new THREE.ParametricBufferGeometry(function(u, v, target) {
return nurbs.getPoint(u, v, target) ;
}, 10, 10),
new THREE.MeshBasicMaterial({
side : THREE.DoubleSide,
opacity : 0.9,
transparent : true,
color : 0x405060
})
) ;
scene.add(surfaceMesh) ;
///////////////////////////////////////////////
let azimut = 0,
pitch = 90,
isDown = false,
prevEv ;
function down(de) {
prevEv = de ;
isDown = true ;
}
function move(me) {
if (!isDown) return ;
azimut -= (me.clientX - prevEv.clientX) * 0.5 ;
azimut %= 360 ;
if (azimut < 0) azimut = 360 - azimut ;
pitch -= (me.clientY - prevEv.clientY) * 0.5 ;
if (pitch < 1) pitch = 1 ;
if (pitch > 180) pitch = 180 ;
prevEv = me ;
let theta = pitch / 180 * PI,
phi = azimut / 180 * PI,
radius = DISTANCE ;
camera.position.set(
radius * sin(theta) * sin(phi),
radius * cos(theta),
radius * sin(theta) * cos(phi),
) ;
camera.lookAt(center) ;
renderer.render(scene, camera) ;
}
function up(ue) {
isDown = false ;
}
renderer.domElement.onmousedown = down ;
window.onmousemove = move ;
window.onmouseup = up ;
renderer.render(scene, camera) ;
body {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background: #1c2228;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/101/three.min.js"></script>
<script src="https://threejs.org/examples/js/curves/NURBSUtils.js"></script>
<script src="https://threejs.org/examples/js/curves/NURBSCurve.js"></script>
<script src="https://threejs.org/examples/js/curves/NURBSSurface.js"></script>
<canvas></canvas>
Here is the way how you can draw Bezier Triangle (snippet below) - algorithm is in
Geometry
class. Number of triangles in one side of the triangle you set inconstructor
. In code I made hard separation between algorithm/calculations (Geometry
class) and drawing code (Draw
class).For bezier triangle we need to use 10 control points (9 for edges and one for "plane") like in below picture (src here ):
In this code, we don't use normals, and b points names are changed to p (eg.
b003
top003
). We use following formula (for cubic Bezier triangles n=3)Where p_ijk is control point (for n=3 above sum has 10 elements so we have 10 control points), and where B^n_ijk(r,s,t) are Bernstein polynomials defined for i,j,k>=0 and i+j+k=n
or 0 in other case. The domain of r,s,t in barycentric coordinates (where r,s,t are real numbers from [0, 1] and r+s+t=1) and where r=(r=1, s=t=0), s=(s=1, r=t=0), t=(t=1, r=s=0) looks as follows (the black points - we divide each triangle side to 5 parts - but we can change it to any number)
We calculate this reqular positions for black domain dots in method
barycentricCoords(n)
and we define which point create which triangles in methodgenTrianglesIndexes(n)
inGeometry
class. However you can change this points positions and density to any (inside triangle) to get different surface-triangle division. Below is snippet which shows domain in 2DBelow is final snippet with 3D bezier cubic triangle ( algorithm starts in method
genTrianglesForCubicBezierTriangle(n, controlPoints)
inGeometry
class)Fiddle version is here . I put info in comments but algorithm is complicated and if you have questions - ask them as comments - I will answer.
In your code you use
NURBSSurface
function from NURBSSurface.js file, that function usesNURBSUtils.calcSurfacePoint
function from NURBSUtils.js file. But thecalcSurfacePoint
calculate point for standard NUBRB surface where where parameter are from rectangle (u,v) wiki.You will not generate "3D cubic bezier triangle" in this way - to do this you need write your own code which will use bezier-triangle formulas (where the input parameters are triangle points in Barycentric_coordinate_system).