I get a peculiar triangle with this code while I am trying to generate a regular octagon:
var geom = new THREE.Geometry();
geom.vertices.push( new THREE.Vector3(100, 250, 0));
geom.vertices.push( new THREE.Vector3(250, 100, 0));
geom.vertices.push( new THREE.Vector3(250, -100, 0));
geom.vertices.push( new THREE.Vector3(100, -250, 0));
geom.vertices.push( new THREE.Vector3(-100, 250, 0));
geom.vertices.push( new THREE.Vector3(-250, -100, 0));
geom.vertices.push( new THREE.Vector3(-250, 100, 0));
geom.vertices.push( new THREE.Vector3(-100, 250, 0));
geom.faces.push( new THREE.Face3(0, 1, 2));
geom.faces.push( new THREE.Face3(0, 2, 3));
geom.faces.push( new THREE.Face3(0, 3, 4));
geom.faces.push( new THREE.Face3(0, 4, 5));
geom.faces.push( new THREE.Face3(0, 5, 6));
geom.faces.push( new THREE.Face3(0, 6, 7));
var material = new THREE.MeshBasicMaterial();
var mesh = new THREE.Mesh( geom, material);
mesh.position.set(0, 0, -1000);
scene.add(mesh);
Moreover one of the vertices of the generated triangle is placed somewhere I did not even mentioned in the code.
It is possible only the last added face is being shown, but I expect to see all the faces adjacent to each other forming a regular octagon.
So, order of vertices in a face is counter-clockwise, 5th and 8th points are different:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 500);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var geom = new THREE.Geometry();
geom.vertices.push(new THREE.Vector3(100, 250, 0));
geom.vertices.push(new THREE.Vector3(250, 100, 0));
geom.vertices.push(new THREE.Vector3(250, -100, 0));
geom.vertices.push(new THREE.Vector3(100, -250, 0));
geom.vertices.push(new THREE.Vector3(-100, -250, 0)); // y-coordinate is corrected
geom.vertices.push(new THREE.Vector3(-250, -100, 0));
geom.vertices.push(new THREE.Vector3(-250, 100, 0));
geom.vertices.push(new THREE.Vector3(-100, 250, 0));
geom.faces.push(new THREE.Face3(0, 2, 1)); // order of vertices
geom.faces.push(new THREE.Face3(0, 3, 2));
geom.faces.push(new THREE.Face3(0, 4, 3));
geom.faces.push(new THREE.Face3(0, 5, 4));
geom.faces.push(new THREE.Face3(0, 6, 5));
geom.faces.push(new THREE.Face3(0, 7, 6));
geom.computeFaceNormals();
geom.computeVertexNormals();
var material = new THREE.MeshBasicMaterial({
color: "white"
});
var mesh = new THREE.Mesh(geom, material);
scene.add(mesh);
var points = new THREE.Points(geom, new THREE.PointsMaterial({
size: 20,
color: 0xff0000
}));
scene.add(points); // just to visualize the points
render();
function render() {
requestAnimationFrame(render);
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>
Trying to paint each cube face with a different color, I found a thread that presents a way to achieve this:
var geometry = new THREE.BoxGeometry(5, 5, 5);
for (var i = 0; i < geometry.faces.length; i++) {
geometry.faces[i].color.setHex(Math.random() * 0xffffff);
}
var material = new THREE.MeshBasicMaterial({
color: 0xffffff,
vertexColors: THREE.FaceColors
});
But with three.js r86, I get the following result:
Got the triangles that make up each face, painted individually.
To achieve the desirable effect, I used the following adaptation of the above code:
var geometry = new THREE.BoxGeometry(5, 5, 5);
for ( var i = 0; i < geometry.faces.length; i += 2 ) {
var faceColor = Math.random() * 0xffffff;
geometry.faces[i].color.setHex(faceColor);
geometry.faces[i+1].color.setHex(faceColor);
}
var material = new THREE.MeshBasicMaterial({
color: 0xffffff,
vertexColors: THREE.FaceColors
});
But this all seems a bit over worked!
'use strict';
var camera, scene, renderer, cube;
init();
render();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// renderer
renderer = new THREE.WebGLRenderer({
alpha: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.z = 12;
// Mesh - cube
var geometry = new THREE.BoxGeometry(5, 5, 5);
for (var i = 0; i < geometry.faces.length; i += 2) {
var faceColor = Math.random() * 0xffffff;
geometry.faces[i].color.setHex(faceColor);
geometry.faces[i + 1].color.setHex(faceColor);
}
var material = new THREE.MeshBasicMaterial({
color: 0xffffff,
vertexColors: THREE.FaceColors
});
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// Light
var pointLight = new THREE.PointLight(0xFFFFFF);
pointLight.position.x = 10;
pointLight.position.y = 50;
pointLight.position.z = 130;
scene.add(pointLight);
}
function render() {
cube.rotation.x = 16;
cube.rotation.y = 4;
cube.rotation.z -= 5;
renderer.render(scene, camera);
}
body,
canvas {
margin: 0;
padding: 0;
}
body {
overflow: hidden;
background-color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.js"></script>
Am I missing something on three.js to accomplish the face painting as a whole ?
If you switch to BufferGeometry you can use groups to control the material of sections of your geometry. Groups are based on the vertex indices, and allow you to define a material index, which will reference a material inside an array of materials.
Consider:
// start, count, material index
bufferGeometry.addGroup(12, 6, 2)
This tells the geometry to start a new group of triangles at indices index 12, and accounts for 6 indices (which reference 6 vertices). The final parameter tells the group of triangles to use material index 2 (index 2 of the array of materials you use to create the mesh).
In the example below, I've given each side of a cube a different color. You might think this is the same effect as setting face colors, but note that this is setting a material per group, not just a color, which can lead to creating some really cool effects.
var renderer, scene, camera, controls, stats, mesh;
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
function populateScene() {
var bg = new THREE.BufferGeometry();
bg.addAttribute("position", new THREE.BufferAttribute(new Float32Array([
// front
-1, 1, 1, // 0
-1, -1, 1, // 1
1, 1, 1, // 2
1, -1, 1, // 3
// right
1, 1, 1, // 4
1, -1, 1, // 5
1, 1, -1, // 6
1, -1, -1, // 7
// back
1, 1, -1, // 8
1, -1, -1, // 9
-1, 1, -1, // 10
-1, -1, -1, // 11
// left
-1, 1, -1, // 12
-1, -1, -1, // 13
-1, 1, 1, // 14
-1, -1, 1, // 15
// top
-1, 1, -1, // 16
-1, 1, 1, // 17
1, 1, -1, // 18
1, 1, 1, // 19
// bottom
-1, -1, 1, // 20
-1, -1, -1, // 21
1, -1, 1, // 22
1, -1, -1 // 23
]), 3));
bg.addAttribute("normal", new THREE.BufferAttribute(new Float32Array([
// front
0, 0, 1, // 0
0, 0, 1, // 1
0, 0, 1, // 2
0, 0, 1, // 3
// right
1, 0, 0, // 4
1, 0, 0, // 5
1, 0, 0, // 6
1, 0, 0, // 7
// back
0, 0, -1, // 8
0, 0, -1, // 9
0, 0, -1, // 10
0, 0, -1, // 11
// left
-1, 0, 0, // 12
-1, 0, 0, // 13
-1, 0, 0, // 14
-1, 0, 0, // 15
// top
0, 1, 0, // 16
0, 1, 0, // 17
0, 1, 0, // 18
0, 1, 0, // 19
// bottom
0, -1, 0, // 20
0, -1, 0, // 21
0, -1, 0, // 22
0, -1, 0 // 23
]), 3));
bg.setIndex(new THREE.BufferAttribute(new Uint32Array([
// front 0
0, 1, 2,
3, 2, 1,
// right 6
4, 5, 6,
7, 6, 5,
// back 12
8, 9, 10,
11, 10, 9,
// left 18
12, 13, 14,
15, 14, 13,
// top 24
16, 17, 18,
19, 18, 17,
// bottom 30
20, 21, 22,
23, 22, 21
]), 1));
bg.clearGroups();
// start, count, material index
bg.addGroup(0, 6, 0);
bg.addGroup(6, 6, 1);
bg.addGroup(12, 6, 2);
bg.addGroup(18, 6, 3);
bg.addGroup(24, 6, 4);
bg.addGroup(30, 6, 5);
var materials = [
new THREE.MeshLambertMaterial({color:"red"}),
new THREE.MeshLambertMaterial({color:"green"}),
new THREE.MeshLambertMaterial({color:"blue"}),
new THREE.MeshLambertMaterial({color:"cyan"}),
new THREE.MeshLambertMaterial({color:"magenta"}),
new THREE.MeshLambertMaterial({color:"yellow"})
];
mesh = new THREE.Mesh(bg, materials);
mesh.scale.set(5, 5, 5);
scene.add(mesh);
}
function init() {
document.body.style.backgroundColor = "slateGray";
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
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 = 50;
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;
populateScene();
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() {
mesh.rotation.x += 0.015;
mesh.rotation.y += 0.017;
mesh.rotation.z += 0.019;
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();
})
})
})
})();
Edit: Adding a second example using the base BoxBufferGeometry
Based on pailhead's comment to the original post, here's a snippet which uses unmodified BoxBufferGeometry. But as they mentioned in their comment, you'll still need to know which group corresponds to which face.
var renderer, scene, camera, controls, stats, mesh;
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
function populateScene() {
var bg = new THREE.BoxBufferGeometry(1, 1, 1);
var materials = [
new THREE.MeshLambertMaterial({color:"red"}),
new THREE.MeshLambertMaterial({color:"green"}),
new THREE.MeshLambertMaterial({color:"blue"}),
new THREE.MeshLambertMaterial({color:"cyan"}),
new THREE.MeshLambertMaterial({color:"magenta"}),
new THREE.MeshLambertMaterial({color:"yellow"})
];
mesh = new THREE.Mesh(bg, materials);
mesh.scale.set(10, 10, 10);
scene.add(mesh);
}
function init() {
document.body.style.backgroundColor = "slateGray";
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
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 = 50;
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;
populateScene();
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() {
mesh.rotation.x += 0.015;
mesh.rotation.y += 0.017;
mesh.rotation.z += 0.019;
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();
})
})
})
})();
Using groups will split the geometry in 6 faces, for drawing a simple cube you can also use a simple custom ShaderMaterial.
Splitting geometry in 6 groups requires more draw calls, instead of using 1 draw call for drawing a cube you are using 6, one for each face.
Using a ShaderMaterial requires only 1 draw call:
Vertex Shader:
attribute vec3 vertexColor;
varying vec3 vColor;
void main() {
vColor = vertexColor;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.);
}
Fragment Shader:
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.);
}
This way you could also use GLSL color blending for merging different colors.
Custom ShaderMaterial just setting vertex and fragment shader source strings:
const ColorCubeShader = function () {
THREE.ShaderMaterial.call(this, {
vertexShader: vertexShaderSrc,
fragmentShader: fragmentShaderSrc
})
}
ColorCubeShader.prototype = Object.create(THREE.ShaderMaterial.prototype)
ColorCubeShader.prototype.constructor = ColorCubeShader
Color Cube custom Mesh:
/**
* Convenience method for coloring a face
* #param {Number} r
* #param {Number} g
* #param {Number} b
* #returns {Array}
*/
const buildVertexColorArrayFace = function (r, g, b) {
return [
r, g, b,
r, g, b,
r, g, b,
r, g, b
]
}
const ColorCube = function (size) {
const geometry = new THREE.BoxBufferGeometry(size, size, size)
// build color array
let colorArray = []
colorArray = colorArray
.concat(buildVertexColorArrayFace(1, 0, 0))
.concat(buildVertexColorArrayFace(0, 1, 0))
.concat(buildVertexColorArrayFace(0, 0, 1))
.concat(buildVertexColorArrayFace(1, 0, 1))
.concat(buildVertexColorArrayFace(1, 1, 0))
.concat(buildVertexColorArrayFace(0, 1, 1))
// create a buffer attribute for the colors (for attribute vec3 vertexColor)
const colorAttribute = new THREE.Float32BufferAttribute(
new Float32Array(colorArray), 3)
// set attribute vertexColor in vertex shader
geometry.setAttribute('vertexColor', colorAttribute)
// custom Shader Material instance
const material = new ColorCubeShader()
THREE.Mesh.call(this, geometry, material)
}
ColorCube.prototype = Object.create(THREE.Mesh.prototype)
ColorCube.prototype.constructor = ColorCube
Use it:
const cube = new ColorCube(1)
cube.position.set(0, 2, -2)
scene.add(cube)
I try to create this sphere in three.js. I need clickable dotes and meshes. So I think, that I can do this if I will give name for every dot in sphere.
Two questions:
1. How I can randomize positions of dots on sphere?
2. How I should to give names for dots and meshes?
THREE.IcosahedronGeometry = function(radius, detail) {
var t = (1 + Math.sqrt(5)) / 2;
var vertices = [-1, t, 0, 1, t, 0, -1, -t, 0, 1, -t, 0,
0, -1, t, 0, 1, t, 0, -1, -t, 0, 1, -t,
t, 0, -1, t, 0, 1, -t, 0, -1, -t, 0, 1, -1, t, 0, 1, t, 0, -1, -t, 0, 1, -t, 0,
0, -1, t, 0, 1, t, 0, -1, -t, 0, 1, -t,
t, 0, -1, t, 0, 1, -t, 0, -1, -t, 0, 1
];
var indices = [
2,10,6,0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11,
1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8,
3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9,
4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1
];
THREE.PolyhedronGeometry.call(this, vertices, indices, radius, detail);
this.type = 'IcosahedronGeometry';
this.parameters = {
radius: radius,
detail: detail
};
};
THREE.IcosahedronGeometry.prototype = Object.create(THREE.PolyhedronGeometry.prototype);
THREE.IcosahedronGeometry.prototype.constructor = THREE.IcosahedronGeometry;
// Scene
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer({
antialias: 1
});
renderer.setClearColor(0xf7f7f7);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene.fog = new THREE.Fog(0xd4d4d4, 8, 20);
// Create vertex points
var mesh = new THREE.IcosahedronGeometry(10, 2); // radius, detail
var vertices = mesh.vertices;
var positions = new Float32Array(vertices.length * 3);
for (var i = 0, l = vertices.length; i < l; i++) {
vertices[i].toArray(positions, i * 3);
}
alert(positions);
var geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
var material = new THREE.PointsMaterial({
size: 0.4,
vertexColors: THREE.VertexColors,
color: 0x252525
});
var points = new THREE.Points(geometry, material);
var object = new THREE.Object3D();
object.add(points);
object.add(new THREE.Mesh(
mesh,
new THREE.MeshPhongMaterial({
color: 0x616161,
emissive: 0xa1a1a1,
wireframe: true,
fog: 1
})
));
scene.add(object);
camera.position.z = 20;
var render = function() {
requestAnimationFrame(render);
object.rotation.x += 0.001;
object.rotation.y += 0.001;
renderer.render(scene, camera);
};
render();
http://codepen.io/anon/pen/ZLpPxB
here is an example how you could randomize the points: http://codepen.io/usefulthink/pen/WRoboo?editors=0010
The interesting bit is this:
var v3 = new THREE.Vector3();
var spherical = new THREE.Spherical();
for (var i = 0; i < vertices.length; i += 3) {
v3.fromArray(vertices, i);
spherical.setFromVector3(v3);
spherical.phi += rnd(variation, -variation);
spherical.theta += rnd(variation, -variation);
v3.setFromSpherical(spherical);
v3.toArray(vertices, i);
}
In short, a good way to manipulate points on the surface of a sphere is to use spherical-coordinates (phi/theta/r instead of x/y/z, think latitude/longitude). In this case I convert each of the vertices into the spherical form, manipulate the theta/phi-values a bit (keeping the radius as it is) and write the modified values back to the vertices-array.
I am not sure what you mean with giving names to these points. Something like this?
var pointNames = {
frank: 12,
maria: 7,
steve: 3,
melissa: 4
};
function getPointByName(name) {
const startIndex = pointNames[name] * 3;
return new THREE.Vector3().fromArray(vertices, index);
}