ThreeJS: Remove object from scene - javascript

I'm using ThreeJS to develop a web application that displays a list of entities, each with corresponding "View" and "Hide" button; e.g. entityName View Hide. When user clicks View button, following function is called and entity drawn on screen successfully.
function loadOBJFile(objFile){
/* material of OBJ model */
var OBJMaterial = new THREE.MeshPhongMaterial({color: 0x8888ff});
var loader = new THREE.OBJLoader();
loader.load(objFile, function (object){
object.traverse (function (child){
if (child instanceof THREE.Mesh) {
child.material = OBJMaterial;
}
});
object.position.y = 0.1;
scene.add(object);
});
}
function addEntity(object) {
loadOBJFile(object.name);
}
And on clicking Hide button, following function is called:
function removeEntity(object){
scene.remove(object.name);
}
The problem is, entity is not removed from screen once loaded when Hide button is clicked. What can I do to make Hide button to work?
I did small experiment. I added scene.remove(object.name); right after scene.add(object); within addEntity function and as result, when "View" button clicked, no entity drawn (as expected) meaning that scene.remove(object.name); worked just fine within addEntity. But still I'm unable to figure out how to use it in removeEntity(object).
Also, I checked contents of scene.children and it shows: [object Object],[object Object],[object Object],[object Object],[object Object],[object Object]
Complete code: http://devplace.in/~harman/model_display1.php.html
Please ask, if more detail is needed. I tested with rev-59-dev and rev-60 of ThreeJS.
Thanks. :)

I think seeing your usage for addEntity and removeEntity code would be helpful, but my first thought is are you actually setting the object.name? Try in your loader just before scene.add(object); something like this:
object.name = "test_name";
scene.add(object);
What might be happening is the default "name" for an Object3D is "", so when you then call your removeEntity function it fails due to the scene objects name being ""
Also, I notice you pass in object.name to your loader? Is this where your storing the URL to the resource? If so, I would recommend using the Object3D's built in .userData method to store that information and keep the name field for scene identification purposes.
Edit: Response to newly added Code
First thing to note is it's not a great idea to have "/" in your object name, it seems to work fine but you never know if some algorithm will decide to escape that string and break your project.
Second item is now that I've seen your code, its actually straight forward whats going on. Your delete function is trying to delete by name, you need an Object3D to delete. Try this:
function removeEntity(object) {
var selectedObject = scene.getObjectByName(object.name);
scene.remove( selectedObject );
animate();
}
Here you see I lookup your Object3D in the Three.js Scene by passing in your object tag's name attribute.

clearScene: function() {
var objsToRemove = _.rest(scene.children, 1);
_.each(objsToRemove, function( object ) {
scene.remove(object);
});
},
this uses undescore.js to iterrate over all children (except the first) in a scene (it's part of code I use to clear a scene). just make sure you render the scene at least once after deleting, because otherwise the canvas does not change! There is no need for a "special" obj flag or anything like this.
Also you don't delete the object by name, just by the object itself, so calling
scene.remove(object);
instead of scene.remove(object.name);
can be enough
PS: _.each is a function of underscore.js

I came in late but after reading the answers more clarification needs to be said.
The remove function you wrote
function removeEntity(object) {
// scene.remove(); it expects as a parameter a THREE.Object3D and not a string
scene.remove(object.name); // you are giving it a string => it will not remove the object
}
A good practice to remove 3D objects from Three.js scenes
function removeObject3D(object3D) {
if (!(object3D instanceof THREE.Object3D)) return false;
// for better memory management and performance
if (object3D.geometry) object3D.geometry.dispose();
if (object3D.material) {
if (object3D.material instanceof Array) {
// for better memory management and performance
object3D.material.forEach(material => material.dispose());
} else {
// for better memory management and performance
object3D.material.dispose();
}
}
object3D.removeFromParent(); // the parent might be the scene or another Object3D, but it is sure to be removed this way
return true;
}

If your element is not directly on you scene go back to Parent to remove it
function removeEntity(object) {
var selectedObject = scene.getObjectByName(object.name);
selectedObject.parent.remove( selectedObject );
}

THIS WORKS GREAT - I tested it
so, please SET NAME for every object
give the name to the object upon creation
mesh.name = 'nameMeshObject';
and use this if you have to delete an object
delete3DOBJ('nameMeshObject');
function delete3DOBJ(objName){
var selectedObject = scene.getObjectByName(objName);
scene.remove( selectedObject );
animate();
}
open a new scene , add object
delete an object and create new

I started to save this as a function, and call it as needed for whatever reactions require it:
function Remove(){
while(scene.children.length > 0){
scene.remove(scene.children[0]);
}
}
Now you can call the Remove(); function where appropriate.

When you use : scene.remove(object);
The object is removed from the scene, but the collision with it is still enabled !
To remove also the collsion with the object, you can use that (for an array) :
objectsArray.splice(i, 1);
Example :
for (var i = 0; i < objectsArray.length; i++) {
//::: each object ::://
var object = objectsArray[i];
//::: remove all objects from the scene ::://
scene.remove(object);
//::: remove all objects from the array ::://
objectsArray.splice(i, 1);
}

I improve Ibrahim code for removeObject3D, added some checks for geometry or material
removeObject3D(object) {
if (!(object instanceof THREE.Object3D)) return false;
// for better memory management and performance
if (object.geometry) {
object.geometry.dispose();
}
if (object.material) {
if (object.material instanceof Array) {
// for better memory management and performance
object.material.forEach(material => material.dispose());
} else {
// for better memory management and performance
object.material.dispose();
}
}
if (object.parent) {
object.parent.remove(object);
}
// the parent might be the scene or another Object3D, but it is sure to be removed this way
return true;
}

this example might give you a different approach . I was trying to implement a similar feature in my project also with scene.remove(mesh). However disposal of geometry and material attributes of mesh worked for me!
source

Use
scene.remove(Object)
The object you want to remove from the scene

I had The same problem like you have. I try this code and it works just fine:
When you create your object put this object.is_ob = true
function loadOBJFile(objFile){
/* material of OBJ model */
var OBJMaterial = new THREE.MeshPhongMaterial({color: 0x8888ff});
var loader = new THREE.OBJLoader();
loader.load(objFile, function (object){
object.traverse (function (child){
if (child instanceof THREE.Mesh) {
child.material = OBJMaterial;
}
});
object.position.y = 0.1;
// add this code
object.is_ob = true;
scene.add(object);
});
}
function addEntity(object) {
loadOBJFile(object.name);
}
And then then you delete your object try this code:
function removeEntity(object){
var obj, i;
for ( i = scene.children.length - 1; i >= 0 ; i -- ) {
obj = scene.children[ i ];
if ( obj.is_ob) {
scene.remove(obj);
}
}
}
Try that and tell me if that works, it seems that three js doesn't recognize the object after added to the scene. But with this trick it works.

You can use this
function removeEntity(object) {
var scene = document.querySelectorAll("scene"); //clear the objects from the scene
for (var i = 0; i < scene.length; i++) { //loop through to get all object in the scene
var scene =document.getElementById("scene");
scene.removeChild(scene.childNodes[0]); //remove all specified objects
}

Related

Javascript Nesting Doll Problem - How Dangerous Is It?

I ran into an issue in my current project where I needed to access the array that held an object, as the event listener that an object inside that object needed to access all of the elements of the array. So, I did a little test and discovered for myself that you can, in fact, store the container array that contains an object inside that object and it will actually reference the original array, not a copy.
There are some fairly obvious problems with this in terms of being maintainable code, but I'm curious as to what the potential harm of doing this is. Is there anything I should know regarding this recursive property scenario before I put this in my project? Is there a better way to do what I'm looking to do?
For clarity: the way I'm planning on setting it up is as follows:
The array (linesArray) contains a series of objects of class GraphLine. Each Graphline object (lineObject) contains a Raphael canvas element object (line) for the purposes of formatting that element post drawing. It would also contain a reference property to linesArray (container)
I am planning on having the lineObject.line.mousedown() [which fires on clicking the line] event run a for loop through lineObject.container, aka linesArray, to transform each of the lineObject.line's within it based off of which lineObject.line fired the lineObject.line.mousedown() event.
It would look something like this:
class GraphLine {
id;
line;
lineStr;
container;
constructor(container, /* Bunch of Inputs */) {
this.container = container;
//...
}
Draw() {
this.line = canvas.path(this.lineStr);
this.line.mousedown( function() {
for(let i = 0; i < this.container.length;i i++) {
if(this.container[i].id != this.id) {
//Do A Thing
}
else {
//Do A Different Thing
}
}
});
}
}
var canvas;
$(document).ready(function() {
function Run() {
var container = [];
canvas = new Raphael($('#canvas'), 100, 100);
container.push(new GraphLine(container, /* Bunch of Inputs */));
//...
container[0].Draw();
}
Run();
});

How to pass a parameter to create a variable in js?

I am trying to work with javascript, classes, and threejs to make a project. I have an idea to have a singular function create a new threejs object, but not sure how to implement it.
Let me go a bit more in detail. I have a class that creates a cube using threejs:
class Cube {
constructor(geometry, material, /*position variables*/, scene) {
this.geometry = geometry;
this.material = material;
/* this.positions */
}
createMesh() {
//creates a mesh for the cube
this.mesh = new THREE.Mesh( this.geometry, this.materials );
}
addToScene() {
this.scene.add( this.mesh );
/* changes position with variables */
}
}
This class works nicely, but I am wanting to create a new function that makes a unique cube, and with a custom variable. I want to create something like this:
var createCube = function ( name, geometry, material, /*position*/, scene) {
name = new Cube(geometry, material, /*position*/, scene;
}
Then after I use this function, I can use the name in the parameter as such:
createCube( customName, cubeGeometry, cubeMaterial, /*position*/, threeScene);
customName.createMesh();
customName.addToScene();
I realize I can always hard code the name for the cube, however, I will be passing it through loops to create multiple.
I gave a lot of explanation for a likely simple solution, but I hope this helps give a picture of what I am trying to do. If you need more explanation, I am open to do so.
I'm not entirely sure I understand the problem here. I might be missing something.
If a Cube needs to be identified by name, could you add it as a property of the Cube?
class Cube {
constructor(name, /* other properties */) {
this.name = name;
/* other properties */
}
createMesh() {
}
addToScene() {
}
}
const names = ['cube1', 'cube2', 'cube3'];
const cubes = names.map(name => new Cube(name, geometry, material, /*position*/, scene));
cubes.forEach(cube => {
console.log(`cube: ${cube.name}`);
cube.createMesh();
cube.addToScene();
});
You can't do that, JS does not allow passing references as method arguments.
What you want to eventually achieve is unclear to me, here is my attempt to have custom named cubes on a cubesObject.
const cubesObject = {};
for (const name of ['a', 'b', 'c']) {
cubesObject[name] = new Cube(geometry, material, /* position */, scene);
}
console.log(cubesObject.a, cubesObject.b, cubesObject.c)
A better way to do this dynamically is to create an array and push an object to it. I got a similar output when working with node applications.
var cubesArray = []
function createCube(name, geometry, material, /*position*/, scene) {
let thisCube = new Cube(geometry, material, /*position*/, scene);
cubesArray[name] = thisCube;
}
Now we can do all the functions and access properties with: cubesArray[name].property or cubesArray[name].function()

Javascript: run object method on DOM object selected through other property

I am very new to javascript.
Here I am failing to run an object method on a DOM element that I selected through another property of the same object. I suspect there is something wrong with my thinking!
Thanks in advance for any piece of help.
var Arrow = function() {
this.current = $('.arrow');
this.previous = null;
this.bend = function() {
// do bend
};
};
var arrow = new Arrow();
arrow.current.bend();
bend() is a method of Arrow, not current. Use arrow.bend() and it will also have access to current using this.current.
arrow.current.bend is not defined.
You have defined:
this.current as the Array of DOM elements.
this.bend as method with a function.
Hence, you can call:
arrow.current >> returns Array of DOMs
arrow.bend() >> executes function bend.
arrow.current.bend() does not exist.
Also, note that arrow.current is an array. You'd first need to get each of the elements:
for (element of arrow.current) { element.bend(); }
However, as said before, element does not have a bend element by default and you have not appended at any point. Only arrow has a bend property.
I hope this guides you on why this does not work.
However, if you want to open a question on what you are trying to achieve, maybe we can help to get it fixed.
You need to call bend() on arrow object. In bend() function, you do what you need to do.
var Arrow = function() {
this.current = $('.arrow');
this.previous = null;
this.bend = function() {
// do bend
current.style = 'bent';
};
};
var arrow = new Arrow();
arrow.bend();
So two things.
You called the right method on the wrong object
arrow.bend(); // not arrow.current.bend()
The second possible problem is with this.current = $('.arrow');. To get the an element from the DOM, you should make sure it's totally loaded. I'd suggest the following
var Arrow = function($arrow) {
this.current = $arrow;
this.previous = null;
};
// To avoid creating the `bend` in every instance of Arrow
Arrow.prototype.bend = function() {
console.log(this.current.attr('id'));
};
$(function () {
// Now it's certain that the DOM is completely loaded
var arrow = new Arrow($('.arrow').first());
arrow.bend();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="arrow" id="toto">arrow<div>

How do I swap an OBJ object for another in Three.JS?

I am using ThreeJS to load OBJs into a webpage, which I have done succesfully, but now I want to add buttons to my page that will swap out the displayed OBJ file for a different one. I have attempted to name the object when loading it:
object.name = "selectedObject";
so that I can remove it from the scene when the new button is clicked
scene.remove( selectedObject );
and attach the new object:
scene.add(newobject);
But I am getting lost in how to implement this into the general code/what the correct syntax would be.
Here's the code for loading the model:
var objectloading = 'obj/male02/new.obj';
var loader = new THREE.OBJLoader( manager );
loader.load( objectloading, function ( object ) {
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material.map = texture;
}
} );
object.position.y = -30;
scene.add( object );
}, onProgress, onError );
Any help is apreciated, thanks!
Well there's many ways to go about it. What it comes down to is that your code needs to account for what has been loaded. You can traverse a scene, but capturing and listing your obj files into specific lists on your own is much cleaner, especially later when implementing things like raycasting.
I would write some functions, perhaps even a class if I planned on supporting other mesh types in the future and wanted a single interface. Importantly, you do not pass the mesh name as the parameter to scene.add and scene.remove, you pass a reference to the actual object. Three.js does this so that it can nullify the parent and call the object "removed" dispatch event in the Three.js library.
So for example purposes, one way is to store your objects in a hash, and use the url to the mesh as the parameter for adding and removal.
var meshes = {};
addObj = function(url){
var objectloading = url;
var loader = new THREE.OBJLoader( manager );
loader.load( objectloading, function ( object ) {
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material.map = texture;
}
} );
object.position.y = -30;
meshes[url] = object;
scene.add(object);
}, onProgress, onError );
}
removeObj = function(url){
scene.remove(meshes[url]);
delete meshes[url];
}
for example, toggle between two models,
in a button click:
removeObj('obj/male02/old.obj');
addObj('obj/male02/new.obj');
and then in another button click:
removeObj('obj/male02/new.obj');
addObj('obj/male02/old.obj');
Although any String can be used for member names in "meshes," using urls could become problematic if the application grows and the url is actually a service uri utilizing a POST request, then you'll need another layer of references, possibly and usually using the UUID give to each object added to Three.js.

Javascript / Why mycode is not synchronous

I have discover that a custom code is not syncronous as I thought.
I have this pseudo code:
ObjectA = function ()
{
var pointer;
var value =[];
this.set_pointer = function (p) {pointer = p;}
this.return_value = function () {return value[pointer];}
}
ObjectB = function ()
{
var SCOPE = this;
var OBJ = new ObjectA();
....
this.reset = function ()
{ OBJ.set_pointer(0);}
this.draw = function (what)
{
SCOPE.update();
OBJ.set_pointer(from);
OBJ.get_value();
// do somethings with Three.js
// draw some lines and some little pointclouds.
// do some things
// update two text elements
}
}
Main = new ObjectB();
Main.draw(7);
Main.reset();
ObjectA is using arraybuffers, dataviews and typedarrays.
ObjectB is using Three.js to draw some very symple 3D things.
The problem is inside 'draw'.
OBJ.get_value(); is using the pointer value 0 (zero) instead 7.
Abnormally (as I think) main.draw(7) is not executed first and later Main.reset(); It seems that Main.reset() is inmediatelly executed, so I have 0 (zero)
I'm not going to wait any DOM synchronism.
What can be the reason of this bechaviour?. Maybe the Three,js use ? The OOP style I'm using ?
Is there any way to check why is this happen?
Any idea would be appreciated
NOTE: Sorry for use the Three.js tag.
SOLVED
I have found the reason (or I think ) of a NO synchonism
Sometimes 'this' could be pointing to 'window' instead of the self instance of your object.
Sometimes, a bad use of this (when it is 'window') can raise an error you have not taken into account. Then a next line of code can be executed, and sometimes you can have the impression of an incorrect (not syncronous) operation.
So.... review the bad use of 'this'....

Categories