Related
I have a simple object that draws a 3d gizmo at 0, 0, 0. If the camera is centered on 0, 0, 0, then it draws the gizmo at the center of the screen.
I would like to "lift" this gizmo and render it at the bottom right of the screen in screen coordinates, without rotating it. Basically, I want the gizmo to show the rotation of the center of the screen without blocking the view and without having to focus on a specific point. So I want to do away with the model matrix, or something.
I got the following to work by translating the projection matrix:
this.gl.uniformMatrix4fv(this.modelMatrixUniform, false, modelMatrix);
this.gl.uniformMatrix4fv(this.viewMatrixUniform, false, viewMatrix);
const bottomRightMat = mat4.create();
mat4.translate(bottomRightMat, projectionMatrix, [5, -3, 0]);
this.gl.uniformMatrix4fv(this.projectionMatrixUniform, false, bottomRightMat);
this.gl.drawElements(this.gl.LINES, this.indexBuffer.getLength(), this.gl.UNSIGNED_SHORT, 0);
But the gizmo has been rotated into its new position. The red line should still point down and to the left, since that's the direction of the positive X axis at the center of the screen. Also, the numbers 5 and 3 are arbitrary, and I don't think they would work at different zooms or camera locations.
Is there a way to specify a matrix transform that takes the center of the screen and translates it in screen space?
One way would be to change the viewport when rendering that object.
// size of area in bottom right
const miniWidth = 150;
const miniHeight = 100;
gl.viewport(gl.canvas.width - miniWidth, gl.canvas.height - miniHeight, miniWidth, miniHeight);
// now draw. you'll need to zoom in, like set the camera closer
// or move the object closer or add a scale matrix after the projection
// matrix as in projection * scale * view * ...
you'll need a projection matrix that matches the aspect ratio of the new viewport and you'll either need to scale the object, put the camera closer, or add a 2D scale between the projection and view matrices.
Remember to put the viewport back to the full canvas to render the rest of the scene.
const vs = `
attribute vec4 position;
uniform mat4 u_worldViewProjection;
void main() {
gl_Position = u_worldViewProjection * position;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(vec3(0), 1);
}
`
const gl = document.querySelector("canvas").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: [
-1, -1, -1,
1, -1, -1,
1, 1, -1,
-1, 1, -1,
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1,
],
indices: {
numComponents: 2,
data: [
0, 1,
1, 2,
2, 3,
3, 0,
4, 5,
5, 6,
6, 7,
7, 4,
0, 4,
1, 5,
2, 6,
3, 7,
],
},
});
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const fov = 90 * Math.PI / 180;
const zNear = 0.5;
const zFar = 100;
const projection = mat4.perspective(mat4.create(),
fov, gl.canvas.clientWidth / gl.canvas.clientHeight, zNear, zFar);
const eye = [0, 0, 10];
const target = [0, 0, 0];
const up = [0, 1, 0];
const view = mat4.lookAt(mat4.create(), eye, target, up);
drawCube([-8, 0, 0], projection, view);
drawCube([-4, 0, 0], projection, view);
drawCube([ 0, 0, 0], projection, view);
drawCube([ 4, 0, 0], projection, view);
drawCube([ 8, 0, 0], projection, view);
const iconAreaWidth = 100;
const iconAreaHeight = 75;
gl.viewport(
gl.canvas.width - iconAreaWidth, 0,
iconAreaWidth, iconAreaHeight);
const iconProjection = mat4.perspective(mat4.create(),
fov, iconAreaWidth / iconAreaHeight, zNear, zFar);
// compute the zoom size need to make things the sngs
const scale = gl.canvas.clientHeight / iconAreaHeight;
mat4.scale(iconProjection, iconProjection, [scale, scale, 1]);
drawCube([ 0, 0, 0], iconProjection, view);
function drawCube(translation, projection, view) {
const viewProjection = mat4.multiply(mat4.create(), projection, view);
const world = mat4.multiply(
mat4.create(),
mat4.fromTranslation(mat4.create(), translation),
mat4.fromRotation(mat4.create(), time, [0.42, 0.56, 0.70]));
const worldViewProjection = mat4.multiply(mat4.create(), viewProjection, world);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, {
u_worldViewProjection: worldViewProjection,
});
twgl.drawBufferInfo(gl, bufferInfo, gl.LINES);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; background: #CDE; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix.js"></script>
Another is to compute an off center frustum projection matrix. Instead of mat4.perspective use mat4.frustum
function perspectiveWithCenter(
fieldOfView, width, height, near, far, centerX = 0, centerY = 0) {
const aspect = width / height;
// compute the top and bottom of the near plane of the view frustum
const top = Math.tan(fieldOfView * 0.5) * near;
const bottom = -top;
// compute the left and right of the near plane of the view frustum
const left = aspect * bottom;
const right = aspect * top;
// compute width and height of the near plane of the view frustum
const nearWidth = right - left;
const nearHeight = top - bottom;
// convert the offset from canvas units to near plane units
const offX = centerX * nearWidth / width;
const offY = centerY * nearHeight / height;
const m = mat4.create();
mat4.frustum(
m,
left + offX,
right + offX,
bottom + offY,
top + offY,
near,
far);
return m;
}
So to draw your gizmo set call something like
const gizmoCenterX = -gl.canvas.clientWidth / 2 + 50;
const gizmoCenterY = gl.canvas.clientHeight / 2 - 50;
const offsetProjection = perspectiveWithCenter(
fov, gl.canvas.clientWidth, gl.canvas.clientHeight, zNear, zFar,
gizmoCenterX, gizmoCenterY);
const vs = `
attribute vec4 position;
uniform mat4 u_worldViewProjection;
void main() {
gl_Position = u_worldViewProjection * position;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(vec3(0), 1);
}
`
const gl = document.querySelector("canvas").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: [
-1, -1, -1,
1, -1, -1,
1, 1, -1,
-1, 1, -1,
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1,
],
indices: {
numComponents: 2,
data: [
0, 1,
1, 2,
2, 3,
3, 0,
4, 5,
5, 6,
6, 7,
7, 4,
0, 4,
1, 5,
2, 6,
3, 7,
],
},
});
function perspectiveWithCenter(
fieldOfView, width, height, near, far, centerX = 0, centerY = 0) {
const aspect = width / height;
// compute the top and bottom of the near plane of the view frustum
const top = Math.tan(fieldOfView * 0.5) * near;
const bottom = -top;
// compute the left and right of the near plane of the view frustum
const left = aspect * bottom;
const right = aspect * top;
// compute width and height of the near plane of the view frustum
const nearWidth = right - left;
const nearHeight = top - bottom;
// convert the offset from canvas units to near plane units
const offX = centerX * nearWidth / width;
const offY = centerY * nearHeight / height;
const m = mat4.create();
mat4.frustum(
m,
left + offX,
right + offX,
bottom + offY,
top + offY,
near,
far);
return m;
}
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const fov = 90 * Math.PI / 180;
const zNear = 0.5;
const zFar = 100;
const projection = perspectiveWithCenter(
fov, gl.canvas.clientWidth, gl.canvas.clientHeight, zNear, zFar);
const eye = [0, 0, 10];
const target = [0, 0, 0];
const up = [0, 1, 0];
const view = mat4.lookAt(mat4.create(), eye, target, up);
drawCube([-8, 0, 0], projection, view);
drawCube([-4, 0, 0], projection, view);
drawCube([ 0, 0, 0], projection, view);
drawCube([ 4, 0, 0], projection, view);
drawCube([ 8, 0, 0], projection, view);
const gizmoCenterX = -gl.canvas.clientWidth / 2 + 50;
const gizmoCenterY = gl.canvas.clientHeight / 2 - 50;
const offsetProjection = perspectiveWithCenter(
fov, gl.canvas.clientWidth, gl.canvas.clientHeight, zNear, zFar,
gizmoCenterX, gizmoCenterY);
drawCube([ 0, 0, 0], offsetProjection, view);
function drawCube(translation, projection, view) {
const viewProjection = mat4.multiply(mat4.create(), projection, view);
const world = mat4.multiply(
mat4.create(),
mat4.fromTranslation(mat4.create(), translation),
mat4.fromRotation(mat4.create(), time, [0.42, 0.56, 0.70]));
const worldViewProjection = mat4.multiply(mat4.create(), viewProjection, world);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, {
u_worldViewProjection: worldViewProjection,
});
twgl.drawBufferInfo(gl, bufferInfo, gl.LINES);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; background: #CDE; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix.js"></script>
How do I get the rotation of an element relative to the world coordination system?
I'm getting the information of an element by the following:
this.viewerComponent.viewer.impl.hitTest(event.layerX, event.layerY, false);
The result of this function is the following
{distance: 186476.22640731235, point: X.Vector3, face: X.Face3, faceIndex: 0, fragId: 372, …}
distance: 186476.22640731235
point: X.Vector3 {x: 70297.79662079967, y: 8922.73091035225, z: 9109.446866256267}
face: X.Face3 {a: 0, b: 1, c: 2, normal: X.Vector3, vertexNormals: Array(0), …}
faceIndex: 0
fragId: 372
dbId: 1959
object: X.Mesh
eulerOrder: (...)
useQuaternion: (...)
uuid: "A3D04442-BB20-4E0C-B371-3A987D212255"
name: ""
type: "Mesh"
parent: undefined
children: []
up: X.Vector3 {x: 0, y: 1, z: 0}
position: X.Vector3 {x: 0, y: 0, z: 0}
rotation: X.Euler {_x: 0, _y: 0, _z: 0, _order: "XYZ", onChangeCallback: ƒ}
quaternion: X.Quaternion {_x: 0, _y: 0, _z: 0, _w: 1, onChangeCallback: ƒ}
scale: X.Vector3 {x: 1, y: 1, z: 1}
rotationAutoUpdate: true
matrix: X.Matrix4
elements: Float32Array(16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
__proto__: Object
matrixWorld: X.Matrix4
elements: Float32Array(16) [10, 0, 0, 0, 0, 10, 0, 0, 0, 0, 10, 0, 79719.9609375, -6109.12646484375,
1962.4998779296875, 1]
__proto__: Object
matrixAutoUpdate: true
matrixWorldNeedsUpdate: false
visible: true
castShadow: false
receiveShadow: false
frustumCulled: true
renderOrder: 0
userData: {}
geometry: h {id: 2265, attributes: {…}, __webglInit: undefined, byteSize: 28, vb: Float32Array(6),
…}
material: X.LineBasicMaterial {uuid: "E36F7B3D-C885-475C-9AA6-A3D1024F7687", name: "", type:
"LineBasicMaterial", side: 0, opacity: 1, …}
isTemp: true
dbId: 529
modelId: 1
fragId: 2264
hide: false
isLine: true
isWideLine: false
isPoint: false
themingColor: undefined
id: 1
__proto__: X.Object3D
As seen, we have the information matrix (local coordination system which is connected to the element coordination system) and matrixWorld which should be the transformation matrix for element -> global coordination system. How do I now get the angles out of the matrixWorld to know what is the rotation by the elmenent in relation to the world coordination system.
Hope it is clear what i want, thank you in advance.
If I understand you correctly you may try:
//Getting fragment info by fragId if necessary
//const matrixWorld = new THREE.Matrix4();
//const fragProxy = NOP_VIEWER.impl.getFragmentProxy(NOP_VIEWER.model, fragId)
//fragProxy.getWorldMatrix(matrixWorld);
const position = new THREE.Vector3();
const quaternion = new THREE.Quaternion();
const scale = new THREE.Vector3();
matrixWorld.decompose( position, quaternion, scale );
See more on extracting rotation here
Thank you. It's a similar approach I've taken. Your solution would probably work, too.
public onMouseMove(event) {
var snapper = new
Autodesk.Viewing.Extensions.Snapping.Snapper(this.viewerComponent.viewer, {});
const hitTestResult = this.viewerComponent.viewer.impl.snappingHitTest(event.layerX,
event.layerY);
snapper.snapping3D(hitTestResult);
// Arrow
const geometry = new THREE.CylinderGeometry(1, 800, 2000, 100);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(geometry, material);
mesh.rotateZ(this.arcSinFunction(normal.x, normal.y, normal.z, 0, 1, 0)[0]);
mesh.name = 'section-mesh';
mesh.position.set(snapper.getSnapResult().intersectPoint.x,
snapper.getSnapResult().intersectPoint.y, snapper.getSnapResult().intersectPoint.z);
if (!this.viewerComponent.viewer.overlays.hasScene('section-scene')) {
this.viewerComponent.viewer.overlays.addScene('section-scene');
}
this.viewerComponent.viewer.overlays.addMesh([mesh, mesh_test], 'section-scene');
}
public arcSinFunction(n1: number, n2: number, n3: number, u1: number, u2: number,
u3:
number): Array<number> {
var zähler: number = Math.abs(n1 * u1 + n2 * u2 + n3 * u3);
// console.log(zähler);
var nenner: number = (Math.sqrt(n1 * n1 + n2 * n2 + n3 * n3)) * (Math.sqrt(u1 * u1 +
u2 * u2 + u3 * u3));
// console.log(nenner);
var resultRadian: number = Math.asin(zähler / nenner);
// console.log(resultRadian);
var pi = Math.PI;
var resultDegree = resultRadian * (180 / pi);
// console.log(resultDegree);
var res: Array<number> = [resultRadian, resultDegree];
// console.log(res);
return res;
}
Best regards
I don't understand why I only see half of crate. I recall that it happened to me before, with other graphics pipelines.
Here is the fiddle link: https://jsfiddle.net/20azohgc/10/
Here is the code:
//Basic THREE setup:
const canvas = document.getElementById("canvasThree");
const scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, 1, 1, 500);
camera.position.set( 0, 0, 100 );
camera.lookAt( new THREE.Vector3( 0, 0, 0 ));
camera.position.set( 0, 0, 100 )
camera.lookAt( new THREE.Vector3( 0, 0, 0 ) )
const renderer = new THREE.WebGLRenderer({ canvas });
const light = new THREE.PointLight(0xFFFFFF);
light.position.set(0, 1, 2);
scene.add(light);
var texture = new THREE.TextureLoader().load( "https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/crate.gif" );
var material = new THREE.MeshBasicMaterial({ color: "white", map: texture });
const pos = new Float32Array([0, 0, 0, 0, 10, 0, 10, 10, 0, 0, 0, 0, 10, 0, 0, 10, 10, 0]);
const nrm = new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]);
const tex = new Float32Array([0,0, 1,0, 1,1, 0,0, 0,1, 1,1]);
const geo = new THREE.BufferGeometry();
geo.addAttribute("position", new THREE.BufferAttribute(pos, 3));
geo.addAttribute("normal", new THREE.BufferAttribute(nrm, 3));
geo.addAttribute("uv", new THREE.BufferAttribute(tex, 2));
//This is a drawing group, each square is 6 indices (2 triangles with 3 vertices each).
geo.addGroup(0 ,6,0);
const mesh = new THREE.Mesh(geo, [material]);
mesh.position.set(-2,-0.5,0);
scene.add(mesh);
function update(){
scene.rotation.y = 0.25 * Math.cos(performance.now() * 0.001)
renderer.render(scene, camera);
requestAnimationFrame(update);
}
update();
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...
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