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 cur
I modified Kamil Kiełczewski's code and separated it into 2 classes:
BarycentricBufferGeometry
based on ParametricBufferGeometry
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);