Rotation animation on another axis - three.js

I'm trying to rotate objects on a different axis than the default one, and with animation.
Here is actually how i am doing it. You can click on buttons L and Li that actually work. But as you can see for the R button the axis of rotation should be changed. I can't figure how to do this. Am I doing things right about this kind of rotation animation ?
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(-2, 1, 3);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight - 60); // -60 to see buttons below
const divid = document.getElementById('myid');
divid.appendChild(renderer.domElement);
// show axis
scene.add(new THREE.AxesHelper(2))
let controls = new THREE.OrbitControls(camera, renderer.domElement);
// one triangle equilateral
const sideLength = 1
const x = 0
const y = 0
const geometry = new THREE.Geometry()
geometry.vertices.push(new THREE.Vector3(x, (Math.sqrt(3) / 2 * sideLength) - (sideLength / 2), 0))
geometry.vertices.push(new THREE.Vector3(x - (sideLength / 2), y - (sideLength / 2), 0))
geometry.vertices.push(new THREE.Vector3(x + (sideLength / 2), y - (sideLength / 2), 0))
geometry.faces.push(new THREE.Face3(0, 1, 2))
const facesColors = [
0xFFFF00, // yellow
0xFF0000, // red
0x0000FF, // blue
0x008000 // green
]
const pos = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 3, 1, 1, 1, 2, 2, 2, 3, 3, 3, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3
]
const d = 0.05 // écart entre les triangles
let face
const facesVectors = [
[0, 1, 2],
[0, 2, 3],
[0, 3, 1],
[1, 3, 2]
]
// https://stackoverflow.com/questions/60774560/drawing-a-pyraminx-with-triangles
// coords des points d'un tétrahèdre
const s89 = Math.sqrt(8 / 9)
const s29 = Math.sqrt(2 / 9)
const s23 = Math.sqrt(2 / 3)
const v = [
new THREE.Vector3(0, 0, 1),
new THREE.Vector3(s89, 0, -1 / 3),
new THREE.Vector3(-s29, s23, -1 / 3),
new THREE.Vector3(-s29, -s23, -1 / 3)
]
const computeMidPts = (pts) => {
const midPts = []
for (let i = 0; i < pts.length; ++i) {
midPts.push(new THREE.Vector3().lerpVectors(pts[i], pts[(i + 1) % 3], 0.5))
}
return midPts
}
const computeInnerPts = (pts, d) => {
const innerPts = []
for (let i = 0; i < pts.length; ++i) {
const va = new THREE.Vector3().lerpVectors(pts[i], pts[(i + 1) % 3], d)
const vb = new THREE.Vector3().lerpVectors(pts[i], pts[(i + 2) % 3], d)
innerPts.push(new THREE.Vector3().lerpVectors(va, vb, 0.5))
}
return innerPts
}
let allfaces = [];
for (let i = 0; i < 4; ++i) {
const pts = [v[facesVectors[i][0]], v[facesVectors[i][1]], v[facesVectors[i][2]]]
const outerPts = computeInnerPts(pts, d)
for (let j = 0; j < 3; ++j) {
const geometry = new THREE.Geometry()
geometry.vertices.push(outerPts[j])
geometry.vertices.push(new THREE.Vector3().lerpVectors(outerPts[j], outerPts[(j + 1) % 3], 0.5 - d / 2))
geometry.vertices.push(new THREE.Vector3().lerpVectors(outerPts[j], outerPts[(j + 2) % 3], 0.5 - d / 2))
geometry.faces.push(new THREE.Face3(0, 1, 2))
const material = new THREE.MeshBasicMaterial({ color: facesColors[i] })
face = new THREE.Mesh(geometry, material)
allfaces.push(face)
scene.add(face)
}
const midPts = computeMidPts(outerPts)
const innerPts = computeInnerPts(midPts, d / 2)
const geometry = new THREE.Geometry()
geometry.vertices.push(...innerPts)
geometry.faces.push(new THREE.Face3(0, 1, 2))
const material = new THREE.MeshBasicMaterial({ color: facesColors[i] })
face = new THREE.Mesh(geometry, material)
allfaces.push(face)
scene.add(face)
}
let movingL = false
let movingLi = false
let movingR = false
let pivot = null
renderer.setAnimationLoop(() => {
if (movingL) {
pivot.rotation.z += 0.05
if (pivot.rotation.z >= (2 * Math.PI) / 3) {
movingL = false
}
} else if (movingLi) {
pivot.rotation.z -= 0.05
if (pivot.rotation.z <= -(2 * Math.PI) / 3) {
movingLi = false
}
} else if (movingR) {
pivot.rotation.x -= 0.05
if (pivot.rotation.x <= -(2 * Math.PI) / 3) {
movingR = false
}
}
renderer.render(scene, camera);
});
document.getElementById("L").addEventListener("click", function(){
pivot = new THREE.Group();
scene.add(pivot);
// faces to move
pivot.attach(allfaces[0]);
pivot.attach(allfaces[4]);
pivot.attach(allfaces[8]);
movingL = true
});
document.getElementById("Li").addEventListener("click", function(){
pivot = new THREE.Group();
scene.add(pivot);
pivot.attach(allfaces[0]);
pivot.attach(allfaces[4]);
pivot.attach(allfaces[8]);
movingLi = true
});
document.getElementById("R").addEventListener("click", function(){
pivot = new THREE.Group();
scene.add(pivot);
pivot.attach(allfaces[1]);
pivot.attach(allfaces[10]);
pivot.attach(allfaces[12]);
movingR = true
});
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div id="myid"></div>
<button id="L">L</button>
<button id="Li">Li</button>
<button id="R">R</button>
<button id="Ri">Ri</button>
...

You're going to have to use Quaternions, which are a much more robust method to apply rotations to objects.
Quaternions have a method called .setFromAxisAngle() that you can use to set whatever axis of rotation you want.
In the example below, each time you click a button, I set the axis of rotation and reset the angle of rotation with:
axisVector.set(x, y, z).normalize();
quatAngle = 0;
(normalize ensures the axis always has a total length of 1. Using an axis vector of (2, 0, 0) would break the rotation)
Once you have your axis established, you can rotate the pivot with
quatAngle += 0.05;
pivot.quaternion.setFromAxisAngle(axisVector, quatAngle);
I wasn't exactly sure where the axis of rotation is for R or U, so I just visually estimated them at (1, 0, -0.33) and (-0.4, 0.7, -0.3), then I used .normalize() to make its length 1.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(-2, 1, 3);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight - 60); // -60 to see buttons below
const divid = document.getElementById('myid');
divid.appendChild(renderer.domElement);
// show axis
scene.add(new THREE.AxesHelper(2))
let controls = new THREE.OrbitControls(camera, renderer.domElement);
// one triangle equilateral
const sideLength = 1
const x = 0
const y = 0
const geometry = new THREE.Geometry()
geometry.vertices.push(new THREE.Vector3(x, (Math.sqrt(3) / 2 * sideLength) - (sideLength / 2), 0))
geometry.vertices.push(new THREE.Vector3(x - (sideLength / 2), y - (sideLength / 2), 0))
geometry.vertices.push(new THREE.Vector3(x + (sideLength / 2), y - (sideLength / 2), 0))
geometry.faces.push(new THREE.Face3(0, 1, 2))
const facesColors = [
0xFFFF00, // yellow
0xFF0000, // red
0x0000FF, // blue
0x008000 // green
]
const pos = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 3, 1, 1, 1, 2, 2, 2, 3, 3, 3, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3
]
const d = 0.05 // écart entre les triangles
let face
const facesVectors = [
[0, 1, 2],
[0, 2, 3],
[0, 3, 1],
[1, 3, 2]
]
// https://stackoverflow.com/questions/60774560/drawing-a-pyraminx-with-triangles
// coords des points d'un tétrahèdre
const s89 = Math.sqrt(8 / 9)
const s29 = Math.sqrt(2 / 9)
const s23 = Math.sqrt(2 / 3)
const v = [
new THREE.Vector3(0, 0, 1),
new THREE.Vector3(s89, 0, -1 / 3),
new THREE.Vector3(-s29, s23, -1 / 3),
new THREE.Vector3(-s29, -s23, -1 / 3)
]
const computeMidPts = (pts) => {
const midPts = []
for (let i = 0; i < pts.length; ++i) {
midPts.push(new THREE.Vector3().lerpVectors(pts[i], pts[(i + 1) % 3], 0.5))
}
return midPts
}
const computeInnerPts = (pts, d) => {
const innerPts = []
for (let i = 0; i < pts.length; ++i) {
const va = new THREE.Vector3().lerpVectors(pts[i], pts[(i + 1) % 3], d)
const vb = new THREE.Vector3().lerpVectors(pts[i], pts[(i + 2) % 3], d)
innerPts.push(new THREE.Vector3().lerpVectors(va, vb, 0.5))
}
return innerPts
}
let allfaces = [];
for (let i = 0; i < 4; ++i) {
const pts = [v[facesVectors[i][0]], v[facesVectors[i][1]], v[facesVectors[i][2]]]
const outerPts = computeInnerPts(pts, d)
for (let j = 0; j < 3; ++j) {
const geometry = new THREE.Geometry()
geometry.vertices.push(outerPts[j])
geometry.vertices.push(new THREE.Vector3().lerpVectors(outerPts[j], outerPts[(j + 1) % 3], 0.5 - d / 2))
geometry.vertices.push(new THREE.Vector3().lerpVectors(outerPts[j], outerPts[(j + 2) % 3], 0.5 - d / 2))
geometry.faces.push(new THREE.Face3(0, 1, 2))
const material = new THREE.MeshBasicMaterial({ color: facesColors[i] })
face = new THREE.Mesh(geometry, material)
allfaces.push(face)
scene.add(face)
}
const midPts = computeMidPts(outerPts)
const innerPts = computeInnerPts(midPts, d / 2)
const geometry = new THREE.Geometry()
geometry.vertices.push(...innerPts)
geometry.faces.push(new THREE.Face3(0, 1, 2))
const material = new THREE.MeshBasicMaterial({ color: facesColors[i] })
face = new THREE.Mesh(geometry, material)
allfaces.push(face)
scene.add(face)
}
let movingL = false
let movingLi = false
let movingR = false
let movingU = false
let pivot = null
let quatAngle = 0;
let axisVector = new THREE.Vector3();
renderer.setAnimationLoop(() => {
if (movingL) {
quatAngle += 0.05
pivot.quaternion.setFromAxisAngle(axisVector, quatAngle);
if (quatAngle >= (2 * Math.PI) / 3) {
movingL = false
}
} else if (movingLi) {
quatAngle -= 0.05
pivot.quaternion.setFromAxisAngle(axisVector, quatAngle);
if (quatAngle <= -(2 * Math.PI) / 3) {
movingLi = false
}
} else if (movingR) {
quatAngle -= 0.05
pivot.quaternion.setFromAxisAngle(axisVector, quatAngle);
if (quatAngle <= -(2 * Math.PI) / 3) {
movingR = false
}
} else if (movingU) {
quatAngle -= 0.05
pivot.quaternion.setFromAxisAngle(axisVector, quatAngle);
if (quatAngle <= -(2 * Math.PI) / 3) {
movingU = false
}
}
renderer.render(scene, camera);
});
document.getElementById("L").addEventListener("click", function(){
pivot = new THREE.Group();
axisVector.set(0, 0, 1);
quatAngle = 0;
scene.add(pivot);
// faces to move
pivot.attach(allfaces[0]);
pivot.attach(allfaces[4]);
pivot.attach(allfaces[8]);
movingL = true
});
document.getElementById("Li").addEventListener("click", function(){
pivot = new THREE.Group();
axisVector.set(0, 0, 1);
quatAngle = 0;
scene.add(pivot);
pivot.attach(allfaces[0]);
pivot.attach(allfaces[4]);
pivot.attach(allfaces[8]);
movingLi = true
});
document.getElementById("R").addEventListener("click", function(){
pivot = new THREE.Group();
axisVector.set(1, 0, -0.33).normalize();
quatAngle = 0;
scene.add(pivot);
pivot.attach(allfaces[1]);
pivot.attach(allfaces[10]);
pivot.attach(allfaces[12]);
movingR = true
});
document.getElementById("U").addEventListener("click", function(){
pivot = new THREE.Group();
axisVector.set(-0.4, 0.7, -0.3).normalize();
quatAngle = 0;
scene.add(pivot);
pivot.attach(allfaces[2]);
pivot.attach(allfaces[5]);
pivot.attach(allfaces[14]);
movingU = true
});
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div id="myid"></div>
<button id="L">L</button>
<button id="Li">Li</button>
<button id="R">R</button>
<button id="U">U</button>

Related

Is there any way to make a quad sphere with BufferGeometry?

I am new to three.js and I want to build a quad sphere with the BufferGeometry(). I want to recreate sebastin lague cube sphere and implementing in three js.
Here is my TerrainFace.js code:
class {
constructor(resolution, radius, localUp, scene) {
this._resolution = resolution;
this._radius = radius;
this._scene = scene;
this._localUp = localUp;
this._axisA = new THREE.Vector3(localUp.y, localUp.z, localUp.x);
this._axisB = new THREE.Vector3().crossVectors(this._localUp, this._axisA);
this._geometry = new THREE.BufferGeometry();
}
Start() {
this._Initialize();
let plane = new THREE.Mesh(this._geometry, new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true }));
this._scene.add(plane);
}
_Initialize() {
let i;
let triIndex = 0;
this._positions = [];
this._normals = [];
this._indices = [];
for (let y = 0; y < this._resolution; y++) {
for (let x = 0; x < this._resolution; x++) {
i = x + (y * this._resolution);
let xPercent = x / (this._resolution - 1);
let yPercent = y / (this._resolution - 1);
let _P = new THREE.Vector3();
let _C = new THREE.Vector3();
let _A = new THREE.Vector3(this._axisA.x, this._axisA.y, this._axisA.z).multiplyScalar(2 * (xPercent - 0.5));
let _B = new THREE.Vector3(this._axisB.x, this._axisB.y, this._axisB.z).multiplyScalar(2 * (yPercent - 0.5));
_C.addVectors(this._localUp, _A);
_P.addVectors(_C, _B);
// _P.normalize(); // this is for cube sphere
_P.multiplyScalar(this._radius);
this._positions[i] = _P.x;
this._positions[i + 1] = _P.y;
this._positions[i + 2] = _P.z;
let ad = new THREE.Mesh(new THREE.SphereGeometry(0.1), new THREE.MeshNormalMaterial({ side: THREE.DoubleSide }));
ad.position.set(this._positions[i], this._positions[i + 1], this._positions[i + 2]);
this._scene.add(ad);
if (x != (this._resolution - 1) && y != (this._resolution - 1)) {
const a = i;
const b = i + 1;
const c = i + this._resolution;
const d = i = this._resolution + 1;
// a - - b
// | |
// | |
// c - - d
this._indices[triIndex] = a;
this._indices[triIndex + 1] = d;
this._indices[triIndex + 2] = c;
this._indices[triIndex + 3] = a;
this._indices[triIndex + 4] = b;
this._indices[triIndex + 5] = d;
triIndex += 6;
}
}
}
this._geometry.setIndex(this._indices);
this._geometry.setAttribute("position", new THREE.Float32BufferAttribute(this._positions, 3));
}
}
This is the basic scene
let groups = new THREE.Group();
let directions = [
new THREE.Vector3(0, 1, 0),
new THREE.Vector3(0, -1, 0),
new THREE.Vector3(1, 0, 0),
new THREE.Vector3(-1, 0, 0),
new THREE.Vector3(0, 0, 1),
new THREE.Vector3(0, 0, -1),
]
for (let i = 0; i < directions.length; i++) {
let plane = new TerrainFace1(2, 10, directions[i], groups);
plane.Start();
}
this._scene.add(groups);
for some reason It is not working and it is creating some weird shapes and I dont know why. Thanks.
Follow SebastianLague sphere generation.

Change edges/triangulation of plane geometry

I am trying to edit the edges/triangulation of a planebuffer geometry in three js.
Example of problem
I want to change the corners in red to be triangulated like the corner in green.
Is this possible via editing a plane, or do I have to start building my own custom mesh buffer.
Here is a fiddle for an example of the kind of manipulation I am doing on the plane. (can click and drag to rotate it)
var camera, scene, renderer;
var geometry, material, mesh;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
camera.position.z = 5;
scene = new THREE.Scene();
geometry = new THREE.PlaneBufferGeometry( 10, 10, 10, 10 );
const pointLight = new THREE.PointLight(0xFFFFFF, 1);
const ambientLight = new THREE.AmbientLight(0xFFFFFF, 0.1);
pointLight.position.y += 2;
pointLight.position.x = -1;
scene.add(pointLight);
scene.add(ambientLight);
const grid = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 2, 2, 2, 2, 2, 1, 1, 0],
[0, 1, 1, 2, 2, 2, 2, 2, 1, 1, 0],
[0, 1, 1, 2, 2, 2, 2, 2, 1, 1, 0],
[0, 1, 1, 2, 2, 2, 2, 2, 1, 1, 0],
[0, 1, 1, 2, 2, 2, 2, 2, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
const planePos = geometry.attributes.position;
for (let x = 0; x < grid.length; x++) {
const row = grid[x];
for (let y = 0; y < row.length; y++) {
const h = row[y];
const i = (x * row.length) + y;
planePos.setZ(i, h * 0.5);
}
}
//
material = new THREE.MeshPhongMaterial({flatShading:true});
mesh = new THREE.Mesh( geometry, material );
mesh.rotateX(Math.PI * -0.5);
mesh.translateZ(-1);
scene.add( mesh );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var controls = new THREE.OrbitControls( camera, renderer.domElement );
}
function animate(t) {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
body {
margin: 0;
}
<script src="https://threejs.org/build/three.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
Ok, as #prisoner849 commented, buffergeometryies index property was the answer.
I redid the triangulation of the mesh by looping over the "squares" of my mesh, getting their corners and then changing the indexes for each squares triangles to one of a pre calculated set, based on which corner had a unique height. It also seems that you want the indexes to go in a specific order to control which side is "up", it seems to be that counter clockwise or so.
const size = 10;
const vertMap = [0, 3, 1, 3, 2, 1];
const triSets = [
[0, 3, 2, 0, 2, 1],
[1, 3, 2, 1, 0, 3],
[0, 3, 2, 0, 2, 1],
[1, 3, 2, 1, 0, 3],
];
const getSquareVerts = (x, y) => {
const i = (y * size) + x + y;
return [i, i + 1, i + (size + 2), i + (size + 1)];
}
const getUniqueHeightPoint = (verts) => {
let unique = -1;
const set = verts.reduce((p, n) => {
const c = p[n.toString()] || 0;
p[n.toString()] = c + 1;
return p;
}, {});
const keys = Object.keys(set);
if (keys.length == 2 && (set[keys[0]] == 1 || set[keys[0]] == 3)) {
for (const key of keys) {
if (set[key] == 1) {
unique = verts.findIndex((v) => v == parseFloat(key));
break;
}
}
}
return unique;
}
const reworkTris = (plane) => {
let indexOffset = 0;
const planeIdx = plane.geometry.index;
for (let y = 0; y < this.size; y++) {
for (let x = 0; x < this.size; x++) {
const verts = this.getSquareVerts(x, y);
const heights = verts.map((v) => this.planePos.getZ(v));
const uniqueHeight = this.getUniqueHeightPoint(heights);
let tris = vertMap.map((i) => verts[i]);
if (uniqueHeight >= 0) {
tris = triSets[uniqueHeight].map((i) => verts[i]);
}
planeIdx.set(tris, indexOffset);
indexOffset += 6;
}
}
planeIdx.needsUpdate = true;
}
const plane = new THREE.Mesh(new THREE.PlaneBufferGeometry());
reqorkTris(plane);

How to show only edge lines in three.js?

I want to show lines only on the edges. Here I have included my output model which I tried using edgeGeometry and LinebasicMaterial. I want to remove the inner edge lines and show only outline edges
You can use EdgesGeometry
You pass it some other geometry and a threshold angle
// only show edges with 15 degrees or more angle between faces
const thresholdAngle = 15;
const lineGeometry = new THREE.EdgesGeometry(geometry, thresholdAngle));
'use strict';
/* global THREE */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 40;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 20;
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAAA);
let solidMesh;
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(1, -2, -4);
scene.add(light);
}
const objects = [];
const spread = 15;
function addObject(x, y, obj) {
obj.position.x = x * spread;
obj.position.y = y * spread;
scene.add(obj);
objects.push(obj);
}
function createMaterial() {
const material = new THREE.MeshPhongMaterial({
side: THREE.DoubleSide,
});
const hue = Math.random();
const saturation = 1;
const luminance = .5;
material.color.setHSL(hue, saturation, luminance);
return material;
}
function addSolidGeometry(x, y, geometry) {
const mesh = new THREE.Mesh(geometry, createMaterial());
addObject(x, y, mesh);
return mesh;
}
function addLineGeometry(x, y, geometry) {
const material = new THREE.LineBasicMaterial({color: 0x000000});
const mesh = new THREE.LineSegments(geometry, material);
addObject(x, y, mesh);
return mesh;
}
{
const shape = new THREE.Shape();
const x = -2.5;
const y = -5;
shape.moveTo(x + 2.5, y + 2.5);
shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
const extrudeSettings = {
steps: 2,
depth: 2,
bevelEnabled: true,
bevelThickness: 1,
bevelSize: 1,
bevelSegments: 2,
};
const geometry = new THREE.ExtrudeBufferGeometry(shape, extrudeSettings);
solidMesh = addSolidGeometry(0, 0, geometry);
const thresholdAngle = 15;
addLineGeometry(0, 0, new THREE.EdgesGeometry(geometry, thresholdAngle));
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
solidMesh.visible = (time | 0) % 2 !== 0;
objects.forEach((obj, ndx) => {
const speed = .1 + ndx * .0;
const rot = time * speed;
obj.rotation.x = rot;
obj.rotation.y = rot;
});
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r108/build/three.min.js"></script>

Groups & THREE.MultiMaterial

As part of prototyping some debugger objects and methods, I created a simple cube with a different color on each side. Rather than creating multiple meshes, I decided to use draw groups and THREE.Multimaterial. Unfortunately, it's only half-working, and I don't understand why.
What IS working is the first two sides (4 triangles). What's NOT working is any of the other sides. None of the other sides are visible. I've added debug code to draw the (index-based) vertex normals, the (index-based, manually calculated) face normals, and the wireframe (THREE.WireframeHelper).
If I increase the count for the second group to 9, nothing happens. If I change the groups to reference different start/count values within the first 8 vertices, those changes work as expected. Doing anything above 8th vertex has no effect.
I've also checked that the drawRange is set to draw from 0 to Infinity. I've also ruled out typos in my data, because otherwise the normals and wireframe wouldn't work. Is there anything else I'm missing? Thanks!
(I found the issue in r76. Code below referenced r79 at the time of writing.)
jsfiddle: http://jsfiddle.net/TheJim01/gumftkm4/
HTML:
<script src="http://threejs.org/build/three.js"></script>
<script src="http://threejs.org/examples/js/controls/TrackballControls.js"></script>
<script src="http://threejs.org/examples/js/libs/stats.min.js"></script>
<div id="host"></div>
<script>
// INITIALIZE
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(WIDTH, HEIGHT);
document.getElementById('host').appendChild(renderer.domElement);
var stats= new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0';
document.body.appendChild(stats.domElement);
var camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 250;
var trackballControl = new THREE.TrackballControls(camera, renderer.domElement);
trackballControl.rotateSpeed = 5.0; // need to speed it up a little
var scene = new THREE.Scene();
var light = new THREE.PointLight(0xffffff, 1, Infinity);
light.position.copy(camera.position);
scene.add(light);
function draw(){
light.position.copy(camera.position);
renderer.render(scene, camera);
stats.update();
}
trackballControl.addEventListener('change', draw);
function navStartHandler(e) {
renderer.domElement.addEventListener('mousemove', navMoveHandler);
renderer.domElement.addEventListener('mouseup', navEndHandler);
}
function navMoveHandler(e) {
trackballControl.update();
}
function navEndHandler(e) {
renderer.domElement.removeEventListener('mousemove', navMoveHandler);
renderer.domElement.removeEventListener('mouseup', navEndHandler);
}
renderer.domElement.addEventListener('mousedown', navStartHandler);
renderer.domElement.addEventListener('mousewheel', navMoveHandler);
</script>
CSS:
html *{
padding: 0;
margin: 0;
width: 100%;
overflow: hidden;
}
#host {
width: 100%;
height: 100%;
}
JavaScript:
// New Color Cube
(function () {
var pos = new Float32Array([
// front
-1, 1, 1,
-1, -1, 1,
1, 1, 1,
1, -1, 1,
// right
1, 1, 1,
1, -1, 1,
1, 1, -1,
1, -1, -1,
// back
1, 1, -1,
1, -1, -1,
-1, 1, -1,
-1, -1, -1,
// left
-1, 1, -1,
-1, -1, -1,
-1, 1, 1,
-1, -1, 1,
// top
-1, 1, -1,
-1, 1, 1,
1, 1, -1,
1, 1, 1,
// bottom
-1, -1, 1,
-1, -1, -1,
1, -1, 1,
1, -1, -1
]),
nor = new Float32Array([
// front
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
// right
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// back
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
// left
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
// top
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
// bottom
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0
]),
idx = new Uint32Array([
// front
0, 1, 2,
3, 2, 1,
// right
4, 5, 6,
7, 6, 5,
// back
8, 9, 10,
11, 10, 9,
// left
12, 13, 14,
15, 14, 13,
// top
16, 17, 18,
19, 18, 17,
// bottom
20, 21, 22,
23, 22, 21
]);
var sideColors = new THREE.MultiMaterial([
new THREE.MeshLambertMaterial({ color: 'red' }), // front
new THREE.MeshLambertMaterial({ color: 'green' }), // right
new THREE.MeshLambertMaterial({ color: 'orange' }), // back
new THREE.MeshLambertMaterial({ color: 'blue' }), // left
new THREE.MeshLambertMaterial({ color: 'white' }), // top
new THREE.MeshLambertMaterial({ color: 'yellow' }) // bottom
]);
var cubeGeometry = new THREE.BufferGeometry();
cubeGeometry.addAttribute("position", new THREE.BufferAttribute(pos, 3));
cubeGeometry.addAttribute("normal", new THREE.BufferAttribute(nor, 3));
cubeGeometry.setIndex(new THREE.BufferAttribute(idx, 3));
cubeGeometry.clearGroups();
cubeGeometry.addGroup(0, 6, 0);
cubeGeometry.addGroup(6, 6, 1);
cubeGeometry.addGroup(12, 6, 2);
cubeGeometry.addGroup(18, 6, 3);
cubeGeometry.addGroup(24, 6, 4);
cubeGeometry.addGroup(30, 6, 5);
THREE.ColorCube = function (scaleX, scaleY, scaleZ) {
THREE.Mesh.call(this, cubeGeometry, sideColors);
var scaler = new THREE.Matrix4().makeScale(scaleX, scaleY, scaleZ);
this.applyMatrix(scaler);
};
THREE.ColorCube.prototype = Object.create(THREE.Mesh.prototype);
THREE.ColorCube.prototype.constructor = THREE.ColorCube;
THREE.ColorCube.prototype.clearVertexNormals = function () {
if (this.vNormals === undefined) {
this.vNormals = [];
}
for (var i = 0, len = this.vNormals.length; i < len; ++i) {
this.parent.remove(this.vNormals[i]);
}
this.vNormals.length = 0;
}
THREE.ColorCube.prototype.drawVertexNormals = function (scale, color) {
this.clearVertexNormals();
scale = (scale === undefined) ? 1 : scale;
color = (color === undefined) ? 0xff : color;
var origin = new THREE.Vector3(),
normalArrow = null,
vert = null,
norm = null,
index = null,
vertexArray = this.geometry.attributes.position.array,
normalArray = this.geometry.attributes.normal.array,
indexArray = this.geometry.index.array;
for (var i = 0, len = indexArray.length; i < len; ++i) {
index = indexArray[i];
vert = new THREE.Vector3(...vertexArray.slice((index * 3), (index * 3) + 3)).applyMatrix4(this.matrix);
norm = new THREE.Vector3(...normalArray.slice((index * 3), (index * 3) + 3));
normalArrow = new THREE.ArrowHelper(
norm,
origin,
1 * scale,
color,
0.2 * scale,
0.1 * scale
);
normalArrow.position.copy(vert);
this.vNormals.push(normalArrow);
this.parent.add(normalArrow);
}
};
THREE.ColorCube.prototype.clearFaceNormals = function () {
if (this.fNormals === undefined) {
this.fNormals = [];
}
for (var i = 0, len = this.fNormals.length; i < len; ++i) {
this.parent.remove(this.fNormals[i]);
}
this.fNormals.length = 0;
}
THREE.ColorCube.prototype.drawFaceNormals = function (scale, color) {
this.clearFaceNormals();
scale = (scale === undefined) ? 1 : scale;
color = (color === undefined) ? 0xffaa00 : color;
var origin = new THREE.Vector3(),
normalArrow = null,
vertices = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()],
normals = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()],
indices = [0, 0, 0],
centroid = new THREE.Vector3(),
faceNormal = new THREE.Vector3(),
vertexArray = this.geometry.attributes.position.array,
normalArray = this.geometry.attributes.normal.array,
indexArray = this.geometry.index.array;
for (var i = 0, len = indexArray.length; i < len; i += 3) {
indices = indexArray.slice(i, i + 3);
vertices[0].set(...vertexArray.slice((indices[0] * 3), (indices[0] * 3) + 3)).applyMatrix4(this.matrix);
vertices[1].set(...vertexArray.slice((indices[1] * 3), (indices[1] * 3) + 3)).applyMatrix4(this.matrix);
vertices[2].set(...vertexArray.slice((indices[2] * 3), (indices[2] * 3) + 3)).applyMatrix4(this.matrix);
normals[0].set(...normalArray.slice((indices[0] * 3), (indices[0] * 3) + 3));
normals[1].set(...normalArray.slice((indices[1] * 3), (indices[1] * 3) + 3));
normals[2].set(...normalArray.slice((indices[2] * 3), (indices[2] * 3) + 3));
centroid.set(
(vertices[0].x + vertices[1].x + vertices[2].x) / 3,
(vertices[0].y + vertices[1].y + vertices[2].y) / 3,
(vertices[0].z + vertices[1].z + vertices[2].z) / 3
);
faceNormal.set(
(normals[0].x + normals[1].x + normals[2].x) / 3,
(normals[0].y + normals[1].y + normals[2].y) / 3,
(normals[0].z + normals[1].z + normals[2].z) / 3
);
faceNormal.normalize();
normalArrow = new THREE.ArrowHelper(
faceNormal,
origin,
1 * scale,
color,
0.2 * scale,
0.1 * scale
);
normalArrow.position.copy(centroid);
this.fNormals.push(normalArrow);
this.parent.add(normalArrow);
}
};
THREE.ColorCube.prototype.clearAllNormals = function () {
THREE.ColorCube.prototype.clearVertexNormals();
THREE.ColorCube.prototype.clearFaceNormals();
}
THREE.ColorCube.prototype.drawWireframe = function (color) {
if (this.wireframe === undefined) {
color = (color === undefined) ? 0 : color;
this.wireframe = new THREE.WireframeHelper(this, color);
this.parent.add(this.wireframe);
}
}
THREE.ColorCube.prototype.clearWireframe = function () {
if (this.wireframe) {
this.parent.remove(this.wireframe);
delete this.wireframe;
}
}
})();
var cc = new THREE.ColorCube(25, 25, 25);
scene.add(cc);
cc.drawVertexNormals(25);
cc.drawFaceNormals(25);
cc.drawWireframe(0xffffff);
draw();
You are setting indices wrong. You have 1 index per vertex. However your code shows 3 index per vertex which does not make sense.
if you change
cubeGeometry.setIndex(new THREE.BufferAttribute(idx, 3));
to
cubeGeometry.setIndex(new THREE.BufferAttribute(idx, 1));
your problem is solved.
Here is the working jsfiddle

Wrong location of texture on the second triangle of BufferGeometry square

I'm trying to add texture to a THREE.BufferGeometry. This is a working fiddle of what I'm accomplished so far.
This is my code:
var Test = new function () {
var scene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera(10, window.innerWidth / window.innerHeight, 1, 1000),
renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true
camera.position.set(-5, 5, 5);
camera.rotation.order = 'YXZ';
camera.rotation.y = -Math.PI / 4;
camera.rotation.x = Math.atan(-1 / Math.sqrt(2));
scene.add(camera)
scene.add(new THREE.AmbientLight(0x222222));
var light = new THREE.PointLight(0xffffff, 0.7);
camera.add(light);
this.init = function () {
$('body').append(renderer.domElement);
Test.render();
}
this.render = function () {
requestAnimationFrame(Test.render);
renderer.render(scene, camera);
}
this.getScene = function getScene() {
return scene
}
}
var CreateSimpleMesh = new function () {
var grassGeometry = new THREE.BufferGeometry(),
grassVertexPositions = [],
imageArray = [
[0, 0],
[1, 0],
[1, 1],
[0, 1]
],
image = new Image()
this.init = function () {
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wwLAi03pl5F1AAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAEkklEQVR42u3dTWzTdRzH8XfXdW03t3XPbmyMlLkNZA9kcwuKBBVBEjXEGDExEAwH44GDJpp4QYnCQcIZoiZGUaKeCFHjIBB5UnnSKW5zj7AVWvfvxsbWre22Dg+1SmHA/iwbHD7v2+/fZYfvK/+n9vCzrDmx7hrqvilBIxCIuk2JGsHsNth0hcvf9dB/1iBkBLFnOkgrc+HeWEr6oow7gwy1DuJt8NB/zs+oJ4DFmoAjx4GrMoviF92klqRryiY69dqxuHXICBIyghgnfFS/X0feyoLbg/y8+ccbjkwy0hNgpCfA5e+7qdxaS/6qQk16mrmWZFL4fDGZNTnYM+wEuodp2fUHg39eof2j5juDpJW7mLd2PlmP5ODMT2FyPMJIT4AL+9rpPeKlbXeTQExUv2dF/HwfclGxtYbjLx0i+PfonS9Zyz5ZGX/XtyWQXp7B4jer6D3iZWJkQlOe6Y3bGR178rwU8zf1ieAEwcsjXNjXDkD+ap0dM+3iVx0APDjFleaWIA3L98etnQXJlL6+mOL1JZroDPIe9HDhy3aSC1NY8HLJ3b+HjA2ECfeHQe/1M8I4/8E5rA4r1dvrsNqt0z9D1pxYF4UYGmPor0HadjfR/U0nWKB8S4WmazLPgYs072wEC1S+W0vqwvS7e1NPSksiuy6XpTvqAPAduqTpmr1nfN1B84eNcA0efnspuY/nz/xNfXI8eq2KjOopy0ydn7XS8XELAIvfqqbw2WJzX52cfeMk819wk74kE1uqjYnhcQabB2jf0xR90anM0pRNFMMAaN7ZGL1sXdcT364lyWW/NUj/GT/9Z/xT/nNbmo3yLUs05VnMcuPvIQPn+7l0oJsrv/oJ94VIsCXgLEghuz6XBetLsGc7NLXZfGm88UBGRRYZFbos3av0e4hAlEAEogQiECUQgSiBCEQJRAlEIEogAlECEYgSiECUQJRABKIEIhAlEIEogQhECUQJRCBKIAJRAhGIEohAlECUQASiBCIQJRCBKIEIRAlECUQgSiACUQIRiBKIQJRAlEAEogQiECUQgSiBCETdu6bcFGxyLELX3ja8P3gI+YM4cpwUPFOEe2MZCTYZmi3QPYxxzIdx3MfV5gHg/20JpwXy+7azGEd9/62DvlE6P20l0DVM9fY6TdhkJ185fPeXLP8vvRhHfViTE6nZtYxVh5+jZtcyrMmJ9B710nfa0IRNlrIglYWvlvHY50+aB/Ed9ADg3lBKdn0eVruV7Po83BtKAfA2eDRhky3/4ilKNi/iAXeaeZCrLYMA5DyaF3c8th5qGdCE5/IpK9wXBMBZEL/Xd2wd8oc0tbkEiYQiADftZBxbR8LaenVOQayO2OAj8VDhGFSipjaXIPZsZ/RR1zsSdzy2duRop885BUlf5Io+/v7UG/84/O86rdylqc0lSP7TRQB07W2j77TB5FiEvtMGXXvbop+vLtLUZrGbNicG+O2dUxjHfTf9ce6KfJbuqNfUTNawfP9tP7/+a5Qpv5iq2laLe1MZjjwnFqsFR54T96Yyqt6r1XTvxRmi7qN7iBKIEohA1DT7B5cwUaoY0hehAAAAAElFTkSuQmCC'
var texture = new THREE.Texture();
texture.image = image;
image.onload = function () {
texture.needsUpdate = true;
};
// first triangle
grassVertexPositions.push([0, 0, 0])
grassVertexPositions.push([1, 0, 0])
grassVertexPositions.push([0, 1, 0])
// second triangle
grassVertexPositions.push([1, 1, 0])
grassVertexPositions.push([0, 1, 0])
grassVertexPositions.push([1, 0, 0])
var grassVertices = new Float32Array(grassVertexPositions.length * 3),
normals = new Float32Array(grassVertexPositions.length * 3),
colors = new Float32Array(grassVertexPositions.length * 3),
uvs = new Float32Array(grassVertexPositions.length * 2)
for (var i = 0; i < grassVertexPositions.length; i++) {
var index = 3 * i
grassVertices[index + 0] = grassVertexPositions[i][0]
grassVertices[index + 1] = grassVertexPositions[i][1]
grassVertices[index + 2] = grassVertexPositions[i][2]
}
uvs[0] = imageArray[0][0]
uvs[1] = imageArray[0][1]
uvs[2] = imageArray[1][0]
uvs[3] = imageArray[1][1]
uvs[4] = imageArray[3][0]
uvs[5] = imageArray[3][1]
uvs[6] = imageArray[1][0]
uvs[7] = imageArray[1][1]
uvs[8] = imageArray[2][0]
uvs[9] = imageArray[2][1]
uvs[10] = imageArray[3][0]
uvs[11] = imageArray[3][1]
grassGeometry.addAttribute('position', new THREE.BufferAttribute(grassVertices, 3))
grassGeometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3))
grassGeometry.addAttribute('color', new THREE.BufferAttribute(colors, 3))
grassGeometry.addAttribute('uv', new THREE.BufferAttribute(uvs, 2))
grassGeometry.computeVertexNormals()
//var textureLoader = new THREE.TextureLoader();
//textureLoader.load('/img/testface.png', function (texture) {
var grassMaterial = new THREE.MeshLambertMaterial({map: texture}),
grassMesh = new THREE.Mesh(grassGeometry, grassMaterial)
grassMesh.rotation.x = -Math.PI / 2;
Test.getScene().add(grassMesh)
var helper = new THREE.WireframeHelper(grassMesh, 0xff00ff); // alternate
helper.material.linewidth = 1;
Test.getScene().add(helper);
console.log(grassMesh.geometry.attributes)
//});
}
}
$(document).ready(function () {
Test.init()
CreateSimpleMesh.init()
});
The problem is that texture on first triangle of the rectangle is correct but on the second triangle something strange is happening with the texture.
This is how it should look like
This is how it look now
Try these texture coordinates (same as x and y of vertex coordinates):
uvs[0] = 0
uvs[1] = 0
uvs[2] = 1
uvs[3] = 0
uvs[4] = 0
uvs[5] = 1
uvs[6] = 1
uvs[7] = 1
uvs[8] = 0
uvs[9] = 1
uvs[10] = 1
uvs[11] = 0

Resources