How to add faces to THREE.BufferGeometry? - javascript

I have created programmatically a simple mesh:
var CreateSimpleMesh = new function () {
var xy = [],
maxX = 7,
maxY = 10,
river = [[0, 5], [0, 4], [1, 3], [2, 2], [3, 2], [4, 1], [5, 1], [6, 0]],
grassGeometry = new THREE.BufferGeometry(),
grassVertexPositions = []
this.init = function () {
for (i = 0; i < maxX; i++) {
for (j = 0; j < maxY; j++) {
xy.push([i, j])
}
}
for (var i = 0; i < xy.length; i++) {
grassVertexPositions.push([xy[i][0], xy[i][1], 0])
grassVertexPositions.push([xy[i][0] + 1, xy[i][1], 0])
grassVertexPositions.push([xy[i][0], xy[i][1] + 1, 0])
grassVertexPositions.push([xy[i][0] + 1, xy[i][1] + 1, 0])
grassVertexPositions.push([xy[i][0], xy[i][1] + 1, 0])
grassVertexPositions.push([xy[i][0] + 1, xy[i][1], 0])
}
for (var i = 0; i < grassVertexPositions.length; i++) {
for (var j = 0; j < river.length; j++) {
if (river[j][0] == grassVertexPositions[i][0] && river[j][1] == grassVertexPositions[i][1]) {
grassVertexPositions[i][2] = -0.5
}
}
}
var grassVertices = new Float32Array(grassVertexPositions.length * 3)
for (var i = 0; i < grassVertexPositions.length; i++) {
grassVertices[i * 3 + 0] = grassVertexPositions[i][0];
grassVertices[i * 3 + 1] = grassVertexPositions[i][1];
grassVertices[i * 3 + 2] = grassVertexPositions[i][2];
}
grassGeometry.addAttribute('position', new THREE.BufferAttribute(grassVertices, 3))
var grassMaterial = new THREE.MeshLambertMaterial({color: 0x00ff00}),
grassMesh = new THREE.Mesh(grassGeometry, grassMaterial)
grassMesh.rotation.x = -Math.PI / 2
Test.getScene().add(grassMesh);
}
}
Problem is that this mesh has only vertices. I have tried to add to it faces like in this question using THREE.Shape.Utils.triangulateShape but BufferGeometry is different than normal geometry and it does not work. Is it possible to add faces to BufferGeometry?
EDIT:
Working fiddle

Here is how to create a mesh having BufferGeometry. This is the simpler "non-indexed" BufferGeometry where vertices are not shared.
// non-indexed buffer geometry
var geometry = new THREE.BufferGeometry();
// number of triangles
var NUM_TRIANGLES = 10;
// attributes
var positions = new Float32Array( NUM_TRIANGLES * 3 * 3 );
var normals = new Float32Array( NUM_TRIANGLES * 3 * 3 );
var colors = new Float32Array( NUM_TRIANGLES * 3 * 3 );
var uvs = new Float32Array( NUM_TRIANGLES * 3 * 2 );
var color = new THREE.Color();
var scale = 15;
var size = 5;
var x, y, z;
for ( var i = 0, l = NUM_TRIANGLES * 3; i < l; i ++ ) {
if ( i % 3 === 0 ) {
x = ( Math.random() - 0.5 ) * scale;
y = ( Math.random() - 0.5 ) * scale;
z = ( Math.random() - 0.5 ) * scale;
} else {
x = x + size * ( Math.random() - 0.5 );
y = y + size * ( Math.random() - 0.5 );
z = z + size * ( Math.random() - 0.5 );
}
var index = 3 * i;
// positions
positions[ index ] = x;
positions[ index + 1 ] = y;
positions[ index + 2 ] = z;
//normals -- we will set normals later
// colors
color.setHSL( i / l, 1.0, 0.5 );
colors[ index ] = color.r;
colors[ index + 1 ] = color.g;
colors[ index + 2 ] = color.b;
// uvs
uvs[ index ] = Math.random(); // just something...
uvs[ index + 1 ] = Math.random();
}
geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
geometry.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );
geometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
geometry.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
// optional
geometry.computeBoundingBox();
geometry.computeBoundingSphere();
// set the normals
geometry.computeVertexNormals(); // computed vertex normals are orthogonal to the face for non-indexed BufferGeometry
See the three.js examples for many additional examples of creating BufferGeometry. Also check out the source code for PlaneGeometry and SphereGeometry, which are reasonably easy to understand.
three.js r.143

You can add faces using three.js internal function- fromBufferGeometry. In your case it would be something like this.
var directGeo = new THREE.Geometry();
directGeo.fromBufferGeometry(grassGeometry);
Then use directGeo to build your mesh, and it will have faces.

Related

adding and updating velocity and acceleration in bufergeometry three js

let pointsM = [];
let img = new THREE.TextureLoader().load( 'image' );
let pointMaterial = new THREE.PointsMaterial({
map: img,transparent:true
});
let bufferGeometry = new THREE.BufferGeometry()
for (let i = 0; i < 10; i++) {
pointsM.push( new THREE.Vector3(Math.random() * 20 - 10, Math.random() * 20 - 10, Math.random() * 20 - 10));
}
bufferGeometry.setFromPoints(pointsM);
bufferGeometry.computeVertexNormals();
pointsmesh = new THREE.Points(bufferGeometry, pointMaterial);
scene.add(pointsmesh)
The above section works fine ,
converted from Geometry to BufferGeometry
What id like to do is add velocity and acceleration to the points created in the above section, and update them in the render loop
i would like to change the code below to work with buffer geometry currently it works with Geometry
render(){
Geo.vertices.forEach(p => {
p.vel += p.accel;
p.z -= p.vel;
});
Geo.verticesNeedUpdate = true;
}
Thanks for the support
Try it with the below code. It assumes that the velocity and acceleration for each vertex is saved in two separate arrays.
// creation
const bufferGeometry = new THREE.BufferGeometry();
const vertices = [];
const velocities = [];
const accelerations = [];
for ( let i = 0; i < 10; i++ ) {
vertices.push( Math.random() * 20 - 10 ); // x
vertices.push( Math.random() * 20 - 10 ); // y
vertices.push( Math.random() * 20 - 10 ); // z
velocities.push( 0 );
accelerations.push( Math.random() );
}
bufferGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
// update
const positionAttribute = geometry.getAttribute( 'position' );
for ( let i = 0; i < positionAttribute.count; i ++ ) {
const z = positionAttribute.getZ( i );
const vel = velocities[ i ];
const accel = accelerations[ i ];
vel *= accel;
velocities[ i ] = vel;
z -= vel;
positionAttribute.setZ( i, z );
}
positionAttribute.needsUpdate = true;
render();
BTW: computeVertexNormals() is not necessary in your use case. Points and lines to not need vertex normals.

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.
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) - (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.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.
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);
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).

Multiple random points Three.js

I am constructing a point with a random x,y,z (in a certain min and max for x,y,z).
How can I multiply the point (e.g. 20 points), but the have different values (random)for x,y and z also.
//Math.random() * (max - min) + min
var x = Math.random() * (80 - 1) + 1
var y = Math.random() * (80 - 1) + 1
var z = Math.random() * (80 - 1) + 1
var dotGeometry = new THREE.Geometry();
dotGeometry.vertices.push(new THREE.Vector3( x, y, z));
var dotMaterial = new THREE.PointCloudMaterial( { size: 10, sizeAttenuation: false, color: 0xFF0000 } );
var dot = new THREE.PointCloud( dotGeometry, dotMaterial );
scene.add( dot );
I tried something, but the point multiplies at the same position over and over.
If you want to create 20 random points, you only have to do the same into a for loop. I've not used Three.js library, but I suppose that it has be something like this:
var NUM_POINTS = 20;
var dots = []; //If you want to use for other task
for (var i = 0 ; i < NUM_POINTS){
var x = Math.random() * (80 - 1) + 1
var y = Math.random() * (80 - 1) + 1
var z = Math.random() * (80 - 1) + 1
var dotGeometry = new THREE.Geometry();
dots.push(dotGeometry);
dotGeometry.vertices.push(new THREE.Vector3( x, y, z));
var dotMaterial = new THREE.PointCloudMaterial( { size: 10, sizeAttenuation: false, color: 0xFF0000 } );
var dot = new THREE.PointCloud( dotGeometry, dotMaterial );
scene.add(dot);
}
More info about for loop here
Good luck!

three.js r74 BufferGeometry Sphere Instead of Squares

I have the following code:
var geometry = new THREE.BufferGeometry(4, 4, 4);
var length = parsedData.length;
var positions = new Float32Array(length * 3);
var colors = new Float32Array(length * 3);
var color = new THREE.Color();
var i = 0;
for (i; i < length * 3; i += 3) {
var index = Math.floor(i / 3);
positions[i] = parsedData[index].vector.x;
positions[i + 1] = parsedData[index].vector.y;
positions[i + 2] = parsedData[index].vector.z;
color.setHex(parsedData[index].color);
colors[i] = color.r;
colors[i + 1] = color.g;
colors[i + 2] = color.b;
}
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3));
var material = new THREE.PointsMaterial({vertexColors: THREE.VertexColors});
var particleSystem = new THREE.Points(geometry, material);
scene.add(particleSystem);
render();
Which will render cubes quickly and fine, but I need spheres. Could anyone explain to me how that works with Revision 74? I have found this question, however it isn't working anymore.
Thanks!

THREE.js generate UV coordinate

I am working on importing a model into a scene using the THREE.js OBJ loader.
I know that I am able to import the geometry fine, because when I assign a MeshNormalMaterial to it, it shows up great. However, if I use anything that requires UV coordinates, It gives me the error:
[.WebGLRenderingContext]GL ERROR :GL_INVALID_OPERATION : glDrawElements: attempt to access out of range vertices in attribute 1
I know this is because the loaded OBJ has no UV coordinates, but I was wondering if there was any way to generate the needed texture coordinates. I have tried
material.needsUpdate = true;
geometry.uvsNeedUpdate = true;
geometry.buffersNeedUpdate = true;
...but to no avail.
Is there any way to automagically generate UV textures using three.js, or do I have to assign the coordinates myself?
To my knowledge there is no automatic way to calculate UV.
You must calculate yourself. Calculate a UV for a plane is quite easy, this site explains how: calculating texture coordinates
For a complex shape, I don't know how. Maybe you could detect planar surface.
EDIT
Here is a sample code for a planar surface (x, y, z) where z = 0:
geometry.computeBoundingBox();
var max = geometry.boundingBox.max,
min = geometry.boundingBox.min;
var offset = new THREE.Vector2(0 - min.x, 0 - min.y);
var range = new THREE.Vector2(max.x - min.x, max.y - min.y);
var faces = geometry.faces;
geometry.faceVertexUvs[0] = [];
for (var i = 0; i < faces.length ; i++) {
var v1 = geometry.vertices[faces[i].a],
v2 = geometry.vertices[faces[i].b],
v3 = geometry.vertices[faces[i].c];
geometry.faceVertexUvs[0].push([
new THREE.Vector2((v1.x + offset.x)/range.x ,(v1.y + offset.y)/range.y),
new THREE.Vector2((v2.x + offset.x)/range.x ,(v2.y + offset.y)/range.y),
new THREE.Vector2((v3.x + offset.x)/range.x ,(v3.y + offset.y)/range.y)
]);
}
geometry.uvsNeedUpdate = true;
The other answers here were a great help but didn't quite fit my requirements to apply a repeating pattern texture to all sides of a shape with mostly flat surfaces. The problem is that using only the x and y components as u and v results in weird stretched textures on vertical surfaces.
My solution below uses surface normals to pick which two components (x, y and z) to map to u and v. It's still pretty crude but it works quite well.
function assignUVs(geometry) {
geometry.faceVertexUvs[0] = [];
geometry.faces.forEach(function(face) {
var components = ['x', 'y', 'z'].sort(function(a, b) {
return Math.abs(face.normal[a]) > Math.abs(face.normal[b]);
});
var v1 = geometry.vertices[face.a];
var v2 = geometry.vertices[face.b];
var v3 = geometry.vertices[face.c];
geometry.faceVertexUvs[0].push([
new THREE.Vector2(v1[components[0]], v1[components[1]]),
new THREE.Vector2(v2[components[0]], v2[components[1]]),
new THREE.Vector2(v3[components[0]], v3[components[1]])
]);
});
geometry.uvsNeedUpdate = true;
}
This function doesn't normalise the UVs to the size of the object. This works better when applying the same texture to different sized objects in the same scene. However depending on the size of your world coordinate system, you'll probably need to scale and repeat the texture as well:
texture.repeat.set(0.1, 0.1);
texture.wrapS = texture.wrapT = THREE.MirroredRepeatWrapping;
Box UV mapping is possibly most useful thing in three.js configurators of any sort, -
https://jsfiddle.net/mmalex/pcjbysn1/
The solution works per face both with indexed and non-indexed buffer geometries.
Example of usage:
//build some mesh
var bufferGeometry = new THREE.BufferGeometry().fromGeometry(new THREE.DodecahedronGeometry(2.5, 0));
let material = new THREE.MeshPhongMaterial({
color: 0x10f0f0,
map: new THREE.TextureLoader().load('http://mbnsay.com/rayys/images/1K_UV_checker.jpg')
});
//find out the dimensions, to let texture size 100% fit without stretching
bufferGeometry.computeBoundingBox();
let bboxSize = bufferGeometry.boundingBox.getSize();
let uvMapSize = Math.min(bboxSize.x, bboxSize.y, bboxSize.z);
//calculate UV coordinates, if uv attribute is not present, it will be added
applyBoxUV(bufferGeometry, new THREE.Matrix4().getInverse(cube.matrix), uvMapSize);
//let three.js know
bufferGeometry.attributes.uv.needsUpdate = true;
The example is based on the following implementation of applyBoxUV
function _applyBoxUV(geom, transformMatrix, bbox, bbox_max_size) {
let coords = [];
coords.length = 2 * geom.attributes.position.array.length / 3;
// geom.removeAttribute('uv');
if (geom.attributes.uv === undefined) {
geom.addAttribute('uv', new THREE.Float32BufferAttribute(coords, 2));
}
//maps 3 verts of 1 face on the better side of the cube
//side of the cube can be XY, XZ or YZ
let makeUVs = function(v0, v1, v2) {
//pre-rotate the model so that cube sides match world axis
v0.applyMatrix4(transformMatrix);
v1.applyMatrix4(transformMatrix);
v2.applyMatrix4(transformMatrix);
//get normal of the face, to know into which cube side it maps better
let n = new THREE.Vector3();
n.crossVectors(v1.clone().sub(v0), v1.clone().sub(v2)).normalize();
n.x = Math.abs(n.x);
n.y = Math.abs(n.y);
n.z = Math.abs(n.z);
let uv0 = new THREE.Vector2();
let uv1 = new THREE.Vector2();
let uv2 = new THREE.Vector2();
// xz mapping
if (n.y > n.x && n.y > n.z) {
uv0.x = (v0.x - bbox.min.x) / bbox_max_size;
uv0.y = (bbox.max.z - v0.z) / bbox_max_size;
uv1.x = (v1.x - bbox.min.x) / bbox_max_size;
uv1.y = (bbox.max.z - v1.z) / bbox_max_size;
uv2.x = (v2.x - bbox.min.x) / bbox_max_size;
uv2.y = (bbox.max.z - v2.z) / bbox_max_size;
} else
if (n.x > n.y && n.x > n.z) {
uv0.x = (v0.z - bbox.min.z) / bbox_max_size;
uv0.y = (v0.y - bbox.min.y) / bbox_max_size;
uv1.x = (v1.z - bbox.min.z) / bbox_max_size;
uv1.y = (v1.y - bbox.min.y) / bbox_max_size;
uv2.x = (v2.z - bbox.min.z) / bbox_max_size;
uv2.y = (v2.y - bbox.min.y) / bbox_max_size;
} else
if (n.z > n.y && n.z > n.x) {
uv0.x = (v0.x - bbox.min.x) / bbox_max_size;
uv0.y = (v0.y - bbox.min.y) / bbox_max_size;
uv1.x = (v1.x - bbox.min.x) / bbox_max_size;
uv1.y = (v1.y - bbox.min.y) / bbox_max_size;
uv2.x = (v2.x - bbox.min.x) / bbox_max_size;
uv2.y = (v2.y - bbox.min.y) / bbox_max_size;
}
return {
uv0: uv0,
uv1: uv1,
uv2: uv2
};
};
if (geom.index) { // is it indexed buffer geometry?
for (let vi = 0; vi < geom.index.array.length; vi += 3) {
let idx0 = geom.index.array[vi];
let idx1 = geom.index.array[vi + 1];
let idx2 = geom.index.array[vi + 2];
let vx0 = geom.attributes.position.array[3 * idx0];
let vy0 = geom.attributes.position.array[3 * idx0 + 1];
let vz0 = geom.attributes.position.array[3 * idx0 + 2];
let vx1 = geom.attributes.position.array[3 * idx1];
let vy1 = geom.attributes.position.array[3 * idx1 + 1];
let vz1 = geom.attributes.position.array[3 * idx1 + 2];
let vx2 = geom.attributes.position.array[3 * idx2];
let vy2 = geom.attributes.position.array[3 * idx2 + 1];
let vz2 = geom.attributes.position.array[3 * idx2 + 2];
let v0 = new THREE.Vector3(vx0, vy0, vz0);
let v1 = new THREE.Vector3(vx1, vy1, vz1);
let v2 = new THREE.Vector3(vx2, vy2, vz2);
let uvs = makeUVs(v0, v1, v2, coords);
coords[2 * idx0] = uvs.uv0.x;
coords[2 * idx0 + 1] = uvs.uv0.y;
coords[2 * idx1] = uvs.uv1.x;
coords[2 * idx1 + 1] = uvs.uv1.y;
coords[2 * idx2] = uvs.uv2.x;
coords[2 * idx2 + 1] = uvs.uv2.y;
}
} else {
for (let vi = 0; vi < geom.attributes.position.array.length; vi += 9) {
let vx0 = geom.attributes.position.array[vi];
let vy0 = geom.attributes.position.array[vi + 1];
let vz0 = geom.attributes.position.array[vi + 2];
let vx1 = geom.attributes.position.array[vi + 3];
let vy1 = geom.attributes.position.array[vi + 4];
let vz1 = geom.attributes.position.array[vi + 5];
let vx2 = geom.attributes.position.array[vi + 6];
let vy2 = geom.attributes.position.array[vi + 7];
let vz2 = geom.attributes.position.array[vi + 8];
let v0 = new THREE.Vector3(vx0, vy0, vz0);
let v1 = new THREE.Vector3(vx1, vy1, vz1);
let v2 = new THREE.Vector3(vx2, vy2, vz2);
let uvs = makeUVs(v0, v1, v2, coords);
let idx0 = vi / 3;
let idx1 = idx0 + 1;
let idx2 = idx0 + 2;
coords[2 * idx0] = uvs.uv0.x;
coords[2 * idx0 + 1] = uvs.uv0.y;
coords[2 * idx1] = uvs.uv1.x;
coords[2 * idx1 + 1] = uvs.uv1.y;
coords[2 * idx2] = uvs.uv2.x;
coords[2 * idx2 + 1] = uvs.uv2.y;
}
}
geom.attributes.uv.array = new Float32Array(coords);
}
function applyBoxUV(bufferGeometry, transformMatrix, boxSize) {
if (transformMatrix === undefined) {
transformMatrix = new THREE.Matrix4();
}
if (boxSize === undefined) {
let geom = bufferGeometry;
geom.computeBoundingBox();
let bbox = geom.boundingBox;
let bbox_size_x = bbox.max.x - bbox.min.x;
let bbox_size_z = bbox.max.z - bbox.min.z;
let bbox_size_y = bbox.max.y - bbox.min.y;
boxSize = Math.max(bbox_size_x, bbox_size_y, bbox_size_z);
}
let uvBbox = new THREE.Box3(new THREE.Vector3(-boxSize / 2, -boxSize / 2, -boxSize / 2), new THREE.Vector3(boxSize / 2, boxSize / 2, boxSize / 2));
_applyBoxUV(bufferGeometry, transformMatrix, uvBbox, boxSize);
}
The answers here are brilliant and helped me a lot.
Only one thing: If you are updating vertices, do not re-assign uvs, but set them, as in (scope is my geometry):
scope.updateUVs = (copy=true) => {
scope.computeBoundingBox();
var max = scope.boundingBox.max;
var min = scope.boundingBox.min;
var offset = new THREE.Vector2(0 - min.x, 0 - min.y);
var range = new THREE.Vector2(max.x - min.x, max.y - min.y);
if (!copy) {
scope.faceVertexUvs[0] = [];
}
var faces = scope.faces;
for (i = 0; i < scope.faces.length ; i++) {
var v1 = scope.vertices[faces[i].a];
var v2 = scope.vertices[faces[i].b];
var v3 = scope.vertices[faces[i].c];
var uv0 = new THREE.Vector2( ( v1.x + offset.x ) / range.x , ( v1.y + offset.y ) / range.y );
var uv1 = new THREE.Vector2( ( v2.x + offset.x ) / range.x , ( v2.y + offset.y ) / range.y );
var uv2 = new THREE.Vector2( ( v3.x + offset.x ) / range.x , ( v3.y + offset.y ) / range.y );
if (copy) {
var uvs =scope.faceVertexUvs[0][i];
uvs[0].copy(uv0);
uvs[1].copy(uv1);
uvs[2].copy(uv2);
} else {
scope.faceVertexUvs[0].push([uv0, uv1, uv2]);
}
}
scope.uvsNeedUpdate = true;
}
This is a general version that works for spherical mapping (yaw, pitch coordinates), see example here, (look at loadSuzanne function):
function assignUVs(geometry) {
geometry.faceVertexUvs[0] = [];
geometry.faces.forEach(function(face) {
var uvs = [];
var ids = [ 'a', 'b', 'c'];
for( var i = 0; i < ids.length; i++ ) {
var vertex = geometry.vertices[ face[ ids[ i ] ] ].clone();
var n = vertex.normalize();
var yaw = .5 - Math.atan( n.z, - n.x ) / ( 2.0 * Math.PI );
var pitch = .5 - Math.asin( n.y ) / Math.PI;
var u = yaw,
v = pitch;
uvs.push( new THREE.Vector2( u, v ) );
}
geometry.faceVertexUvs[ 0 ].push( uvs );
});
geometry.uvsNeedUpdate = true;
}

Categories

Resources