Related
I'm looking to draw a continuous line with a given thickness showing only the edges using three.js. I have achieved it. I'm trying to add thickness to the line but it is not getting reflected in the scene due to some angle in three.js. Can anyone help me out with the issue.
Here's the fiddle https://jsfiddle.net/16vhjm0y/1/
var renderer, scene, camera;
var line;
var count = 0;
var mouse = new THREE.Vector3();
var mesh3D;
var maxPoint = 6;
var height = window.innerHeight * .99;
var plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0); // facing us for mouse intersection
var raycaster = new THREE.Raycaster();
var point3ds = [];
var usePerspectiveCamera = false; // toggles back and forth
var perspOrbit;
var perspCam;
var orthoOrbit;
var orthoCam;
var labelRenderer, labelAjay;
var testBoolean = false;
var mouseDownBoolean = false;
var distanceData, showDistanceData;
var ajay;
var arrAjay = [];
var arrAjayFinal = [];
var mouseUpBoolean = false;
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, height);
document.body.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
// camera perspective
perspCam = new THREE.PerspectiveCamera(45, window.innerWidth / height, 1, 10000);
perspCam.position.set(0, 0, 200);
// camera ortho
var width = window.innerWidth;
//var height = window.innerHeight;
orthoCam = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, 0, 1200);
// assign cam
camera = perspCam;
someMaterial = new THREE.MeshBasicMaterial({ color: 0xA9A9A9, side: THREE.DoubleSide, transparent: true, opacity: 0.3 });
// grid
var grid = new THREE.GridHelper(1024, 56);
grid.rotateX(Math.PI / 2);
// scene.add(grid);
// geometry
var geometry = new THREE.BufferGeometry();
var MAX_POINTS = 500;
positions = new Float32Array(MAX_POINTS * 3);
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
// material
var material = new THREE.LineBasicMaterial({
color: 0xff0000,
linewidth: 10
});
// line
line = new THREE.Line(geometry, material);
// line.position.z = 20;
scene.add(line);
// var geometry = new THREE.BoxBufferGeometry( 10, 2, 20 );
// var edgesPavement = new THREE.EdgesGeometry( geomPavement );
// var lineGeometry = new THREE.LineSegmentsGeometry().setPositions( edgesPavement.attributes.position.array );
// var lineMaterial = new THREE.LineMaterial( { color: 0xff0000, linewidth: 10 } );
// lineMaterial.resolution.set( window.innerWidth, window.innerHeight ); // important, for now...
// var line = new THREE.LineSegments2( lineGeometry, lineMaterial );
// scene.add( line );
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener('mousedown', onMouseDown, false);
document.addEventListener('mouseup', onMouseUp, false);
createUI();
labelRenderer = new THREE.CSS2DRenderer();
ajay = document.createElement('div');
ajay.className = 'ajay';
ajay.style.color = "black";
ajayInsert = document.createElement('div');
ajayInsert.className = 'ajay';
ajayInsert.style.color = "black";
// ajay.style.color = "black";
// console.log(ajay)
labelAjay = new THREE.CSS2DObject(ajay);
labelAjayFinal = new THREE.CSS2DObject(ajayInsert);
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
ajay.style.display = "none";
ajayInsert.style.display = "none";
}
// update line
function updateLine() {
positions[count * 3 - 3] = mouse.x;
positions[count * 3 - 2] = mouse.y;
positions[count * 3 - 1] = mouse.z;
line.geometry.attributes.position.needsUpdate = true;
}
// mouse move handler
function onMouseMove(event) {
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = (event.clientX - rect.left) / (rect.right - rect.left) * 2 - 1;
mouse.y = - ((event.clientY - rect.top) / (rect.bottom - rect.top)) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
mouse = raycaster.ray.intersectPlane(plane, mouse);
if (count !== 0 && count < maxPoint) {
updateLine();
}
testBoolean = true;
if (testBoolean == true) {
// scene.remove(labelAjay);
var geometry = line.geometry;
geometry.computeBoundingBox();
center = geometry.boundingBox.getCenter();
// line.localToWorld(center);
// console.log(center);
if (mouseDownBoolean == true) {
labelAjay.position.set(mouse.x, mouse.y, mouse.z);
// console.log(line.position)
scene.add(labelAjay);
document.body.appendChild(labelRenderer.domElement);
// console.log(positions);
distanceData = point3ds[0].distanceTo(new THREE.Vector3(mouse.x, mouse.y, mouse.z));
showDistanceData = Math.round(distanceData * 1000);
// console.log(point3ds[0]);
// console.log(point3ds[1]);
// console.log(distanceData);
// console.log(showDistanceData)
ajay.textContent = showDistanceData + ' mm';
// console.log(labelRenderer)
}
// console.log(labelRenderer.domElement)
// document.getElementsByClassName("ajay").remove();
// document.getElementsByClassName("ajay").outerHTML = "";
}
}
// add point
function addPoint(event) {
if (count < maxPoint) {
console.log("point nr " + count + ": " + mouse.x + " " + mouse.y + " " + mouse.z);
positions[count * 3 + 0] = mouse.x;
positions[count * 3 + 1] = mouse.y;
positions[count * 3 + 2] = mouse.z
count++;
line.geometry.setDrawRange(0, count);
updateLine();
point3ds.push(new THREE.Vector3(mouse.x, mouse.y, mouse.z));
} else {
console.log('max points reached: ' + maxPoint);
}
}
function getPointInBetweenByLen(pointA, pointB, length) {
var dir = pointB.clone().sub(pointA).normalize().multiplyScalar(length);
return pointA.clone().add(dir);
}
// mouse down handler
function onMouseDown(evt) {
mouseDownBoolean = true;
// force add an extra point on first click so buffer line can display
// buffer geometry requires two points to display, so first click should add two points
if (count === 0) {
addPoint();
}
if (count < maxPoint) {
addPoint();
}
}
function onMouseUp(event){
mouseUpBoolean = true;
if(mouseUpBoolean == true){
// showDistanceData = Math.round(distanceData * 1000);
arrAjay.push(showDistanceData);
console.log(arrAjay);
arrAjayFinal = arrAjay.splice(-1)[0];
var geometry = line.geometry;
geometry.computeBoundingBox();
center = geometry.boundingBox.getCenter();
if (mouseDownBoolean == true) {
labelAjayFinal.position.set(center.x, center.y, center.z);
scene.add(labelAjayFinal);
document.body.appendChild(labelRenderer.domElement);
// distanceData = point3ds[0].distanceTo(new THREE.Vector3(mouse.x, mouse.y, mouse.z));
// showDistanceData = Math.round(distanceData * 1000);
console.log('arrAjayFinal', arrAjayFinal);
ajayInsert.textContent = arrAjayFinal;
}
}
}
// render
function render() {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
// animate
function animate() {
requestAnimationFrame(animate);
render();
}
// loop through all the segments and create their 3D
function create3D() {
if (!mesh3D && point3ds && point3ds.length) {
console.log('creating 3D');
mesh3D = new THREE.Mesh(); // metpy mesh but is the root mesh for all 3D
scene.add(mesh3D);
// prepare create segments from point3ds - every two points create a segement
var index = 1;
var segmentHeight = 56;
point3ds.forEach(point3d => {
if (index < point3ds.length) {
var seg = new Segment(point3d, point3ds[index], someMaterial, segmentHeight);
mesh3D.add(seg.mesh3D);
index++;
}
});
}
}
function createUI() {
// create3D
var btn = document.createElement('button');
document.body.appendChild(btn);
btn.innerHTML = 'Create3D';
btn.addEventListener('mousedown', () => {
create3D();
// add orbiting controls to both cameras
var controls;
if (!perspOrbit) {
perspOrbit = new THREE.OrbitControls(perspCam, renderer.domElement);
perspOrbit.screenSpacePanning = true;
// raotation is enabled once create3D is pressed
setToFullOrbit(perspOrbit);
perspOrbit.enabled = true; // set to true by default
}
// add orbit to orthocam
if (!orthoOrbit) {
orthoOrbit = new THREE.OrbitControls(orthoCam, renderer.domElement);
orthoOrbit.screenSpacePanning = true;
orthoOrbit.enabled = false; // set to false by default
//orthoOrbit.enableDamping = true;
//orthoOrbit.dampingFactor = .15;
}
});
}
function switchCam() {
usePerspectiveCamera = !usePerspectiveCamera;
if (usePerspectiveCamera) {
if (perspCam) {
camera = perspCam;
perspOrbit.enabled = true;
orthoOrbit.enabled = false;
} else {
throw new Error('Switch to perspective cam failed, perspective cam is null');
}
} else {
if (orthoCam) {
camera = orthoCam;
orthoOrbit.enabled = true;
perspOrbit.enabled = false;
} else {
throw new Error('Switch to ortho cam failed, orthoCam is null');
}
}
}
function rotateCam90() {
if (camera instanceof THREE.OrthographicCamera) {
orthoOrbit.update();
camera.applyMatrix(new THREE.Matrix4().makeRotationZ(Math.PI / 2));
}
}
function reset() {
scene.remove(mesh3D);
mesh3D = null;
for (var i = 0; i < 3 * 8; i++) {
positions[i] = 0;
}
count = 0;
line.geometry.setDrawRange(0, count);
updateLine();
point3ds = [];
}
function setToFullOrbit(orbitControl) {
// how far you can orbit vertically
orbitControl.minPolarAngle = 0;
orbitControl.maxPolarAngle = Math.PI;
// How far you can dolly in and out ( PerspectiveCamera only )
orbitControl.minDistance = 0;
orbitControl.maxDistance = Infinity;
orbitControl.enableZoom = true; // Set to false to disable zooming
orbitControl.zoomSpeed = 1.0;
orbitControl.enableRotate = true;
// allow keyboard arrows
orbitControl.enableKeys = true;
// Set to false to disable panning (ie vertical and horizontal translations)
orbitControl.enablePan = true;
}
// each segment knows how to create its 3D
class Segment {
constructor(start, end, material, height) {
this.start = start;
this.end = end;
this.height = height; // height of the segment's 3D
this.material = material;
this.mesh3D = null;
this.create3D();
}
create3D() {
if (this.start && this.end) {
//create the shape geometry
var distStartToEnd = this.start.distanceTo(this.end);
var vec2s = [
new THREE.Vector2(),
new THREE.Vector2(0, this.height),
new THREE.Vector2(distStartToEnd, this.height),
new THREE.Vector2(distStartToEnd, 0)
];
console.log('vec2s', vec2s);
var shape = new THREE.Shape(vec2s);
var geo = new THREE.BoxGeometry(5, 5, 5);
// console.log('shape', shape);
var geo = new THREE.ShapeGeometry(shape);
geo.applyMatrix(new THREE.Matrix4().makeRotationX(THREE.Math.degToRad(90)));
this.mesh3D = new THREE.Mesh(geo, this.material);
this.alignRotation();
this.alignPosition();
// the mesh3D should be added to the scene outside of this class
}
}
alignRotation() {
var p1 = this.start.clone();
var p2 = this.end.clone();
var direction = new THREE.Vector3();
direction.subVectors(p2, p1);
direction.normalize();
this.mesh3D.quaternion.setFromUnitVectors(new THREE.Vector3(1, 0, 0), direction);
}
alignPosition() {
if (this.mesh3D) {
this.mesh3D.position.copy(this.start);
} else {
throw new Error('mesh3D null');
}
}
}
The linewidth parameter relies on native WebGL support for drawing line thickness, but its performance is very spotty across browsers & operating systems. I think Windows doesn't support it, but MacOS does, so it shouldn't be relied upon. See this discussion on the Three.js Github for several bug reports.
As a workaround, they've created LineGeometry, which sort of re-builds a line with geometry to allow for thickness. See this example for how to use it. It even allows for dashed lines. After importing the module, you can implement it with:
const geometry = new LineGeometry();
geometry.setPositions( positions );
geometry.setColors( colors );
matLine = new LineMaterial( {
color: 0xffffff,
linewidth: 5, // in pixels
vertexColors: true,
dashed: false
} );
line = new Line2( geometry, matLine );
line.computeLineDistances();
scene.add( line );
I have two problems in my experimental project - (WASDEQ - control key, and mouse)
Rotation of a child object (camera) relative parent object.
How can I rotate the spaceship on 180-degree relative the camera?
When I rotate the spaceship with mouse only the Y-axis (right or left mouse moving), it automatically rotates on the other axis.
var loader = new THREE.JSONLoader();
loader.load("https://api.myjson.com/bins/2w5m2", function (geom, mater) {
var mater = new THREE.MeshNormalMaterial({ color: 0x00ff00 });
var spaceShip = new THREE.Mesh(geom, mater);
onLoadCompleted(spaceShip);
});
var onLoadCompleted = function (spaceShip) {
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.1, 2000);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.BoxGeometry(10, 10, 10);
var material = new THREE.MeshNormalMaterial({ color: 0x00ff00 });
var cube = new THREE.Mesh(geometry, material);
cube.position.z = -300;
cube.position.y = 10;
camera.position.y = 5;
camera.position.z = 30;
scene.add(cube);
scene.add(spaceShip);
spaceShip.add(camera);
for (var i = 0; i < 1000; ++i) {
var dotGeometry = new THREE.Geometry();
dotGeometry.vertices.push(new THREE.Vector3(Math.random() * (1500 + 1500) - 1500, Math.random() * (1500 + 1500) - 1500, Math.random() * (1500 + 1500) - 1500));
var dotMaterial = new THREE.PointCloudMaterial({ size: 1, sizeAttenuation: true });
var dot = new THREE.Points(dotGeometry, dotMaterial);
scene.add(dot);
}
var cameraControl = new CameraControl(spaceShip);
var render = function () {
cameraControl.update();
requestAnimationFrame(render);
renderer.render(scene, camera);
};
render();
}
function CameraControl(object3D) {
var rotationQuaternion = new THREE.Quaternion();
var rotationVector = new THREE.Vector3(0, 0, 0);
var movingVector = new THREE.Vector3(0, 0, 0);
var rotationSpeed = 0.01;
var movingSpeed = 1;
this.update = function () {
object3D.translateX(movingVector.x);
object3D.translateY(movingVector.y);
object3D.translateZ(movingVector.z);
rotationQuaternion.set(rotationVector.x, rotationVector.y, rotationVector.z, 1).normalize();
object3D.quaternion.multiply(rotationQuaternion);
object3D.rotation.setFromQuaternion(object3D.quaternion, object3D.rotation.order);
};
window.addEventListener('keydown', function (event) {
if (event.keyCode == 87) {
movingVector.z = -1;
}
else if (event.keyCode == 83) {
movingVector.z = 1;
}
else if (event.keyCode == 65) {
movingVector.x = -1;
}
else if (event.keyCode == 68) {
movingVector.x = 1;
}
else if (event.keyCode == 81) {
movingVector.y = -1;
}
else if (event.keyCode == 69) {
movingVector.y = 1;
}
movingVector.multiplyScalar(movingSpeed);
});
window.addEventListener('keyup', function (event) {
if (event.keyCode == 87) {
movingVector.z = 0;
}
else if (event.keyCode == 83) {
movingVector.z = 0;
}
else if (event.keyCode == 65) {
movingVector.x = 0;
}
else if (event.keyCode == 68) {
movingVector.x = 0;
}
else if (event.keyCode == 81) {
movingVector.y = 0;
}
else if (event.keyCode == 69) {
movingVector.y = 0;
}
});
window.addEventListener('mousemove', function (event) {
rotationVector.y = (window.innerWidth / 2 - event.clientX) / (window.innerWidth / 2) * rotationSpeed;
rotationVector.x = (window.innerHeight / 2 - event.clientY) / (window.innerHeight / 2) * rotationSpeed;
});
}
The second problem is not actual. that's my carelessness. I forgot that quaternions are used for rotation.
I have created a room in three js and added various objects in it. But,
the objects are moving out of the room. Here is the url on which my code is running:
http://istation-demo.cladev.com/room.html
In order to prevent objects leaving the room or colliding with each other, you must have at least a simple form of collision detection. Most 3D engines, three.js included, have Axis-Aligned Bounding Boxes ( or AABB ) for all the 3d objects that are created. In three.js to get access to bounding boxes, the easiest way is to call
var boundingBoxHelperObject = new THREE.BoundingBoxHelper( object )
If you add it to the scene, it shows up as a wireframe grey box that fits tightly around the object. If you call update on the helper (as you will see shortly), it will update to the dimensions and movement of the 3d object in real time - pretty cool!
Then, to perform collision detection on all these boxes, you call
boundingBoxHelperObject.box.intersectsBox( anotherBoxHelperObject.box )
which outputs true if colliding or false if not colliding. Then perform the appropriate actions, such as moving the box back a frame to prevent it from escaping a room or penetrating another solid object, or changing both colliding objects' colors, or whatever you want.
Here's the final code sample with my corrected picking/dragging code from the last post you made months ago, plus the new bounding boxes and collision testing code:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head lang="en">
<meta charset="UTF-8">
<title>Room</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.js"></script>
<script src="http://alexan0308.github.io/threejs/examples/js/loaders/OBJLoader.js"></script>
<script src="http://alexan0308.github.io/threejs/examples/js/loaders/MTLLoader.js"></script>
<script src="http://alexan0308.github.io/threejs/examples/js/loaders/OBJMTLLoader.js"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div id="workspace"></div>
<script>
//define global variables here
var container, renderer;
var camera, scene, projector, mouseVector, controls;
var mouseX, mouseY, draggable;
var pen, c_mesh, interactiveObj = [], rotateObj = [], groundRaycastObj = [];
var wallWidth = 1200;
var wallHeight = 400;
var chair_model, sofa_model;
var chair_selected = false;
var sofa_selected = false;
var raycaster;
var mouse = new THREE.Vector2(), INTERSECTED;
var radius = 100, theta = 0;
var oldIntersectPoint = new THREE.Vector3();
var newIntersectPoint = new THREE.Vector3();
var intersectOffset = new THREE.Vector3();
var chairOldPosition = new THREE.Vector3();
var sofaOldPosition = new THREE.Vector3();
var chair_rotate = false;
var walls;
var mesh_box;
var wallright, wallleft, wallback, wallfront, ceiling, ground;
var strDownloadMime = "image/octet-stream";
var chairBox, sofaBox;
var wallrightBox, wallleftBox, wallbackBox, wallfrontBox;
init();
animate();
function init() {
container = document.getElementById('workspace'); //document.createElement('div');
document.body.appendChild(container);
//camera
//camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 10, 10000);
// camera.position.set(0, -wallHeight / 2 + 10, wallWidth);
// camera.lookAt(new THREE.Vector3(10, 10, 10));
//renderer
renderer = new THREE.WebGLRenderer({preserveDrawingBuffer: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x889988);
renderer.shadowMapEnabled = true;
container.appendChild(renderer.domElement);
scene = new THREE.Scene();
raycaster = new THREE.Raycaster();
var ambient = new THREE.AmbientLight(0xffffff);
scene.add(ambient);
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z= wallWidth;
camera.position.y= wallWidth/2;
controls = new THREE.OrbitControls( camera, renderer.domElement );
//controls.addEventListener( 'change', render ); // add this only if there is no animation loop (requestAnimationFrame)
controls.enableDamping = true;
controls.dampingFactor = 0.25;
//controls.enableZoom = false;
//walls
walls = new THREE.Object3D();
var groundGeo_2 = new THREE.PlaneGeometry(wallWidth, wallWidth); //for roof and floor
var groundGeo = new THREE.PlaneGeometry(wallWidth, wallHeight);
var wallTextureRight = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('textures/walls/rainbow.jpg')
});
wallTextureRight.map.needsUpdate = true;
var wallTextureLeft = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('textures/walls/rainbow.jpg')
});
var wallTextureFront = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('textures/walls/wall4.jpg')
});
var wallTextureBack = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('textures/walls/wall3.png')
});
var floorTexture = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('textures/walls/floor.jpg')
});
floorTexture.map.needsUpdate = true;
var ceilTexture = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('textures/walls/wall4.jpg')
});
ground = new THREE.Mesh(groundGeo_2, floorTexture);
ground.overdraw = true;
ground.position.set(0, 0, 0);
ground.rotation.x = -Math.PI / 2;
walls.add(ground);
console.log(ground);
wallleft = new THREE.Mesh(groundGeo, wallTextureLeft);
wallleft.overdraw = true;
wallleft.position.set(-wallWidth / 2, wallHeight / 2, 0);
wallleft.rotation.y = Math.PI / 2;
walls.add(wallleft);
wallleftBox = new THREE.BoundingBoxHelper( wallleft );
wallleftBox.update(wallleft);
//wallleftBox.box.min.x -= 0.1;
//wallleftBox.box.max.x += 0.1;
//scene.add(wallleftBox);
wallright = new THREE.Mesh(groundGeo, wallTextureRight);
wallright.overdraw = true;
wallright.position.set(wallWidth / 2, wallHeight / 2, 0);
wallright.rotation.y = -Math.PI / 2;
walls.add(wallright);
wallrightBox = new THREE.BoundingBoxHelper( wallright );
wallrightBox.update(wallright);
//wallrightBox.box.min.x -= 0.1;
//wallrightBox.box.max.x += 0.1;
//scene.add(wallrightBox);
wallback = new THREE.Mesh(groundGeo, wallTextureBack);
wallback.overdraw = true;
wallback.position.set(0, wallHeight / 2, -wallWidth / 2);
walls.add(wallback);
wallbackBox = new THREE.BoundingBoxHelper( wallback );
wallbackBox.update(wallback);
//scene.add(wallbackBox);
wallfront = new THREE.Mesh(groundGeo, wallTextureFront);
wallfront.overdraw = true;
wallfront.position.set(0, wallHeight / 2, wallWidth / 2);
wallfront.rotation.y = -Math.PI;
walls.add(wallfront);
wallfrontBox = new THREE.BoundingBoxHelper( wallfront );
wallfrontBox.update(wallfront);
//scene.add(wallfrontBox);
ceiling = new THREE.Mesh(groundGeo_2, ceilTexture);
ceiling.position.set(0, wallHeight, 0);
ceiling.rotation.x = Math.PI / 2;
walls.add(ceiling);
scene.add(walls);
groundRaycastObj.push(walls);
//load bed texture
var bed_texture = new THREE.ImageUtils.loadTexture("textures/cb-rochelle-gray_baked.png");
var bedMaterial = new THREE.MeshBasicMaterial({
map: bed_texture,
side: THREE.DoubleSide
});
//load bed
var loader = new THREE.JSONLoader();
loader.load('js/sofa.js', function (geometry) {
sofa_model = new THREE.Mesh(geometry, bedMaterial);
for (var i = 0; i < sofa_model.children.length; i++) {
sofa_model.children[i].material = material;
sofa_model.children[i].userDataParent = sofa_model;
sofa_model.children[i].name = 'sofa_model';
}
sofa_model.position.set(200,0, -200);
sofa_model.rotation.set(0, 0, 0);
sofa_model.scale.set(3, 3, 3);
sofa_model.name = 'sofa_model';
interactiveObj.push(sofa_model);
scene.add(sofa_model);
sofaBox = new THREE.BoundingBoxHelper( sofa_model );
// comment next line out if you don't want to see the wireframe sofa boxHelper
scene.add(sofaBox);
});
//load chair texture
var chair_texture = new THREE.ImageUtils.loadTexture("textures/chair.png");
var chairMaterial = new THREE.MeshBasicMaterial({
map: chair_texture,
side: THREE.DoubleSide
});
//load chair
var loader = new THREE.JSONLoader();
loader.load('js/chair_model.js', function (geometry) {
chair_model = new THREE.Mesh(geometry, chairMaterial);
for (var i = 0; i < chair_model.children.length; i++) {
chair_model.children[i].material = material;
chair_model.children[i].userDataParent = sofa_model;
chair_model.children[i].name = 'chair_model';
}
chair_model.position.set(-300,0, -200);
chair_model.rotation.set(0, 0, 0);
chair_model.scale.set(3, 3, 3);
chair_model.name = 'chair_model';
interactiveObj.push(chair_model);
scene.add(chair_model);
chairBox = new THREE.BoundingBoxHelper( chair_model );
// comment next line out if you don't want to see the wireframe chair boxHelper
scene.add(chairBox);
});
//IE, Chrome, Safari, Opera
document.addEventListener('mousewheel', onDocumentMouseWheel, false);
//Firefox
document.addEventListener('DOMMouseScroll', onDocumentMouseWheel, false);
document.addEventListener('mousemove', onDocumentMouseMove, false);
document.addEventListener('mousedown', onDocumentMouseDown, false);
document.addEventListener('mouseup', onDocumentMouseUp, false);
window.addEventListener('resize', onWindowResize, false);
}
function animate() {
requestAnimationFrame(animate);
chair_model.rotation.y += 0.02;
chairBox.update(chair_model);
sofaBox.update(sofa_model);
//wallrightBox.update(wallright);
//wallleftBox.update(wallleft);
//wallfrontBox.update(wallfront);
//wallbackBox.update(wallback);
controls.update();
// Render the frame
//Don't render twice, it will slow down your animation!
//render();
renderer.render(scene, camera);
}
function render() {
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
//controls.handleResize();
}
function onDocumentMouseDown(event) {
draggable = true;
event.preventDefault();
var testIntersects;
testIntersects = raycaster.intersectObjects(groundRaycastObj, true);
if (testIntersects.length > 0)
oldIntersectPoint.copy(testIntersects[0].point);
// find intersections
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(interactiveObj, true);
if (intersects.length > 0) {
controls.enabled=false;
if (intersects[0].object.name == 'chair_model') {
container.style.cursor = 'pointer';
chair_selected = true;
//oldIntersectPoint.copy(chair_model.position);
chairBox.material.color.set('white');
} else if (intersects[0].object.name == 'sofa_model') {
container.style.cursor = 'pointer';
sofa_selected = true;
//oldIntersectPoint.copy(sofa_model.position);
sofaBox.material.color.set('white');
}
else {
chair_selected = false;
sofa_selected = false;
}
draggable = false;
}
}
function onDocumentMouseUp(event) {
draggable = false;
chair_selected = false;
sofa_selected = false;
chair_rotate = false;
container.style.cursor = 'auto';
controls.enabled=true;
oldIntersectPoint.set(0,0,0);
newIntersectPoint.set(0,0,0);
intersectOffset.set(0,0,0);
chairBox.material.color.set('grey');
sofaBox.material.color.set('grey');
}
function onDocumentMouseMove(event) {
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = -( event.clientY / window.innerHeight ) * 2 + 1;
var deltaX = event.clientX - mouseX;
var deltaY = event.clientY - mouseY;
var testIntersects;
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(interactiveObj, true);
if (intersects.length > 0) {
container.style.cursor = 'pointer';
//addRotationLine(intersects[0].object);
} else {
container.style.cursor = 'auto';
}
if (draggable) {
} else if (chair_selected == true) {
testIntersects = raycaster.intersectObjects(groundRaycastObj, true);
if (testIntersects.length > 0) {
var okToMove = true;
chairOldPosition.copy(chair_model.position);
newIntersectPoint.copy(testIntersects[0].point);
intersectOffset.copy(newIntersectPoint);
intersectOffset.sub(oldIntersectPoint);
//uncomment below if you want more precision mouse movements of objects
//intersectOffset.multiplyScalar(0.1);
// store old intersect point for next frame
oldIntersectPoint.copy(newIntersectPoint);
chair_model.position.add(intersectOffset);
chair_model.updateMatrixWorld(true);
//chairBox.updateMatrixWorld(true);
chairBox.update(chair_model);
// default
chairBox.material.color.set('white');
if( chairBox.box.intersectsBox(sofaBox.box) ) {
okToMove = false;
chairBox.material.color.set('red');
}
else if( chairBox.box.intersectsBox(wallrightBox.box) ) {
okToMove = false;
chairBox.material.color.set('red');
}
else if( chairBox.box.intersectsBox(wallleftBox.box) ) {
okToMove = false;
chairBox.material.color.set('red');
}
else if( chairBox.box.intersectsBox(wallfrontBox.box) ) {
okToMove = false;
chairBox.material.color.set('red');
}
else if( chairBox.box.intersectsBox(wallbackBox.box) ) {
okToMove = false;
chairBox.material.color.set('red');
}
// if NOT ok to move and chair is hitting something,
if ( !okToMove ) {
// put chair back where it was
chair_model.position.copy(chairOldPosition);
}
}
// clamp chair position to the ground
chair_model.position.y = 0;
} else if (chair_rotate == true) {
rotate_object(chair_model, event);
}
else if (sofa_selected == true) {
testIntersects = raycaster.intersectObjects(groundRaycastObj, true);
if (testIntersects.length > 0) {
var okToMove = true;
sofaOldPosition.copy(sofa_model.position);
newIntersectPoint.copy(testIntersects[0].point);
intersectOffset.copy(newIntersectPoint);
intersectOffset.sub(oldIntersectPoint);
//uncomment below if you want more precision mouse movements of objects
//intersectOffset.multiplyScalar(0.1);
oldIntersectPoint.copy(newIntersectPoint);
sofa_model.position.add(intersectOffset);
sofa_model.updateMatrixWorld(true);
//sofaBox.updateMatrixWorld(true);
sofaBox.update(sofa_model);
// default
sofaBox.material.color.set('white');
if( sofaBox.box.intersectsBox(chairBox.box) ) {
okToMove = false;
sofaBox.material.color.set('red');
}
else if( sofaBox.box.intersectsBox(wallrightBox.box) ) {
okToMove = false;
sofaBox.material.color.set('red');
}
else if( sofaBox.box.intersectsBox(wallleftBox.box) ) {
okToMove = false;
sofaBox.material.color.set('red');
}
else if( sofaBox.box.intersectsBox(wallfrontBox.box) ) {
okToMove = false;
sofaBox.material.color.set('red');
}
else if( sofaBox.box.intersectsBox(wallbackBox.box) ) {
okToMove = false;
sofaBox.material.color.set('red');
}
// if NOT ok to move and sofa is hitting something,
if ( !okToMove ) {
// put sofa back where it was
sofa_model.position.copy(sofaOldPosition);
}
}
// clamp sofa position to the ground
sofa_model.position.y = 0;
}
mouseX = event.clientX;
mouseY = event.clientY;
//render(); // no need to render
}
function onDocumentMouseWheel(event) {
// This is automatically handled for you by orbitControls.js,
// but you can't disable zoom on the controls - so don't type controls.enableZoom = false;
//mouseDelta = (-event.wheelDeltaY || event.detail);
//camera.position.z += mouseDelta * 1;
//render(); // no need to render
}
function addRotationLine(objModel) {
var material = new THREE.LineBasicMaterial({
color: 0x0000ff,
linewidth: 6
});
var geometry = new THREE.Geometry();
geometry.vertices.push(
new THREE.Vector3(-10, 500, 0),
new THREE.Vector3(1000, 500, 0)
);
var line = new THREE.Line(geometry, material);
objModel.add(line);
}
function rotate_object(object, event) {
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = -( event.clientY / window.innerHeight ) * 2 + 1;
var deltaX = event.clientX - mouseX;
var deltaY = event.clientY - mouseY;
object.rotation.y += deltaX * 0.02;
object.rotation.y += deltaY * 0.01;
}
</script>
</body>
</html>
Hope this works for you! :)
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.
I am trying to use Ray intersect to find out if 2 collada object collide.
But so far no success :(
my code http://jsfiddle.net/XqQzF/
object.updateMatrix();
// ray
var f_vector = new THREE.Vector3( 0, 0, -1 );
var b_vector = new THREE.Vector3( 0, 0, 1 );
var l_vector = new THREE.Vector3( -1, 0, 0 );
var r_vector = new THREE.Vector3( 1, 0, 0 );
everytime i use something its removed from the newest three.js revision.
Can you help me on the way ?
I recently update my three.js examples collection to be compatible with the latest version (v56 at time of writing), and this includes an example of collision detection. Check out http://stemkoski.github.com/Three.js/Collision-Detection.html (see http://stemkoski.github.com/Three.js/#collision-detection for instructions).
The main difference in your case will be choosing a central point of your model as an origin point for the rays, and choosing a set of vertices of your model to use as endpoints for the rays, as rays are what is used for collision detection.
Hope this helps!
#Lee Stemkoski answer is ok, when we doesn't use collada data.
First what I know is that we need min. 2 Mesh objects. So I took all Mesh objects from collada object. I wrote two scripts - first tried colliding 2 collada objects. Second tried to collide collada object with wall(simple Mesh). In both cases script it doesn't recognize colliding 😕. So how i should write it?
At last I tried to detect mouse on collada object. It's not about this topic but it's similar problem so I write it here.
Dae file was generated by SketchUp.
Code for two collada objects:
var container;
var meshs = [];
var camera, scene, renderer, raycaster;
var controls;
var mouse = new THREE.Vector2();
var dae, dae2;
var collidableMeshList = [];
var clock = new THREE.Clock();
var keyboard = new THREEx.KeyboardState();
$(document).ready(function() {
init();
animate();
});
function init() {
container = document.createElement('div');
$('.container').append(container);
camera = new THREE.PerspectiveCamera(45, 500 / 500, 1, 1000);
group = new THREE.Group();
scene = new THREE.Scene();
scene.add(camera);
camera.position.set(10 * 10, 12 * 10, 16 * 10);
camera.lookAt(scene.position);
// Lights
group.add(new THREE.AmbientLight(0x777777));
var light = new THREE.DirectionalLight(0xdfebff, 1.25);
light.position.set(300, 400, 50);
light.position.multiplyScalar(1.3);
light.castShadow = true;
group.add(light);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setClearColor(0xcccccc, 1);
renderer.setSize(500, 500);
container.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load('./sample.dae', loadDae);
function loadDae(collada) {
dae = collada.scene;
dae.scale.x = 0.5;
dae.scale.y = 0.3;
dae.scale.z = 0.3;
dae.updateMatrix();
group.add(dae);
}
var wallGeometry = new THREE.CubeGeometry(10, 10, 20, 1, 1, 1);
var wallMaterial = new THREE.MeshBasicMaterial({
color: 0x8888ff
});
var wireMaterial = new THREE.MeshBasicMaterial({
color: 0x000000,
wireframe: true
});
loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load('./sample.dae', loadDae2);
function loadDae2(collada) {
dae2 = collada.scene;
dae2.scale.x = 0.5;
dae2.scale.y = 0.3;
dae2.scale.z = 0.3;
dae2.updateMatrix();
dae2.position.set(-40, 0, 0);
dae2.traverse(function(child) {
if (child instanceof THREE.Mesh) {
collidableMeshList.push(child);
}
});
group.add(dae2);
}
group.translateX(-20);
scene.add(group);
raycaster = new THREE.Raycaster();
window.addEventListener('resize', onWindowResize, false);
window.addEventListener('mousemove', onDocumentMouseMove, false);
}
function onDocumentMouseMove(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function onWindowResize() {
camera.aspect = 500 / 500;
camera.updateProjectionMatrix();
renderer.setSize(500, 500);
}
function update() {
var delta = clock.getDelta();
var moveDistance = 200 * delta;
var rotateAngle = Math.PI / 2 * delta;
if (keyboard.pressed("A"))
dae.rotation.y += rotateAngle;
if (keyboard.pressed("D"))
dae.rotation.y -= rotateAngle;
if (keyboard.pressed("left"))
dae.translateX(-moveDistance);
if (keyboard.pressed("right"))
dae.translateX(moveDistance);
if (keyboard.pressed("up"))
dae.translateZ(-moveDistance);
if (keyboard.pressed("down"))
dae.translateZ(moveDistance);
meshs = [];
if (typeof dae !== 'undefined') {
dae.traverse(function(child) {
if (child instanceof THREE.Mesh) {
meshs.push(child);
}
});
$.each(meshs, function(number_mesh, mesh) {
var originPoint = mesh.position.clone();
for (var vertexIndex = 0; vertexIndex < mesh.geometry.vertices.length; vertexIndex++) {
var localVertex = mesh.geometry.vertices[vertexIndex].clone();
var globalVertex = localVertex.applyMatrix4(mesh.matrix);
var directionVector = globalVertex.sub(mesh.position);
var ray = new THREE.Raycaster(originPoint, directionVector.clone().normalize());
var collisionResults = ray.intersectObjects(collidableMeshList);
if (collisionResults.length > 0 && collisionResults[0].distance < directionVector.length()) {
console.log(" Hit ");
}
}
});
}
controls.update();
}
function animate() {
requestAnimationFrame(animate);
render();
update();
}
function render() {
renderer.render(scene, camera);
}
Code for collada + wall(Mesh):
var container;
var meshs = [];
var camera, scene, renderer, raycaster;
var controls;
var mouse = new THREE.Vector2();
var dae;
var collidableMeshList = [];
var clock = new THREE.Clock();
var keyboard = new THREEx.KeyboardState();
$(document).ready(function() {
init();
animate();
});
function init() {
container = document.createElement('div');
$('.container').append(container);
camera = new THREE.PerspectiveCamera(45, 500 / 500, 1, 1000);
group = new THREE.Group();
scene = new THREE.Scene();
scene.add(camera);
camera.position.set(10 * 10, 12 * 10, 16 * 10);
camera.lookAt(scene.position);
// Lights
group.add(new THREE.AmbientLight(0x777777));
var light = new THREE.DirectionalLight(0xdfebff, 1.25);
light.position.set(300, 400, 50);
light.position.multiplyScalar(1.3);
light.castShadow = true;
group.add(light);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setClearColor(0xcccccc, 1);
renderer.setSize(500, 500);
container.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load('./sample.dae', loadDae);
function loadDae(collada) {
dae = collada.scene;
dae.scale.x = 0.5;
dae.scale.y = 0.3;
dae.scale.z = 0.3;
dae.updateMatrix();
group.add(dae);
}
var wallGeometry = new THREE.CubeGeometry(10, 10, 20, 1, 1, 1);
var wallMaterial = new THREE.MeshBasicMaterial({
color: 0x8888ff
});
var wireMaterial = new THREE.MeshBasicMaterial({
color: 0x000000,
wireframe: true
});
var wall = new THREE.Mesh(wallGeometry, wallMaterial);
wall.position.set(-40, 5, -10);
group.add(wall);
collidableMeshList.push(wall);
var wall = new THREE.Mesh(wallGeometry, wireMaterial);
wall.position.set(-40, 5, -10);
group.add(wall);
group.translateX(-20);
scene.add(group);
raycaster = new THREE.Raycaster();
window.addEventListener('resize', onWindowResize, false);
window.addEventListener('mousemove', onDocumentMouseMove, false);
}
function onDocumentMouseMove(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function onWindowResize() {
camera.aspect = 500 / 500;
camera.updateProjectionMatrix();
renderer.setSize(500, 500);
}
function update() {
var delta = clock.getDelta();
var moveDistance = 200 * delta;
var rotateAngle = Math.PI / 2 * delta;
if (keyboard.pressed("A"))
dae.rotation.y += rotateAngle;
if (keyboard.pressed("D"))
dae.rotation.y -= rotateAngle;
if (keyboard.pressed("left"))
dae.translateX(-moveDistance);
if (keyboard.pressed("right"))
dae.translateX(moveDistance);
if (keyboard.pressed("up"))
dae.translateZ(-moveDistance);
if (keyboard.pressed("down"))
dae.translateZ(moveDistance);
meshs = [];
if (typeof dae !== 'undefined') {
dae.traverse(function(child) {
if (child instanceof THREE.Mesh) {
meshs.push(child);
}
});
$.each(meshs, function(number_mesh, mesh) {
var originPoint = mesh.position.clone();
for (var vertexIndex = 0; vertexIndex < mesh.geometry.vertices.length; vertexIndex++) {
var localVertex = mesh.geometry.vertices[vertexIndex].clone();
var globalVertex = localVertex.applyMatrix4(mesh.matrix);
var directionVector = globalVertex.sub(mesh.position);
var ray = new THREE.Raycaster(originPoint, directionVector.clone().normalize());
var collisionResults = ray.intersectObjects(collidableMeshList);
if (collisionResults.length > 0 && collisionResults[0].distance < directionVector.length()) {
console.log(" Hit ");
}
}
});
}
controls.update();
}
function animate() {
requestAnimationFrame(animate);
render();
update();
}
function render() {
renderer.render(scene, camera);
}
Detect mouse on collada object. Short code version:
var meshs = [],
raycaster,
mouse = new THREE.Vector2();
...
function init() {
...
function loadDae(collada) {
dae = collada.scene;
dae.scale.x = 0.5;
dae.scale.y = 0.3;
dae.scale.z = 0.3;
dae.updateMatrix();
group.add(dae);
dae.traverse(function(child) {
if (child instanceof THREE.Mesh) {
meshs.push(child);
}
});
}
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load('./sample.dae', loadDae);
raycaster = new THREE.Raycaster();
window.addEventListener('mousemove', onDocumentMouseMove, false);
...
}
...
function onDocumentMouseMove(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function render() {
raycaster.setFromCamera(mouse, camera);
if (group.children.length === 5) {
var intersects = raycaster.intersectObjects(meshs);
if (intersects.length > 0) {
console.log(" Hit ");
}
}
renderer.render(scene, camera);
}
...
General - yes, it works, but throw errors with some $meshs 😕.
TypeError: undefined is not an object (evaluating 'O')
three.min.js:8304
cthree.min.js:3544
intersectObjectsthree.min.js:3609:144
Why and how to recognize which mesh is correct for intersectObjects and which not?