I'm trying to figure out how to create a minimal car with physics using Three.js and Cannon.js. I have created my visual and physics-based elements of my car and wheels, and my car responds to up arrow and left/right arrow commands to speed up and turn:
var container = document.querySelector('body'),
w = container.clientWidth,
h = container.clientHeight,
scene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera(75, w/h, 0.001, 100),
renderConfig = {antialias: true, alpha: true},
renderer = new THREE.WebGLRenderer(renderConfig);
camera.position.set(0, 1, -10);
camera.lookAt(0,0,0);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(w, h);
container.appendChild(renderer.domElement);
window.addEventListener('resize', function() {
w = container.clientWidth;
h = container.clientHeight;
camera.aspect = w/h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
})
var geometry = new THREE.PlaneGeometry(10, 10, 10);
var material = new THREE.MeshBasicMaterial({color: 0xff0000, side: THREE.DoubleSide});
var plane = new THREE.Mesh(geometry, material);
plane.rotation.x = Math.PI/2;
scene.add(plane);
var sunlight = new THREE.DirectionalLight(0xffffff, 1.0);
sunlight.position.set(-10, 10, 0);
scene.add(sunlight)
/**
* Physics
**/
var world = new CANNON.World();
world.broadphase = new CANNON.SAPBroadphase(world);
world.gravity.set(0, -10, 0);
world.defaultContactMaterial.friction = 0;
var groundMaterial = new CANNON.Material('groundMaterial');
var wheelMaterial = new CANNON.Material('wheelMaterial');
var wheelGroundContactMaterial = new CANNON.ContactMaterial(wheelMaterial, groundMaterial, {
friction: 0.3,
restitution: 0,
contactEquationStiffness: 1000,
});
world.addContactMaterial(wheelGroundContactMaterial);
// car physics body
var chassisShape = new CANNON.Box(new CANNON.Vec3(1, 0.3, 2));
var chassisBody = new CANNON.Body({mass: 150});
chassisBody.addShape(chassisShape);
chassisBody.position.set(0, 0.2, 0);
chassisBody.angularVelocity.set(0, 0, 0); // initial velocity
// car visual body
var geometry = new THREE.BoxGeometry(2, 0.6, 4); // double chasis shape
var material = new THREE.MeshBasicMaterial({color: 0xffff00, side: THREE.DoubleSide});
var box = new THREE.Mesh(geometry, material);
scene.add(box);
// parent vehicle object
vehicle = new CANNON.RaycastVehicle({
chassisBody: chassisBody,
indexRightAxis: 0, // x
indexUpAxis: 1, // y
indexForwardAxis: 2, // z
});
// wheel options
var options = {
radius: 0.3,
directionLocal: new CANNON.Vec3(0, -1, 0),
suspensionStiffness: 45,
suspensionRestLength: 0.4,
frictionSlip: 5,
dampingRelaxation: 2.3,
dampingCompression: 4.5,
maxSuspensionForce: 200000,
rollInfluence: 0.01,
axleLocal: new CANNON.Vec3(-1, 0, 0),
chassisConnectionPointLocal: new CANNON.Vec3(1, 1, 0),
maxSuspensionTravel: 0.25,
customSlidingRotationalSpeed: -30,
useCustomSlidingRotationalSpeed: true,
};
var axlewidth = 0.7;
options.chassisConnectionPointLocal.set(axlewidth, 0, -1);
vehicle.addWheel(options);
options.chassisConnectionPointLocal.set(-axlewidth, 0, -1);
vehicle.addWheel(options);
options.chassisConnectionPointLocal.set(axlewidth, 0, 1);
vehicle.addWheel(options);
options.chassisConnectionPointLocal.set(-axlewidth, 0, 1);
vehicle.addWheel(options);
vehicle.addToWorld(world);
// car wheels
var wheelBodies = [],
wheelVisuals = [];
vehicle.wheelInfos.forEach(function(wheel) {
var shape = new CANNON.Cylinder(wheel.radius, wheel.radius, wheel.radius / 2, 20);
var body = new CANNON.Body({mass: 1, material: wheelMaterial});
var q = new CANNON.Quaternion();
q.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI / 2);
body.addShape(shape, new CANNON.Vec3(), q);
wheelBodies.push(body);
// wheel visual body
var geometry = new THREE.CylinderGeometry( wheel.radius, wheel.radius, 0.4, 32 );
var material = new THREE.MeshPhongMaterial({
color: 0xd0901d,
emissive: 0xaa0000,
side: THREE.DoubleSide,
flatShading: true,
});
var cylinder = new THREE.Mesh(geometry, material);
wheelVisuals.push(cylinder);
scene.add(cylinder);
});
// update the wheels to match the physics
world.addEventListener('postStep', function() {
for (var i=0; i<vehicle.wheelInfos.length; i++) {
vehicle.updateWheelTransform(i);
var t = vehicle.wheelInfos[i].worldTransform;
// update wheel physics
wheelBodies[i].position.copy(t.position);
wheelBodies[i].quaternion.copy(t.quaternion);
// update wheel visuals
wheelVisuals[i].position.copy(t.position);
wheelVisuals[i].quaternion.copy(t.quaternion);
}
});
var q = plane.quaternion;
var planeBody = new CANNON.Body({
mass: 0, // mass = 0 makes the body static
material: groundMaterial,
shape: new CANNON.Plane(),
quaternion: new CANNON.Quaternion(-q._x, q._y, q._z, q._w)
});
world.add(planeBody)
/**
* Main
**/
function updatePhysics() {
world.step(1/60);
// update the chassis position
box.position.copy(chassisBody.position);
box.quaternion.copy(chassisBody.quaternion);
}
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
updatePhysics();
}
function navigate(e) {
if (e.type != 'keydown' && e.type != 'keyup') return;
var keyup = e.type == 'keyup';
vehicle.setBrake(0, 0);
vehicle.setBrake(0, 1);
vehicle.setBrake(0, 2);
vehicle.setBrake(0, 3);
var engineForce = 800,
maxSteerVal = 0.3;
switch(e.keyCode) {
case 38: // forward
vehicle.applyEngineForce(keyup ? 0 : -engineForce, 2);
vehicle.applyEngineForce(keyup ? 0 : -engineForce, 3);
break;
case 40: // backward
vehicle.applyEngineForce(keyup ? 0 : engineForce, 2);
vehicle.applyEngineForce(keyup ? 0 : engineForce, 3);
break;
case 39: // right
vehicle.setSteeringValue(keyup ? 0 : -maxSteerVal, 2);
vehicle.setSteeringValue(keyup ? 0 : -maxSteerVal, 3);
break;
case 37: // left
vehicle.setSteeringValue(keyup ? 0 : maxSteerVal, 2);
vehicle.setSteeringValue(keyup ? 0 : maxSteerVal, 3);
break;
}
}
window.addEventListener('keydown', navigate)
window.addEventListener('keyup', navigate)
render();
* {
margin: 0;
padding: 0;
overflow: hidden;
}
html,
body,
canvas {
width: 100%;
height: 100%;
background: #aaa;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.js'></script>
I'm now trying to figure out how to rotate the wheels so they roll like proper tires. Previously, inside the world.addEventListener('postStep') callback, I was manually running wheelVisuals[i].rotation.z = Math.PI/2 after setting the wheel's quaternion. That worked fine until I turned the car, at which point the tires were thrown out of alignment...
If anyone could give a little pointer on how one is meant to configure the wheels with Cannon.js, I'd be super grateful!
Aha, I was applying the hacky rotation to the mesh when I should have been applying it to the geometry!
The answer below just adds one line to the vehicle.wheelInfos loop:
cylinder.geometry.rotateZ(Math.PI/2);
var container = document.querySelector('body'),
w = container.clientWidth,
h = container.clientHeight,
scene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera(75, w/h, 0.001, 100),
renderConfig = {antialias: true, alpha: true},
renderer = new THREE.WebGLRenderer(renderConfig);
camera.position.set(0, 1, -10);
camera.lookAt(0,0,0);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(w, h);
container.appendChild(renderer.domElement);
window.addEventListener('resize', function() {
w = container.clientWidth;
h = container.clientHeight;
camera.aspect = w/h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
})
var geometry = new THREE.PlaneGeometry(10, 10, 10);
var material = new THREE.MeshBasicMaterial({color: 0xff0000, side: THREE.DoubleSide});
var plane = new THREE.Mesh(geometry, material);
plane.rotation.x = Math.PI/2;
scene.add(plane);
var sunlight = new THREE.DirectionalLight(0xffffff, 1.0);
sunlight.position.set(-10, 10, 0);
scene.add(sunlight)
/**
* Physics
**/
var world = new CANNON.World();
world.broadphase = new CANNON.SAPBroadphase(world);
world.gravity.set(0, -10, 0);
world.defaultContactMaterial.friction = 0;
var groundMaterial = new CANNON.Material('groundMaterial');
var wheelMaterial = new CANNON.Material('wheelMaterial');
var wheelGroundContactMaterial = new CANNON.ContactMaterial(wheelMaterial, groundMaterial, {
friction: 0.3,
restitution: 0,
contactEquationStiffness: 1000,
});
world.addContactMaterial(wheelGroundContactMaterial);
// car physics body
var chassisShape = new CANNON.Box(new CANNON.Vec3(1, 0.3, 2));
var chassisBody = new CANNON.Body({mass: 150});
chassisBody.addShape(chassisShape);
chassisBody.position.set(0, 0.2, 0);
chassisBody.angularVelocity.set(0, 0, 0); // initial velocity
// car visual body
var geometry = new THREE.BoxGeometry(2, 0.6, 4); // double chasis shape
var material = new THREE.MeshBasicMaterial({color: 0xffff00, side: THREE.DoubleSide});
var box = new THREE.Mesh(geometry, material);
scene.add(box);
// parent vehicle object
vehicle = new CANNON.RaycastVehicle({
chassisBody: chassisBody,
indexRightAxis: 0, // x
indexUpAxis: 1, // y
indexForwardAxis: 2, // z
});
// wheel options
var options = {
radius: 0.3,
directionLocal: new CANNON.Vec3(0, -1, 0),
suspensionStiffness: 45,
suspensionRestLength: 0.4,
frictionSlip: 5,
dampingRelaxation: 2.3,
dampingCompression: 4.5,
maxSuspensionForce: 200000,
rollInfluence: 0.01,
axleLocal: new CANNON.Vec3(-1, 0, 0),
chassisConnectionPointLocal: new CANNON.Vec3(1, 1, 0),
maxSuspensionTravel: 0.25,
customSlidingRotationalSpeed: -30,
useCustomSlidingRotationalSpeed: true,
};
var axlewidth = 0.7;
options.chassisConnectionPointLocal.set(axlewidth, 0, -1);
vehicle.addWheel(options);
options.chassisConnectionPointLocal.set(-axlewidth, 0, -1);
vehicle.addWheel(options);
options.chassisConnectionPointLocal.set(axlewidth, 0, 1);
vehicle.addWheel(options);
options.chassisConnectionPointLocal.set(-axlewidth, 0, 1);
vehicle.addWheel(options);
vehicle.addToWorld(world);
// car wheels
var wheelBodies = [],
wheelVisuals = [];
vehicle.wheelInfos.forEach(function(wheel) {
var shape = new CANNON.Cylinder(wheel.radius, wheel.radius, wheel.radius / 2, 20);
var body = new CANNON.Body({mass: 1, material: wheelMaterial});
var q = new CANNON.Quaternion();
q.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI / 2);
body.addShape(shape, new CANNON.Vec3(), q);
wheelBodies.push(body);
// wheel visual body
var geometry = new THREE.CylinderGeometry( wheel.radius, wheel.radius, 0.4, 32 );
var material = new THREE.MeshPhongMaterial({
color: 0xd0901d,
emissive: 0xaa0000,
side: THREE.DoubleSide,
flatShading: true,
});
var cylinder = new THREE.Mesh(geometry, material);
cylinder.geometry.rotateZ(Math.PI/2);
wheelVisuals.push(cylinder);
scene.add(cylinder);
});
// update the wheels to match the physics
world.addEventListener('postStep', function() {
for (var i=0; i<vehicle.wheelInfos.length; i++) {
vehicle.updateWheelTransform(i);
var t = vehicle.wheelInfos[i].worldTransform;
// update wheel physics
wheelBodies[i].position.copy(t.position);
wheelBodies[i].quaternion.copy(t.quaternion);
// update wheel visuals
wheelVisuals[i].position.copy(t.position);
wheelVisuals[i].quaternion.copy(t.quaternion);
}
});
var q = plane.quaternion;
var planeBody = new CANNON.Body({
mass: 0, // mass = 0 makes the body static
material: groundMaterial,
shape: new CANNON.Plane(),
quaternion: new CANNON.Quaternion(-q._x, q._y, q._z, q._w)
});
world.add(planeBody)
/**
* Main
**/
function updatePhysics() {
world.step(1/60);
// update the chassis position
box.position.copy(chassisBody.position);
box.quaternion.copy(chassisBody.quaternion);
}
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
updatePhysics();
}
function navigate(e) {
if (e.type != 'keydown' && e.type != 'keyup') return;
var keyup = e.type == 'keyup';
vehicle.setBrake(0, 0);
vehicle.setBrake(0, 1);
vehicle.setBrake(0, 2);
vehicle.setBrake(0, 3);
var engineForce = 800,
maxSteerVal = 0.3;
switch(e.keyCode) {
case 38: // forward
vehicle.applyEngineForce(keyup ? 0 : -engineForce, 2);
vehicle.applyEngineForce(keyup ? 0 : -engineForce, 3);
break;
case 40: // backward
vehicle.applyEngineForce(keyup ? 0 : engineForce, 2);
vehicle.applyEngineForce(keyup ? 0 : engineForce, 3);
break;
case 39: // right
vehicle.setSteeringValue(keyup ? 0 : -maxSteerVal, 2);
vehicle.setSteeringValue(keyup ? 0 : -maxSteerVal, 3);
break;
case 37: // left
vehicle.setSteeringValue(keyup ? 0 : maxSteerVal, 2);
vehicle.setSteeringValue(keyup ? 0 : maxSteerVal, 3);
break;
}
}
window.addEventListener('keydown', navigate)
window.addEventListener('keyup', navigate)
render();
* {
margin: 0;
padding: 0;
overflow: hidden;
}
html,
body,
canvas {
width: 100%;
height: 100%;
background: #aaa;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.js'></script>
Related
i have a 3D Room that i create and a shelf in my Room that i can move arround.
I want to make all the objects i have in my Room to stay in my room, the user should not be able to drag anything out of it.
I have tried multiple ways to make it work but nothing i did worked well enough.
At the moment the shelf goes a bit out of the room and as soon as it is out i cant move it anymore. I should still be able to drag it while the mouse is clicked.
I hope someone can help me. i worked way to long on this topic and my brain just starts to give up :)
At the moment my code looks like this:
on Mouse Move:
let bb = this.selectedHelper.geometry.boundingBox;
let originPoint = this.selected.position.clone();
originPoint.y = bb.getSize().y / 2;
originPoint.z += bb.getSize().z / 2;
this.raycasterLeft.set(originPoint, new THREE.Vector3(-1, 0, 0));
this.raycasterRight.set(new THREE.Vector3(originPoint.x + bb.getSize().x, originPoint.y, originPoint.z), new THREE.Vector3(1, 0, 0));
this.raycasterFront.set(new THREE.Vector3(originPoint.x + bb.getSize().x / 2, originPoint.y, originPoint.z + bb.getSize().z / 2), new THREE.Vector3(0, 0, 1));
this.raycasterBack.set(new THREE.Vector3(originPoint.x + bb.getSize().x / 2, originPoint.y, originPoint.z - bb.getSize().z / 2), new THREE.Vector3(0, 0, -1));
this.intersectionLeft = this.raycasterLeft.intersectObjects(this.walls, true);
this.intersectionRight = this.raycasterRight.intersectObjects(this.walls, true);
this.intersectionFront = this.raycasterFront.intersectObjects(this.walls, true);
this.intersectionBack = this.raycasterBack.intersectObjects(this.walls, true);
if (this.oktomove) {
this.selected.position.x = this.intersection.sub(this.offsetControls).x;
this.selected.position.z = this.intersection.z;
if (this.selected.name != "door" && this.selected.name != "regal") this.selected.position.y = this.intersection.y;
else { this.selected.position.y = 0; }
}
if (this.intersectionLeft.length > 0
&& this.intersectionRight.length > 0
&& this.intersectionFront.length > 0
&& this.intersectionBack.length > 0) {
if (this.intersectionLeft[0].distance >= 10
|| this.intersectionRight[0].distance >= 10
|| this.intersectionFront[0].distance >= 10
|| this.intersectionBack[0].distance >= 10) {
this.oktomove = true;
}
}
else {
console.log("drauflen")
this.oktomove = false;
}
this.showCasters(this.raycasterLeft, this.raycasterRight, this.raycasterFront, this.raycasterBack);
and on mouse Down
this.oktomove = true;
this.orbitControl.enabled = false;
if (intersectRegal[intersectRegal.length - 1].object.parent.name == "regal") {
this.selected = intersectRegal[intersectRegal.length - 1].object.parent;
}
else if (intersectRegal[intersectRegal.length - 1].object.parent.parent.name == "regal") {
this.selected = intersectRegal[intersectRegal.length - 1].object.parent.parent;
}
else if (intersectRegal[intersectRegal.length - 1].object.parent.parent.parent.name == "regal") {
this.selected = intersectRegal[intersectRegal.length - 1].object.parent.parent.parent;
}
this.selectedHelper = new THREE.BoxHelper(this.selected);
this.selectedHelper.geometry.computeBoundingBox();
let bb = this.selectedHelper.geometry.boundingBox;
let originPoint = this.selected.position.clone();
originPoint.y = bb.getSize().y / 2;
originPoint.z += bb.getSize().z / 2;
this.raycasterLeft = new THREE.Raycaster(originPoint, new THREE.Vector3(-1, 0, 0));
this.raycasterRight = new THREE.Raycaster(new THREE.Vector3(originPoint.x + bb.getSize().x, originPoint.y, originPoint.z), new THREE.Vector3(1, 0, 0));
this.raycasterFront = new THREE.Raycaster(new THREE.Vector3(originPoint.x + bb.getSize().x / 2, originPoint.y, originPoint.z + bb.getSize().z / 2), new THREE.Vector3(0, 0, 1));
this.raycasterBack = new THREE.Raycaster(new THREE.Vector3(originPoint.x + bb.getSize().x / 2, originPoint.y, originPoint.z - bb.getSize().z / 2), new THREE.Vector3(0, 0, -1));
this.intersectionLeft = this.raycasterLeft.intersectObjects(this.walls, true);
this.intersectionRight = this.raycasterRight.intersectObjects(this.walls, true);
this.intersectionFront = this.raycasterFront.intersectObjects(this.walls, true);
this.intersectionBack = this.raycasterBack.intersectObjects(this.walls, true);
this.showCasters( this.raycasterLeft , this.raycasterRight , this.raycasterFront , this.raycasterBack);
here are some screenshots from my scene:
so the problem is that the shelf stops to late and once it stops i cant move it anymore. The arrows are my raycasters
i could not add the pictures so i created a imgur album
https://imgur.com/a/xOd98hc
Here is just an option, made from a scratch.
The idea is to use .clamp(min, max) method of THREE.Vector3().
It may look complicated, but most of the stuff here is made for visualization, important parts are marked with comments:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 5, 5);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var roomGeom = new THREE.BoxGeometry(7, 2, 7);
roomGeom.translate(0, 1, 0);
var room = new THREE.Mesh(roomGeom, [
new THREE.MeshBasicMaterial({
color: "red",
side: THREE.BackSide
}),
new THREE.MeshBasicMaterial({
color: "red",
side: THREE.BackSide
}),
new THREE.MeshBasicMaterial({
color: "green",
side: THREE.BackSide
}),
new THREE.MeshBasicMaterial({
color: "green",
side: THREE.BackSide
}),
new THREE.MeshBasicMaterial({
color: "blue",
side: THREE.BackSide
}),
new THREE.MeshBasicMaterial({
color: "blue",
side: THREE.BackSide
})
]);
scene.add(room);
var objGeom = new THREE.CylinderGeometry(1, 1, 2);
objGeom.translate(0, 1, 0);
var moveObj = new THREE.Mesh(objGeom, new THREE.MeshBasicMaterial({
color: "aqua",
wireframe: true
}));
moveObj.position.set(1, 0, 0);
scene.add(moveObj);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects = [];
var isDragging = false;
var plane = new THREE.Plane();
var planePoint = new THREE.Vector3();
var planeNormal = new THREE.Vector3(0, 1, 0);
var movePoint = new THREE.Vector3();
var moveObjBox = new THREE.Box3();
var moveObjBoxSize = new THREE.Vector3();
moveObjBox.setFromObject(moveObj);
var boxHelper = new THREE.Box3Helper(moveObjBox, "yellow");
scene.add(boxHelper);
var moveObjShift = new THREE.Vector3();
var roomBox = new THREE.Box3().setFromObject(room);
var roomBoxMin = new THREE.Vector3();
var roomBoxMax = new THREE.Vector3();
renderer.domElement.addEventListener("mousedown", onMouseDown, false);
renderer.domElement.addEventListener("mousemove", onMouseMove, false);
renderer.domElement.addEventListener("mouseup", onMouseUp, false);
function onMouseDown(event) {
setPlane(event);
}
function onMouseMove(event) {
if (!isDragging) return;
setMouse(event);
raycaster.setFromCamera(mouse, camera);
raycaster.ray.intersectPlane(plane, movePoint);
moveObj.position.copy(movePoint).sub(moveObjShift).clamp(roomBoxMin, roomBoxMax); // clamp the position of an object
moveObjBox.setFromObject(moveObj);
}
function onMouseUp() {
isDragging = false;
}
function setPlane(event) {
setMouse(event);
raycaster.setFromCamera(mouse, camera);
moveObjBox.setFromObject(moveObj);
raycaster.ray.intersectBox(moveObjBox, planePoint)
isDragging = true;
plane.setFromNormalAndCoplanarPoint(planeNormal, planePoint);
moveObjShift.copy(planePoint).sub(moveObj.position);
roomBoxMin.copy(roomBox.min);
roomBoxMax.copy(roomBox.max);
moveObjBox.getSize(moveObjBoxSize);
// adjust clamping vectors
roomBoxMin.x += moveObjBoxSize.x * 0.5;
roomBoxMin.z += moveObjBoxSize.z * 0.5;
roomBoxMax.x -= moveObjBoxSize.x * 0.5;
roomBoxMax.z -= moveObjBoxSize.z * 0.5;
}
function setMouse(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
They implemented placement restrictions in here. Maybe you'll find it useful:
http://furnishup.github.io/blueprint3d/example/
I already asked a Question here: Add Thickness to faces
The core question is solved but i ran into another problem.
Before my walls were set on side:THREE.BackSide so that they didnt show when they faced the camera but now when they have a thickness that doesnt work anymore and i dont realy understand why.
Before:
Before
After: After
How can i make the thick walls behave like the Plane walls ?
A very rough concept of controlling the visibility of a wall (I've slightly changed translating and positioning of a geometry):
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 5, 5);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper(10, 10));
var points = [
new THREE.Vector3(-2, 0, 2),
new THREE.Vector3(2, 0, 2),
new THREE.Vector3(2, 0, -2),
new THREE.Vector3(-2, 0, -2)
]
var walls = [];
points.forEach((p, idx, points) => {
let nextIdx = idx === points.length - 1 ? 0 : idx + 1;
buildWall(p, points[nextIdx], 2, 0.1);
});
function buildWall(pointStart, pointEnd, height, thickness) {
var boxW = pointEnd.clone().sub(pointStart).length();
var boxH = height;
var boxD = thickness;
var boxGeometry = new THREE.BoxGeometry(boxW, boxH, boxD);
boxGeometry.translate(0, boxH * 0.5, 0);
boxGeometry.rotateY(-Math.PI * 0.5);
var wall = new THREE.Mesh(boxGeometry, new THREE.MeshBasicMaterial({
color: "aqua",
wireframe: true
}));
wall.position.copy(pointStart).add(pointEnd).multiplyScalar(0.5);
wall.lookAt(pointEnd);
scene.add(wall);
walls.push(wall);
}
var currentPosition = new THREE.Vector3();
render();
function render() {
requestAnimationFrame(render);
walls.forEach(w => {
w.visible = currentPosition.copy(w.position).sub(camera.position).lengthSq() > camera.position.lengthSq();
})
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
I've created a 3D marquee by bending two meshes into a circle, however I'm having trouble getting them to center to the camera.
Please reference https://jsfiddle.net/siiiick/1jh49e1u/
var text = "EXPRESS FREE SHIPPING WORLDWIDE OVER 200€ / 200% 150$ ";
var geoParams = {
size: 208,
height: 1,
curveSegments: 4,
font: "junicode",
// bevelEnabled: false,
// bevelThickness: 1,
// bevelSize: 1,
}
var textMaterial = new THREE.MeshPhongMaterial({
color: 0x000000
});
var deg = Math.PI / 4.8;
var geoTop = new THREE.TextGeometry(text, geoParams);
var textTop = new THREE.Mesh(geoTop, textMaterial);
geoTop.computeBoundingBox();
textWidth = geoTop.boundingBox.max.x - geoTop.boundingBox.min.x;
controls.target.set(-textWidth * .1 - 10, 0, -textWidth / 3.8);
textTop.rotation.y = Math.PI;
modifier.set(new THREE.Vector3(0, 0, -1), new THREE.Vector3(0, 1, 0), deg).modify(textTop.geometry);
modifier.set(new THREE.Vector3(0, 0, -1), new THREE.Vector3(0, 1, 0), deg).modify(textTop.geometry);
textTop.position.set(-0.5 * textWidth + textWidth * .867, 0, -textWidth * .577);
var geoBot = new THREE.TextGeometry(text, geoParams);
var textBot = new THREE.Mesh(geoBot, textMaterial);
modifier.set(new THREE.Vector3(0, 0, -1), new THREE.Vector3(0, 1, 0), deg).modify(textBot.geometry);
modifier.set(new THREE.Vector3(0, 0, -1), new THREE.Vector3(0, 1, 0), deg).modify(textBot.geometry);
textBot.position.set(-0.5 * textWidth, 0, 0);
scene.add(textTop);
scene.add(textBot);
As you can see after a few seconds the the marquee isn't centered. Do you think it's as a result of the camera positioning or the mesh positioning?
Thanks
//Testing some easy camera centering code...
var renderer = new THREE.WebGLRenderer();
var w = 300;
var h = 200;
renderer.setSize(w, h);
document.body.appendChild(renderer.domElement);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
45, // Field of view
w / h, // Aspect ratio
0.1, // Near
10000 // Far
);
camera.position.set(15, 10, 15);
camera.lookAt(scene.position);
controls = new THREE.OrbitControls(camera, renderer.domElement);
var light = new THREE.PointLight(0xFFFF00);
light.position.set(20, 20, 20);
scene.add(light);
var light1 = new THREE.AmbientLight(0x808080);
light1.position.set(20, 20, 20);
scene.add(light1);
var light2 = new THREE.PointLight(0x00FFFF);
light2.position.set(-20, 20, -20);
scene.add(light2);
var light3 = new THREE.PointLight(0xFF00FF);
light3.position.set(-20, -20, -20);
scene.add(light3);
var sphereGeom = new THREE.SphereGeometry(5, 16, 16);
var material = new THREE.MeshLambertMaterial({
color: 0x808080
});
var mesh = new THREE.Mesh(sphereGeom, material);
scene.add(mesh);
var mesh1 = new THREE.Mesh(sphereGeom, material);
mesh1.position.x += 5;
mesh.add(mesh1);
var mesh2 = new THREE.Mesh(sphereGeom, material);
mesh2.position.y += 5;
mesh2.position.x += 9;
mesh.add(mesh2);
var grp0 = mesh;
var mesh = new THREE.Mesh(sphereGeom, material);
scene.add(mesh);
mesh.position.x += 30;
var mesh1 = new THREE.Mesh(sphereGeom, material);
mesh1.position.x += 15;
mesh.add(mesh1);
var mesh2 = new THREE.Mesh(sphereGeom, material);
mesh2.position.y += 12;
mesh2.position.x += 9;
mesh.add(mesh2);
renderer.setClearColor(0xdddddd, 1);
var grp1 = mesh;
var curGrp;
var targPos;
var targLook;
var tmp = new THREE.Vector3();
var vbox;
function focusCam(targ) {
const boundingBox = new THREE.Box3();
boundingBox.setFromObject(targ)
var center = boundingBox.getCenter(new THREE.Vector3())
var sz = boundingBox.getSize(new THREE.Vector3());
var minZ = sz.length() + camera.near;
var lookOffset = new THREE.Vector3(0, 0, 1);
lookOffset.applyQuaternion(camera.quaternion);
lookOffset.multiplyScalar(minZ)
targLook = center.clone();
targPos = new THREE.Vector3().copy(center).add(lookOffset)
if (!vbox) {
vbox = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), material.clone())
vbox.material.transparent = true;
vbox.material.opacity = 0.15;
scene.add(vbox)
}
vbox.scale.copy(sz);
vbox.position.copy(center);
}
renderer.domElement.onclick = (evt) => {
if (!curGrp) {
curGrp = grp0;
} else if (curGrp === grp0) {
curGrp = grp1
} else if (curGrp === grp1) {
curGrp = grp0
}
focusCam(curGrp);
}
(function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
if (targPos) {
tmp.copy(targPos).sub(camera.position).multiplyScalar(0.01)
if (tmp.length() > 0.01) {
camera.position.add(tmp)
controls.target.add(tmp);//copy(targLook);
//camera.lookAt(targLook);
} else targPos = undefined;
}
})();
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
I'm trying to create a simple app where I can move a cube either left or right. I've got that part figured out. Now I'm trying to make it so that the cube cannot leave the plane. What's the easiest way to go about this?
Below is the code I currently have.
<html>
<head>
<title>Time Car</title>
<style>
body {
width: 100%;
height: 100%;
margin: 0;
padding 0;
}
</style>
</head>
<body>
<script src="js/three.min.js"></script>
<script src="js/Detector.js"></script>
<script>
var scene, camera, renderer, object, raycaster, board1, board2, board3, board4;
var vy = 0,
vx = 0,
direction = "",
gravity = 0.3;
function init() {
if (!Detector.webgl) Detector.addGetWebGLMessage();
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xCCFFFF);
document.body.appendChild(renderer.domElement);
var light = new THREE.DirectionalLight(0xffffff, 2);
light.position.set(1, 1, 1).normalize();
scene.add(light);
var light = new THREE.DirectionalLight(0xffffff);
light.position.set(-1, -1, -1).normalize();
scene.add(light);
var geometry = new THREE.BoxGeometry(20, 10, 10);
object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
color: 0x3333FF
}));
scene.add(object);
var geometry = new THREE.BoxGeometry(80, 160, 10);
board1 = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
color: 0xCC0000
}));
board1.position.set(0, 0, -200);
scene.add(board1);
var geometry = new THREE.BoxGeometry(80, 160, 10);
board2 = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
color: 0xCC0000
}));
board2.position.set(400, 0, -200);
scene.add(board2);
var geometry = new THREE.BoxGeometry(80, 160, 10);
board3 = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
color: 0xCC0000
}));
board3.position.set(800, 0, -200);
scene.add(board3);
var geometry = new THREE.BoxGeometry(80, 160, 10);
board4 = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
color: 0xCC0000
}));
board4.position.set(1200, 0, -200);
scene.add(board4);
geometry = new THREE.PlaneGeometry(5000, 800, 800);
var plane = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
color: 0x99FF66
}));
plane.rotation.x = -Math.PI / 2;
plane.position.set(0, -20, 0);
scene.add(plane);
camera.position.set(0, 100, 100);
raycaster = new THREE.Raycaster();
raycaster.ray.direction.set(0, -1, 0);
render();
}
function render() {
requestAnimationFrame(render);
if (direction == "left") {
vx = -2;
}
if (direction == "right") {
vx = 2;
}
object.position.x += vx;
vx = vx * 0.95;
camera.lookAt(object.position);
camera.position.x += (((object.position.x - 20) - camera.position.x)) * 0.03;
camera.position.y += (((object.position.y + 50) - camera.position.y));
renderer.render(scene, camera);
}
window.addEventListener('keydown', function (e) {
switch (e.keyCode) {
case 65: //left
direction = "left";
break;
break;
case 68: //right
direction = "right";
break;
};
}, false);
window.addEventListener('keyup', function (e) {
switch (e.keyCode) {
case 65: //left
direction = "";
break;
case 68: //right
direction = "";
break;
};
}, false);
init();
</script>
</body>
</html>
If I got you right. You set the plane as
geometry = new THREE.PlaneGeometry(5000, 800, 800);
...
plane.position.set(0, -20, 0);
Here you can compute its bounding box
plane.geometry.computeBoundingBox();
Its size along x-axis is 5000 and you don't translate it along this axis, so plane.geometry.boundingBox.min.x is -2500, plane.geometry.boundingBox.max.x is 2500. Then in your animation loop you can check the object's position. Something like this
object.position.x += vx;
var objHalfWidth = object.geometry.parameters.width / 2;
if (object.position.x + objHalfWidth >= plane.geometry.boundingBox.max.x){
object.position.x = plane.geometry.boundingBox.max.x - objHalfWidth;
}
if (object.position.x - objHalfWidth <= plane.geometry.boundingBox.min.x){
object.position.x = plane.geometry.boundingBox.min.x + objHalfWidth;
}
Screenshot 1
Screenshot 2
I have some models(STL) turn to pure black while other models get color(from lights) correctly.
As you can see in Screenshot 1 and 2,the fighter got a nice color but the miku is totally black. It seems no light illuminates her or she behaves like being applied with black BasicMaterial.
The 2 models are applied with PhongMaterial. And I tried several combnations of lights with different position/type/parameter but it didn't work.
I'm sure about that the model of miku is normal. I just downloaded the fighter and miku from the same website,where they both displays with color. I also have some models which turn to pure black in my scene ,though they're normal STL model.
There must be some mistakes in my scene.
Here is my code:
<script>
var renderer;
function initThree() {
width = document.getElementById('canvas-frame').clientWidth;
height = document.getElementById('canvas-frame').clientHeight;
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(width, height);
document.getElementById('canvas-frame').appendChild(renderer.domElement);
}
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
camera.position.x = 50;
camera.position.y = 0;
camera.position.z = 0;
camera.lookAt({
x: 0,
y: 0,
z: 0
});
}
var scene;
function initScene() {
scene = new THREE.Scene();
}
//光源1
var light;
function initLight() {
light = new THREE.DirectionalLight(0xfefff7, 2.0, 0);
light.position.set(0, 500, 0);
scene.add(light);
}
//光源2
var light2;
function initLight2() {
light2 = new THREE.DirectionalLight(0x414141, 1.0, 0);
light2.position.set(0, -500, 0);
scene.add(light2);
}
//此光源暂时无用
var light3;
function initLight3() {
light3 = new THREE.SpotLight(0xffffff, .7, 0);
light3.spotLight = new THREE.SpotLight(0xffffff, .7, 0);
light3.position.set(-700, 1000, 1000);
light3.castShadow = true;
scene.add(light3);
}
//此光源暂时无用
var light4;
function initLight4() {
light4 = new THREE.PointLight(0xffffff, 0.7, 0);
light4.position.set(3200, -3900, 3500);
light4.castShadow = true;
scene.add(light4);
}
//
//创造木星
var sphere;
function initSphere() {
var bitmap = new Image();
bitmap.src = 'img/jupiter.jpg';
var texture = THREE.ImageUtils.loadTexture(bitmap.src);
var material = new THREE.MeshPhongMaterial({
map: texture
});
var geometry = new THREE.SphereGeometry(50, 64, 64);
sphere = new THREE.Mesh(geometry, material);
sphere.position.set(0, 17, -120);
scene.add(sphere);
}
//加载模型文件
var loader = new THREE.STLLoader();
function initLoad() {
loader.addEventListener('load', function (event) {
var geometryOfFiles = event.content;
var materialOfFiles = new THREE.MeshPhongMaterial({
wrapAround: true,
wrapRGB: new THREE.Vector3(0, 1, 1),
color: 0xFFFFFF,
specular: 0xFFFFFF,
shading: THREE.SmoothShading,
shininess: 630,
fog: false,
side: THREE.DoubleSide
});
var object = new THREE.Mesh(geometryOfFiles, materialOfFiles);
object.position.set(0, 0, 0);
object.rotation.set(-Math.PI / 2, 0, Math.PI / 111);
object.scale.set(2, 2, 2);
scene.add(object);
});
loader.load('miku.stl');
}
var loader1 = new THREE.STLLoader();
function initLoad1() {
loader1.addEventListener('load', function (event) {
var geometryOfFiles = event.content;
var materialOfFiles = new THREE.MeshPhongMaterial({
wrapAround: true,
wrapRGB: new THREE.Vector3(0, 1, 1),
color: 0xFFFFFF,
specular: 0xFFFFFF,
shading: THREE.SmoothShading,
shininess: 230,
fog: false,
side: THREE.DoubleSide
});
var object = new THREE.Mesh(geometryOfFiles, materialOfFiles);
object.position.set(0, 20, 50);
object.rotation.set(-Math.PI / 2, 0, Math.PI / 111);
object.scale.set(0.5, 0.5, 0.5);
scene.add(object);
});
loader1.load('f35.stl');
}
var controls;
function setControl() {
controls = new THREE.TrackballControls(camera);
/*
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 5;
controls.panSpeed = 2;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = false;
controls.dynamicDampingFactor = 0.3;*/
}
function animate() {
controls.update();
renderer.clear();
renderer.render(scene, camera);
requestAnimationFrame(animate);
sphere.position.x -= 0.1;
sphere.rotation.y += -0.005;
}
function threeStart() {
initThree();
initCamera();
initScene();
initLight3();
initLight4();
initSphere();
setControl();
animate();
initLoad();
initLoad1();
}
</script>
seeing that adding an AmbientLight helped it will probably be normals calculated incorrectly or missing altogether check them here
geometry.faces[0].normal;
and to calculate the correct values call
geometry.computeFaceNormals()
To answer your question about how thingiverse shows them correct: after looking at their code I noticed they dont actually use those STL files they provide, but have a JSON file to load from (using THREE.JSONLoader)
these files have normals correct(they might have done some precalculations on files uploaded by users) but you would have to check the STL yourself
the miku JSON file can be found at http://thingiverse-production-new.s3.amazonaws.com/threejs_json/51/67/c8/34/6d/293cd764miku.js
don't know if computeFaceNormals() is outdated, but in my case i had to do:
const caseGeometry = useLoader(STLLoader, CaseObject)
caseGeometry.computeVertexNormals()
works like a charm, though 👍