Three.js assign array of materials to an indexed BufferGeometry - three.js

I have a BufferGeometry triangular prism; where I have specified the 6 vertices and then added indexes to create the 8 faces (2 triangle faces per square plane).
I have then setup 5 groups, so that each side of the prism is a group.
And it works here when I have a single material assigned to the whole object: https://jsfiddle.net/30ez17dw/1/
However I want to be able to assign an array of materials, so that each side (group) has it's own materials. But when I do this the object disappears:
https://jsfiddle.net/30ez17dw/2/
How can I fix my code to make the array of materials work?

You have not specified your groups correctly. Try it like so:
var camera, scene, renderer;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10);
camera.position.z = 5;
camera.position.y = 2;
camera.lookAt(0, 0, 0);
scene = new THREE.Scene();
var geometry = new THREE.BufferGeometry();
const y = 0.866025404;
const y2 = 0.5;
const h = 1;
const vertices = new Float32Array([
-y2, 0, 0,
y2, 0, 0,
0, 0, y,
-y2, h, 0,
y2, h, 0,
0, h, y,
]);
const indices = [
0, 1, 2, // Top
5, 4, 3, // Bottom
3, 1, 0, // Back
1, 3, 4, // Back
0, 2, 3, // Left
5, 3, 2, // Left
4, 2, 1, // Right
2, 4, 5, // Right
];
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(indices);
geometry.computeVertexNormals();
geometry.clearGroups();
geometry.addGroup(0, 3, 0);
geometry.addGroup(3, 6, 1);
geometry.addGroup(6, 12, 2);
geometry.addGroup(12, 18, 3);
geometry.addGroup(18, 24, 4);
var material = [
new THREE.MeshBasicMaterial({
color: 0x00ff00
}),
new THREE.MeshBasicMaterial({
color: 0xff0000
}),
new THREE.MeshBasicMaterial({
color: 0x0000ff,
}),
new THREE.MeshBasicMaterial({
color: 0xffff00
}),
new THREE.MeshBasicMaterial({
color: 0x00ffff
})
];
var mesh = new THREE.Mesh(geometry, material); // , side: THREE.DoubleSide
scene.add(mesh);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
body {
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.132.0/build/three.min.js"></script>
The indices you pass to addGroup() are meant to be consecutive.

Related

Drawing a tetrahedron with triangles

Trying to create a tetrahedron with three triangles. I could'nt orient faces. Here is the code :
HTML
<div id="myid"></div>
JAVASCRIPT
const divid = document.getElementById("myid");
let facesVectors = [ // triangles vertices
[0, 0, 0],
[-1, 0, 0],
[0, 1, 0],
]
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
1,
1000
);
let renderer = new THREE.WebGLRenderer({ alpha: true });
divid.appendChild(renderer.domElement);
camera.position.z = 2.5;
// equilateral triangle template
const geometry = new THREE.Geometry()
geometry.vertices.push(new THREE.Vector3(-0.48, 0, 0))
geometry.vertices.push(new THREE.Vector3(0.48, 0, 0))
geometry.vertices.push(new THREE.Vector3(0, 0.72, 0))
geometry.faces.push(new THREE.Face3(0, 1, 2))
let material, face, i
let faces = []
for (i = 0; i < facesVectors.length; i++) {
material = new THREE.MeshBasicMaterial({ color: 0x00FF00, side: THREE.DoubleSide })
face = new THREE.Mesh(geometry, material)
face.position.set(facesVectors[i][0], facesVectors[i][1], facesVectors[i][2])
if (i === 1) {
face.rotation.x = -(Math.PI / 2)
} else if (i === 2) {
face.rotation.x = -(Math.PI / 2)
}
faces.push(face)
scene.add(face)
}
let animate = function() {
requestAnimationFrame(animate);
faces.forEach((face)=>{
face.rotation.x += 0.005;
face.rotation.y += 0.01;
})
renderer.render(scene, camera);
};
animate();
demo : https://codepen.io/jeffprod/pen/GRJGYoQ
How to rotate triangles 1 and 2 so that i get a tetrahedron ? Is there a simplest way to do ?
Of course i don't want to use the TetrahedronBufferGeomety because i will need to manage faces colors and a lot of other triangles oriented differently.
Based on this forum topic. Just commented out two lines for faces and uvs:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, -1, 2);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
// tetrahedron
// ---------------------------------------------------------------------------------------
var pts = [ // https://en.wikipedia.org/wiki/Tetrahedron#Coordinates_for_a_regular_tetrahedron
new THREE.Vector3(Math.sqrt(8 / 9), 0, -(1 / 3)),
new THREE.Vector3(-Math.sqrt(2 / 9), Math.sqrt(2 / 3), -(1 / 3)),
new THREE.Vector3(-Math.sqrt(2 / 9), -Math.sqrt(2 / 3), -(1 / 3)),
new THREE.Vector3(0, 0, 1)
];
var faces = [ // triangle soup
//pts[0].clone(), pts[2].clone(), pts[1].clone(),
pts[0].clone(), pts[1].clone(), pts[3].clone(),
pts[1].clone(), pts[2].clone(), pts[3].clone(),
pts[2].clone(), pts[0].clone(), pts[3].clone()
];
var geom = new THREE.BufferGeometry().setFromPoints(faces);
geom.rotateX(-Math.PI * 0.5);
geom.computeVertexNormals();
geom.setAttribute("uv", new THREE.Float32BufferAttribute([ // UVs
//0.5, 1, 0.06698729810778059, 0.2500000000000001, 0.9330127018922194, 0.2500000000000001,
0.06698729810778059, 0.2500000000000001, 0.9330127018922194, 0.2500000000000001, 0.5, 1,
0.06698729810778059, 0.2500000000000001, 0.9330127018922194, 0.2500000000000001, 0.5, 1,
0.06698729810778059, 0.2500000000000001, 0.9330127018922194, 0.2500000000000001, 0.5, 1
], 2));
// ---------------------------------------------------------------------------------------
var tetrahedron = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load("https://threejs.org/examples/textures/uv_grid_opengl.jpg"),
side: THREE.DoubleSide
}));
scene.add(tetrahedron);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

Create object with different colors on faces

Drawing a Tetrahedron, I would like to set one color per face, for example, red, green, blue and orange.
Here is the code :
const divid = document.getElementById("myid");
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
1,
1000
);
let renderer = new THREE.WebGLRenderer({ alpha: true });
divid.appendChild(renderer.domElement);
camera.position.z = 3;
let triangle = new THREE.Mesh(
new THREE.TetrahedronBufferGeometry(),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
scene.add(triangle);
let animate = function() {
requestAnimationFrame(animate);
triangle.rotation.x += 0.005;
triangle.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
Can't we pass an array of 4 colors to set on each face ?
Code available here too : https://codepen.io/jeffprod/pen/JjdLdjO
Use .vertexColors: true with the material and add color attribute to the geometry, setting the same colour to each vertex of a face:
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(
70,
innerWidth / innerHeight,
1,
1000
);
let renderer = new THREE.WebGLRenderer({
alpha: true
});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.z = 3;
let geom = new THREE.TetrahedronBufferGeometry();
geom.setAttribute("color", new THREE.Float32BufferAttribute([
1, 0, 0, //red
1, 0, 0,
1, 0, 0,
0, 1, 0, //green
0, 1, 0,
0, 1, 0,
0, 0, 1, //blue
0, 0, 1,
0, 0, 1,
1, 0.75, 0.25, //orange (sort of)
1, 0.75, 0.25,
1, 0.75, 0.25
], 3));
let triangle = new THREE.Mesh(
geom,
new THREE.MeshBasicMaterial({
vertexColors: true
})
);
scene.add(triangle);
let animate = function() {
requestAnimationFrame(animate);
triangle.rotation.x += 0.005;
triangle.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>

Cannot get object to change position using tween

Apologies for asking this, but I am losing my mind.
In the context of a three.js scene I have built a cube with the following bit of code.
var gcap = new THREE.BoxGeometry( 10, 10, 1, 2, 2, 2 );
mcap = new THREE.MeshBasicMaterial( { color: 0x3182bd, wireframe: false, transparent: true, opacity: 0.5} );
cap = new THREE.Mesh( gcap, mcap );
cap.position.set( - 12, 19, 0 );
gcap.center();
cap.rotation.z = (28 * Math.PI)/180; //convert to radians
app.scene.add(cap);
So why does this tween not work (and by not working I mean there is not noticeable change in the scene):
new TWEEN.Tween(cap.position)
.to(-12, 19, 100 ).start();
but this one does:
new TWEEN.Tween(app.controls.target).to({
x: 31/2,
y: 29/2,
z: 11/2
}).start();
I realize this is probably a super-dumb question, but I'm new to tween (and really three.js in general).
In .to() you have to pass an object of the same structure that you pass in .Tween(), fully or partially. It depends on what values of the object you want to change.
And the second paremeter in .to() is duration.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var box1 = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial({
color: "red",
wireframe: true
}));
box1.position.set(-3, 0, 0);
scene.add(box1);
var box2 = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial({
color: "blue",
wireframe: true
}));
box2.position.set(3, 0, 0);
scene.add(box2);
var tween1 = new TWEEN.Tween(box1.position) // here you pass the position {x, y, z}
.to({ // here you pass an object with properties you want to change (now you want to change all of them)
x: 1,
y: 3,
z: -2
}, 2000).delay(500).repeat(Infinity).yoyo(true).start();
var tween2 = new TWEEN.Tween(box2.position) // the same, position {x, y, z}
.to({ // but you want to change only y-coordinate, so you pass an object of {y}
y: 3
}, 1000).delay(500).repeat(Infinity).yoyo(true).start();
render();
function render() {
requestAnimationFrame(render);
TWEEN.update();
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/90/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>

Triangle Strip artifacts

I'm playing with draw groups in THREE.TriangleStripDrawMode draw mode, and I'm seeing some odd artifacts. I think it might be a bug, but I'd like someone else to confirm that I'm not doing something wrong before submitting a report.
In the snippet below, I create 4 squares composed of 4 triangles each. They're all indexed for right-hand rendering. (I realize that triangle strips don't technically need indexing, but I could potentially have complex shapes with re-used vertices.) Their other properties and results are as follows:
Red Square
Two groups: 0-9 and 9-12
Standard front-side rendering
Black artifact on the back-face of triangle index 1
Green Square
One group: 0-12
Standard front-side rendering
Black artifacts on the back-face of triangles index 1 & 3
The same thing happens when I don't use grouping at all
Blue Square
Two groups: 0-9 and 9-12
Double-side rendering
Renders as expected
Yellow Square
One group: 0-12
Double-side rendering
Triangle index 3 renders black on both sides
So did I miss something, or should I submit this as a bug in three.js?
var renderer, scene, camera, controls, stats;
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
function createShapes(){
var bg = new THREE.BufferGeometry();
bg.addAttribute("position", new THREE.BufferAttribute(new Float32Array([
-1, 1, 0,
-1, -1, 0,
0, 1, 0,
0, -1, 0,
1, 1, 0,
1, -1, 0
]), 3));
bg.addAttribute("normal", new THREE.BufferAttribute(new Float32Array([
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1
]), 3));
bg.setIndex(new THREE.BufferAttribute(new Uint32Array([
0, 1, 2,
3, 2, 1,
2, 3, 4,
5, 4, 3
]), 1));
var group1 = bg.clone(),
group2 = bg.clone(),
group3 = bg.clone(),
group4 = bg.clone();
/**/
group1.clearGroups();
group1.addGroup(0, 9, 0);
group1.addGroup(9, 3, 0);
group2.clearGroups();
group2.addGroup(0, 12, 0);
group3.clearGroups();
group3.addGroup(0, 9, 0);
group3.addGroup(9, 3, 0);
group4.clearGroups();
group4.addGroup(0, 12, 0);
/**/
var mat1 = new THREE.MeshPhongMaterial({color: "red"}),
mat2 = new THREE.MeshPhongMaterial({color: "green"}),
mat3 = new THREE.MeshPhongMaterial({color: "blue", side: THREE.DoubleSide}),
mat4 = new THREE.MeshPhongMaterial({color: "yellow", side: THREE.DoubleSide});
var m1 = new THREE.Mesh(group1, [mat1]),
m2 = new THREE.Mesh(group2, [mat2]),
m3 = new THREE.Mesh(group3, [mat3]),
m4 = new THREE.Mesh(group4, [mat4]);
m1.drawMode = THREE.TriangleStripDrawMode;
m2.drawMode = THREE.TriangleStripDrawMode;
m3.drawMode = THREE.TriangleStripDrawMode;
m4.drawMode = THREE.TriangleStripDrawMode;
m1.position.set(-2, 2, 0);
m2.position.set(2, 2, 0);
m3.position.set(-2, -2, 0);
m4.position.set(2, -2, 0);
scene.add(m1, m2, m3, m4);
}
function init() {
document.body.style.backgroundColor = "slateGray";
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
document.body.appendChild(renderer.domElement);
document.body.style.overflow = "hidden";
document.body.style.margin = "0";
document.body.style.padding = "0";
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 20;
scene.add(camera);
controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.dynamicDampingFactor = 0.5;
controls.rotateSpeed = 3;
var light = new THREE.PointLight(0xffffff, 1, Infinity);
camera.add(light);
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0';
document.body.appendChild(stats.domElement);
resize();
window.onresize = resize;
// POPULATE EXAMPLE
createShapes();
animate();
}
function resize() {
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
if (renderer && camera && controls) {
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
controls.handleResize();
}
}
function render() {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
render();
controls.update();
stats.update();
}
function threeReady() {
init();
}
(function () {
function addScript(url, callback) {
callback = callback || function () { };
var script = document.createElement("script");
script.addEventListener("load", callback);
script.setAttribute("src", url);
document.head.appendChild(script);
}
addScript("https://threejs.org/build/three.js", function () {
addScript("https://threejs.org/examples/js/controls/TrackballControls.js", function () {
addScript("https://threejs.org/examples/js/libs/stats.min.js", function () {
threeReady();
})
})
})
})();
r86 (the problem has actually been around for a while)
I had an "AHA!" moment while looking at some other examples. My indexing was off, which is also throwing off my grouping.
The indexing should be:
bg.setIndex(new THREE.BufferAttribute(new Uint32Array([
0, 1, 2, 3, 4, 5
]), 1));
Which makes sense, because that's how the triangle strip defines the steps of its vertices.
Then to draw a full square, I needed a single group:
group1.addGroup(0, 6, 0);
Which means start at the group index of 0, for 6 group indices (which covers all of them).
There's still a problem when trying to render an (odd index) individual triangle. Because the winding order for odd triangles is backwards, creating a group that starts with an odd triangle will not be lit correctly (renders black). But that's for another question...

Three JS how to render the xyz axis over all the models inside a scene?

Currently I want to render the axis over the cube (rather than through it). But I can't figure out a way to do that. Does any one know how to render the axis lines over the cube module? Thanks.
Have provided the JS code below.
The cube will be rendered in scene and axis lines will be rendered in fscene. (The axis lines rendered in fscene is supposed to cover the cube which rendered in scene)
var container, stats;
var camera, scene, fscene, renderer;
var cube, plane;
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.01, 999999 );
camera.position.y = 5;
camera.position.z = 5;
camera.position.x = 4;
camera.lookAt(new THREE.Vector3());
scene = new THREE.Scene();
fscene = new THREE.Scene();
// Cube
var geometry = new THREE.CubeGeometry( 1, 1, 1 );
for ( var i = 0; i < geometry.faces.length; i ++ ) {
geometry.faces[ i ].color.setHex( 0xcccccc * i );
}
var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors } );
cube = new THREE.Mesh( geometry, material );
scene.add(cube);
addAxis();
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.autoClear = false;
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
animate();
}
function animate() {
requestAnimationFrame(animate);
renderer.clear();
renderer.render(scene, camera);
renderer.render(fscene, camera);
}
function addAxis() {
var sceneSize = 9000;
line({ begin: [0, 0, 0], end: [sceneSize, 0, 0], color: 0xff0000, scene: scene });
line({ begin: [0, 0, 0], end: [-sceneSize, 0, 0], color: 0xff0000, dashed: true, scene: scene });
line({ begin: [0, 0, 0], end: [0, sceneSize, 0], color: 0x00ff00, scene: scene });
line({ begin: [0, 0, 0], end: [0, -sceneSize, 0], color: 0x00ff00, dashed: true, scene: scene });
line({ begin: [0, 0, 0], end: [0, 0, sceneSize], color: 0x0000ff, scene: scene });
line({ begin: [0, 0, 0], end: [0, 0, -sceneSize], color: 0x0000ff, dashed: true, scene: scene });
}
function line(cfg){
var p = cfg.begin;
var q = cfg.end;
if (cfg.color) {
cfg.colorb = cfg.colorb || cfg.color;
cfg.colore = cfg.colore || cfg.color;
}
var geometry = new THREE.Geometry();
var material = cfg.dashed ? new THREE.LineDashedMaterial({ linewidth: 1, color: cfg.color, dashSize: 1, gapSize: 1, depthWrite:false }) : new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors, depthWrite:false });
var cp = new THREE.Color(cfg.colorb);
var cq = new THREE.Color(cfg.colore);
geometry.vertices.push(new THREE.Vector3(p[0], p[1], p[2]));
geometry.vertices.push(new THREE.Vector3(q[0], q[1], q[2]));
geometry.colors.push(cp,cq);
geometry.computeLineDistances();
var line = new THREE.Line(geometry, material, THREE.LinePieces);
fscene.add(line);
}
TransformControls.js contains the code that might help you.
depthTest should be set to false and transparent should be set to true.

Resources