Three.js collision detection with different sizes - javascript

I have made a collision detection. You can place objects at the raycaster/mouse position on a floor. Therefore you need to click on a button 'Add object', then you get a object(helper) that follows the mouse to see if the new object get a collision with another object. When you click on the position you want, the new object will be placed to the world if there is no collision.
The collision detection I have made works perfectly when the object that is already placed in the world has the same size as the helper/new object.
On the next screenshot you can see a big object and a small(red) helper. The color red means that there is a collision. When I move the mouse more to the right it turns green.
Why does my collision detection work only with 2 objects that have the same size and why doesn't it not with different ones?
Here is my code in the Click event to show the big object:
var geometry = new THREE.BoxGeometry(200, 200, 300);
var bigobject = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
color: 0xFBF5D7,
opacity: 1
}));
bigobject.position.copy(intersects[0].point);
bigobject.position.y = 100;
objects.push(bigobject);
scene.add(bigobject);
Here is my code to show the helper when the button 'Add object' is clicked:
var geometry = new THREE.BoxGeometry( 50, 50, 100 );
helper = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: 0x00ff00, opacity: 1 } ) );
helper.name = 'helper';
scene.add( helper );
Here is my code in the MouseMove event to detect the collision:
if(scene.getObjectByName( 'helper' )) {
helper.position.copy( intersects[ 0 ].point );
helper.position.y = 25;
var helperWidth = helper.geometry.parameters.width;
var helperLength = helper.geometry.parameters.depth;
var validpositionObject = true;
for (var i = 0; i < objects.length; i++) {
var objectWidth = objects[i].geometry.parameters.width;
var objectLength = objects[i].geometry.parameters.depth;
// MIN X
var helperMinX = helper.position.x;
var objectMinX = objects[i].position.x;
// MAX X
var helperMaxX = helperWidth + helper.position.x;
var objectMaxX = objectWidth + objects[i].position.x;
// MIN Z
var helperMinZ = helper.position.z;
var objectMinZ = objects[i].position.z;
// MAX Z
var helperMaxZ = helperLength + helper.position.z;
var objectMaxZ = objectLength + objects[i].position.z;
if (objectMinX <= helperMaxX && objectMaxX >= helperMinX && objectMinZ <= helperMaxZ && objectMaxZ >= helperMinZ) {
validpositionObject = false;
}
}
if ( validpositionObject === true ) {
helper.material.color.setHex( 0x00ff00 );
validposition = true;
}else{
helper.material.color.setHex( 0xff0000 );
validposition = false;
}
}
What goes wrong with the position when it is a big and a small object.. Can anyone help me in the right direction? Many thanks

Use this:
function collisonXZ(o1, o2) {
if (Math.abs(o1.position.x - o2.position.x) > (o1.geometry.parameters.width + o2.geometry.parameters.width) / 2)
return false;
if (Math.abs(o1.position.z - o2.position.z) > (o1.geometry.parameters.depth + o2.geometry.parameters.depth) / 2)
return false;
return true;
}
var validpositionObject = true;
for (var i = 0; i < objects.length; i++) {
if (collisionXZ(helper, objects[i])) {
validpositionObject = false;
break;
}
}

Related

Lil-Gui not updating ui correctly when value is changed in code

I am building an app in three.js, and basically the point is to have rows and columns of individual blocks that also extend upwards. When you click on a block it will change colour, so I have implemented clipping along all three axes, so that the middle blocks can be reached as well. I have also added a Gui using lil-gui, and that is where the problem comes in. I have controls for the amount of rows, columns and the height, as well as for how much it should clip in each direction. When initialized originally, the rows, columns and height are all set to 1, and if you increase any of them from there, the amount of blocks will correctly update in the specified direction.
My problem comes in when I load a previous save. The previous state is saved to localStorage, in a function that runs every few minutes, and saving also occurs with ctrl + s. It converts my current state, a 3 dimensional array, into a much simpler object, which is also a 3 dimensional object. It saves correctly, I have logged the output multiple times. When it loads, I convert the string into a JSON object, which I then convert into a state object, and I then render that state object. The strangest thing is that it only displays roughly half of what it should in all 3 directions, E.G A 3x3x3 grid is rendered as a 2x2x2 grid, but as soon as I click on rows, columns or height in my GUI, and click out again, it magically renders properly. What is also odd is that although all of my GUI fields have a min, a max and a step, they are unconstrained in the gui.
I have simplified my code so that it only deals with rows, as an mcve but the problem scales up as well.
The lil-gui panel code
function setUpPanel(){
const panel = new GUI( {width:310} );
const folder1 = panel.addFolder( "Size" );
const folder2 = panel.addFolder( "X Clipping" );
settings = {
'rows' : rows,
planeX: {
blocks: rows,
},
};
rowsObj = folder1.add(settings, 'rows').min(1).step(1).max(50).setValue( rows ).onFinishChange( function ( size ) {
rowsElement.max(size);
rowsElement.setValue(size);
rowsElement.updateDisplay();
rows = size;
redraw();
} );
rowsElement = folder2.add(settings.planeX, 'blocks',1, rows, 1).onChange( setClippingPlaneRows );
}
The code called when the app is started
import * as THREE from './js/three.module.js';
import { GUI } from './js/lil-gui.module.min.js';
import { OrbitControls } from './js/OrbitControls.js';
const blockOffset = 0.1;
const blockSize = 1;
const BlockStates = {
Empty: Symbol('e'),
}
const clippingPlanes = [
new THREE.Plane( new THREE.Vector3( -1, 0, 0 ), 0, {name: "plane"} ),
];
//gui object
let rowsObj
//more gui object
let rowsElement
const emptyMaterial = new THREE.MeshStandardMaterial( {color: 0xffffff, opacity: 0.5, transparent: true} );
emptyMaterial.clippingPlanes = clippingPlanes;
let rows=1;
let state = [];
let boxes = new THREE.Group();
let settings;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight , 0.1, 1000 );
camera.position.set(-1.5,2.5,3);
const renderer = new THREE.WebGLRenderer();
renderer.localClippingEnabled = true;
renderer.setSize( window.innerWidth, window.innerHeight);
document.body.appendChild( renderer.domElement );
var autoSaveID = setInterval(function() {
localStorage.lastSave = JSON.stringify(createJsonObjectFromState());
localStorage.rows = rows;
}, 300000);
if(localStorage.lastSave){
let loadSave = confirm("There is a previous save detected. Would you like to load it?");
if(loadSave){
const loadedObj = createStateObjectFromJsonString(localStorage.lastSave);
state = loadedObj.state;
boxes = loadedObj.boxes;
rows = localStorage.rows;
boxes.position.set(-(rows * (blockSize + blockOffset)- blockSize - blockOffset)/2,0, 0);
}
}
const light = new THREE.AmbientLight( 0x404040, 10);
const controls = new OrbitControls(camera, renderer.domElement );
setUpPanel();
drawBoxes();
boxes.position.set(0,0,0);
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.key === 's') {
// Prevent the Save dialog from opening
e.preventDefault();
saveCurrent();
}
});
scene.add( boxes );
scene.add( light );
renderer.render(scene, camera);
The save function
function saveCurrent(){
let stringVersion = JSON.stringify(createJsonObjectFromState());
//save to local storage
if(localStorage.lastSave){
let writeSave = confirm("There is a previous save detected. Would you like to overwrite it?");
if(writeSave){
localStorage.lastSave = stringVersion;
localStorage.rows = rows;
}
}
else{
localStorage.lastSave = stringVersion;
localStorage.rows = rows;
}
}
createStateObjectFromJsonString
function createStateObjectFromJsonString(jsonString){
let newObj =[];
let newBoxes=new THREE.Group();
let jsonObj = JSON.parse(jsonString);
for(let i = 0; i < jsonObj.length; i++){
let box;
switch(jsonObj[i]){
case "E":
box = drawBox(BlockStates.Empty);
box.position.set(i*(blockSize + blockOffset ), 0, 0);
newBoxes.add(box);
newObj.push({state: BlockStates.Empty, item:box});
break;
}
}
return {state: newObj, boxes: newBoxes};
}
createJsonObjectFromState
function createJsonObjectFromState(){
const JSONObj = [];
for(let i = 0; i < rows; i++){
switch(state[i].state){
case BlockStates.Empty:
JSONObj.push("E");
break;
}
}
return JSONObj;
}
The function to set the clipping to a specific amount of blocks
function setClippingPlaneRows(amt){
clippingPlanes[0].constant = rows%2 === 0 ? (blockSize + blockOffset) * (amt - Math.floor(rows/2) ) : (blockSize + blockOffset) *( amt - Math.floor(rows/2) - 0.5);
}
The redraw function
function redraw(){
drawBoxes();
}
The drawBox function
function drawBox(state){
const geometry = new THREE.BoxGeometry(blockSize, blockSize, blockSize);
let material = emptyMaterial;
return new THREE.Mesh( geometry, material );
}
The drawBoxes function
function drawBoxes(){
if(state.length === 0)
{
boxes.clear();
state = [];
for(let i = 0; i < rows; i++){
const box = drawBox(BlockStates.Empty);
box.position.set(i*(blockSize + blockOffset ), 0, 0);
boxes.add(box);
state.push({state : BlockStates.Empty, item: box});
}
}
else{
if(state.length <= rows){
//just draw in the extra rows
for(let i = state.length; i < rows; i++){
const box = drawBox(BlockStates.Empty);
box.position.set(i*(blockSize + blockOffset ), 0, 0);
boxes.add(box);
state.push({state : BlockStates.Empty, item: box});
}
}
else{
//give a warning, and if accepted remove neccesary rows
let accepted = confirm("Making the rows smaller could remove some of the work you have done. Are you sure?");
if(accepted){
for(let i = 0; i < state.length - rows; i++){
const box = state[state.length - 1].item;
boxes.remove(box);
state.pop();
}
}
}
}
//plane initial positions
boxes.position.set(-(rows * (blockSize + blockOffset)- blockSize - blockOffset)/2, 0, 0);
setClippingPlaneRows(rows);
}
The animation part
function animate() {
let frame = requestAnimationFrame( animate );
renderer.render( scene, camera );
}
animate();
I apologize for posting so much code, I just have no idea where the problem is stemming from. I have tried many things, such as checking the size of the rows, columns, etc at many points, but they are always correct. I have tried using setValue() to force the gui to call the onChange function, which hasn't done anything. I have tried many other variations too, but no luck.
Edit
I have discovered while playing around that if I set rows to a number other than 1 in the code itself, such as 7, and don't load a saved file, a very similar problem occurs. Only about half of the blocks are displaying, but this time the gui behaves itself and remains constrained.

Three.js - Extrude face along its normal vector

I want to extrude faces from a THREE.Geometry object, so my approach was to:
- specify the faces to extrude
- extract vertices on the outer edges
- Draw a THREE.Shape with those vertices
- Extrude it using THREE.ExtrudeGeometry (I'm actually using a modified version which is not generating front faces, as I'm cloning them by myself to keep original topology).
It seems to work having a CircleGeometry on a XY plane, so extruding on Z direction.
but if I rotate the circle a bit this is what I get. It extrudes on Z World Axes, not following its local orientation
I can I solve this? Here's the function I'm using:
function faceExtrude( geometry, face_group, amount ) {
if ( geometry.type === "undefined" ) {
console.log( "faceextrude error" );
return;
}
//convert face_group to vertex_group
var vertex_group = [];
for ( var i = 0; i < face_group.length; i++ ) {
//get vertices ID
var f = face_group[i];
var a = f.a;
var b = f.b;
var c = f.c;
//clone vertices
var v1 = geometry.vertices[a].clone();
var v2 = geometry.vertices[b].clone();
var v3 = geometry.vertices[c].clone();
//add them to an array
vertex_group.push(v1, v2, v3);
}
// Side Faces
var vertsToExtrude = [];
//Get only verts in outer edge
for ( var v = 0; v < vertex_group.length; v++ ) {
var shareCount = 0;
//count their presence in each face
for ( var f = 0; f < face_group.length; f++ ) {
if( v == face_group[ f ].a ||
v == face_group[ f ].b ||
v == face_group[ f ].c )
{
shareCount++;
}
}
//if < 4 than it's in an outer edge. Add it to an array
if ( shareCount < 4) {
vertsToExtrude.push( vertex_group[ v ].clone() );
}
}
//make a shape with them
var shape = new THREE.Shape();
shape.moveTo( vertsToExtrude[ 0 ].x, vertsToExtrude[ 0 ].y );
for ( var i = 1; i < vertsToExtrude.length; i++ ){
shape.lineTo( vertsToExtrude[ i ].x, vertsToExtrude[ i ].y );
}
shape.lineTo( vertsToExtrude[ 0 ].x, vertsToExtrude[ 0 ].y );
//extrude it
var extrudeSettings = { amount: amount, steps: 2, bevelEnabled: false };
var sideGeo = new THREE.MyExtrudeGeometry( shape, extrudeSettings );
//Front Faces
var frontGeo = new THREE.Geometry();
for ( var i = 0; i < face_group.length; i++ ) {
//get vertices ID
var f = face_group[i];
var a = f.a;
var b = f.b;
var c = f.c;
//new vertices IDs
var new_a = frontGeo.vertices.length;
var new_b = frontGeo.vertices.length + 1;
var new_c = frontGeo.vertices.length + 2;
//clone vertices
var v1 = geometry.vertices[a].clone();
var v2 = geometry.vertices[b].clone();
var v3 = geometry.vertices[c].clone();
frontGeo.vertices.push(v1, v2, v3);
//translate them depending on "amount"
v1.z = v1.z + amount;
v2.z = v2.z + amount;
v3.z = v3.z + amount;
//add them to a face
var f = new THREE.Face3(new_a, new_b, new_c);
frontGeo.faces.push(f);
}
frontGeo.computeFaceNormals();
geometry.merge( sideGeo );
geometry.merge( frontGeo );
};
I'm blind. Missed this while reading docs about ExtrudeGeometry:
"extrudePath — THREE.CurvePath. 3d spline path to extrude shape along. (creates Frames if (frames aren't defined)"
Think this is the solution I'm looking for :)

three.js memory leak .dispose() not working / wrong usage

Good day!
Have issue with memory handling. I read lots of forums but still can't find whats wrong with my code.
I'm working on project where I combine d3.js with three.js to visualize nodes like planets on orbits in space.
I have a lot of data - like 8K planets in 8+ orbits. But when I try to load new data - I can't destroy current tree without memory leak.
I would be grateful for any help! Here is part of code where I create planets and where I try to destroy them:
function initTree(root) {
var start, end;
var nodes = tree.nodes(root); //this is d3.js tree init
var depth = getDepth(root);
var first_x_offset = nodes[0].x;
if (isNaN(first_x_offset)) {first_x_offset = 0}
//create orbits
var orbitTexture = new THREE.ImageUtils.loadTexture('img/orbit_texture.png');
var orbitMaterial = new THREE.MeshBasicMaterial({map: orbitTexture, transparent:true, side: THREE.DoubleSide, alphaTest: 0.05, opacity:0.3});
var sphereGeometry = new THREE.SphereGeometry(1, 6, 6);
var orbitSize = 30;
for (var k=1; k<depth; k++) {
var orbit = new THREE.Mesh(new THREE.CircleGeometry(orbitSize*k, 64), orbitMaterial);
orbit.rotation.x = -90*Math.PI/180;
orbit.name = 'orbit';
scene.add(orbit);
}
//end orbits
//camera position
camera.position.x = 0;
camera.position.y = 70;
camera.position.z = -orbitSize*depth-100;
controls.target.x = 0;
controls.target.y = 0;
controls.target.z = 0;
camera.up.x = 0;
camera.up.y = 1;
camera.up.z = 0;
//this is parent object to place in center
var parent = new THREE.Object3D();
parent.name = 'parent';
scene.add(parent);
y=0;
spheres = {};
objects = [];
nodes.forEach(function(d) {
if (d.type == 'BLANK') {return}
d.x = d.x - first_x_offset;
if (isNaN(d.x)) {d.x = 0}
var sphereMaterial = new THREE.MeshLambertMaterial({color: 0xdddddd, wireframe: false, opacity: 0.7, transparent: true});
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial );
sphere.material.color.setHex(color_type[d.type]);
sphere.castShadow = false; //maybe change to true
sphere.id2 = y;
d.id2 = y;
sphere.d = d;
sphere.scale.x = radius_type[d.type];
sphere.scale.y = radius_type[d.type];
sphere.scale.z = radius_type[d.type];
sphere.name = 'sphere';
spheres[y] = sphere;
//count items of each type
count_type[d.type]++;
//how many nodes in tree
y++;
//create pivot
var pivot = new THREE.Object3D;
//rotate it
pivot.rotation.y = d.x*Math.PI/180-90;
//append to parent
pivot.name = 'pivot';
parent.add(pivot);
//add mesh to pivot
var default_distance = size/(depth-1);
if (d.y > 0) {
d.y = (Math.round(d.y/default_distance)) * (orbitSize-8.8);
}
sphere.position.x = d.y;
sphere.position.y = 0; //should be 0!
sphere.position.z = d.y;
objects.push(sphere);
pivot.add(sphere);
});
nodesLength = y;
render();
$('.loading').fadeOut(500);
if (!animationId) {
animate();
}
temp = null;
nodes = null;
}
So I'm adding spheres to parent Object3D and then add it to scene.
And here is destroy function:
function destroyTree() {
//spheres
//console.log(renderer.info);
var to_delete = [];
for (var i=0; i<spheres.length; i++) {
scene.remove(spheres[i]);
spheres[i].material.dispose();
spheres[i].geometry.dispose();
}
for (var i=0; i<spheres.length; i++) {
spheres[i] = undefined;
}
spheres = {};
for (var i=0; i<objects.length; i++) {
scene.remove(objects[i]);
}
for (var i=0; i<objects.length; i++) {
objects[i] = undefined;
}
objects = [];
var parent = scene.getObjectByName('parent');
scene.remove(parent);
if (links.length) {
for (var i=0; i<links.length; i++) {
scene.remove(links[i]);
}
}
links = [];
scene.traverse(function (child) {
if (child instanceof THREE.Mesh) {
if (child.name.length) {
to_delete.push(child);
}
}
});
for (var i=0; i<to_delete.length; i++) {
scene.remove(to_delete[i]);
to_delete[i].geometry.dispose();
to_delete[i].material.dispose();
to_delete[i] = undefined;
}
to_delete = [];
}
Wouldn't traversing the scene find the spheres too? And in that loop you could dispose directly without need for the to_delete array. If all speres are not children of the scene then maybe reconsider when to create them? These would just be optimizations and probably do little other then clarify where it might be leaking.
Then again maybe try holding an array of textures and releasing those directly?
Wait here it is, this link says to remove the objects and textures from the renderer as well.
Memory leak in Three.js
renderer.deallocateObject
renderer.deallocateTexture

Three.js ray picking face segments of plane / mesh v69

As quads faces are no longer supported in three.js. I am having trouble Raycast picking segments between 4 vertices points as a square as they are now made up of triangle faces.
So my picking returns triangle shape not square:
var faces = 10;
var myplane = new THREE.Mesh(
new THREE.PlaneGeometry(1000, 1000, faces, faces),
new THREE.MeshNormalMaterial({
color: 0x993333,
wireframe: false
}));
objects.push(myplane);
scene.add(myplane);
//raycast code
if ( intersects.length > 0 ) {
_controls.noRotate = true;
var face = intersects[0].face;
var temp = intersects[0].object;
var geo = temp.geometry;
geo.vertices[face.a].z -= 20;
geo.vertices[face.b].z -= 20;
geo.vertices[face.c].z -= 20;
//want other face verts below basically
geo.vertices[face.d].z -= 20;
geo.vertices[face.e].z -= 20;
geo.vertices[face.f].z -= 20;
geo.verticesNeedUpdate = true;
if(intersects[ 0 ].faceIndex % 2 == 0){
// geo.vertices[intersects[0].faceIndex + 1].z = 40;
// console.log("Even face Index: " + intersects[ 0 ].faceIndex);
}else{
// console.log("Odd face Index: " + intersects[ 0 ].faceIndex -1);
//geo.vertices[intersects[0].faceIndex - 1].z = 40, of course doest work
}
}
Below is a 2d example for a better vsual...
Currently I get the red out-come , but I'm wanting to pick like the green example:
try this code to get intersection faces
if ( intersects.length > 0 )
{
var faceIndex = intersects[0].faceIndex;
var obj = intersects[0].object;
var geom = obj.geometry;
var faces = obj.geometry.faces;
var facesIndices = ["a","b","c"];
facesIndices.forEach(function(indices){
geom.vertices[faces[faceIndex][indices]].setZ(-10);
});
if(faceIndex%2 == 0){
faceIndex = faceIndex+1;
}else{
faceIndex = faceIndex-1;
}
facesIndices.forEach(function(indices){
geom.vertices[faces[faceIndex][indices]].setZ(-10);
});
geom.verticesNeedUpdate = true;
}
In the general case, getting and changing the geometry of a raycast to mesh triangle intersection is just:
var face = intersects[0].face;
var obj = intersects[0].object;
var geom = obj.geometry;
var vertA = geom.vertices[face.a];
var vertB = geom.vertices[face.b];
var vertC = geom.vertices[face.c];
vertA.setY(100);
vertB.setY(110);
vertC.setY(105);
geom.verticesNeedUpdate = true;
No need to iterate through the faces array, or build a contrived face vertex array just to loop through it in the next step.

Loading JSON Animating Model using tQuery [Three.js]

I am learning to load a JSON format and manipulate it using tQuery. I did successfully load the animating model, but it did not load the textures, as it loads the pitch-black model animating in screen. The model is perfectly fine, as it is the example model used in Three.js, named stork.js.
This is my code.
<script>
var world=tQuery.createWorld().boilerplate().start();
world.removeCameraControls();
// Animation Variables
var animOffset = 0, // starting frame of animation
duration = 1000, // milliseconds to complete animation
keyframes = 12, // total number of animation frames = SEE THE FILE OF MODEL!
interpolation = duration / keyframes, // milliseconds per frame
lastKeyframe = 0, // previous keyframe
currentKeyframe = 0,
bird,bird_material;
var jsonLoader = new THREE.JSONLoader();
jsonLoader.load("models/stork.js",
function(geometry, materials){
if(geometry.morphColors && geometry.morphColors.length) {
var colorMap = geometry.morphColors[0];
for (var i = 0; i < colorMap.colors.length; i ++) {
geometry.faces[i].color = colorMap.colors[i];
geometry.faces[i].color.offsetHSL( 0, 0.3, 0 );
}
}
geometry.computeMorphNormals();
addModelToScene(geometry, materials);
});
function addModelToScene(geometry, materials){
for (var i = 0; i < materials.length; i++) {
materials[i].morphTargets = true;
}
bird_material = new THREE.MeshPhongMaterial({
color: 0xffffff, morphTargets: true, morphNormals: true,
vertexColors: THREE.FaceColors, shading: THREE.SmoothShading
});
bird = new THREE.Mesh( geometry,bird_material );
bird.scale.set(0.02,0.02,0.02);
bird.position.set(0,0,0);
bird.rotation.set(0,3.9,0);
bird.castShadow = true;
var object3DNew = new tQuery.Object3D();
object3DNew.add(bird).addTo(world);
}
world.hook(function(){
if(bird){
var time = new Date().getTime() % duration;
var keyframe = Math.floor(time / interpolation) + animOffset;
if (keyframe != currentKeyframe){
bird.morphTargetInfluences[lastKeyframe] = 0;
bird.morphTargetInfluences[currentKeyframe] = 1;
bird.morphTargetInfluences[keyframe] = 0;
lastKeyframe = currentKeyframe;
currentKeyframe = keyframe;
}
bird.morphTargetInfluences[keyframe] = (time % interpolation) / interpolation;
bird.morphTargetInfluences[lastKeyframe] = 1 - bird.morphTargetInfluences[keyframe];
}
});
</script>
This is the result I get:
It was really stupid of me, Thankyou Jerome Etienne for the answer. The answer is, add the light to the scene.

Categories