在this topic之后,我试图生成一个3D弯曲三角形作为NURBS曲面,但是我不知道如何设置3D点来实现。
这是当前的实现:
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) ;
结果如下:
我尝试了许多不同的设置,但找不到任何有效的方法。
注意:白点是边缘末端;红色点是贝塞尔曲线的中间点。
注2:dots[0]
指向示例图片中的点0
,依此类推。
这里是有效的代码段(和小提琴版本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>
答案 0 :(得分:4)
这是绘制Bezier三角形(以下代码段)的方式-算法在Geometry
类中。在constructor
中设置的三角形的一侧中的三角形数量。在代码中,我在算法/计算(Geometry
类)和绘图代码(Draw
类)之间进行了严格的分离。
对于贝塞尔三角形,我们需要使用10个控制点(9个用于边缘,一个用于“平面”),如下图所示(src here):
在此代码中,我们不使用法线,并且 b 点名称更改为 p (例如,b003
更改为p003
)。我们使用以下公式(对于三次贝塞尔三角形 n = 3 )
其中 p_ijk 是控制点(对于n = 3,以上的和有10个元素,因此我们有10个控制点),其中B ^ n_ijk(r,s,t)是为以下项定义的伯恩斯坦多项式i,j,k> = 0且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)
中为黑色域点计算此规则位置,并在genTrianglesIndexes(n)
类的方法Geometry
中定义哪个点创建哪些三角形。但是,您可以将此点的位置和密度更改为任何(内部三角形),以获得不同的表面三角形划分。以下是显示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>
下面是带有3D贝塞尔曲线立方三角形的最终代码段(算法从genTrianglesForCubicBezierTriangle(n, controlPoints)
类的方法Geometry
开始)
///////////////////////////////////////////////////////
// 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>
小提琴版本为here。我在评论中添加了信息,但是算法很复杂,如果您有任何问题-将其作为评论-我会回答。
答案 1 :(得分:0)
在代码中,您使用NURBSSurface.js文件中的NURBSSurface
函数,该函数使用NURBSUtils.js文件中的NURBSUtils.calcSurfacePoint
函数。但是calcSurfacePoint
为标准NUBRB曲面计算点,其中参数来自矩形(u,v)wiki。
您不会以这种方式生成“ 3D三次方贝塞尔三角形”-为此,您需要编写自己的代码,该代码将使用bezier-triangle formulas(其中输入参数是Barycentric_coordinate_system中的三角形点)。
答案 2 :(得分:0)
我修改了Kamil Kiełczewski的代码并将其分为2类:
BarycentricBufferGeometry
基于ParametricBufferGeometry
BezierTriangle
基于NURBSSurface
现在,它的功能类似于NURBSSurface.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
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 };
示例:
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);