How to create rotation on perspective camera on Three.js? - three.js

I have build a little 3D-TileMap in Three.js.
Currently, i have problems with my PerspectiveCamera. I wan't to add some camera handling like Map rotating or zooming. The zooming always works, here i'm only use the field of view and mousewheel.
But how i can implement a rotating of my map? When i'm using the coordinates of camera to modify x, y or z, i've misunderstand the calculation.
Here is my current work:
function Input(renderer, camera) {
var press = false
var sensitivity = 0.2
renderer.domElement.addEventListener('mousemove', event => {
if(!press){ return }
camera.position.x += event.movementX * sensitivity
camera.position.y += event.movementY * sensitivity
camera.position.z += event.movementY * sensitivity / 10
})
renderer.domElement.addEventListener('mousedown', () => { press = true })
renderer.domElement.addEventListener('mouseup', () => { press = false })
renderer.domElement.addEventListener('mouseleave', () => { press = false })
renderer.domElement.addEventListener('mousewheel', event => {
// Add MIN/MAX LIMITS
const ratio = camera.position.y / camera.position.z
camera.position.y -= (event.wheelDelta * sensitivity * ratio)
camera.position.z -= (event.wheelDelta * sensitivity)
camera.updateProjectionMatrix()
})
}
var controls;
const Type = 'WebGL'; // WebGL or Canvas
var _width, _height, CUBE_SIZE, GRID, TOTAL_CUBES, WALL_SIZE, HALF_WALL_SIZE,
MAIN_COLOR, SECONDARY_COLOR, cubes, renderer, camera, scene, group
var clock = new THREE.Clock();
clock.start();
var FOV = 45;
_width = window.innerWidth
_height = window.innerHeight
CUBE_SIZE = 80 /* width, height */
GRID = 12 /* cols, rows */
TOTAL_CUBES = (GRID * GRID)
WALL_SIZE = (GRID * CUBE_SIZE)
HALF_WALL_SIZE = (WALL_SIZE / 2)
MAIN_COLOR = 0xFFFFFF
SECONDARY_COLOR = 0x222222
cubes = []
var directions = [];
var normalized = [];
switch(Type) {
case 'WebGL':
renderer = new THREE.WebGLRenderer({antialias: true})
break;
case 'Canvas':
renderer = new THREE.CanvasRenderer({antialias: true})
break;
}
camera = new THREE.PerspectiveCamera(FOV, (_width / _height), 0.1, 10000)
scene = new THREE.Scene()
group = new THREE.Object3D()
/* -- -- */
setupCamera(0, 0, 800)
setupBox(group)
setupFloor(group)
setupCubes(group)
setupLights(group)
group.position.y = 10
group.rotation.set(-60 * (Math.PI/180), 0, -45 * (Math.PI/180))
scene.add(group)
setupRenderer(document.body)
window.addEventListener('resize', resizeHandler, false)
new Input(renderer, camera);
/* -- -- */
function resizeHandler() {
_width = window.innerWidth
_height = window.innerHeight
renderer.setSize(_width, _height)
camera.aspect = _width / _height
camera.updateProjectionMatrix()
}
/* -- CAMERA -- */
function setupCamera(x, y, z) {
camera.position.set(x, y, z)
scene.add(camera)
}
/* -- BOX -- */
function setupBox(parent) {
var i, boxesArray, geometry, material
boxesArray = []
geometry = new THREE.BoxGeometry(WALL_SIZE, WALL_SIZE, 0.05)
geometry.faces[8].color.setHex(SECONDARY_COLOR)
geometry.faces[9].color.setHex(SECONDARY_COLOR)
geometry.colorsNeedUpdate = true
material = new THREE.MeshBasicMaterial({
color : MAIN_COLOR,
vertexColors : THREE.FaceColors,
overdraw: 0.5
})
for (i = 0; i < 5; i++) {
boxesArray.push(new THREE.Mesh(geometry, material))
}
// back
boxesArray[0].position.set(0, HALF_WALL_SIZE, -HALF_WALL_SIZE)
boxesArray[0].rotation.x = 90 * (Math.PI/180)
// right
boxesArray[1].position.set(HALF_WALL_SIZE, 0, -HALF_WALL_SIZE)
boxesArray[1].rotation.y = -90 * (Math.PI/180)
// front
boxesArray[2].position.set(0, -HALF_WALL_SIZE, -HALF_WALL_SIZE)
boxesArray[2].rotation.x = -90 * (Math.PI/180)
// left
boxesArray[3].position.set(-HALF_WALL_SIZE, 0, -HALF_WALL_SIZE)
boxesArray[3].rotation.y = 90 * (Math.PI/180)
// bottom
boxesArray[4].position.set(0, 0, -WALL_SIZE)
boxesArray.forEach(function(box) {
box.renderOrder = 1;
parent.add(box)
});
}
/* -- FLOOR -- */
function setupFloor(parent) {
var i, tilesArray, geometry, material
tilesArray = []
geometry = new THREE.PlaneBufferGeometry(WALL_SIZE, WALL_SIZE)
material = new THREE.MeshLambertMaterial({
color : MAIN_COLOR,
overdraw: 1
})
for (i = 0; i < 8; i++) {
tilesArray.push(new THREE.Mesh(geometry, material))
}
tilesArray[0].position.set(-WALL_SIZE, WALL_SIZE, 0)
tilesArray[1].position.set(0, WALL_SIZE, 0)
tilesArray[2].position.set(WALL_SIZE, WALL_SIZE, 0)
tilesArray[3].position.set(-WALL_SIZE, 0, 0)
tilesArray[4].position.set(WALL_SIZE, 0, 0)
tilesArray[5].position.set(-WALL_SIZE, -WALL_SIZE, 0)
tilesArray[6].position.set(0, -WALL_SIZE, 0)
tilesArray[7].position.set(WALL_SIZE, -WALL_SIZE, 0)
tilesArray.forEach(function(tile) {
tile.receiveShadow = true
tile.renderOrder = 4;
parent.add(tile)
})
}
/* -- CUBES --*/
function setupCubes(parent) {
var i, geometry, material, x, y, row, col
geometry = new THREE.BoxGeometry(CUBE_SIZE, CUBE_SIZE, 0.05)
material = new THREE.MeshPhongMaterial( {
map: new THREE.TextureLoader().load('http://ak.game-socket.de/assets/grass.png'),
normalMap: new THREE.TextureLoader().load('http://ak.game-socket.de/assets/paper_low_nmap.png'),
overdraw: 1,
depthTest: true,
depthWrite: true
} );
x = 0
y = 0
row = 0
col = 0
for (i = 0; i < TOTAL_CUBES; i++) {
cubes.push(new THREE.Mesh(geometry, material))
if ((i % GRID) === 0) {
col = 1
row++
} else col++
x = -(((GRID * CUBE_SIZE) / 2) - ((CUBE_SIZE) * col) + (CUBE_SIZE/2))
y = -(((GRID * CUBE_SIZE) / 2) - ((CUBE_SIZE) * row) + (CUBE_SIZE/2))
cubes[i].position.set(x, y, 0)
}
cubes.forEach(function(cube, index) {
if(index % 2 == 0) {
directions[index] = -1;
normalized[index] = false;
} else {
directions[index] = 1;
normalized[index] = true;
}
cube.castShadow = true
cube.receiveShadow = true
cube.rotation.x = 0;
cube.renderOrder = 3;
cube.doubleSide = true;
parent.add(cube)
})
}
/* -- LIGHTS -- */
function setupLights(parent) {
var light, soft_light
light = new THREE.DirectionalLight(MAIN_COLOR, 1.25)
soft_light = new THREE.DirectionalLight(MAIN_COLOR, 1.5)
light.position.set(-WALL_SIZE, -WALL_SIZE, CUBE_SIZE * GRID)
light.castShadow = true
soft_light.position.set(WALL_SIZE, WALL_SIZE, CUBE_SIZE * GRID)
parent.add(light).add(soft_light)
}
/* -- RENDERER -- */
function setupRenderer(parent) {
renderer.setSize(_width, _height)
renderer.setClearColor(MAIN_COLOR, 1.0);
parent.appendChild(renderer.domElement)
}
var speed = 0.003;
var reach = 40;
function render() {
var delta = clock.getDelta();
requestAnimationFrame(render);
cubes.forEach(function(cube, index) {
cube.castShadow = true
cube.receiveShadow = true
if(directions[index] >= 1) {
++directions[index];
if(directions[index] >= reach) {
directions[index] = -1
}
cube.rotation.x += speed;
} else if(directions[index] <= -1) {
--directions[index];
if(directions[index] <= -reach) {
directions[index] = 1
}
cube.rotation.x -= speed;
}
});
renderer.render(scene, camera)
}
render();
html, body, canvas {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
display: block;
}
<script src="https://rawcdn.githack.com/mrdoob/three.js/dev/build/three.min.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/dev/examples/js/renderers/CanvasRenderer.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/dev/examples/js/renderers/Projector.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/dev/examples/js/controls/TrackballControls.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/dev/examples/js/shaders/ParallaxShader.js"></script>
By option, i don't want, that the rotating/zooming reach the end of the map - The user is not able to look under the map, as example.

Related

Three JS: Add Zoom function to the Bounding Box (Rect)

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();
});

Can I create point cloud from depth and rgb image?

I am new to 3js, I have 2 images ie RGB image and Depth image. Can I create a point cloud combining these two using 3js?
if yes then how?
To solve this problem I went the three.js examples and searched for "point". I checked each matching sample for one that had different colors for each particle. Then I clicked the "view source" button to checkout the code. I ended up starting with this example and looked at the source. It made it pretty clear how to make a set of points of different colors.
So after that I just needed to load the 2 images, RGB and Depth, make a grid of points, for each point set the Z position to the depth and the color to the color of the image.
I used my phone to take these RGB and Depth images using this app
To get the data I draw the image into a canvas and then call getImageData. That gives me the data in values from 0 to 255 for each channel, red, green, blue, alpha.
I then wrote a function to get a single pixel out and return the colors in the 0 to 1 range. Just to be safe it checks the boundaries.
// return the pixel at UV coordinates (0 to 1) in 0 to 1 values
function getPixel(imageData, u, v) {
const x = u * (imageData.width - 1) | 0;
const y = v * (imageData.height - 1) | 0;
if (x < 0 || x >= imageData.width || y < 0 || y >= imageData.height) {
return [0, 0, 0, 0];
} else {
const offset = (y * imageData.width + x) * 4;
return Array.from(imageData.data.slice(offset, offset + 4)).map(v => v / 255);
}
}
result
'use strict';
/* global THREE */
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = (e) => { resolve(img); };
img.onerror = reject;
img.src = url;
});
}
function getImageData(img) {
const ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(img, 0, 0);
return ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
}
// return the pixel at UV coordinates (0 to 1) in 0 to 1 values
function getPixel(imageData, u, v) {
const x = u * (imageData.width - 1) | 0;
const y = v * (imageData.height - 1) | 0;
if (x < 0 || x >= imageData.width || y < 0 || y >= imageData.height) {
return [0, 0, 0, 0];
} else {
const offset = (y * imageData.width + x) * 4;
return Array.from(imageData.data.slice(offset, offset + 4)).map(v => v / 255);
}
}
async function main() {
const images = await Promise.all([
loadImage("https://i.imgur.com/UKBsvV0.jpg"), // RGB
loadImage("https://i.imgur.com/arPMCZl.jpg"), // Depth
]);
const data = images.map(getImageData);
const canvas = document.querySelector('canvas');
const renderer = new THREE.WebGLRenderer({canvas: canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 1;
const far = 4000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2000;
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0, 0, 0);
controls.update();
const scene = new THREE.Scene();
const rgbData = data[0];
const depthData = data[1];
const skip = 20;
const across = Math.ceil(rgbData.width / skip);
const down = Math.ceil(rgbData.height / skip);
const positions = [];
const colors = [];
const color = new THREE.Color();
const spread = 1000;
const depthSpread = 1000;
const imageAspect = rgbData.width / rgbData.height;
for (let y = 0; y < down; ++y) {
const v = y / (down - 1);
for (let x = 0; x < across; ++x) {
const u = x / (across - 1);
const rgb = getPixel(rgbData, u, v);
const depth = 1 - getPixel(depthData, u, v)[0];
positions.push(
(u * 2 - 1) * spread * imageAspect,
(v * -2 + 1) * spread,
depth * depthSpread,
);
colors.push( ...rgb.slice(0,3) );
}
}
const geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
geometry.computeBoundingSphere();
const material = new THREE.PointsMaterial( { size: 15, vertexColors: THREE.VertexColors } );
const points = new THREE.Points( geometry, material );
scene.add( points );
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<canvas></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r94/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r94/js/controls/OrbitControls.js"></script>

How to remove merged Geometry

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

Move Objects on sphere away in a straight line and back to origin

I have 2 spheres 1 inner and 1 outer sphere. on the outer sphere i have 128 particles/sprites.
i would like to move each sprite/particle out and away from the sphere to a certain distance.
The particles/sprites must act like an audio equalizer moving away from the sphere to a certain distance and the back to the rest position.
Please can you assist.
var geometryInner = new THREE.SphereGeometry(60, 32, 32);
geometryInner.applyMatrix( new THREE.Matrix4().makeScale( 1.0, 1.8, 1.0 ) );
//var geometryMid = new THREE.SphereGeometry(90, 32, 32);
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vertexShaderGeneral').textContent,
fragmentShader: document.getElementById('fragmentShaderLineColors').textContent,
transparent: false
});
sphereInner = THREE.SceneUtils.createMultiMaterialObject( geometryInner, [
material,
new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true} )
]);
// sphereInner.rotation.x = 0.3;
// sphereInner.position.y = -30;
scene.add(sphereInner);
//
geometryOuter = new THREE.SphereGeometry(80, 32, 32);
geometryOuter.applyMatrix( new THREE.Matrix4().makeScale( 1.0, 1.8, 1.0 ) );
var materialOuter = new THREE.MeshLambertMaterial({color: 0xFF0000, transparent: true, opacity: 0.1});
var sphereOuter = new THREE.Mesh(geometryOuter, materialOuter);
scene.add(sphereOuter);
points = THREE.GeometryUtils.randomPointsInGeometry( geometryOuter, particleCount );
var pMaterial = new THREE.PointCloudMaterial({
color: 0xFFFFCC,
size: 20,
map: THREE.ImageUtils.loadTexture(
"particle.png"
),
blending: THREE.AdditiveBlending,
opacity : 0.8,
depthWrite : false,
transparent: true
});
// create the particle variables
particles = new THREE.Geometry();
// create the particle variables
// now create the individual particles
for (var p = 0; p < points.length; p++)
{
// create a particle with random
// position values, -250 -> 250
pX = points[p].x;
pY = points[p].y;
pZ = points[p].z;
// add it to the geometry
particles.vertices.push(point);
}
// create the particle system
particleSystem = new THREE.PointCloud(particles,
pMaterial);
// also update the particle system to
// sort the particles which enables
// the behaviour we want
particleSystem.sortParticles = true;
// add it to the scene
scene.add(particleSystem);
I have tried this...when i render. I just need some help.
function render()
{
var timer = 0.0001 * Date.now();
// add some rotation to the system
//particleSystem.rotation.y += boost * 0.0001;
var delta = clock.getDelta();
if(typeof array === 'object' && array.length > 0) {
var k = 0;
for(var i = 0; i < particleSystem.geometry.vertices.length ; i++) {
particleSystem.geometry.verticesNeedUpdate = true;
// particleSystem.position.z = boost/2;
particleSystem.geometry.vertices[k].z = array[i]/4 - 30;
// geometryOuter.applyMatrix( new THREE.Matrix4().makeScale( 2, 1.8, 1.0 ) );
// particleSystem.rotation.y += array[2] * 0.0000005 ;
// sphereInner.rotation.z -= array[0] * 0.0000005;
// sphereInner.position.z = boost/3;
sphereInner.rotation.y += array[0] * 0.0000005;
uniforms.time.value -= array[0] * 0.0000005;
uniformsPlane.time.value += array[1] * 0.0000008;
k += (k < array.length ? 1 : 0);
}
}
// uniforms2.time.value = clock.elapsedTime;
// camera.position.x += ( mouseX - camera.position.x ) * .05;
// camera.position.y += ( - mouseY - camera.position.y ) * .05;
camera.lookAt( scene.position );
renderText();
// renderer.clear();
effect.render( scene, camera );
}
Here is the fix...
if(typeof array === 'object' && array.length > 0) {
var k = 0;
for(var i = 0; i < particleSystem.geometry.vertices.length ; i++) {
particleSystem.geometry.verticesNeedUpdate = true;
// particleSystem.position.z = boost/2;
if(array[k] == 0)
{
particleSystem.geometry.vertices[k].z = points[k].z;
}
else if(points[k].z + array[k] + 100 < points[k].z)
{
particleSystem.geometry.vertices[k].z = points[k].z;
}
else
{
particleSystem.geometry.vertices[k].z = points[k].z + array[k] + 100;// * 0.1 - 60;
}
// particleSystem.geometry.vertices[k].x = -array[i];
// particleSystem.geometry.vertices[k].y = array[i];
// geometryOuter.applyMatrix( new THREE.Matrix4().makeScale( 2, 1.8, 1.0 ) );
// particleSystem.rotation.y += array[2] * 0.0000005 ;
// sphereInner.rotation.z -= array[0] * 0.0000005;
// sphereInner.position.z = boost/3;
sphereInner.rotation.y += array[0] * 0.0000005;
uniforms.time.value -= array[0] * 0.0000005;
uniformsPlane.time.value += array[1] * 0.0000008;
k += (k < array.length ? 1 : 0);
}
}

Threejs/Physijs Game MMO Character controlls

I'm working on a third person character control for a game i'm developing. I'm happy with the results so far. The character controls have a lot of neat features like: if an object is in front of the camera it will move forward so you can still see the character, however the camera stutters horribly when I rotate it to the side and then turn my player away from it. I uploaded a test on JSFiddle: http://jsfiddle.net/nA8SV/ I have only tested this in chrome, and for some reason the results part doesn't get the keypresses until you click on the white space bordering the canvas on that frame.
[also i started chrome with "--disable-web-security" to ignore the cross origin]
But once you click the page the key presses work. The controls are a modified version of the orbital controls. So you can left click and rotate the view. Additionally you can use the wasd keys to move around and the camera view should return behind the player when you are moving/rotating.
I apologize for the buggyness this was very difficult to get working on JSFiddle.
(But the rotation bug is happening so it at least reproduces that.)
Basically I'm trying to get my camera rotation back behind my character, so i have some code that fixes the rotation on line 250, but the camera stutters as the character moves.
Here are my theories I think the camera overall jerkyness has something to do with the physics simulation bouncing the player around slightly, but I'm not sure what to do to solve this, any help would be appreciated.
here is the code for completeness but I would recommend the JSFiddle link, I'ts much easier to see it work.
THREE.PlayerControls = function (anchor, scene, player, camera, domElement) {
this.walking = false;
this.occ = false;
this.scene = scene;
this.occLastZoom = 0;
this.jumpRelease = true;
this.jumping = false;
this.falling = false;
this.moving = false;
this.turning = false;
this.anchor = anchor;
this.player = player;
this.camera = camera;
this.camera.position.set(0, 8.25, -20);
this.domElement = (domElement !== undefined) ? domElement : document;
this.anchor.add(this.camera);
// API
this.enabled = true;
this.center = new THREE.Vector3(0, 4, 0);
this.userZoom = true;
this.userZoomSpeed = 2.0;
this.userRotate = true;
this.userRotateSpeed = 1.0;
this.minPolarAngle = 0; // radians
this.maxPolarAngle = Math.PI; // radians
this.minDistance = 2;
this.maxDistance = 30;
this.keys = {
LEFT: 65,
STRAFFLEFT: 81,
UP: 87,
RIGHT: 68,
STRAFFRIGHT: 69,
DOWN: 83,
JUMP: 32,
SLASH: 191
};
// internals
var scope = this;
var EPS = 0.000001;
var PIXELS_PER_ROUND = 1800;
var rotateStart = new THREE.Vector2();
var rotateEnd = new THREE.Vector2();
var rotateDelta = new THREE.Vector2();
var zoomStart = new THREE.Vector2();
var zoomEnd = new THREE.Vector2();
var zoomDelta = new THREE.Vector2();
var phiDelta = 0;
var thetaDelta = 0;
var scale = 1;
var lastPosition = new THREE.Vector3();
var STATE = {
NONE: -1,
ROTATE: 0,
ZOOM: 1
};
var state = STATE.NONE;
var key_state = [];
// events
var changeEvent = {
type: 'change'
};
this.rotateLeft = function (angle) {
thetaDelta -= angle;
};
this.rotateRight = function (angle) {
thetaDelta += angle;
};
this.rotateUp = function (angle) {
phiDelta -= angle;
};
this.rotateDown = function (angle) {
phiDelta += angle;
};
this.zoomIn = function (zoomScale) {
if (zoomScale === undefined) {
zoomScale = getZoomScale();
}
scale /= zoomScale;
};
this.zoomOut = function (zoomScale) {
if (zoomScale === undefined) {
zoomScale = getZoomScale();
}
scale *= zoomScale;
};
this.update = function (delta) {
// detect falling
if (this.scene.children.length > 0) {
var originPoint = this.anchor.position.clone();
var ray = new THREE.Raycaster(originPoint, new THREE.Vector3(0, -1, 0));
var collisionResults = ray.intersectObjects(this.scene.children.filter(function (child) {
return child.occ;
}));
if (collisionResults.length > 0) {
if (collisionResults[0].distance < 1.25 && this.falling) {
this.falling = false;
this.jumping = false;
} else if (collisionResults[0].distance > 2 + (this.jumping ? 1 : 0) && !this.falling) {
this.falling = true;
}
}
}
// handle movement
if (!this.falling) {
if (key_state.indexOf(this.keys.JUMP) > -1 && this.jumpRelease && !this.jumping) {
// jump
var lv = this.anchor.getLinearVelocity();
this.anchor.setLinearVelocity(new THREE.Vector3(lv.x, 15, lv.z));
this.jumpRelease = false;
this.jumping = true;
//jump
} else if (!this.jumping) {
// move
if (key_state.indexOf(this.keys.UP) > -1) {
var rotation_matrix = new THREE.Matrix4().extractRotation(this.anchor.matrix);
var speed = this.walking ? 2.5 : 10;
var force_vector;
// straffing?
if (key_state.indexOf(this.keys.STRAFFLEFT) > -1 && key_state.indexOf(this.keys.STRAFFRIGHT) < 0) {
force_vector = new THREE.Vector3((2 * speed / 3), 0, (2 * speed / 3)).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, Math.PI / 4, 0);
} else if (key_state.indexOf(this.keys.STRAFFRIGHT) > -1) {
force_vector = new THREE.Vector3((-2 * speed / 3), 0, (2 * speed / 3)).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, -Math.PI / 4, 0);
} else {
force_vector = new THREE.Vector3(0, 0, speed).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, 0, 0);
}
this.anchor.setLinearVelocity(force_vector);
this.moving = true;
// forward
} else if (key_state.indexOf(this.keys.DOWN) > -1) {
var rotation_matrix = new THREE.Matrix4().extractRotation(this.anchor.matrix);
var speed = this.walking ? -2.5 : -5;
var force_vector;
// straffing?
if (key_state.indexOf(this.keys.STRAFFLEFT) > -1 && key_state.indexOf(this.keys.STRAFFRIGHT) < 0) {
force_vector = new THREE.Vector3((-2 * speed / 3), 0, (2 * speed / 3)).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, -Math.PI / 4, 0);
} else if (key_state.indexOf(this.keys.STRAFFRIGHT) > -1) {
force_vector = new THREE.Vector3((2 * speed / 3), 0, (2 * speed / 3)).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, Math.PI / 4, 0);
} else {
force_vector = new THREE.Vector3(0, 0, speed).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, 0, 0);
}
this.anchor.setLinearVelocity(force_vector);
this.moving = true;
//back
} else if (key_state.indexOf(this.keys.STRAFFLEFT) > -1) {
var rotation_matrix = new THREE.Matrix4().extractRotation(this.anchor.matrix);
var speed = this.walking ? 2.5 : 10;
var force_vector = new THREE.Vector3(speed, 0, 0).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, Math.PI / 2, 0);
this.anchor.setLinearVelocity(force_vector);
this.moving = true;
//straff
} else if (key_state.indexOf(this.keys.STRAFFRIGHT) > -1) {
var rotation_matrix = new THREE.Matrix4().extractRotation(this.anchor.matrix);
var speed = this.walking ? 2.5 : 10;
var force_vector = new THREE.Vector3(-speed, 0, 0).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, -Math.PI / 2, 0);
this.anchor.setLinearVelocity(force_vector);
this.moving = true;
//straff
} else if (this.moving) {
this.player.rotation.set(0, 0, 0);
this.anchor.setLinearVelocity(new THREE.Vector3(0, 0, 0));
this.moving = false;
}
//turn
if (key_state.indexOf(this.keys.LEFT) > -1 && key_state.indexOf(this.keys.RIGHT) < 0) {
this.anchor.setAngularVelocity(new THREE.Vector3(0, 1.5, 0));
this.turning = true;
//turning
} else if (key_state.indexOf(this.keys.RIGHT) > -1) {
this.anchor.setAngularVelocity(new THREE.Vector3(0, -1.5, 0));
this.turning = true;
//turning
} else if (this.turning) {
this.anchor.setAngularVelocity(new THREE.Vector3(0, 0, 0));
this.turning = false;
}
//idle
}
if (key_state.indexOf(this.keys.JUMP) == -1) {
this.jumpRelease = true;
}
} else {
//falling
}
var position = this.camera.position;
var offset = position.clone().sub(this.center);
// angle from z-axis around y-axis
var theta = Math.atan2(offset.x, offset.z);
// angle from y-axis
var phi = Math.atan2(Math.sqrt(offset.x * offset.x + offset.z * offset.z), offset.y);
theta += thetaDelta;
phi += phiDelta;
if ((this.moving || this.turning) && state != STATE.ROTATE) {
// fix camera rotation
if (theta < 0) theta -= Math.max(delta, (-1 * Math.PI) + theta);
else theta += Math.min(delta, Math.PI - theta);
// fix pitch (should be an option or it could get anoying)
//phi = 9*Math.PI/24;
}
// restrict phi to be between desired limits
phi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, phi));
// restrict phi to be betwee EPS and PI-EPS
phi = Math.max(EPS, Math.min(Math.PI - EPS, phi));
var radius;
if (this.occ) {
this.occLastZoom = Math.max(this.minDistance, Math.min(this.maxDistance, this.occLastZoom * scale));
radius = this.occLastZoom;
} else {
radius = offset.length() * scale;
}
// restrict radius to be between desired limits
radius = Math.max(this.minDistance, Math.min(this.maxDistance, radius));
// check for objects infront of camera
var projector = new THREE.Projector();
var vector = new THREE.Vector3(0, 0, 1);
projector.unprojectVector(vector, camera);
var point = new THREE.Vector3(this.anchor.position.x + this.center.x, this.anchor.position.y + this.center.y, this.anchor.position.z + this.center.z);
var vec = camera.position.clone().sub(vector).normalize()
var checkray = new THREE.Raycaster(point, vec, this.minDistance, this.maxDistance);
var checkcollisionResults = checkray.intersectObjects(this.scene.children.filter(function (child) {
return child.occ;
}));
if (checkcollisionResults.length > 0) {
var min = radius;
for (var i = 0; i < checkcollisionResults.length; i++) {
if (min > checkcollisionResults[i].distance) min = checkcollisionResults[i].distance;
}
if (min < radius) {
if (!this.occ) {
this.occ = true;
this.occLastZoom = radius;
}
radius = min;
} else {
this.occ = false;
}
}
offset.x = radius * Math.sin(phi) * Math.sin(theta);
offset.y = radius * Math.cos(phi);
offset.z = radius * Math.sin(phi) * Math.cos(theta);
if (radius < 5) {
this.player.material.opacity = Math.max(0, radius / 5.0);
this.center.y = 4 + ((5 - radius) / 2.5);
} else {
if (this.player.material.opacity != 1.0) {
this.player.material.opacity = 1.0;
this.center.y = 4;
}
}
position.copy(this.center).add(offset);
this.camera.lookAt(this.center);
thetaDelta = 0;
phiDelta = 0;
scale = 1;
if (lastPosition.distanceTo(this.camera.position) > 0) {
this.dispatchEvent(changeEvent);
lastPosition.copy(this.camera.position);
}
};
function roundTothree(num) {
return +(Math.round(num + "e+3") + "e-3");
}
function getZoomScale() {
return Math.pow(0.95, scope.userZoomSpeed);
}
function onMouseDown(event) {
if (scope.enabled === false) return;
if (scope.userRotate === false) return;
event.preventDefault();
if (state === STATE.NONE) {
if (event.button === 0) state = STATE.ROTATE;
}
if (state === STATE.ROTATE) {
rotateStart.set(event.clientX, event.clientY);
}
document.addEventListener('mousemove', onMouseMove, false);
document.addEventListener('mouseup', onMouseUp, false);
}
function onMouseMove(event) {
if (scope.enabled === false) return;
event.preventDefault();
if (state === STATE.ROTATE) {
rotateEnd.set(event.clientX, event.clientY);
rotateDelta.subVectors(rotateEnd, rotateStart);
scope.rotateLeft(2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * scope.userRotateSpeed);
scope.rotateUp(2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * scope.userRotateSpeed);
rotateStart.copy(rotateEnd);
} else if (state === STATE.ZOOM) {
zoomEnd.set(event.clientX, event.clientY);
zoomDelta.subVectors(zoomEnd, zoomStart);
if (zoomDelta.y > 0) {
scope.zoomIn();
} else {
scope.zoomOut();
}
zoomStart.copy(zoomEnd);
}
}
function onMouseUp(event) {
if (scope.enabled === false) return;
if (scope.userRotate === false) return;
document.removeEventListener('mousemove', onMouseMove, false);
document.removeEventListener('mouseup', onMouseUp, false);
state = STATE.NONE;
}
function onMouseWheel(event) {
if (scope.enabled === false) return;
if (scope.userZoom === false) return;
var delta = 0;
if (event.wheelDelta) { // WebKit / Opera / Explorer 9
delta = event.wheelDelta;
} else if (event.detail) { // Firefox
delta = -event.detail;
}
if (delta > 0) {
scope.zoomOut();
} else {
scope.zoomIn();
}
}
function onKeyDown(event) {
console.log('onKeyDown')
if (scope.enabled === false) return;
switch (event.keyCode) {
case scope.keys.UP:
var index = key_state.indexOf(scope.keys.UP);
if (index == -1) key_state.push(scope.keys.UP);
break;
case scope.keys.DOWN:
var index = key_state.indexOf(scope.keys.DOWN);
if (index == -1) key_state.push(scope.keys.DOWN);
break;
case scope.keys.LEFT:
var index = key_state.indexOf(scope.keys.LEFT);
if (index == -1) key_state.push(scope.keys.LEFT);
break;
case scope.keys.STRAFFLEFT:
var index = key_state.indexOf(scope.keys.STRAFFLEFT);
if (index == -1) key_state.push(scope.keys.STRAFFLEFT);
break;
case scope.keys.RIGHT:
var index = key_state.indexOf(scope.keys.RIGHT);
if (index == -1) key_state.push(scope.keys.RIGHT);
break;
case scope.keys.STRAFFRIGHT:
var index = key_state.indexOf(scope.keys.STRAFFRIGHT);
if (index == -1) key_state.push(scope.keys.STRAFFRIGHT);
break;
case scope.keys.JUMP:
var index = key_state.indexOf(scope.keys.JUMP);
if (index == -1) key_state.push(scope.keys.JUMP);
break;
}
}
function onKeyUp(event) {
switch (event.keyCode) {
case scope.keys.UP:
var index = key_state.indexOf(scope.keys.UP);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.DOWN:
var index = key_state.indexOf(scope.keys.DOWN);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.LEFT:
var index = key_state.indexOf(scope.keys.LEFT);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.STRAFFLEFT:
var index = key_state.indexOf(scope.keys.STRAFFLEFT);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.RIGHT:
var index = key_state.indexOf(scope.keys.RIGHT);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.STRAFFRIGHT:
var index = key_state.indexOf(scope.keys.STRAFFRIGHT);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.JUMP:
var index = key_state.indexOf(scope.keys.JUMP);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.SLASH:
scope.walking = !scope.walking;
break;
}
}
this.domElement.addEventListener('contextmenu', function (event) {
event.preventDefault();
}, false);
this.domElement.addEventListener('mousedown', onMouseDown, false);
this.domElement.addEventListener('mousewheel', onMouseWheel, false);
this.domElement.addEventListener('DOMMouseScroll', onMouseWheel, false); // firefox
window.addEventListener('keydown', onKeyDown, false);
window.addEventListener('keyup', onKeyUp, false);
};
THREE.PlayerControls.prototype = Object.create(THREE.EventDispatcher.prototype);
// end player controlls
Physijs.scripts.worker = 'https://rawgithub.com/chandlerprall/Physijs/master/physijs_worker.js';
Physijs.scripts.ammo = 'http://chandlerprall.github.io/Physijs/examples/js/ammo.js';
// standard global variables
var container, scene, camera, renderer, controls;
//var keyboard = new THREEx.KeyboardState();
var clock = new THREE.Clock();
// MAIN //
window.onload = function() {
console.log('loaded')
// SCENE //
scene = new Physijs.Scene();
scene.setGravity(new THREE.Vector3(0, -32, 0));
scene.addEventListener(
'update',
function () {
scene.simulate();
});
// CAMERA //
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight;
var VIEW_ANGLE = 45,
ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT,
NEAR = 1,
FAR = 1000;
camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
// RENDERER //
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.shadowMapEnabled = true;
// to antialias the shadow
renderer.shadowMapSoft = true;
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
container = document.getElementById('container');
container.appendChild(renderer.domElement);
// EVENTS //
//THREEx.WindowResize(renderer, camera);
// LIGHT //
var hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6);
hemiLight.color.setHSL(0.6, 1, 0.6);
hemiLight.groundColor.setHSL(0.095, 1, 0.75);
hemiLight.position.set(0, 500, 0);
scene.add(hemiLight);
var light = new THREE.DirectionalLight(0xffffff, 1);
light.color.setHSL(0.1, 1, 0.95);
light.position.set(-1, 1.75, 1);
light.position.multiplyScalar(50);
light.castShadow = true;
light.shadowMapWidth = 2048;
light.shadowMapHeight = 2048;
light.shadowDarkness = 0.5;
var d = 50;
light.shadowCameraLeft = -d;
light.shadowCameraRight = d;
light.shadowCameraTop = d;
light.shadowCameraBottom = -d;
light.shadowCameraFar = 3500;
light.shadowBias = -0.0001;
light.shadowDarkness = 0.35;
scene.add(light);
// GEOMETRY //
var checkerboard = new THREE.ImageUtils.loadTexture('http://www.cns.nyu.edu/lcv/texture/artificial-periodic/checkerboard.o.jpg');
checkerboard.wrapS = checkerboard.wrapT = THREE.RepeatWrapping;
checkerboard.repeat.set(4, 4);
var checkerboard2 = new THREE.ImageUtils.loadTexture('http://www.cns.nyu.edu/lcv/texture/artificial-periodic/checkerboard.o.jpg');
var cubeMaterial = Physijs.createMaterial(
new THREE.MeshLambertMaterial({
map: checkerboard2
}),
1.0, // high friction
0.0 // low restitution
);
var cubeGeometry = new THREE.CubeGeometry(10, 5, 10, 1, 1, 1);
var cube = new Physijs.BoxMesh(
cubeGeometry,
cubeMaterial,
1);
cube.position.set(-10, 1, -10);
cube.castShadow = true;
cube.receiveShadow = true;
cube.occ = true;
scene.add(cube);
var cubeMaterial2 = Physijs.createMaterial(
new THREE.MeshLambertMaterial({
map: checkerboard2
}),
1.0, // high friction
0.0 // low restitution
);
var cubeGeometry2 = new THREE.CubeGeometry(10, 5, 10, 1, 1, 1);
var cube2 = new Physijs.BoxMesh(
cubeGeometry2,
cubeMaterial2,
1);
cube2.position.set(-10, 7, -1);
cube2.castShadow = true;
cube2.receiveShadow = true;
cube2.occ = true;
scene.add(cube2);
var cubeMaterial3 = Physijs.createMaterial(
new THREE.MeshLambertMaterial({
map: checkerboard2
}),
1.0, // high friction
0.0 // low restitution
);
var cubeGeometry3 = new THREE.CubeGeometry(10, 5, 10, 1, 1, 1);
var cube3 = new Physijs.BoxMesh(
cubeGeometry3,
cubeMaterial3,
1);
cube3.position.set(-10, 13, 8);
cube3.castShadow = true;
cube3.receiveShadow = true;
cube3.occ = true;
scene.add(cube3);
var cone = new Physijs.ConeMesh(
new THREE.CylinderGeometry(0, 5, 4, 30, 30, true),
Physijs.createMaterial(
new THREE.MeshLambertMaterial({
map: checkerboard2
}),
1.0, // high friction
0.0 // low restitution
),
0);
cone.position.set(0, 2, 0);
scene.castShadow = true;
scene.receiveShadow = true;
cone.occ = true;
scene.add(cone);
// FLOOR //
var floorMaterial = new THREE.MeshLambertMaterial({
map: checkerboard
});
var floorGeometry = new THREE.PlaneGeometry(100, 100, 1, 1);
var floor = new Physijs.PlaneMesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.castShadow = false;
floor.receiveShadow = true;
floor.occ = true;
scene.add(floor);
// SKY //
var skyBoxGeometry = new THREE.CubeGeometry( 1000, 1000, 1000 );
var skyBox = new THREE.Mesh(skyBoxGeometry, new THREE.MeshLambertMaterial({
color: '#3333bb'
}));
scene.add(skyBox);
// fog must be added to scene before first render
scene.fog = new THREE.FogExp2(0x999999, 0.001);
var bounding = new Physijs.SphereMesh(
new THREE.SphereGeometry(0.75, 4, 4),
Physijs.createMaterial(
new THREE.MeshBasicMaterial({
color: '#ff0000'
}),
1.0, // high friction
0.0 // low restitution
),
0.1);
var player = new THREE.Mesh(
new THREE.CubeGeometry(1, 6, 1, 1, 1, 1),
new THREE.MeshLambertMaterial({
color: '#00ff00'
}),
1);
player.position.set(0, 3, 0);
bounding.position.set(10, 0.75, -10);
bounding.add(player);
scene.add(bounding);
bounding.setAngularFactor(new THREE.Vector3(0, 0, 0));
controls = new THREE.PlayerControls(bounding, scene, player, camera, renderer.domElement);
// animation loop / game loop
scene.simulate();
animate();
};
function animate() {
requestAnimationFrame(animate);
render();
update();
}
function update() {
// delta = change in time since last call (in seconds)
var delta = clock.getDelta();
THREE.AnimationHandler.update(delta);
if (controls) controls.update(delta);
}
function render() {
renderer.render(scene, camera);
}
Thank you!!!
Ok, I ended up fixing this on my own, but it was a very difficult process.
I have spent so much time on this. I tried starting over completely and ended up rewriting all my controls objects in different ways with no success in fact things got slightly worse with that approach. And I learned some things:
updating the control after rendering causes horrible stutter (or makes the physics stutter worse). I must of not been paying attention to where i put my update function, but it needed to be before render.
I also started looking at the demos for Physijs to see what settings they used to get things smooth. this one specifically (http://chandlerprall.github.io/Physijs/examples/body.html)
I tweaked around with my friction and mass settings and I started using a BoxMesh for the floor instead of a plane, that seems to help with the jitters.
Finally I changed player control class a bit:
instead of straight my camera to my player, i started using a gyroscope to buffer the rotation.
this.camera_anchor_gyro = new THREE.Gyroscope();
this.camera_anchor_gyro.add(this.camera);
this.anchor.add(this.camera_anchor_gyro);
next i wanted to rotate the camera_anchor_gyro instead of the camera to match up the rotations, and this became a huge headache until i learned about: http://en.wikipedia.org/wiki/Gimbal_lock
so i soon added this after the gyro stuff:
this.anchor.rotation.order = "YXZ";
this.camera_anchor_gyro.rotation.order = "YXZ";
this.camera.rotation.order = "YXZ";
finally here is my updated rotation fix logic:
if ((this.moving || this.turning) && state != STATE.ROTATE) {
var curr_rot = new THREE.Euler(0, 0, 0, "YXZ").setFromRotationMatrix(this.camera.matrixWorld).y;
var dest_rot = new THREE.Euler(0, 0, 0, "YXZ").setFromRotationMatrix(this.anchor.matrixWorld).y;
var dest_rot = dest_rot + (dest_rot > 0 ? -Math.PI : Math.PI);
var step = shortestArc(curr_rot,dest_rot)*delta*2;
this.camera_anchor_gyro.rotation.y += step;//Math.max(-delta, diff);
// fix pitch (should be an option or it could get anoying)
//phi = 9*Math.PI/24;
}
I have updated my fiddle http://jsfiddle.net/nA8SV/2/ and this works so much better. but there is still a slight stuttering issue but i will have to continue to investigate.

Resources