admin管理员组

文章数量:1313972

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=".js/101/three.min.js"></script>
<script src=".js"></script>
<script src=".js"></script>
<script src=".js"></script>

<canvas></canvas>

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./ajax/libs/three.js/101/three.min.js"></script>
<script src="https://threejs/examples/js/curves/NURBSUtils.js"></script>
<script src="https://threejs/examples/js/curves/NURBSCurve.js"></script>
<script src="https://threejs/examples/js/curves/NURBSSurface.js"></script>

<canvas></canvas>

Share Improve this question edited Feb 25, 2019 at 3:24 Kamil Kiełczewski 92.7k34 gold badges395 silver badges370 bronze badges asked Feb 20, 2019 at 13:11 TotTot 9153 gold badges13 silver badges30 bronze badges 4
  • can you provide link to working example which show what you are done already and be easy to edit by others (using e.g. jsfiddle ) ? – Kamil Kiełczewski Commented Feb 20, 2019 at 17:16
  • Just did ! I've edited my question and added a link. – Tot Commented Feb 21, 2019 at 19:43
  • do my answer solve problem from yor question? – Kamil Kiełczewski Commented Feb 26, 2019 at 12:45
  • Hey ! Sorry I didn't have time to test it. But I'm sure it does solve the problem. I'll tell you once I've tested ! – Tot Commented Feb 27, 2019 at 11:41
Add a ment  | 

3 Answers 3

Reset to default 9 +50

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 in constructor. 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 to p003). 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 method genTrianglesIndexes(n) in Geometry 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 2D

let pp= ((s='.myCanvas',c=document.querySelector(s),ctx=c.getContext('2d'),id=ctx.createImageData(1,1)) => (x,y,r=0,g=0,b=0,a=255)=>(id.data.set([r,g,b,a]),ctx.putImageData(id, x, y),c))()


cr=[255,0,0,255];
cg=[0,255,0,255];
cb=[0,0,255,255];

w=400;
h=400;

const p1=[0,h-1];
const p2=[w-1,h-1];
const p3=[w/2,0];

mainTriangle=[p1,p2,p3];
//mainTriangle.map(p => pp(...p,...cr));

let n=5;
let points=[];

function calcPoint(p1,p2,p3,r,s,t) {
  const px=p1[0]*r + p2[0]*s + p3[0]*t;
  const py=p1[1]*r + p2[1]*s + p3[1]*t;
  return [px,py];
}

// barycentric coordinates r,s,t of point in triangle
// the points given from triangle bottom to top line by line
// first line has n+1 pojnts, second has n, third n-1
// coordinates has property r+s+t=1
function barycentricCoords(n) {
  let rst=[];
  for(let i=0; i<=n; i++) for(let j=0; j<=n-i; j++) {
    s=(j/n);
    t=(i/n);    
    r=1-s-t;
    rst.push([r,s,t]);    
  }
  return rst;
}

// Procedure calc indexes for each triangle from 
// points list (in format returned by barycentricCoords(n) )
function genTrianglesIndexes(n) {
  let st=0; 
  let m=n;  
  let triangles=[];

  for(let j=n; j>0; j--) {    
    for(let i=0; i<m; i++) {    
      triangles.push([st+i, st+i+1, st+m+i+1]);
      if(i<m-1) triangles.push([st+i+1, st+m+i+2, st+m+i+1 ]);
    }
    m--;
    st+=j+1;  
  }
  
  return triangles;
}

function drawLine(p1,p2,c) {
  let n=Math.max(Math.abs(p1[0]-p2[0]),Math.abs(p1[1]-p2[1]))/2;
	for(let i=0; i<=n; i++) {
  	let s=i/n;
    let x=p1[0]*s + p2[0]*(1-s);
    let y=p1[1]*s + p2[1]*(1-s);
    pp(x,y,...c);
  }
}

function drawTriangle(p1,p2,p3,c) {
	drawLine(p1,p2,c);
  drawLine(p2,p3,c);
  drawLine(p3,p1,c);
}

// Bernstein Polynomial, i+j+k=n
function bp(n,i,j,k, r,s,t) {
  const f=x=>x?f(x-1)*x:1 // number fractional f(4)=1*2*3*4=24
  
  return r**i * s**j * t**k * f(n) / (f(i)*f(j)*f(k));  
}

//drawTriangle(...mainTriangle,cr); // draw main triangle

let bar=barycentricCoords(n);  // each domain point barycentric coordinates

let ti=genTrianglesIndexes(n); // indexes in bar for each triangle

// triangles calculated to cartesian coordinate system
let triangles = ti.map(tr=> tr.map(x=>calcPoint(...mainTriangle,...bar[x]) ) ); 

triangles.map(t => drawTriangle(...t, cg));

// domain points calculated to cartesian coordinate system (for draw)
let dp = bar.map(x=> calcPoint(...mainTriangle,...x) );

// draw black dots (4 pixels for each dot)
dp.map(x=> pp(x[0],x[1]) )
dp.map(x=> pp(x[0],x[1]-1) )
dp.map(x=> pp(x[0]-1,x[1]) )
dp.map(x=> pp(x[0]-1,x[1]-1) )
<canvas class="myCanvas" width=400 height=400 style="background: white"></canvas>

Below is final snippet with 3D bezier cubic triangle ( algorithm starts in method genTrianglesForCubicBezierTriangle(n, controlPoints) in Geometry class) - (caution: It is strange, but in SO snippets after first run you will NOT see lines, and you need reload page and run it again to see triangles-lines)

///////////////////////////////////////////////////////
// THIS PART/CLASS IS FOR ALGORITHMS AND CALCULATIONS
///////////////////////////////////////////////////////
class Geometry {

  constructor() { this.init(); } 

  init(n) {
    this.pts = [
      { x:-16, y: -8, z:0,  color:0xcc0000 }, // p003 RED
      { x:8,   y:-12, z:0,  color:0x888888 }, // p201
      { x:-8,  y:-12, z:0,  color:0x999999 }, // p102    
      { x:16,  y:-8,  z:0,  color:0x00cc00 }, // p300 GREEN
      { x:12,  y:-6,  z:-8, color:0x777777 }, // p210
      { x:8,   y:6,   z:-8, color:0x666666 }, // p120
      { x:0,   y:12,  z:0,  color:0x0000cc }, // p030 BLUE
      { x:-8,  y:6,   z:-8, color:0x555555 }, // p021
      { x:-12, y:-6,  z:-8, color:0x444444 }, // p012
      { x:0,   y:0,   z:8,  color:0xffff00 }, // p111 YELLOW (plane control point)
    ];
    
    this.mainTriangle = [this.pts[0],this.pts[3],this.pts[6]];
    
    this.bezierCurvesPoints = [
    	[ this.pts[0], this.pts[2], this.pts[1], this.pts[3] ],
        [ this.pts[3], this.pts[4], this.pts[5], this.pts[6] ],
        [ this.pts[6], this.pts[7], this.pts[8], this.pts[0] ]
    ];
    
    //this.triangles = [
    // { points: [this.pts[0], this.pts[1], this.pts[2]], color: null }, // wireframe
    // { points: [this.pts[1], this.pts[2], this.pts[3]], color: 0xffff00 } // yellow
    //]
    
    this.triangles = this.genTrianglesForCubicBezierTriangle(25, this.pts);
  }
  
  // n = number of triangles per triangle side
  genTrianglesForCubicBezierTriangle(n, controlPoints) {
    let bar= this.barycentricCoords(n);     // domain in barycentric coordinats   
    let ti = this.genTrianglesIndexes(n);   // indexes of triangles (in bar array)
        
    let val= bar.map(x => this.calcCubicBezierTriangleValue(controlPoints,...x));  // Calc Bezier triangle vertex for each domain (bar) point    
    let tv= ti.map(tr=> tr.map(x=>val[x]) );         // generate triangles using their indexes (ti) and val    
    return tv.map(t=> ({ points: t, color: null}) ); // map triangles to proper format (color=null gives wireframe)
    
    
    // Generate domain triangles
    //let td= ti.map(tr=> tr.map(x=>this.calcPointFromBar(...this.mainTriangle,...bar[x]) ) );     
    //this.trianglesDomain = td.map(t=> ({ points: t, color: null}) );
  }
  
  // more: https://www.mdpi./2073-8994/8/3/13/pdf
  // Bézier Triangles with G2 Continuity across Boundaries
  // Chang-Ki Lee, Hae-Do Hwang and Seung-Hyun Yoon
  calcCubicBezierTriangleValue(controlPoints, r,s,t ) {
    let p = controlPoints, b=[];  
    b[0]= this.bp(0,0,3,r,s,t); // p[0]=p003
    b[1]= this.bp(2,0,1,r,s,t); // p[1]=p201 
    b[2]= this.bp(1,0,2,r,s,t); // p[2]=p102
    b[3]= this.bp(3,0,0,r,s,t); // p[3]=p300
    b[4]= this.bp(2,1,0,r,s,t); // p[4]=p210
    b[5]= this.bp(1,2,0,r,s,t); // p[5]=p120
    b[6]= this.bp(0,3,0,r,s,t); // p[6]=p030
    b[7]= this.bp(0,2,1,r,s,t); // p[7]=p021
    b[8]= this.bp(0,1,2,r,s,t); // p[8]=p012
    b[9]= this.bp(1,1,1,r,s,t); // p[9]=p111
    
    let x=0, y=0, z=0;
    for(let i=0; i<=9; i++) {
      x+=p[i].x*b[i];
      y+=p[i].y*b[i];
      z+=p[i].z*b[i];
    }
    return { x:x, y:y, z:z };
  }
  
  // Bernstein Polynomial degree n, i+j+k=n
  bp(i,j,k, r,s,t, n=3) {
    const f=x=>x?f(x-1)*x:1 // number fractional f(4)=1*2*3*4=24    
    return r**i * s**j * t**k * f(n) / (f(i)*f(j)*f(k));  
  }
  
  coordArrToObj(p) { return { x:p[0], y:p[1], z:p[2] } } 
  
  // Calc cartesian point from barycentric coords system
  calcPointFromBar(p1,p2,p3,r,s,t) {  
    const px=p1.x*r + p2.x*s + p3.x*t;
    const py=p1.y*r + p2.y*s + p3.y*t;
    const pz=p1.z*r + p2.z*s + p3.z*t;       
    return { x:px, y:py,  z:pz};
  }

  // barycentric coordinates r,s,t of point in triangle
  // the points given from triangle bottom to top line by line
  // first line has n+1 pojnts, second has n, third n-1
  // coordinates has property r+s+t=1
  barycentricCoords(n) {
    let rst=[];
    for(let i=0; i<=n; i++) for(let j=0; j<=n-i; j++) {
      let s=(j/n);
      let t=(i/n);    
      let r=1-s-t;
      rst.push([r,s,t]);    
    }
    return rst;
  }

  // Procedure calc indexes for each triangle from 
  // points list (in format returned by barycentricCoords(n) )
  genTrianglesIndexes(n) {
    let st=0; 
    let m=n;  
    let triangles=[];

    for(let j=n; j>0; j--) {    
      for(let i=0; i<m; i++) {    
        triangles.push([st+i, st+i+1, st+m+i+1]);
        if(i<m-1) triangles.push([st+i+1, st+m+i+2, st+m+i+1 ]);
      }
      m--;
      st+=j+1;  
    }

    return triangles;
  }
  
  // This procedures are interface for Draw class 
  getPoints() { return this.pts }
  getTriangles() { return this.triangles }
  getBezierCurves() { return this.bezierCurvesPoints; }
}


///////////////////////////////////////////////
// THIS PART IS FOR DRAWING
///////////////////////////////////////////////

// init tree js and draw geometry objects
class Draw {

  constructor(geometry) { this.init(geometry); }
  
  initGeom() {
  	this.geometry.getPoints().forEach(p=> this.createPoint(p));
    this.geometry.getTriangles().forEach(t=> this.createTriangle(t));
    
    this.geometry.getBezierCurves().forEach(c=> this.createEdge(...c));
  }

  init(geometry) {
    this.geometry = geometry;
    this.W = 480,
    this.H = 400,
    this.DISTANCE = 100 ;
    this.PI = Math.PI,
    
  
    this.renderer = new THREE.WebGLRenderer({
      canvas : document.querySelector('canvas'),
      antialias : true,
      alpha : true
    }),
    this.camera = new THREE.PerspectiveCamera(25, this.W/this.H),
    this.scene = new THREE.Scene(),
    this.center = new THREE.Vector3(0, 0, 0),

		this.pts = [] ;
    
    this.renderer.setClearColor(0x000000, 0) ;

    this.renderer.setSize(this.W, this.H) ;
    // camera.position.set(-48, 32, 80) ;
    this.camera.position.set(0, 0, this.DISTANCE) ;
    this.camera.lookAt(this.center) ;
    
    this.initGeom();
    
    this.azimut = 0;
    this.pitch = 90;
    this.isDown = false;
    this.prevEv = null;

    

    this.renderer.domElement.onmousedown = e => this.down(e) ;
    window.onmousemove = e => this.move(e) ;
    window.onmouseup = e => this.up(e) ;

    this.renderer.render(this.scene, this.camera) ;
    
  }
    
  createPoint(p) {
    let {x, y, z, color} = p;
		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 ;
    this.pts.push(pt) ;
    
    this.scene.add(pt) ;
	}
  
  createTriangle(t) {    
    var geom = new THREE.Geometry();
    var v1 = new THREE.Vector3(t.points[0].x, t.points[0].y, t.points[0].z);
    var v2 = new THREE.Vector3(t.points[1].x, t.points[1].y, t.points[1].z);
    var v3 = new THREE.Vector3(t.points[2].x, t.points[2].y, t.points[2].z);

    geom.vertices.push(v1);
    geom.vertices.push(v2);
    geom.vertices.push(v3);
        
    let material = new THREE.MeshNormalMaterial({wireframe: true,}) 
    if(t.color != null) material = new THREE.MeshBasicMaterial( { 
    	color: t.color,
      side: THREE.DoubleSide,
      } );
    

    geom.faces.push( new THREE.Face3( 0, 1, 2 ) );
    geom.puteFaceNormals();

    var mesh= new THREE.Mesh( geom, material);
    this.scene.add(mesh) ;
  }
  
  createEdge(pt1, pt2, pt3, pt4) {
 
		let curve = new THREE.CubicBezierCurve3(
          new THREE.Vector3(pt1.x, pt1.y, pt1.z),
          new THREE.Vector3(pt2.x, pt2.y, pt2.z),
          new THREE.Vector3(pt3.x, pt3.y, pt3.z),
          new THREE.Vector3(pt4.x, pt4.y, pt4.z),
        ),
    		mesh = new THREE.Mesh(
          new THREE.TubeGeometry(curve, 8, 0.5, 8, false),
          new THREE.MeshBasicMaterial({
            color : 0x203040
          })
        ) ;
        
    this.scene.add(mesh) ;
}
  
  down(de) {
        this.prevEv = de ;
        this.isDown = true ;
    }

  move(me) {
    if (!this.isDown) return ;

    this.azimut -= (me.clientX - this.prevEv.clientX) * 0.5 ;
    this.azimut %= 360 ;
    if (this.azimut < 0) this.azimut = 360 - this.azimut ;

    this.pitch -= (me.clientY - this.prevEv.clientY) * 0.5 ;
    if (this.pitch < 1) this.pitch = 1 ;
    if (this.pitch > 180) this.pitch = 180 ;

    this.prevEv = me ;

    let theta = this.pitch / 180 * this.PI,
        phi = this.azimut / 180 * this.PI,
        radius = this.DISTANCE ;

    this.camera.position.set(
      radius * Math.sin(theta) * Math.sin(phi),
      radius * Math.cos(theta),
      radius * Math.sin(theta) * Math.cos(phi),
    ) ;
    this.camera.lookAt(this.center) ;

    this.renderer.render(this.scene, this.camera) ;
  }

  up(ue) {
    this.isDown = false ;
  }
}

// SYSTEM SET UP
let geom= new Geometry();
let draw = new Draw(geom);
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./ajax/libs/three.js/101/three.min.js"></script>

<canvas></canvas>

Fiddle version is here . I put info in ments but algorithm is plicated and if you have questions - ask them as ments - I will answer.

I modified Kamil Kiełczewski's code and separated it into 2 classes:

  1. BarycentricBufferGeometry based on ParametricBufferGeometry
  2. BezierTriangle based on NURBSSurface

Now it functions similar to NURBSSurface.js and is more efficient.

BarycentricBufferGeometry.js

import { BufferGeometry, Float32BufferAttribute, Vector3 } from './three.module.js';

class BarycentricBufferGeometry extends BufferGeometry {

    constructor(func, slices) {

        super();

        this.type = 'BezierTriangleGeometry';

        this.parameters = {
            func: func,
            slices: slices
        };

        // buffers
        const indices = [];
        const vertices = [];
        const normals = [];
        const uvs = [];

        const EPS = 0.00001;

        const normal = new Vector3();

        const p0 = new Vector3(), p1 = new Vector3();
        const pu = new Vector3(), pv = new Vector3();

        if (func.length < 3) {

            console.error('THREE.ParametricGeometry: Function must now modify a Vector3 as third parameter.');

        }

        // generate vertices, normals and uvs
        for (let i = 0; i <= slices; i++) {

            for (let j = 0; j <= slices - i; j++) {

                const u = j / slices;
                const v = i / slices;

                // vertex
                func(u, v, p0);
                vertices.push(p0.x, p0.y, p0.z);

                // normal
                // approximate tangent vectors via finite differences
                if (u - EPS >= 0) {

                    func(u - EPS, v, p1);
                    pu.subVectors(p0, p1);

                } else {

                    func(u + EPS, v, p1);
                    pu.subVectors(p1, p0);

                }

                if (v - EPS >= 0) {

                    func(u, v - EPS, p1);
                    pv.subVectors(p0, p1);

                } else {

                    func(u, v + EPS, p1);
                    pv.subVectors(p1, p0);

                }

                // cross product of tangent vectors returns surface normal
                normal.crossVectors(pu, pv).normalize();
                normals.push(normal.x, normal.y, normal.z);

                // uv
                uvs.push(u, v);

            }

        }

        // generate indices
        let st = 0;
        let m = slices;

        for (let j = slices; j > 0; j--) {

            for (let i = 0; i < m; i++) {

                const a = st + i;
                const b = st + i + 1;
                const c = st + i + 1 + m;

                indices.push(a, b, c);

                if (i < m - 1)
                    indices.push(st + i + 1, st + m + i + 2, st + m + i + 1);
            }

            m = m - 1;
            st += j + 1;
        }

        // build geometry
        this.setIndex(indices);
        this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
        this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
        this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));

    }
}

// BarycentricBufferGeometry.prototype = Object.create( BufferGeometry.prototype );
;


export { BarycentricBufferGeometry };

BezierTriangle.js

class BezierTriangle {

    constructor(controlPoints) {

        this.controlPoints = controlPoints;
    }

    static bp(i, j, k, r, s, t, n = 3) {
        const f = x => x ? f(x - 1) * x : 1;
        return r ** i * s ** j * t ** k * f(n) / (f(i) * f(j) * f(k));
    }

    static calcSurfacePoint(p, u, v, target) {

        const t = 1 - u - v;
        let b = [];

        b[0] = BezierTriangle.bp(0, 0, 3, u, v, t);
        b[1] = BezierTriangle.bp(1, 0, 2, u, v, t);
        b[2] = BezierTriangle.bp(2, 0, 1, u, v, t);
        b[3] = BezierTriangle.bp(3, 0, 0, u, v, t);
        b[4] = BezierTriangle.bp(2, 1, 0, u, v, t);
        b[5] = BezierTriangle.bp(1, 2, 0, u, v, t);
        b[6] = BezierTriangle.bp(0, 3, 0, u, v, t);
        b[7] = BezierTriangle.bp(0, 2, 1, u, v, t);
        b[8] = BezierTriangle.bp(0, 1, 2, u, v, t);
        b[9] = BezierTriangle.bp(1, 1, 1, u, v, t);

        let x = 0,
            y = 0,
            z = 0;

        for (let i = 0; i < 10; i++) {
            x += p[i].x * b[i];
            y += p[i].y * b[i];
            z += p[i].z * b[i];
        }

        target.set(x, y, z);
    }

    getPoint(u, v, target) {

        BezierTriangle.calcSurfacePoint(this.controlPoints, u, v, target);
    }
}


export { BezierTriangle };

Example:

import * as THREE from './three.module.js';

import { BarycentricBufferGeometry } from './BarycentricBufferGeometry.js';
import { BezierTriangle } from './BezierTriangle.js';


//setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, .01, 10000);
camera.position.set(2, 2, 6)

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);


// bezier triangle points
const points = [
    { x: 0, y: 0, z: 0, c: 'red' },

    { x: 0, y: 1, z: 0, c: 'grey' },
    { x: 0, y: 2, z: 0, c: 'grey' },
    { x: 0, y: 3, z: 1, c: 'green' },

    { x: 1, y: 3, z: 1, c: 'grey' },
    { x: 2, y: 3, z: 1, c: 'grey' },
    { x: 3, y: 3, z: 2, c: 'blue' },

    { x: 2, y: 2, z: 0, c: 'grey' },
    { x: 1, y: 1, z: 0, c: 'grey' },

    { x: 1, y: 2, z: 0, c: 'yellow' },
];

// add some colored spheres to help identify points
points.forEach(p => {
    const sphere = new THREE.Mesh(
        new THREE.SphereBufferGeometry(.1, 32, 32),
        new THREE.MeshBasicMaterial({ color: p.c ? p.c : 'white' })
    );
    sphere.position.set(p.x, p.y, p.z);
    scene.add(sphere);
});

// draw bezier triangle
const triangle = new BezierTriangle(points);

function getSurfacePoint(u, v, target) {
    return triangle.getPoint(u, v, target);
}

const geometry = new BarycentricBufferGeometry(getSurfacePoint, 3);
const material = new THREE.MeshBasicMaterial({ color: 'gold', wireframe: true });
const mesh = new THREE.Mesh(geometry, material);

scene.add(mesh);

renderer.render(scene, camera);

In your code you use NURBSSurface function from NURBSSurface.js file, that function uses NURBSUtils.calcSurfacePoint function from NURBSUtils.js file. But the calcSurfacePoint 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).

本文标签: javascriptHow can I create a 3D cubicbezier curved triangle from 3D points in ThreejsStack Overflow