your step 4 is wrong. Read the code again you didn’t got it.
@seanwasere you are not even trying to understand at this point. You didn’t even read the code I posted on codesandbox… why even reply.
Maybe your problem is not with Threejs, but with nested JavaScript arrays.
Yeah, sure.
I’m glad you have a solution that you like.
Nevertheless, I do think you try to over-optimize issues which might not need optimization. If I were you, I would first try a simple solution, like traversing the scene’s tree (top-down) in the beginning of rendering only if there are added/removed objects. Traversing is not that bad – it is already done on every frame independent on whether you want it or not. Also, this will be done only if there are changes.
I hope you do not add/remove objects every single frame. If this is the case, this traversal would be your least significant concern.
Three.js has the core functionality. The functionality that you need looks like very rarely needed and is not something of O(1) complexity. So, having it implemented and active at all times and in every Three.js program will degrade performance for almost everyone except … for you.
Anyway, good luck with your project. I hope when it is completed you will share it as a showcase in this forum, so we can see how all this add-remove-from-scene concept works in reality.
Apparently adding event listeners to every object every frame is smooth as butter ![]()
Yes, scene.add(object) // <--- your object is now in the scene
You’ve clearly got this figured out, well done and good luck with your next steps on the project!
adding event listeners to every object every frame
Lawrence my man, read my code on codesandbox, don’t be afraid of reading code before writing things like that because you look cringe af.
Yes,
scene.add(object)
again, Mr. Lawrence, if you are not going to read the thread, why even post?
Yes… you do all of this (lets be honest… noise)…
import * as THREE from "three";
export class StageAwareObject extends THREE.Object3D {
private _isOnStage: boolean = false;
private myParents: THREE.Object3D[] = [];
private readonly onAddedListener: () => void;
private readonly onRemovedListener: () => void;
constructor() {
super();
this.onAddedListener = this.onAdded.bind(this);
this.onRemovedListener = this.onRemoved.bind(this);
// Listen for added/removed events
this.addEventListener("added", this.onAddedListener);
this.addEventListener("removed", this.onRemovedListener);
}
private onAdded() {
const wasOnStage = this._isOnStage;
this._isOnStage = false;
//check parents...
if (this.isInStage(this.myParents)) {
this._isOnStage = true; // <----- for this.
}
for (let i = 0; i < this.myParents.length; i++) {
const parent = this.myParents[i];
if (this._isOnStage) {
parent.addEventListener("removed", this.onRemovedListener);
}
}
if (this._isOnStage) {
if (!wasOnStage) this.onAddedToStage();
} else {
this.myParents.length &&
this.myParents[this.myParents.length - 1].addEventListener(
"added",
this.onAddedListener
);
}
}
private onRemoved() {
const wasOnStage = this._isOnStage;
// reset...
for (let i = 0; i < this.myParents.length; i++) {
const parent = this.myParents[i];
parent.removeEventListener("added", this.onAddedListener);
parent.removeEventListener("removed", this.onRemovedListener);
}
this.myParents.length = 0;
this.onAdded();
if (wasOnStage) this.onRemovedFromStage();
}
private isInStage(collectParents: THREE.Object3D[] = []) {
let parent = this.parent;
let out = collectParents;
while (parent) {
out.push(parent);
if (parent instanceof THREE.Scene) {
return true;
}
parent = parent.parent;
}
return false;
}
protected onAddedToStage() {
// hook for classes to override
console.log("ADDED TO STAGE");
}
protected onRemovedFromStage() {
console.log("REMOVED FROM STAGE");
}
}
for this…
if (this.isInStage(this.myParents)) {
this._isOnStage = true; /// <----- here
}
![]()
and in the process your doing the above for every Object3D added to your scene graph, eg… every Object3D runs a for loop of its parents ( traverseAncestors
) to add event listeners to every parent without even checking if a particular parent already has the event listener you’re adding, nest an object 5 deep 3 times and your top parent now has what, an exponent of 5^(3*5) event listeners? This is simply illogical and I’m sorry for the harsh news pal but amongst everyone trying to help direct your thinking on this, you’re wrong.
EDIT:
*meanwhile*: index.ts…
setTimeout(() => {
scene.add(cube);
}, 1000);
setTimeout(() => {
cube.remove(dummy);
}, 2000);
![]()
here’s a version of your index.ts file that achieves the exact same result as the class you’ve created…
import * as THREE from "three";
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
const dummy = new THREE.Object3D();
const foo = new THREE.Object3D();
const add = (o1, o2) => {
o1.add(o2);
o2.userData.isOnStage = true;
console.log(`${o2} was added to ${o1} `);
};
const remove = (o1, o2) => {
o1.remove(o2);
o2.userData.isOnStage = false;
console.log(`${o2} was removed from ${o1} `);
};
add(dummy, foo);
add(cube, dummy);
setTimeout(() => {
add(scene, cube);
}, 1000);
setTimeout(() => {
remove(cube, dummy);
}, 2000);
setTimeout(() => {
add(cube, dummy);
}, 3000);
camera.position.z = 5;
function animate() {
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
goodluck with it
You are asuming that every object in my project extends “StageAwareObject”. Only the object that need the stage extends that class… not every object like I mentioned before.
add event listeners to every parent without even checking if a particular parent already has the event listener
read the code. Everything is reset onRemoved
you’re adding, nest an object 5 deep 3 times and your top parent now has what, an exponent of 5^(3*5) event listeners? This is simply illogical and I’m sorry for the harsh news pal but amongst everyone trying to help direct your thinking on this, you’re wrong
Read the code from top to bottom…
here’s a version of your index.ts file that achieves the exact same result as the class you’ve created…
Basically your add and remove are behaving like singletons, globally accessed methods. I mentioned I don’t want to use that type of solution because I think it is “dirty” I want everything decoupled, plug in and plug out type of thing. Using global variables is usually something you want to avoid in coding design…
Though, the example you’ve shared does this…
If it works it works ![]()
it is for the sample code to prove the hooks are working… are you for real? really???
haa is this like a tag team? 2 vs 1 or something? anyone else?
I haven’t read this whole thread but I added “childadded” and “childremoved” events to Object3D awhile back so I could track when objects were added and remove from a subhierarchy to make sure material and settings were managed properly for my app, which sounds like what you’re trying to do. It’s not always convenient or possible track the addition and removal of every object especially when libraries are involved that manage their own objects. And a per-frame diff to check whether the hierarchy has changed was expensive.
You can use the events like so to track which objects are added or removed from a scene or other object:
// set up events to track when children are added / removed to the scene
const objectsInScene = new Set();
const addedCallback = ( { child } ) => {
child.traverse( c => {
c.addEventListener( 'childadded', addedCallback );
c.addEventListener( 'childremoved', removedCallback );
objectsInScene.add( c );
} );
};
const removedCallback = ( { child } ) => {
child.traverse( c => {
c.removeEventListener( 'childadded', addedCallback );
c.removeEventListener( 'childremoved', removedCallback );
objectsInScene.delete( c );
} );
};
// initialize the scene
const scene = new Scene();
scene.addEventListener( 'childadded', addedCallback );
scene.addEventListener( 'childremoved', removedCallback );
// test
const ob1 = new Group();
const ob2 = new Group();
ob1.add( ob2 );
scene.add( ob1 );
console.log( objectsInScene ); // set contains ob1, ob2
ob1.remove( ob2 );
console.log( objectsInScene ); // set contains ob1
You can pipe these added children into a callback or whatever you need instead, as well.
childadded only triggers when a child has been added to the object, not if a child has been added to any of it’s childrens and below. And I don’t want to use global variables in that way, I want everything decoupled. Plug in Plug out.
Apparently there’s no other way other than to do what I did in the codesandbox code if I don’t want to reference outside methods or objects.
I could just make the object just registr it’s own requestAnimationFrame and do stuff there but I would never know when to stop unless I know when I’m on the scene… so again… circle back to this thread.
If you read and understand the code I gave you you’ll see that events are registered on the children, as well, which can be used to trigger some event on the root - eg your own custom “onAddedToScene” member function.
I don’t want to use global variables in that way, I want everything decoupled
There are no global variables required… with a small amount of creativity you can take the concept I provided and create a self contained class.
Did you really saw my code and thought the one you just posted is better?
You are registering way too many listeners that might not be needed at all… In my code only the interested objects that extend my class will act on this requirement. In your code you just grab everything…
There are no global variables required…
And what is objectsInScene then? Both addedCallback and removedCallback are referencing it from outside of their scopes… in a way this is like accessing a global (a variable not in the current scope)
You literally used a shotgun to kill a mosquito…
At my last watering hole, we had to count prods in a prodkit. We dropped Bucket A in a cubicle. It was (1) visible and (2) inside. The problem was, they extended the license to include “chairy” objects.
I can’t breach NDA but let’s pretend this included “clock-ears” and “ear-cots”. Suddenly you could have a pillow on a pillow. Again, I can’t breach NDA, but we kept a hashtable (Set) of furniture.
Unless someone walked up and edited the JavaScript it was usually accurate. But we did debate a nightly to poll corporate (at $5,000 a pwplayhouse).
Wave Genuine,
Bigint Lots S8kur±
