如何从Three.js中的3D点创建3D立方贝塞尔曲线三角形? [英] How can I create a 3D cubic-bezier curved triangle from 3D points in Three.js?

查看:730
本文介绍了如何从Three.js中的3D点创建3D立方贝塞尔曲线三角形?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

关注



我尝试了很多不同的设置,但找不到任何正常工作。



注意:白点是边缘;红点是bezier曲线中间点。

注2: dots [0] 指的是<样本图片中的code> 0 ,依此类推。



这是工作片段(和小提琴版< a href =https://jsfiddle.net/Totjoss/v0dkztyw/ =nofollow noreferrer>这里)



  const PI = Math.PI,sin = Math.sin,cos = Math.cos,W = 480,H = 400,log = console .log,DISTANCE = 100;让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。网格(新的THREE.TubeGeometry(曲线,8,0.5,8,假),新的THREE.MeshBasicMaterial({color:0x203040})); scene.add(mesh);} ////////////////////////////////////////// /////// POINTS // createPoint(-16,-8,0,0xcc0000); // REDcreatePoint(-8,-12,0,0x999999); createPoint(8,-12,0,0x888888); createPoint(16,-8,0,0x00cc00); // GREENcreatePoint(12,-6,-8,0x777777); createPoint(8,6,-8,0x666666); createPoint(0,12,0,0x0000cc); // BLUEcreatePoint(-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 //让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),新的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,不透明度:0.9,透明:真,颜色:0x405060})); scene.add(surfaceMesh); ////////////////////////// /////////////////////让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;令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;弯曲方向:行;辩解内容:中心; align-items:center;身高:100vh;保证金:0;背景:#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>

解决方案

以下是如何绘制Bezier Triangle(下面的代码段)的方法 - 算法在几何类。在构造函数中设置的三角形一侧的三角形数。在代码中,我在算法/计算( Geometry 类)和绘图代码( Draw 类)之间进行了硬性分离。



对于bezier三角形,我们需要使用10个控制点(边缘为9,平面为1),如下图所示(src



在此代码中,我们不使用法线, b 点名称更改为 p (例如 b003 p003 )。我们使用以下公式(对于三次贝塞尔三角形 n = 3





其中 p_ijk 是控制点(对于n = 3,总和有10个元素,所以我们有10个控制点),其中B ^ n_ijk(r, s,t)是为i,j,k> = 0定义的Bernstein多项式,i + j + k = n





或其他情况下为0。 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个零件 - 但我们可以将其更改为任意数字)





我们计算方法 barycentricCoords(n)中黑色域点的这个reqular位置我们定义哪个点在 Geometry 类中的方法 genTrianglesIndexes(n)中创建哪些三角形。但是,您可以将此点位置和密度更改为任何(内三角形)以获得不同的曲面三角形除法。下面是以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));设n = 5;设点= [];函数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];} //重心坐标r,s,t三角形点//从三角形底部到顶部逐行给出的点//第一行有n + 1个pojnts,第二个有n,第三个n -1 //坐标具有属性r + s + t = 1function 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;} //来自//点列表的每个三角形的过程计算索引(由barycentricCoords(n)返回的格式)函数genTrianglesIndexes(n){let st = 0;让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;返回三角形;}函数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;令x = p1 [0] * s + p2 [0] *(1-s);令y = p1 [1] * s + p2 [1] *(1-s);第(X,Y,......,C); function drawTriangle(p1,p2,p3,c){drawLine(p1,p2,c);的drawLine(P2,P3,C); drawLine(p3,p1,c);} // Bernstein多项式,i + j + k = nfunction bp(n,i,j,k,r,s,t){const f = x => x?f( x-1)* x:1 //数字小数f(4)= 1 * 2 * 3 * 4 = 24返回r ** i * s ** j * t ** k * f(n)/(f( ⅰ)* F(j)的* F(K)); } // drawTriangle(... mainTriangle,CR); // draw main trianglelet bar = barycentricCoords(n); //每个域点重心坐标ti = genTrianglesIndexes(n); //每个三角形的条形索引//计算为笛卡儿坐标系统的三角形三角形= ti.map(tr => tr.map(x => calcPoint(... mainTriangle,... bar [x])) ); triangles.map(t => drawTriangle(... t,cg)); //计算到笛卡尔坐标系的域点(用于绘制)让dp = bar.map(x => calcPoint(... mainTriangle, ... x)); //绘制黑点(每个点4个像素)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 =myCanvaswidth = 400 height = 400 style =background:white>< / canvas>  



以下是带有3D贝塞尔三角形三角形的最终片段(算法以方法 genTrianglesForCubicBezierTriangle(n,controlPoints)在<$中开始c $ c> Geometry class)



  /////////////////////////////////// //////////////////////这个部分/类用于算法和计算/////////////////// //////////////////////////////////// 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,颜色:0x999999},// p102 {x:16,y:-8,z:0,颜色:0x00cc00},/ / p300 GREEN {x:12,y:-6,z:-8,颜色:0x777777},// p210 {x:8,y:6,z:-8,颜色:0x666666},// p120 {x :0,y:12,z:0,颜色:0x0000cc},// p030蓝色{x:-8,y:6,z:-8,颜色:0x555555},// p021 {x:-12,y :-6,z:-8,颜色:0x444444},// p012 {x:0,y:0,z:8,颜色:0xffff00},// p111黄色(平面控制点)]; 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 =每个三角形的三角形数量genTrianglesForCubicBezierTriangle(n,controlPoints){let bar = this.barycentricCoords(n); //域中的重心坐标让ti = this.genTrianglesIndexes(n); //三角形的索引(在条形数组中)让val = bar.map(x => this.calcCubicBezierTriangleValue(controlPoints,... x)); //每个域(bar)点的Calc Bezier三角形顶点让tv = ti.map(tr => tr.map(x => val [x])); //使用索引(ti)生成三角形,val返回tv.map(t =>({points:t,color:null})); //将三角形映射到正确的格式(color = null给出线框)//生成域三角形//让td = ti.map(tr => tr.map(x => this.calcPointFromBar(... this.mainTriangle) ,... bar [x]))); //this.trianglesDomain = td.map(t =>({points:t,color:null})); } //更多:https://www.mdpi.com/2073-8994/8/3/13/pdf //Bézier三角形与G2连续性跨界//张昌基,Hae-Do Hwang和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令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多项式次数n,i + j + k = n bp(i,j,k,r,s,t,n = 3){const f = x => x?f(x-1)* x:1 //数字小数f(4)= 1 * 2 * 3 * 4 = 24返回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]}} //从重心坐标系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}; } //重心坐标r,s,t三角形点//从三角形底部到顶部逐行给出的点//第一行有n + 1个pojnts,第二个有n,第三个n-1 //坐标有属性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);设t =(i / n);设r = 1-s-t; rst.push([R,S,T]); } return rst; // //来自//点列表的每个三角形的过程计算索引(由barycentricCoords(n)返回的格式)genTrianglesIndexes(n){let st = 0;让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;返回三角形;这个程序是Draw类的接口getPoints(){return this.pts} getTriangles(){return this.triangles} getBezierCurves(){return this.bezierCurvesPoints; }} //////////////////////////////////////////////// /这部分用于绘图//////////////////////////////////////////// /////init tree js和draw geometry objectsclass 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;让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),新的THREE.Vector3(pt3.x,pt3.y,pt3.z),新的THREE.Vector3(pt4.x,pt4.y,pt4.z),),mesh = new THREE.Mesh (新的THREE.TubeGeometry(曲线,8,0.5,8,假),新的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 =我;设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 UPlet geom = new Geometry(); let draw = new Draw(geom);  

  body {display:flex;弯曲方向:行;辩解内容:中心; align-items:center;身高:100vh;保证金:0;背景:#1c2228; overflow:hidden;}  

 < script src =https ://cdnjs.cloudflare.com/ajax/libs/three.js/101/three.min.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.

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 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)

///////////////////////////////////////////////////////
// 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<=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.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.

这篇关于如何从Three.js中的3D点创建3D立方贝塞尔曲线三角形?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆