简体   繁体   English

如何从 Three.js 中的 3D 点创建 3D 三次贝塞尔曲线三角形?

[英]How can I create a 3D cubic-bezier curved triangle from 3D points in Three.js?

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.按照这个主题,我试图生成一个 3D 弯曲三角形作为 NURBS 曲面,但我不明白如何设置我的 3D 点来做到这一点。

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.注2: dots[0]指的是样张图片中的点0 ,以此类推。

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.这是绘制贝塞尔三角形的方法(下面的代码段) - 算法在Geometry类中。 Number of triangles in one side of the triangle you set in constructor .您在constructor设置的三角形一侧的三角形数。 In code I made hard separation between algorithm/calculations ( Geometry class) and drawing code ( Draw class).在代码中,我在算法/计算( Geometry类)和绘图代码( Draw类)之间进行了硬分离。

For bezier triangle we need to use 10 control points (9 for edges and one for "plane") like in below picture (src here ):对于贝塞尔三角形,我们需要使用 10 个控制点(9 个用于边缘,1 个用于“平面”),如下图所示(此处为src ):

在此处输入图片说明

In this code, we don't use normals, and b points names are changed to p (eg. b003 to p003 ).在这段代码中,我们不使用法线和B点名称更改为P(如: b003p003 )。 We use following formula (for cubic Bezier triangles n=3 )我们使用以下公式(对于三次贝塞尔三角形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其中p_ijk是控制点(对于上面的 n=3,sum 有 10 个元素,所以我们有 10 个控制点),其中 B^n_ijk(r,s,t) 是为 i,j,k>=0 和 i 定义的 Bernstein 多项式+j+k=n

在此处输入图片说明

or 0 in other case.或在其他情况下为 0。 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) r,s,t 的重心坐标域(其中 r,s,t 是来自 [0, 1] 和 r+s+t=1 的实数),其中r =(r=1, s=t=0 ), s =(s=1, r=t=0), t =(t=1, r=s=0) 看起来如下(黑点 - 我们将每个三角形的边分成 5 部分 - 但我们可以改变它到任何数字)

在此处输入图片说明

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.我们在方法barycentricCoords(n)计算黑域点的规则位置,并在Geometry类的方法genTrianglesIndexes(n)中定义哪个点创建哪些三角形。 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下面是在 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<=ni; j++) { s=(j/n); t=(i/n); r=1-st; 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)下面是带有 3D 贝塞尔三次三角形的最终片段(算法从Geometry类中的genTrianglesForCubicBezierTriangle(n, controlPoints)方法开始)- (注意:这很奇怪,但在首次运行后的 SO 片段中,您将看不到线条,您需要重新加载页面并再次运行以查看三角形线)

 /////////////////////////////////////////////////////// // 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.com/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<=ni; j++) { let s=(j/n); let t=(i/n); let r=1-st; 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.computeFaceNormals(); 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.com/ajax/libs/three.js/101/three.min.js"></script> <canvas></canvas>

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.我把信息放在评论中,但算法很复杂,如果你有问题 - 作为评论提出 - 我会回答。

I modified Kamil Kiełczewski 's code and separated it into 2 classes:我修改了Kamil Kiełczewski的代码并将其分为 2 个类:

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

Now it functions similar to NURBSSurface.js and is more efficient.现在它的功能类似于NURBSSurface.js,并且效率更高。

BarycentricBufferGeometry.js 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 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.在你的代码使用NURBSSurface功能从NURBSSurface.js文件,该功能使用NURBSUtils.calcSurfacePoint从功能NURBSUtils.js文件。 But the calcSurfacePoint calculate point for standard NUBRB surface where where parameter are from rectangle (u,v) wiki .但是calcSurfacePoint计算标准 NUBRB 表面的点,其中参数来自矩形 (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 ).您不会以这种方式生成“3D 三次贝塞尔三角形” - 为此,您需要编写自己的代码,该代码将使用贝塞尔三角形公式(其中输入参数是Barycentric_coordinate_system中的三角形点)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM