I have this little test script which I'll try to include below. It works fine in Chrome but not in IE10. IE10 gives me a nice white screen. I tried putting in the meta-equiv thing to help IE10 get the hint, but that did not change anything (in either browser). Please help.
<!-- language: lang-js -->
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="three.min.js"></script>
<script defer="defer">
// http://www.aerotwist.com/tutorials/getting-started-with-three-js/
var cubes = [];
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// camera
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 5000);
camera.position.z = 800;
// scene
var scene = new THREE.Scene();
// material
var phongMaterial = new THREE.MeshPhongMaterial({ambient: 0x555555,
color: 0x555555,
specular: 0xffffff,
shininess: 50,
side: THREE.FrontSide,
shading: THREE.SmoothShading});
var phongBack = new THREE.MeshPhongMaterial({ambient: 0x555555,
color: 0x995555,
specular: 0xffffff,
shininess: 50,
side: THREE.BackSide,
shading: THREE.SmoothShading});
var materials = [phongMaterial, phongBack];
// cube
//var cube = new THREE.Mesh(new THREE.CubeGeometry(200, 200, 200), material);
var geom = new THREE.Geometry();
var a = 100;
var b = 100;
var c = 300;
var geom = new THREE.Geometry();
var halfPi = (Math.PI / 2.0);
var u = -halfPi;
var uInc = Math.PI / 200.0;
var v = - Math.PI;
var vInc = uInc * 2.0;
var vertexNdx = 0;
var vs = [];
var on = true;
while (u < halfPi) {
var oneLine = [];
vs.push(oneLine);
while (v < Math.PI) {
var x = a * Math.cos(u) * Math.cos(v);
var y = b * Math.cos(u) * Math.sin(v);
var z = c * Math.sin(v) * Math.sin(u);
x += Math.random();
y += Math.random();
z += Math.random();
var v1 = new THREE.Vector3(x, y, z);
geom.vertices.push(v1);
oneLine.push(vertexNdx++);
if (on)
{
if (vs.length > 1 && oneLine.length > 1)
{
var uNdx = vs.length - 1;
var vNdx = oneLine.length - 1;
geom.faces.push(new THREE.Face3(vs[uNdx - 1][vNdx - 1],
vs[uNdx - 1][vNdx],
vs[uNdx][vNdx - 1]));
geom.faces.push(new THREE.Face3(vs[uNdx][vNdx - 1],
vs[uNdx - 1][vNdx],
vs[uNdx][vNdx]));
}
//on = false;
}
else
{
on = true;
}
v += vInc;
}
v = -Math.PI;
u += uInc;
}
var oneLine = vs[0];
var uNdx = vs.length - 1;
for (var vNdx = 1; vNdx < oneLine.length; vNdx++)
{
geom.faces.push(new THREE.Face3(vs[0][vNdx - 1],
vs[0][vNdx],
vs[uNdx][vNdx - 1]));
geom.faces.push(new THREE.Face3(vs[uNdx][vNdx - 1],
vs[0][vNdx],
vs[uNdx][vNdx]));
}
geom.computeFaceNormals();
var cube = new THREE.SceneUtils.createMultiMaterialObject(geom, materials);
cube.overdraw = true;
cube.rotation.x = Math.PI * 0.1;
scene.add(cube);
cubes.push(cube);
var geom = new THREE.Geometry();
var a = 100;
var b = 100;
var c = 300;
var geom = new THREE.Geometry();
var halfPi = (Math.PI / 2.0);
var u = -halfPi;
var uInc = Math.PI / 200.0;
var v = - Math.PI;
var vInc = uInc * 2.0;
var vertexNdx = 0;
var vs = [];
var on = true;
while (u < halfPi) {
var oneLine = [];
vs.push(oneLine);
var xRand = Math.random();
while (v < Math.PI) {
var x = a * Math.cos(u) * Math.cos(v);
var y = b * Math.cos(u) * Math.sin(v);
var z = c * Math.sin(v) * Math.sin(u);
x += xRand;
y += Math.random();
z += Math.random();
var v1 = new THREE.Vector3(x, y, z);
geom.vertices.push(v1);
oneLine.push(vertexNdx++);
if (on)
{
if (vs.length > 1 && oneLine.length > 1)
{
var uNdx = vs.length - 1;
var vNdx = oneLine.length - 1;
geom.faces.push(new THREE.Face3(vs[uNdx - 1][vNdx - 1],
vs[uNdx - 1][vNdx],
vs[uNdx][vNdx - 1]));
geom.faces.push(new THREE.Face3(vs[uNdx][vNdx - 1],
vs[uNdx - 1][vNdx],
vs[uNdx][vNdx]));
}
//on = false;
}
else
{
on = true;
}
v += vInc;
}
v = -Math.PI;
u += uInc;
}
var oneLine = vs[0];
var uNdx = vs.length - 1;
for (var vNdx = 1; vNdx < oneLine.length; vNdx++)
{
geom.faces.push(new THREE.Face3(vs[0][vNdx - 1],
vs[0][vNdx],
vs[uNdx][vNdx - 1]));
geom.faces.push(new THREE.Face3(vs[uNdx][vNdx - 1],
vs[0][vNdx],
vs[uNdx][vNdx]));
}
geom.applyMatrix(new THREE.Matrix4().translate(new THREE.Vector3(200, 0, 0)));
geom.computeFaceNormals();
var cube = new THREE.SceneUtils.createMultiMaterialObject(geom, materials);
cube.overdraw = true;
cube.rotation.x = Math.PI * 0.1;
scene.add(cube);
cubes.push(cube);
var geom = new THREE.Geometry();
var a = 100;
var b = 100;
var c = 300;
var geom = new THREE.Geometry();
var halfPi = (Math.PI / 2.0);
var u = -halfPi;
var uInc = Math.PI / 200.0;
var v = - Math.PI;
var vInc = uInc * 2.0;
var vertexNdx = 0;
var vs = [];
var on = true;
while (u < halfPi) {
var oneLine = [];
vs.push(oneLine);
var yRand = Math.random();
while (v < Math.PI) {
var x = a * Math.cos(u) * Math.cos(v);
var y = b * Math.cos(u) * Math.sin(v);
var z = c * Math.sin(v) * Math.sin(u);
x += Math.random();
y += yRand;
z += Math.random();
var v1 = new THREE.Vector3(x, y, z);
geom.vertices.push(v1);
oneLine.push(vertexNdx++);
if (on)
{
if (vs.length > 1 && oneLine.length > 1)
{
var uNdx = vs.length - 1;
var vNdx = oneLine.length - 1;
geom.faces.push(new THREE.Face3(vs[uNdx - 1][vNdx - 1],
vs[uNdx - 1][vNdx],
vs[uNdx][vNdx - 1]));
geom.faces.push(new THREE.Face3(vs[uNdx][vNdx - 1],
vs[uNdx - 1][vNdx],
vs[uNdx][vNdx]));
}
//on = false;
}
else
{
on = true;
}
v += vInc;
}
v = -Math.PI;
u += uInc;
}
var oneLine = vs[0];
var uNdx = vs.length - 1;
for (var vNdx = 1; vNdx < oneLine.length; vNdx++)
{
geom.faces.push(new THREE.Face3(vs[0][vNdx - 1],
vs[0][vNdx],
vs[uNdx][vNdx - 1]));
geom.faces.push(new THREE.Face3(vs[uNdx][vNdx - 1],
vs[0][vNdx],
vs[uNdx][vNdx]));
}
geom.applyMatrix(new THREE.Matrix4().translate(new THREE.Vector3(-200, 0, 0)));
geom.computeFaceNormals();
var cube = new THREE.SceneUtils.createMultiMaterialObject(geom, materials);
cube.overdraw = true;
cube.rotation.x = Math.PI * 0.1;
scene.add(cube);
cubes.push(cube);
// add subtle ambient lighting
var ambientLight = new THREE.AmbientLight(0xdddddd);
scene.add(ambientLight);
// directional lighting
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
var screenW = window.innerWidth;
var screenH = window.innerHeight;
var spdx = 0, spdy = 0, mouseX = 0, mouseY = 0, mouseDown = false;
document.addEventListener('mousemove', function(event) {
mouseX = event.clientX;
mouseY = event.clientY;
}, false);
document.body.addEventListener('mousedown', function(event) {
mouseDown = true;
}, false);
document.body.addEventListener('mouseup', function(event) {
mouseDown = false;
}, false);
function animate() {
spdy = (screenH / 2 - mouseY) / 40;
spdx = (screenW / 2 - mouseX) / 40;
if (mouseDown) {
for (var loop = 0; loop < cubes.length; loop++) {
var cube = cubes[loop];
cube.rotation.x = spdy;
cube.rotation.y = spdx;
}
}
renderer.render(scene, camera);
requestAnimationFrame(function(){
animate();
});
};
// start animation
animate();
</script>
</body>
</html>
And feel free to steal my little play test code if you like it. If you make something that looks cool, let me see it! I'm looking for organic-looking shapes that are made mathematically.
IE10 does not support WebGL. I think your code will work if you just switch from WebGLRenderer to CanvasRenderer, but the lighting won't be as accurate.
Related
Here I have created a rect in which it gets zoom in to the mid point of the scene. Here I'm looking to zoom in to the center of the rect (where we select the object) towards the object.
I have an idea to get the center point of the rect and and pass it to the zoom function where I'm struggling in.
Please help me out
Heres the snippet
/**
* #author HypnosNova / https://www.threejs.org.cn/gallery
* This is a class to check whether objects are in a selection area in 3D space
*/
THREE.SelectionBox = (function() {
var frustum = new THREE.Frustum();
var center = new THREE.Vector3();
function SelectionBox(camera, scene, deep) {
this.camera = camera;
this.scene = scene;
this.startPoint = new THREE.Vector3();
this.endPoint = new THREE.Vector3();
this.collection = [];
this.deep = deep || Number.MAX_VALUE;
}
SelectionBox.prototype.select = function(startPoint, endPoint) {
this.startPoint = startPoint || this.startPoint;
this.endPoint = endPoint || this.endPoint;
this.collection = [];
this.updateFrustum(this.startPoint, this.endPoint);
this.searchChildInFrustum(frustum, this.scene);
return this.collection;
};
SelectionBox.prototype.updateFrustum = function(startPoint, endPoint) {
startPoint = startPoint || this.startPoint;
endPoint = endPoint || this.endPoint;
this.camera.updateProjectionMatrix();
this.camera.updateMatrixWorld();
var tmpPoint = startPoint.clone();
tmpPoint.x = Math.min(startPoint.x, endPoint.x);
tmpPoint.y = Math.max(startPoint.y, endPoint.y);
endPoint.x = Math.max(startPoint.x, endPoint.x);
endPoint.y = Math.min(startPoint.y, endPoint.y);
var vecNear = this.camera.position.clone();
var vecTopLeft = tmpPoint.clone();
var vecTopRight = new THREE.Vector3(endPoint.x, tmpPoint.y, 0);
var vecDownRight = endPoint.clone();
var vecDownLeft = new THREE.Vector3(tmpPoint.x, endPoint.y, 0);
vecTopLeft.unproject(this.camera);
vecTopRight.unproject(this.camera);
vecDownRight.unproject(this.camera);
vecDownLeft.unproject(this.camera);
var vectemp1 = vecTopLeft.clone().sub(vecNear);
var vectemp2 = vecTopRight.clone().sub(vecNear);
var vectemp3 = vecDownRight.clone().sub(vecNear);
vectemp1.normalize();
vectemp2.normalize();
vectemp3.normalize();
vectemp1.multiplyScalar(this.deep);
vectemp2.multiplyScalar(this.deep);
vectemp3.multiplyScalar(this.deep);
vectemp1.add(vecNear);
vectemp2.add(vecNear);
vectemp3.add(vecNear);
var planes = frustum.planes;
planes[0].setFromCoplanarPoints(vecNear, vecTopLeft, vecTopRight);
planes[1].setFromCoplanarPoints(vecNear, vecTopRight, vecDownRight);
planes[2].setFromCoplanarPoints(vecDownRight, vecDownLeft, vecNear);
planes[3].setFromCoplanarPoints(vecDownLeft, vecTopLeft, vecNear);
planes[4].setFromCoplanarPoints(vecTopRight, vecDownRight, vecDownLeft);
planes[5].setFromCoplanarPoints(vectemp3, vectemp2, vectemp1);
planes[5].normal.multiplyScalar(-1);
};
SelectionBox.prototype.searchChildInFrustum = function(frustum, object) {
if (object.isMesh) {
if (object.material !== undefined) {
object.geometry.computeBoundingSphere();
center.copy(object.geometry.boundingSphere.center);
center.applyMatrix4(object.matrixWorld);
if (frustum.containsPoint(center)) {
this.collection.push(object);
}
}
}
if (object.children.length > 0) {
for (var x = 0; x < object.children.length; x++) {
this.searchChildInFrustum(frustum, object.children[x]);
}
}
};
return SelectionBox;
})();
/**
* #author HypnosNova / https://www.threejs.org.cn/gallery
*/
THREE.SelectionHelper = (function() {
function SelectionHelper(selectionBox, renderer, cssClassName) {
this.element = document.createElement('div');
this.element.classList.add(cssClassName);
this.element.style.pointerEvents = 'none';
this.renderer = renderer;
this.startPoint = new THREE.Vector2();
this.pointTopLeft = new THREE.Vector2();
this.pointBottomRight = new THREE.Vector2();
this.isDown = false;
this.renderer.domElement.addEventListener('mousedown', function(event) {
this.isDown = true;
this.onSelectStart(event);
}.bind(this), false);
this.renderer.domElement.addEventListener('mousemove', function(event) {
if (this.isDown) {
this.onSelectMove(event);
}
}.bind(this), false);
this.renderer.domElement.addEventListener('mouseup', function(event) {
this.isDown = false;
this.onSelectOver(event);
}.bind(this), false);
}
SelectionHelper.prototype.onSelectStart = function(event) {
this.renderer.domElement.parentElement.appendChild(this.element);
this.element.style.left = event.clientX + 'px';
this.element.style.top = event.clientY + 'px';
this.element.style.width = '0px';
this.element.style.height = '0px';
this.startPoint.x = event.clientX;
this.startPoint.y = event.clientY;
};
SelectionHelper.prototype.onSelectMove = function(event) {
this.pointBottomRight.x = Math.max(this.startPoint.x, event.clientX);
this.pointBottomRight.y = Math.max(this.startPoint.y, event.clientY);
this.pointTopLeft.x = Math.min(this.startPoint.x, event.clientX);
this.pointTopLeft.y = Math.min(this.startPoint.y, event.clientY);
this.element.style.left = this.pointTopLeft.x + 'px';
this.element.style.top = this.pointTopLeft.y + 'px';
this.element.style.width = (this.pointBottomRight.x - this.pointTopLeft.x) + 'px';
this.element.style.height = (this.pointBottomRight.y - this.pointTopLeft.y) + 'px';
};
SelectionHelper.prototype.onSelectOver = function() {
this.element.parentElement.removeChild(this.element);
};
return SelectionHelper;
})();
var container;
var camera, scene, renderer;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 5000);
camera.position.z = 1000;
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
scene.add(new THREE.AmbientLight(0x505050));
var light = new THREE.SpotLight(0xffffff, 1.5);
light.position.set(0, 500, 2000);
light.angle = Math.PI / 9;
light.castShadow = true;
light.shadow.camera.near = 1000;
light.shadow.camera.far = 4000;
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
scene.add(light);
var geometry = new THREE.BoxBufferGeometry(20, 20, 20);
for (var i = 0; i < 200; i++) {
var object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
color: Math.random() * 0xffffff
}));
object.position.x = Math.random() * 1600 - 800;
object.position.y = Math.random() * 900 - 450;
object.position.z = Math.random() * 900 - 500;
object.rotation.x = Math.random() * 2 * Math.PI;
object.rotation.y = Math.random() * 2 * Math.PI;
object.rotation.z = Math.random() * 2 * Math.PI;
object.scale.x = Math.random() * 2 + 1;
object.scale.y = Math.random() * 2 + 1;
object.scale.z = Math.random() * 2 + 1;
object.castShadow = true;
object.receiveShadow = true;
scene.add(object);
}
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFShadowMap;
container.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
var selectionBox = new THREE.SelectionBox(camera, scene);
var helper = new THREE.SelectionHelper(selectionBox, renderer, 'selectBox');
document.addEventListener('mousedown', function(event) {
for (var item of selectionBox.collection) {
item.material.emissive = new THREE.Color(0x000000);
}
selectionBox.startPoint.set(
(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1,
0.5);
});
document.addEventListener('mousemove', function(event) {
if (helper.isDown) {
for (var i = 0; i < selectionBox.collection.length; i++) {
selectionBox.collection[i].material.emissive = new THREE.Color(0x000000);
}
selectionBox.endPoint.set(
(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1,
0.5);
var allSelected = selectionBox.select();
for (var i = 0; i < allSelected.length; i++) {
allSelected[i].material.emissive = new THREE.Color(0x0000ff);
}
}
});
document.addEventListener('mouseup', function(event) {
selectionBox.endPoint.set(
(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1,
0.5);
var allSelected = selectionBox.select();
for (var i = 0; i < allSelected.length; i++) {
allSelected[i].material.emissive = new THREE.Color(0x0000ff);
}
});
body {
background-color: #f0f0f0;
color: #000;
margin: 0;
}
canvas {
display: block;
}
a {
color: #08e;
}
.selectBox {
border: 1px solid #55aaff;
background-color: rgba(75, 160, 255, 0.3);
position: fixed;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/105/three.min.js"></script>
You want to zoom in to the selected area? Based on what you're doing, you could try something like this:
function zoomToSelection() {
topLeft = helper.pointTopLeft;
bottomRight = helper.pointBottomRight;
// Get the centerpoint of the selection.
let center = new THREE.Vector2(topLeft.x + (bottomRight.x - topLeft.x) / 2, topLeft.y + (bottomRight.y - topLeft.y) / 2);
// Get the center position in world space.
var vector = new THREE.Vector3(
(center.x / window.innerWidth) * 2 - 1,
-(center.y / window.innerHeight) * 2 + 1,
0.5
);
vector.unproject(camera);
camera.lookAt(vector);
var movement = vector.clone();
// Get the ratio between the box size and the window.
let zoomNeeded = (bottomRight.y - topLeft.y) / window.innerHeight;
// Get a scalar by which to move the camera in the direction it's looking.
let distanceToOrigin = camera.position.distanceTo(new THREE.Vector3(0, 0, 0))
let distance = distanceToOrigin - distanceToOrigin * zoomNeeded;
movement.sub(camera.position).normalize().multiplyScalar(-zoom);
var toDirection = camera.position.clone().sub(movement);
camera.position.set(toDirection.x, toDirection.y, toDirection.z);
}
Now just add to your 'mouseup' event listener:
document.addEventListener('mouseup', function(event) {
//...
zoomToSelection();
});
I came across this truly excellent example of how to implement billboarding via a vertex shader to offload the hard work of drawing and rotating a large number of labels to always face the camera.
var scene;
var book;
var shaderMaterial;
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
var camera = new THREE.PerspectiveCamera(55, 1, 0.1, 40000);
window.onresize = function () {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
};
window.onresize();
scene = new THREE.Scene();
camera.position.z = 25;
camera.position.y = 15;
scene.add(camera);
var grid = new THREE.GridHelper(100, 10);
scene.add(grid);
var controls = new THREE.OrbitControls(camera);
controls.damping = 0.2;
var lettersPerSide = 16;
function createGlpyhSheet() {
var fontSize = 64;
var c = document.createElement('canvas');
c.width = c.height = fontSize * lettersPerSide;
var ctx = c.getContext('2d');
ctx.font = fontSize + 'px Monospace';
var i = 0;
for (var y = 0; y < lettersPerSide; y++) {
for (var x = 0; x < lettersPerSide; x++, i++) {
var ch = String.fromCharCode(i);
ctx.fillText(ch, x * fontSize, -(8 / 32) * fontSize + (y + 1) * fontSize);
}
}
var tex = new THREE.Texture(c);
tex.flipY = false;
tex.needsUpdate = true;
return tex;
}
function createLabels(textArrays, positions) {
//console.log(textArrays, positions);
var master_geometry = new THREE.Geometry();
for (var k = 0; k < textArrays.length; k++) {
var geo = new THREE.Geometry();
geo.dynamic = true;
var str = textArrays[k];
var vec = positions[k];
//console.log(shaderMaterial);
//console.log('str is', str, 'vec is', vec);
var j = 0,
ln = 0;
for (i = 0; i < str.length; i++) {
//console.log('creating glyph', str[i]);
var code = str.charCodeAt(i);
var cx = code % lettersPerSide;
var cy = Math.floor(code / lettersPerSide);
var oneDotOne = .55;
geo.vertices.push(
new THREE.Vector3(j * oneDotOne + 0.05, ln * oneDotOne + 0.05, 0),
new THREE.Vector3(j * oneDotOne + 1.05, ln * oneDotOne + 0.05, 0),
new THREE.Vector3(j * oneDotOne + 1.05, ln * oneDotOne + 1.05, 0),
new THREE.Vector3(j * oneDotOne + 0.05, ln * oneDotOne + 1.05, 0));
shaderMaterial.attributes.labelpos.value.push(vec);
shaderMaterial.attributes.labelpos.value.push(vec);
shaderMaterial.attributes.labelpos.value.push(vec);
shaderMaterial.attributes.labelpos.value.push(vec);
var face = new THREE.Face3(i * 4 + 0, i * 4 + 1, i * 4 + 2);
geo.faces.push(face);
face = new THREE.Face3(i * 4 + 0, i * 4 + 2, i * 4 + 3);
geo.faces.push(face);
var ox = (cx + 0.05) / lettersPerSide;
var oy = (cy + 0.05) / lettersPerSide;
var off = 0.9 / lettersPerSide;
geo.faceVertexUvs[0].push([
new THREE.Vector2(ox, oy + off),
new THREE.Vector2(ox + off, oy + off),
new THREE.Vector2(ox + off, oy)]);
geo.faceVertexUvs[0].push([
new THREE.Vector2(ox, oy + off),
new THREE.Vector2(ox + off, oy),
new THREE.Vector2(ox, oy)]);
if (code == 10) {
ln--;
j = 0;
} else {
j++;
}
}
// i can only get this working with merge.
// Building one giant geometry doesn't work for some reason
master_geometry.merge(geo);
}
console.log(shaderMaterial);
shaderMaterial.attributes.labelpos.needsUpdate = true;
book = new THREE.Mesh(
master_geometry,
shaderMaterial);
//book.doubleSided = true;
scene.add(book);
}
var uniforms = {
map: {
type: "t",
value: createGlpyhSheet()
}
};
var attributes = {
labelpos: {
type: 'v3',
value: []
}
};
shaderMaterial = new THREE.ShaderMaterial({
attributes: attributes,
uniforms: uniforms,
vertexShader: document.querySelector('#vertex').textContent,
fragmentShader: document.querySelector('#fragment').textContent
});
shaderMaterial.transparent = true;
shaderMaterial.depthTest = false;
strings = [];
vectors = [];
var sizeOfWorld = 100;
var halfSize = sizeOfWorld * 0.5;
for (var i = 0; i < 500; i++) {
strings.push('test' + i);
var vector = new THREE.Vector3();
vector.x = Math.random() * sizeOfWorld - halfSize;
vector.y = Math.random() * sizeOfWorld - halfSize;
vector.z = Math.random() * sizeOfWorld - halfSize;
vectors.push(vector);
}
console.log('creating labels');
createLabels(strings, vectors);
function animate() {
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate, renderer.domElement);
}
animate();
html {
background-color: #ffffff;
}
* {
margin: 0;
padding: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/69/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/4862f5f1111346a957ac3e0cb0858be1568d0e03/examples/js/controls/OrbitControls.js"></script>
<script id="vertex" type="text/x-glsl-vert">
varying vec2 vUv;
attribute vec3 labelpos;
void main() {
vUv = uv;
gl_Position = projectionMatrix *
(modelViewMatrix * vec4(labelpos, 1) +
vec4(position.xy, 0, 0));
}
</script>
<script id="fragment" type="text/x-glsl-frag">
varying vec2 vUv;
uniform sampler2D map;
void main() {
vec4 diffuse = texture2D(map, vUv);
vec4 letters = mix(diffuse, vec4(1.0, 1.0, 1.0, diffuse.a), 1.0);
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0) * letters;
}
</script>
The code was created before THREE js transitioned away from allowing attributes in uniforms, and enforcing we now use buffered Geometry instead. After some digging I found you can easily create a buffered Geometry from a standard geometry using:
buffGeometry = new THREE.BufferGeometry().fromGeometry( <my old Geometry object> );
How cool is that! - works a treat, however I cannot work out how or where to pass the long list of attribute vec3's to the shader to tell it where my mid point for each label should be, to achieve the same effect as the older example given.
Has anyone got any ideas on how to solve this? The example posted is exactly what I am after, but I really don't want to be stuck using an old version of THREE for the rest of time...
Many thanks for any suggestions :)
FR
So after much experimentation I figured it out myself - go me.
You convert the old Geometry object to a THREE.BufferGeometry() using the aforementioned fromGeometry() function, create an Float32Array array of the location of each labels x,y,z coordinates for each and every vertices and pass that array to the BufferGeometry via the addAttribute function, the shader knows both where to draw the labels and where to pivot when rotating the camera, re-creating the billboard effect using the latest version of THREE.js. 8) See working attached code example, hope someone else finds this useful! :)
var scene;
var book;
var shaderMaterial;
var stats;
var container;
container = document.createElement('div');
document.body.appendChild(container);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
var camera = new THREE.PerspectiveCamera(55, 1, 0.1, 40000);
window.onresize = function() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
};
window.onresize();
scene = new THREE.Scene();
camera.position.z = 25;
camera.position.y = 15;
scene.add(camera);
var labelPosArray = [];
var grid = new THREE.GridHelper(100, 10);
scene.add(grid);
stats = new Stats();
container.appendChild(stats.dom);
container.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.damping = 0.2;
var lettersPerSide = 16;
function createGlpyhSheet() {
var fontSize = 64;
var c = document.createElement('canvas');
c.width = c.height = fontSize * lettersPerSide;
var ctx = c.getContext('2d');
ctx.font = fontSize + 'px Monospace';
var i = 0;
for (var y = 0; y < lettersPerSide; y++) {
for (var x = 0; x < lettersPerSide; x++, i++) {
var ch = String.fromCharCode(i);
ctx.fillText(ch, x * fontSize, -(8 / 32) * fontSize + (y + 1) * fontSize);
}
}
var tex = new THREE.Texture(c);
tex.flipY = false;
tex.needsUpdate = true;
return tex;
}
function createLabels(textArrays, positions) {
var master_geometry = new THREE.Geometry();
for (var k = 0; k < textArrays.length; k++) {
var geo = new THREE.Geometry();
geo.dynamic = true;
var str = textArrays[k];
var vec = positions[k];
var j = 0,
ln = 0;
for (i = 0; i < str.length; i++) {
var code = str.charCodeAt(i);
var cx = code % lettersPerSide;
var cy = Math.floor(code / lettersPerSide);
var oneDotOne = .55;
geo.vertices.push(
new THREE.Vector3(j * oneDotOne + 0.05, ln * oneDotOne + 0.05, 0),
new THREE.Vector3(j * oneDotOne + 1.05, ln * oneDotOne + 0.05, 0),
new THREE.Vector3(j * oneDotOne + 1.05, ln * oneDotOne + 1.05, 0),
new THREE.Vector3(j * oneDotOne + 0.05, ln * oneDotOne + 1.05, 0));
labelPosArray.push(vec);
labelPosArray.push(vec);
labelPosArray.push(vec);
labelPosArray.push(vec);
labelPosArray.push(vec);
labelPosArray.push(vec);
var face = new THREE.Face3(i * 4 + 0, i * 4 + 1, i * 4 + 2);
geo.faces.push(face);
face = new THREE.Face3(i * 4 + 0, i * 4 + 2, i * 4 + 3);
geo.faces.push(face);
var ox = (cx + 0.05) / lettersPerSide;
var oy = (cy + 0.05) / lettersPerSide;
var off = 0.9 / lettersPerSide;
geo.faceVertexUvs[0].push([
new THREE.Vector2(ox, oy + off),
new THREE.Vector2(ox + off, oy + off),
new THREE.Vector2(ox + off, oy)
]);
geo.faceVertexUvs[0].push([
new THREE.Vector2(ox, oy + off),
new THREE.Vector2(ox + off, oy),
new THREE.Vector2(ox, oy)
]);
if (code == 10) {
ln--;
j = 0;
} else {
j++;
}
}
master_geometry.merge(geo);
}
var lps = new Float32Array(labelPosArray.length * 3);
var cnt = 0;
for (i = 0; i < labelPosArray.length; i++) {
lps[cnt++] = labelPosArray[i].x;
lps[cnt++] = labelPosArray[i].y;
lps[cnt++] = labelPosArray[i].z;
} // for
buffGeometry = new THREE.BufferGeometry().fromGeometry(master_geometry);
buffGeometry.addAttribute('labelpos', new THREE.BufferAttribute(lps, 3));
book = new THREE.Mesh(
buffGeometry,
shaderMaterial);
scene.add(book);
}
var uniforms = {
map: {
type: "t",
value: createGlpyhSheet()
}
};
shaderMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.querySelector('#vertex').textContent,
fragmentShader: document.querySelector('#fragment').textContent
});
shaderMaterial.transparent = true;
shaderMaterial.depthTest = false;
strings = [];
vectors = [];
var sizeOfWorld = 100;
var halfSize = sizeOfWorld * 0.5;
for (var i = 0; i < 500; i++) {
strings.push('label ' + i);
var vector = new THREE.Vector3();
vector.x = Math.random() * sizeOfWorld - halfSize;
vector.y = Math.random() * sizeOfWorld - halfSize;
vector.z = Math.random() * sizeOfWorld - halfSize;
vectors.push(vector);
}
//console.log('creating labels');
createLabels(strings, vectors);
function animate() {
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate, renderer.domElement);
stats.update();
}
animate();
html {
background-color: #ffffff;
}
* {
margin: 0;
padding: 0;
}
<script src="https://raw.githack.com/mrdoob/three.js/r124/build/three.js"></script>
<script src="https://raw.githack.com/mrdoob/three.js/r124/examples/js/controls/OrbitControls.js"></script>
<script src="https://raw.githack.com/mrdoob/three.js/r124/examples/js/libs/stats.min.js"></script>
<script id="vertex" type="text/x-glsl-vert">
varying vec2 vUv; attribute vec3 labelpos; void main() { vUv = uv; gl_Position = projectionMatrix * (modelViewMatrix * vec4(labelpos, 1) + vec4(position.xy, 0, 0)); }
</script>
<script id="fragment" type="text/x-glsl-frag">
varying vec2 vUv; uniform sampler2D map; void main() { vec4 diffuse = texture2D(map, vUv); vec4 letters = mix(diffuse, vec4(1.0, 1.0, 1.0, diffuse.a), 1.0); gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0) * letters; }
</script>
I made a house with walls, ceiling and floor.
Now I am trying to make holes in walls for windows/doors.
But there is an issue in textures of the walls.
This is function to build wall:
function build_wall(start, end, materialFront, id){
var dx = end.x - start.x;
var dy = end.y - start.y;
var wall_length = Math.sqrt(dx*dx + dy*dy);
var centroid_x = start.x + dx/2;
var centroid_y = (start.y + dy/2) * -1;
var ry = Math.atan2(dy, dx);
var materialBack = new THREE.MeshLambertMaterial( { color: 0xd9d9d9, shading: THREE.FlatShading, side: THREE.BackSide} );
var materialTop = new THREE.MeshBasicMaterial({color: 0xb3b3b3, side: THREE.DoubleSide});
var materials = [materialFront, materialBack, materialTop];
var material = new THREE.MeshFaceMaterial(materials);
var rectShape = new THREE.Shape();
rectShape.moveTo( 0, 0 );
rectShape.lineTo( 0, wall_height );
rectShape.lineTo( wall_length, wall_height );
rectShape.lineTo( wall_length, 0 );
rectShape.lineTo( 0, 0 );
var windowHole = new THREE.Path();
windowHole.moveTo(20, 180);
windowHole.lineTo(20, 160);
windowHole.lineTo(40, 160);
windowHole.lineTo(40, 180);
rectShape.holes.push(windowHole);
var extrudeSettings = { amount: 5, bevelEnabled: true, bevelSegments: 0, steps: 1, bevelSize: 0, bevelThickness: 1 };
var wall = new THREE.ExtrudeGeometry( rectShape, extrudeSettings );
for ( var face in wall.faces ) {
if (wall.faces[ face ].normal.z > 0.9) wall.faces[ face ].materialIndex = 0;
else if (wall.faces[ face ].normal.z < -0.9) wall.faces[ face ].materialIndex = 1;
else wall.faces[ face ].materialIndex = 2;
}
var wall_mesh = new THREE.Mesh(wall, material);
wall_mesh.position.set( start.x, 0, -start.y );
wall_mesh.rotation.set(0, ry, 0);
wall_mesh.name = id;
wall_mesh.data = 'wall';
scene.add(wall_mesh);
}
and I am calling this function in init():
//------Add Walls
coordArray.push(coordArray[0]); //push the first corner to close loop
for(var i = 0; i < coordArray.length-1; i++){ //iterate over the coordinate array, pushing vertices to the geometry
var start_wall = coordArray[i];
var end_wall = coordArray[(i+1)];
if(!Rooms[r].wall_show || Rooms[r].wall_show[i] == 1){
var wallTexture = new THREE.TextureLoader().load( "images/room_" + r + "_wall_" + i + ".jpg" );
var wall_material = new THREE.MeshBasicMaterial({
map: wallTexture,
side: THREE.FrontSide,
overdraw: 0.5
});
build_wall( start_wall, end_wall, wall_material, scene_id);
}
//find tour boundary, find center target
if(start_wall.x > maxX) maxX = start_wall.x;
if(start_wall.y > maxY) maxY = start_wall.y;
if(start_wall.x < minX) minX = start_wall.x;
if(start_wall.y < minY) minY = start_wall.y;
}
The result is as following:
The screenshot of the result
Sorry, It was cuz I didn't adjust the UVs to the [ 0, 1 ] range.
var uvs = wall.faceVertexUvs[0];
for (var i = 0; i < uvs.length; i++) {
uv = uvs[i];
for (var j = 0; j < uv.length; j++) {
u = uv[j];
u.x = u.x / wall_length;
u.y = u.y/ wall_height;
}
}
var wall_mesh = new THREE.Mesh(wall, material);
wall_mesh.position.set( start.x, 0, -start.y );
wall_mesh.rotation.set(0, ry, 0);
wall_mesh.name = id;
wall_mesh.data = 'wall';
scene.add(wall_mesh);
For performance reasons I merged geometry. I have tens of thousands of cubes to display. I have that working with reasonable performance.
Now I have to deal with removing some. I almost have it but can't figure out how to make this work, so I cut my code up to make this complete sample.
In the onDocumentMouseDown function when a cube is clicked on I try to remove it. And it sort of does. But instead of removing one cube it removes two. (Then it basically acts worse) It removes the one I pointed at and the next one I added.
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>Measurement</title>
<style>
body { margin: 0; }
</style>
</head>
<body>
<div id="canvas_container" style="position: absolute; left:0px; top:0px; touch-action:none;"></div>
<div id="MessageDisplay1" style="position: absolute; top: 50px; left: 50px; background-color: black; opacity: 0.8; color:white; touch-action:none;"></div>
<div id="MessageDisplay" style="position: absolute; top: 50px; left: 200px; background-color: black; opacity: 0.8; color:white; touch-action:none;"></div>
<script src="js/three.js"></script>
<script>
// player motion parameters
var motioncontrol = {
airborne: false,
bumpposition: 5.0,
bumpdegrees: 4.0,
rotationanglezx: 0,
tiltangle: 0,
distancePointZ : 10000.0,
distancePointY: 100.0,
position : new THREE.Vector3(), velocity : new THREE.Vector3(),
rotation: new THREE.Vector3(), spinning: new THREE.Vector2(),
prevposition : new THREE.Vector3(),
prevrotation : new THREE.Vector3()
};
var mouseDown = 0;
var mouse = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var INTERSECTED;
motioncontrol.position.y = 15;
motioncontrol.position.x = 0;
motioncontrol.position.z = 0;
motioncontrol.rotation.x = 0;
motioncontrol.rotation.z = motioncontrol.distancePointZ * Math.cos(0);
motioncontrol.rotation.x = motioncontrol.distancePointZ * Math.sin(0);
motioncontrol.prevposition.copy(motioncontrol.position);
motioncontrol.prevrotation.copy(motioncontrol.rotation);
// Our Javascript will go here.
var domContainer = null;
domContainer = document.getElementById("canvas_container");
var camera = new THREE.PerspectiveCamera( 50, window.innerWidth/window.innerHeight, 0.1, 5000 );
var aspectratio = window.innerWidth/window.innerHeight;
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
domContainer.appendChild(renderer.domElement);
var materials = THREE.ImageUtils.loadTexture('texture/sky.jpg');
addEventListener('mousemove', onDocumentMouseMove, false);
domContainer.addEventListener('mousedown', onDocumentMouseDown, false);
domContainer.addEventListener('mouseup', onDocumentMouseUp, false);
var scene = new THREE.Scene();
scene.add(camera);
window.addEventListener('resize', resize, false);
camera.position.x = 0;
camera.position.y = 100;
camera.position.z = -100;
camera.rotation.y = Math.PI / 180.0 * 90;
var directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
directionalLight.position.set(0, -10, 0);
scene.add(directionalLight);
directionalLight = new THREE.DirectionalLight(0xffffff, 3.0);
directionalLight.position.set(0, -50,-1000);
scene.add(directionalLight);
directionalLight = new THREE.DirectionalLight(0xffffff, 3.0);
directionalLight.position.set(0, -50, 1000);
scene.add(directionalLight);
directionalLight = new THREE.DirectionalLight(0xffffff, 3.0);
directionalLight.position.set(-200, -10, 0);
scene.add(directionalLight);
directionalLight = new THREE.DirectionalLight(0xffffff, 3.0);
directionalLight.position.set(200, -10, 0);
scene.add(directionalLight);
addGround(scene);
// array of unsorted geometries.
var CubeGeometryArray = new Array();
var CubeGeometryTier1 = new THREE.Geometry();
var CubeGeometryTier2 = new THREE.Geometry();
var CubeGeometryTier3 = new THREE.Geometry();
var CubeGeometryTier4 = new THREE.Geometry();
var CubeGeometryTier5 = new THREE.Geometry();
// array of materials
var CubeMaterials = new Array();
// array of meshes used for hit testing.
var CubeArray = new Array();
var cMaterialCount = 0;
var Cube20Mesh;
var Cube40Mesh;
var CubesLoaded = false;
LoadCubeMaterial();
LoadCubeMeshs();
var controls;
function resize()
{
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
};
function onMouseWheel(event)
{
var delta = 0;
if ( event.wheelDelta !== undefined ) {
// WebKit / Opera / Explorer 9
delta = event.wheelDelta;
} else if ( event.detail !== undefined ) {
// Firefox
delta = - event.detail;
}
if ( delta > 0 ) { // forward
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
motioncontrol.position.z += motioncontrol.bumpposition * Math.cos(angle);
motioncontrol.position.x += motioncontrol.bumpposition * Math.sin(angle);
} else if ( delta < 0 ) {
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
angle += Math.PI;
motioncontrol.position.z += motioncontrol.bumpposition * Math.cos(angle);
motioncontrol.position.x += motioncontrol.bumpposition * Math.sin(angle);
}
};
function onDocumentMouseMove(event)
{
event.preventDefault();
if (mouseDown > 0) {
if (((event.clientX / window.innerWidth) * 2 - 1) > mouse.x) {
motioncontrol.rotationanglezx -= motioncontrol.bumpdegrees;
if (motioncontrol.rotationanglezx < 0)
motioncontrol.rotationanglezx += 360;
var angle = (Math.PI / 180.0) * motioncontrol.rotationanglezx;
motioncontrol.rotation.x = motioncontrol.distancePointZ * Math.cos(angle) - motioncontrol.distancePointZ * Math.sin(angle);
motioncontrol.rotation.z = motioncontrol.distancePointZ * Math.sin(angle) + motioncontrol.distancePointZ * Math.cos(angle);
}
if (((event.clientX / window.innerWidth) * 2 - 1) < mouse.x) {
motioncontrol.rotationanglezx += motioncontrol.bumpdegrees;
if (motioncontrol.rotationanglezx > 360)
motioncontrol.rotationanglezx -= 360;
var angle = (Math.PI / 180.0) * motioncontrol.rotationanglezx;
motioncontrol.rotation.x = motioncontrol.distancePointZ * Math.cos(angle) - motioncontrol.distancePointZ * Math.sin(angle);
motioncontrol.rotation.z = motioncontrol.distancePointZ * Math.sin(angle) + motioncontrol.distancePointZ * Math.cos(angle);
}
}
};
function onDocumentMouseDown(event)
{
++mouseDown;
event.preventDefault();
var mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObjects(CubeArray);
if(intersects.length > 0)
{
if(intersects[0].object.name != null)
{
var offset = intersects[0].object.name * 8;
var offsetfaces = intersects[0].object.name * 12;
var index = intersects[0].object.name;
var selectedObject = scene.getObjectByName("Tier1");
scene.remove(selectedObject);
CubeArray.splice(index, 1);
CubeGeometryTier1 = CubeGeometryArray[0];
CubeGeometryTier1.vertices.splice(offset, 8);
CubeGeometryTier1.faces.splice(offsetfaces, 12);
CubeGeometryTier1.faceVertexUvs[0].splice(offsetfaces, 12);
CubeGeometryArray[0] = CubeGeometryTier1.clone();
CubeGeometryTier1.sortFacesByMaterialIndex();
var cmesh = new THREE.Mesh(CubeGeometryTier1, new THREE.MeshFaceMaterial(CubeMaterials));
cmesh.matrixAutoUpdate = false;
cmesh.updateMatrix();
cmesh.name = "Tier1";
scene.add(cmesh);
}
else
INTERSECTED = null;
}
};
function onDocumentMouseUp(event) {
mouseDown = 0;
event.preventDefault();
}
function addGround(scene1)
{
var materialg = new THREE.MeshBasicMaterial( { color: 0x333333 , side: THREE.BackSide } );
var groundmesh = new THREE.Mesh(new THREE.PlaneGeometry(9000, 4500, 1), materialg);
groundmesh.receiveShadow = true;
groundmesh.rotation.x = Math.PI / 180.0 * 90;
groundmesh.position.set(0, 0, 0);
groundmesh.name = "asphalt";
scene1.add(groundmesh);
};
function writeToScreen(message)
{
var pre = document.getElementById("MessageDisplay");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
}
function writeToScreen2(message)
{
var pre = document.getElementById("MessageDisplay1");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
}
function addCube20(name, x1,z1,azimuth,height,add)
{
var cube;
if (add)
{
if (height > 5)
height = 5;
cube = Cube20Mesh.clone();
cube.visible = true;
cube.receiveShadow = true;
cube.position.set(x1, ((height - 1) * 8.5) + 4.25, z1);
cube.rotation.y = (Math.PI / 180.0) * azimuth;
cube.name = name;
cube.updateMatrix();
AddCubeGeometry(cube.geometry, cube.matrix, height);
cube.matrixWorld = cube.matrix;
CubeArray.push(cube); // kept for hit test
}
};
function addCube40(name, x1, z1, azimuth,height,add)
{
var cube;
if (add)
{
if (height > 5)
height = 1;
cube = Cube40Mesh.clone();
cube.visible = true;
cube.receiveShadow = true;
cube.position.set(x1, ((height - 1) * 8.5) + 4.25, z1);
cube.rotation.y = (Math.PI / 180.0) * azimuth;
cube.name = name;
cube.updateMatrix();
AddCubeGeometry(cube.geometry, cube.matrix, height + 5);
cube.matrixWorld = cube.matrix;
CubeArray.push(cube); // kept for hit test
}
};
function LoadCubeMeshs()
{
var CubeGeometry20 = new THREE.BoxGeometry(20, 8.5, 9.5);
var CubeGeometry40 = new THREE.BoxGeometry(40, 8.5, 9.5);
Cube20Mesh = new THREE.Mesh(CubeGeometry20);
Cube40Mesh = new THREE.Mesh(CubeGeometry40);
CubesLoaded = true;
};
function LoadCubeMaterial()
{
CubeMaterials[0] = new THREE.MeshBasicMaterial({color:0xff0000 });
cMaterialCount++;
CubeMaterials[1] = new THREE.MeshBasicMaterial({color:0xffff00 });
cMaterialCount++;
CubeMaterials[2] = new THREE.MeshBasicMaterial({color:0xffffff });
cMaterialCount++;
CubeMaterials[3] = new THREE.MeshBasicMaterial({color:0x0000ff });
cMaterialCount++;
CubeMaterials[4] = new THREE.MeshBasicMaterial({color:0x00ffff });
cMaterialCount++;
CubeMaterials[5] = new THREE.MeshBasicMaterial({color:0x772255 });
cMaterialCount++;
CubeMaterials[6] = new THREE.MeshBasicMaterial({color:0x552277 });
cMaterialCount++;
CubeMaterials[7] = new THREE.MeshBasicMaterial({color:0x222299 });
cMaterialCount++;
CubeMaterials[8] = new THREE.MeshBasicMaterial({color:0x992222 });
cMaterialCount++;
CubeMaterials[9] = new THREE.MeshBasicMaterial({color:0x000000 });
cMaterialCount++;
};
function DisplayCubes(scene1)
{
if(CubeGeometryTier1.faces.length > 0)
{
var material = new THREE.MeshNormalMaterial();
// save the unsorted geometry.
CubeGeometryArray.push(CubeGeometryTier1.clone());
CubeGeometryTier1.sortFacesByMaterialIndex();
var Cubemesh = new THREE.Mesh(CubeGeometryTier1, new THREE.MeshFaceMaterial(CubeMaterials));
Cubemesh.matrixAutoUpdate = false;
Cubemesh.updateMatrix();
Cubemesh.name = "Tier1";
scene1.add(Cubemesh);
}
if(CubeGeometryTier2.faces.length > 0)
{
// save the unsorted geometry.
CubeGeometryArray.push(CubeGeometryTier2.clone());
// sorting is a HUGE performance boost
CubeGeometryTier2.sortFacesByMaterialIndex();
var Cubemesh = new THREE.Mesh(CubeGeometryTier2, CubeMaterials);
Cubemesh.matrixAutoUpdate = false;
Cubemesh.updateMatrix();
Cubemesh.name = "Tier2";
scene1.add(Cubemesh);
}
if(CubeGeometryTier3.faces.length > 0)
{
CubeGeometryArray.push(CubeGeometryTier3.clone());
CubeGeometryTier3.sortFacesByMaterialIndex();
var Cubemesh = new THREE.Mesh(CubeGeometryTier3, CubeMaterials);
Cubemesh.matrixAutoUpdate = false;
Cubemesh.updateMatrix();
Cubemesh.name = "Tier3";
scene1.add(Cubemesh);
}
if(CubeGeometryTier4.faces.length > 0)
{
CubeGeometryArray.push(CubeGeometryTier4.clone());
CubeGeometryTier4.sortFacesByMaterialIndex();
var Cubemesh = new THREE.Mesh(CubeGeometryTier4, CubeMaterials);
Cubemesh.matrixAutoUpdate = false;
Cubemesh.updateMatrix();
Cubemesh.name = "Tier4";
scene1.add(Cubemesh);
}
if(CubeGeometryTier5.faces.length > 0)
{
CubeGeometryArray.push(CubeGeometryTier5.clone());
CubeGeometryTier5.sortFacesByMaterialIndex();
var Cubemesh = new THREE.Mesh(CubeGeometryTier5, CubeMaterials);
Cubemesh.matrixAutoUpdate = false;
Cubemesh.updateMatrix();
Cubemesh.name = "Tier5";
scene1.add(Cubemesh);
}
};
// merging geometry for improved performance.
function AddCubeGeometry(geom, matrix,tier)
{
switch(tier)
{
case 1:
//CubeGeometryTier1.merge(geom, matrix, tier - 1);
CubeGeometryTier1.merge(geom, matrix);
break;
case 2:
CubeGeometryTier2.merge(geom, matrix,tier - 1);
break;
case 3:
CubeGeometryTier3.merge(geom, matrix,tier - 1);
break;
case 4:
CubeGeometryTier4.merge(geom, matrix,tier - 1);
break;
case 5:
CubeGeometryTier5.merge(geom, matrix,tier - 1);
break;
// forty footers
case 6:
// CubeGeometryTier1.merge(geom, matrix,tier - 1);
CubeGeometryTier1.merge(geom, matrix);
break;
case 7:
CubeGeometryTier2.merge(geom, matrix,tier - 1);
break;
case 8:
CubeGeometryTier3.merge(geom, matrix,tier - 1);
break;
case 9:
CubeGeometryTier4.merge(geom, matrix,tier - 1);
break;
case 10:
CubeGeometryTier5.merge(geom, matrix,tier - 1);
break;
default:
CubeGeometryTier1.merge(geom, matrix,0);
break;
}
};
motioncontrol.position.y = 10;
motioncontrol.position.x = -50;
motioncontrol.position.z = 0;
motioncontrol.rotation.x = 0;
motioncontrol.rotation.z = motioncontrol.distancePointZ * Math.cos(0);
motioncontrol.rotation.x = motioncontrol.distancePointZ * Math.sin(0);
function LoadCubes()
{
var cnt = 0;
for(var x = 0; x < 5; x++)
{
addCube20(cnt, 0, x * 23.0, 90, 1, true);
cnt++;
addCube40(cnt, 10, x * 43, 90, 1, true);
cnt++;
}
};
// game systems code
var keyboardControls = (function() {
var keys = { SP : 32, Q:81, E:69, W : 87, A : 65, S : 83, D : 68, UP : 38, LT : 37, DN : 40, RT : 39 };
var keysPressed = {};
(function( watchedKeyCodes ) {
var handler = function( down ) {
return function( e ) {
var index = watchedKeyCodes.indexOf( e.keyCode );
if( index >= 0 ) {
keysPressed[watchedKeyCodes[index]] = down; e.preventDefault();
}
};
};
window.addEventListener( "keydown", handler( true ), false );
window.addEventListener( "keyup", handler( false ), false );
})([
keys.SP, keys.Q, keys.E, keys.W, keys.A, keys.S, keys.D, keys.UP, keys.LT, keys.DN, keys.RT
]);
return function() {
// look around
if (keysPressed[keys.Q])
{
motioncontrol.tiltangle += motioncontrol.bumpdegrees;
if (motioncontrol.tiltangle < 0)
motioncontrol.tiltangle += 360;
var angle = (Math.PI / 180.0) * motioncontrol.tiltangle;
motioncontrol.rotation.y = motioncontrol.distancePointZ * Math.sin(angle);
}
if (keysPressed[keys.E])
{
motioncontrol.tiltangle -= motioncontrol.bumpdegrees;
if (motioncontrol.tiltangle < 0)
motioncontrol.tiltangle += 360;
var angle = (Math.PI / 180.0) * motioncontrol.tiltangle;
motioncontrol.rotation.y = motioncontrol.distancePointZ * Math.sin(angle);
}
if (keysPressed[keys.W])
{
motioncontrol.position.y += motioncontrol.bumpposition;
if (motioncontrol.position.y > 1000.0)
motioncontrol.position.y = 1000.0;
}
if (keysPressed[keys.S])
{
motioncontrol.position.y += -motioncontrol.bumpposition;
if (motioncontrol.position.y < 1.0)
motioncontrol.position.y = 1;
}
if(keysPressed[keys.A])
{
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
angle += Math.PI / 180.0 * 90;
var message = "Angle " + angle * 180/Math.PI;
motioncontrol.position.z += motioncontrol.bumpposition * Math.cos(angle);
motioncontrol.position.x += motioncontrol.bumpposition * Math.sin(angle);
}
if(keysPressed[keys.D])
{
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
angle += Math.PI / 180.0 * -90;
var message = "Angle " + angle * 180/Math.PI;
motioncontrol.position.z += motioncontrol.bumpposition * Math.cos(angle);
motioncontrol.position.x += motioncontrol.bumpposition * Math.sin(angle);
}
// forward
if (keysPressed[keys.UP])
{
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
var message = "Angle " + angle * 180/Math.PI;
motioncontrol.position.z += motioncontrol.bumpposition * Math.cos(angle);
motioncontrol.position.x += motioncontrol.bumpposition * Math.sin(angle);
}
// backward
if(keysPressed[keys.DN])
{
var deltaX = motioncontrol.rotation.z - motioncontrol.position.z;
var deltaY = motioncontrol.rotation.x - motioncontrol.position.x;
// var angle = Math.atan2(deltaY, deltaX);
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
angle += Math.PI;
var message = "Angle " + angle * 180/Math.PI;
motioncontrol.position.z += motioncontrol.bumpposition * Math.cos(angle);
motioncontrol.position.x += motioncontrol.bumpposition * Math.sin(angle);
}
if(keysPressed[keys.LT])
{
motioncontrol.rotationanglezx -= motioncontrol.bumpdegrees;
if (motioncontrol.rotationanglezx < 0)
motioncontrol.rotationanglezx += 360;
var angle = (Math.PI / 180.0) * motioncontrol.rotationanglezx;
motioncontrol.rotation.x = motioncontrol.distancePointZ * Math.cos(angle) - motioncontrol.distancePointZ * Math.sin(angle);
motioncontrol.rotation.z = motioncontrol.distancePointZ * Math.sin(angle) + motioncontrol.distancePointZ * Math.cos(angle);
}
if(keysPressed[keys.RT])
{
motioncontrol.rotationanglezx += motioncontrol.bumpdegrees;
if (motioncontrol.rotationanglezx > 360)
motioncontrol.rotationanglezx -= 360;
var angle = (Math.PI / 180.0) * motioncontrol.rotationanglezx;
motioncontrol.rotation.x = motioncontrol.distancePointZ * Math.cos(angle) - motioncontrol.distancePointZ * Math.sin(angle);
motioncontrol.rotation.z = motioncontrol.distancePointZ * Math.sin(angle) + motioncontrol.distancePointZ * Math.cos(angle);
}
};
})();
var updateCamera = (function() {
return function() {
camera.position.copy(motioncontrol.position);
camera.lookAt(motioncontrol.rotation);
var message = "Rotation " + motioncontrol.rotationanglezx + "<BR>";
message += "X " + motioncontrol.position.x + "<BR>";
message += "Y " + motioncontrol.position.y + "<BR>";
message += "Z " + motioncontrol.position.z + "<BR>";
message += "X " + motioncontrol.rotation.x + "<BR>";
message += "Y " + motioncontrol.rotation.y + "<BR>";
message += "Z " + motioncontrol.rotation.z + "<BR>";
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
message += "Angle " + angle * 180 / Math.PI + "<BR>";
message += "Use Arrows w,s,e,d,q,a to navigate<BR>";
writeToScreen2(message);
};
})();
function render() {
if(cMaterialCount > 9 && cMaterialCount < 200 && CubesLoaded)
{
cMaterialCount = 200;
LoadCubes();
DisplayCubes(scene);
}
keyboardControls();
updateCamera();
renderer.render( scene, camera );
requestAnimationFrame( render );
};
render();
</script>
</body>
</html>
TLDR Array Mutation and Static Offsets are a dangerous mix
First, I recommend you post fiddles of some sort of your code. I made one here of your example. Second, you could really use some DRYing to shorten and clarify your code. Third, in code of this size, I recommend separating and grouping your code somehow (files, tasks, even comment blocks). Last, in a demo like this, I see no reason to roll your own controls. Check out Orbit Camera or the like that THREE.js offers.
Anyway, I gathered you collect a large number of cubes into 10 'tiers' of THREE.Geometry for rendering purposes. Then on click (~line 180), raycast out, and try to remove just that cube from the geometry. Here's your relevant code:
intersects = raycaster.intersectObjects(CubeArray);
if(intersects.length > 0)
{
if(intersects[0].object.name != null)
{
var offset = intersects[0].object.name * 8;
var offsetfaces = intersects[0].object.name * 12;
var index = intersects[0].object.name;
var selectedObject = scene.getObjectByName("Tier1");
scene.remove(selectedObject);
CubeArray.splice(index, 1);
CubeGeometryTier1 = CubeGeometryArray[0];
CubeGeometryTier1.vertices.splice(offset, 8);
CubeGeometryTier1.faces.splice(offsetfaces, 12);
CubeGeometryTier1.faceVertexUvs[0].splice(offsetfaces, 12);
CubeGeometryArray[0] = CubeGeometryTier1.clone();
CubeGeometryTier1.sortFacesByMaterialIndex();
var cmesh = new THREE.Mesh(CubeGeometryTier1, new THREE.MeshFaceMaterial(CubeMaterials));
cmesh.matrixAutoUpdate = false;
cmesh.updateMatrix();
cmesh.name = "Tier1";
scene.add(cmesh);
}
else
INTERSECTED = null;
}
Here's how I read this snippet:
Cast out against the CubeArray
Without a hit something or if it lacks a name, return
Implict cast some string names (!) to numbers to compute location in Tier
Remove an object from the scene by the name "Tier1", regardless of any other input
Set CubeGeometryTier1 to the first index of CubeGeometryArray, regardless of raycast
Fiddle with now overwritten CubeGeometryTier1 to remove geometry
Reassign CubeGeometryArray[0] with the changed object of CubeGeometryTier1
Build a new mesh based on CubeGeometryTier1, call it "Tier1" and dump it back into the scene
I'll admit I haven't entirely traced the hundreds of line where you build your cubes, but this makes little sense to me. Assuming your use of CubeGeometry[Tier|Array] and hard coded names and indices are correct, what really grabs me is the use of static offsets when you mutate the array.
You splice CubeArray to remove that 'ghost' cube from getting picked again, but none of the other 'ghost' cubes changed, notably their offsets names, while the geometry that you rebuilt into Tier1 did. Past the spliced cube, all of them index names will be wrong.
Here's an example in simpler form:
//set up
var baseArray = [0, 1, 2, 3, 4, 5].map(i => '' + i);
const getRandomInt = (min, max) => {
return Math.floor(Math.random() * (max - min)) + min;
};
const pickRandomElementFrombaseArray = () => {
const pickedIndex = getRandomInt(0, baseArray.length);
return baseArray[pickedIndex];
};
// operationally equivilent to splicing your Tiers of their geometry
const yankIndex = (value) => {
//value is a string in this case
const index = +value;
if (index < 0 || index > baseArray.length - 1) {
throw `Unable to remove invalid index ${index}`
} else {
baseArray.splice(index, 1);
}
};
// Run the test until empty or failure
var messages = [`Starting with ${baseArray}`];
while (baseArray.length > 0) {
const pickedValue = pickRandomElementFrombaseArray();
messages.push(`Picked element ${pickedValue} to remove`);
try {
yankIndex(pickedValue);
messages.push(`Now array is ${baseArray}`);
} catch (e) {
messages.push(`ALERT: ${e}`);
break;
}
}
messages.push('Test complete');
const div = $('#div');
messages.map(msg => `<p>${msg}</p>`).forEach(msg => div.append(msg))
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title></title>
</head>
<body>
<h2>Results</h2>
<div id="div"></div>
</body>
</html>
Try it a few times. There's only a 0.8% chance the array will be exhausted before an error.
Sneaky errors can creep into code when you are dealing with mutation of arrays. Ignoring restructuring the code entirely, options that spring to mind:
Maintain an offset map on each removal. Essentially, rebuild each cube's offset on every action. You could do this in the CubeArray or, if creating/mutating 10k heavy THREE.js objects is not to your liking, yet another level of indirection that maps each cube id to an offset in the Tiers
Invisible Objects Never really remove the geometry (ie, don't splice the Tier array), just hide it. Scale the faces to 0, invisible mat, whatever. This means all the offsets you pre-generate will hold. Downsides are invisible geo isn't free, this won't help if you need to change the scene in other ways, and you'll have to scan the other hits from thee.raycast
Design.prototype.get3dPointZAxis = function(event) {
var mouseX = event.clientX - this.container.getClientRects()[0].left;
var mouseY = event.clientY - this.container.getClientRects()[0].top;
var vector = new THREE.Vector3(( mouseX / this.container.offsetWidth ) * 2 - 1, mouseY / this.container.offsetHeight ) * 2 + 1, 0.5 );
this.projector.unprojectVector( vector, this.camera );
var dir = vector.sub( this.camera.position ).normalize();
var distance = - this.camera.position.z / dir.z;
var pos = this.camera.position.clone().add( dir.multiplyScalar( distance ) );
return pos;
};
Design.prototype.stopDraw= function(event) {
this.twoDLine.push(this.tempLine.geometry);
this.tempLine = null;
this.lastPoint = null;
};
Design.prototype.startDraw= function(event) {
this.lastPoint = this.get3dPointZAxis(event);
};
Design.prototype.handleMouseMove = function(event) {
if (this.lastPoint) {
var pos = this.get3dPointZAxis(event);
if (!this.tempLine) {
var material = new THREE.LineBasicMaterial({
color: 0x0089ff
});
var geometry = new THREE.Geometry();
geometry.vertices.push(this.lastPoint);
geometry.vertices.push(pos);
this.tempLine = new THREE.Line(geometry, material);
this.scene.add(this.tempLine);
}
else {
this.tempLine.geometry.verticesNeedUpdate = true;
this.tempLine.geometry.vertices[1] = pos;
}
}
};
I am using mouse events to draw 2D lines and shape inside a container. I need to convert these lines and shapes into 3D. I was trying the following technique... but getting no result. Please please Help!
Design.prototype.convertTo3D = function() {
console.log("convertTo3D");
this.tempLine.geometry.vertices.z = 10;
};
Design.prototype.convertTo3D = function () {
for (var i in this.linesToDraw) {
var line = this.linesToDraw[i];
this.scene.remove(line);
var x1 = line.geometry.vertices[0].x;
var y1 = line.geometry.vertices[0].y;
var x2 = line.geometry.vertices[1].x;
var y2 = line.geometry.vertices[1].y;
var length = Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2));
var slope = (y2 - y1) / (x2 - x1);
var xMid = (x2 + x1) / 2;
var yMid = (y2 + y1) / 2;
slope = Math.atan(slope);
var material = new THREE.MeshPhongMaterial({ambient: 0x030303,shading: THREE.FlatShading});
var geometry = new THREE.CubeGeometry(length, 1, 6);
var cube = new THREE.Mesh(geometry, material);
cube.addEventListener("click", function (event) {
console.log("CUBE CLICKED");
}, false);
cube.rotateZ(slope);
cube.position = new THREE.Vector3(xMid, yMid, 3);
cube.castShadow = true;
cube.receiveShadow = true;
this.scene.add(cube);
}