Closed pailhead closed 8 years ago
You are comparing Unitys Entity-Component architecture to three.js inheritance. In the EC model each component add something to the base entity, the entity itself usually does nothing (except maybe a unique id). This is not the approach three.js took and it would be fairly hard to go to it now without breaking the world. I'm not sure if it would be any better in JS land either, more objects in mem and more maps/iteration to get your component.
If you have Scene -> Mesh
you should not think of it as scene Scene -> Object3D -> Mesh
or you are inventing that middle thing into there, its really Scene -> Mesh (inherits Object3D)
. When the scene is iterated, there is only one child at the root, not a Object3D that in turn has a Mesh. Your JsonLoader example does exactly this, one mesh in the scene root.
You can of course build it like that if you wish. If you do [Scene]->[Object3D]->[Object3D]->[Objec3D]->[Mesh]
without those in the middle objects having any transforms, it is indeed adding nothing. But if its useful to do those parent to achieve some offsetting etc. then go ahead.
All the different formats and loaders can behave differently in what they output. If you use e.g. Obj or Collada loaders, they will give you multiple objects back (depending on the parsed file) and try to represent the parsed file best they can.
Afaik Group
is a fairly new addition. A while back when it arrived I'm pretty sure I read the code and understood that it should be used if you dont need pos/rot/scale, but just a container of children. Back then I think it could skip Group typed objects in the frustum culling steps in projectObject
, looking at the code now though, it is no longer there. Seems that THREE.Group
is not utilized at all essentially in the current build. I dont know what the current purpose of it is then (?!). At least loaders use it, but I dont really see the point as its still inheriting Object3D (e.g. https://github.com/mrdoob/three.js/blob/dev/examples/js/loaders/OBJLoader.js#L576)
What about:
var wheelParts = [ 'tire.js','rim.js', 'bolt.js','logo.js', 'tireValve.js' ]:
var car = new THREE.Object3D();
var wheels = []; //is this where Group should go?
var frontRightWheel = new THREE.Object3D();
frontRightWheel.set( + X , 0 , +Z );
wheels.push( frontRightWheel );
var frontLeftWheel = ...
//...
for( var i = 0 ; i < wheelParts.length ; i ++ ) {
if(wheelParts[i] !== 'bolt.js') //special case
loader.load( wheelParts[ i ] , function( geometry ) {
//just flat mesh added to a "wheel" Object3D
for( var j = 0 ; j < wheels.length ; j ++ ){
wheels[j].add( new THREE.Mesh( geometry ) ); //mesh?, no positioning no scaling no rotation
}
}
else //bolt.js needs positioning
loader.load( wheelParts[ i ] , function( geometry ){
for( var j = 0 ; j < wheels.length ; j ++ ){
for ( var radial = 0 ; radial < 5 ; radial ++ ){
var m = new THREE.Mesh( geometry ); //objec3d?
m.rotation.x = Math.PI * 2 * radial / 5;
wheels[j].add(m);
}
}
//what if i just wanted to say
for( var j = 0 ; j < wheels.length ; j ++ ){
for ( var radial = 0 ; radial < 5 ; radial ++ ){
var myBolt = new THREE.Object3D();
m.rotation.x = Math.PI * 2 * radial / 5;
wheels[j].add(myBolt);
}
}
//but can't say it with a mesh because i don't have a geometry, unless i set up some proxy system
The most common case of Mesh use i come across is as just a wrapper for geometry and material, never positioned on it's own but rather through some hierarchy of object3ds.
I need to get more familiar with what graph loaders do. I thought obj also doesn't load a graph, only geometry, with subgeometries? The case above is for a "single mesh". I can see it making sense with it should be used if you dont need pos/rot/scale,
for grouping sub meshes?
THREE.Group
(which extends THREE.Object3D
) is just a nicer API and it also hints the renderer to not look for geometries or materials inside.
When dealing with a THREE.Object3D
, the renderer doesn't know if it has a geometry or material, as the object could be, in fact, a THREE.Mesh
.
but then you need to assume that your graph should never look like this?
+Scene
|
+-Object3D //cant be group
| |
| +-Mesh //because of this guy
| | |
| | +-Mesh
| | |
| | +-Object3D
| | |
| | +-Mesh
| | +-Mesh
| |
| +-Object3D //even though it has many more of these without geometry
| +-Object3D
| +-Object3D
| +-Object3D
|
|
...
But this is valid:
+Scene
|
+-Group
| |
| +-OBJECT3D
| | |
| | +-FIRSTMESH
| | +-Mesh
| | |
| | +-Object3D
| | |
| | +-Mesh
| | +-Mesh
| |
| +-Object3D
| +-Object3D
| +-Object3D
| +-Object3D
|
|
...
+Scene | +-Object3D //cant be group | | | +-Mesh //because of this guy
That's incorrect. The renderer will continue parsing the object's children. Geometries and Materials are not children.
Should it be group in that case?
Group.children[ Mesh, Object3D, Object3D... Object3D]
Both would work. It's just nicer looking (code reading wise) and faster for the WebGLRenderer
to use THREE.Group
.
But do you see my point? I may be confused over nothing, but if it's faster, why ever use Object3D for any kind of grouping? I.e. why should any other type of Object3D other than Scene
and Group
have .children.length > 0
?
Well, I think Mesh
should be able to have children, and *Light
, and *Camera
. There are many cases where you want to add a Mesh
as children of the camera, or the other way around. So it was easier to let Object3D
have children and anything that extends it.
If im understanding correctly, Object3D
should never find itself in a chain in its "raw" form. Group
should replace abstract positioning and offseting, while anything else should be specialized Light
, Camera
Mesh
Scene
etc.?
How would you do this otherwise? If a Mesh
couldn't have children, that example wouldn't be possible.
If im understanding correctly,
Object3D
should never find itself in a chain in its "raw" form. Group should replace abstract positioning and offseting,
Yes, but it's also fine if you want to use Object3D
for some reason. Group
it's more of a syntactical sugar.
It would completely overcomplicate this example, but this is the thing that is possible, but tricky to do in my first example, with loaders.
Any async operation creating a Mesh
chops up the graph creation of a branch.
example from documentation, more geometries means N async points in time where Meshes get appended to a graph.
var boxCluster = new THREE.Group();
loader.load( 'fancyBox' , function( geometry ) {
for ( var i = 0 ; i < 100 ; i ++ ){
var m = new THREE.Mesh( geometry );
applyRandomPosition( m ); //set up spatial property on mesh
boxCluster.add(m) //set up graph relation of nodes
}
}
/********
for( var i = 0 ; i < boxTypes.length ; i ++){
loader.load( boxType[i] ...
// many chopped up callbacks appending to "boxCluster" branch
Constructing a mesh like this i believe wasn't possible in older versions? This is now not async as far as the graph is concerned, but a delegation is a bit complicated?
var boxCluster = new THREE.Group();
for ( var i = 0 ; i < 100 ; i ++ ){
var m = new THREE.Mesh();
applyRandomPosition( m ); //set up spatial property on mesh
boxCluster.add(m) //set up graph relation of nodes
}
loader.load( 'fancyBox' , function( geometry ) {
//delegate single geometry to some branch
//which can make sense since the loader creates this instance when parsing
//but this loader also has to be aware of that branch, and a graph existance alltogether
for( var i = 0 ; i < boxCluster.children.length ; i ++ ){
boxCluster.children[i].geometry = geometry;
}
)
/*******************
//loading geometry without any awarness of any graphs
boxCluster.userData.boxOptions = [];
loader.load('fancyBoxA' , function(geometry){
boxCluster.userData.boxOptions.push(geometry);
Vs a proxy:
var boxCluster = new THREE.Group();
var fancyBoxProxy = new THREE.Geometry();
for ( var i = 0 ; i < 100 ; i ++ ){
var m = new THREE.Mesh( fancyBoxProxy );
applyRandomPosition( m ); //set up spatial property on mesh
boxCluster.add(m) //set up graph relation of nodes
}
//box cluster is created
loader.load( 'fancyBox' , function( geometry ) {
//loader not aware of graph and delegation, only updates geometry
fancyBox.merge( geometry);
)
@mrdoob
Any async operation creating a
Mesh
chops up the graph creation of a branch.
I don't see how...?
the example's complicated im updating the previous answer :)
@pailhead The first two examples of boxCluster
share the same geometry and are essentially identical. Its not automatically cloned inside new THREE.Mesh(geom)
.
Afaik the renderer is smart enough to upload it once to the GPU and reference it in each mesh draw call. (correct me if I'm wrong @mrdoob).
I all your examples the loader is not aware of your scene graph (like your comment suggest), the anon function you pass it does. Its not part of the loader, that is part of your app, that should know about the scene graph and where to stick the objects/geometries.
All the examples are identical other than how youd prefer to write it. If I would write that code, I would pick the first one and only put meshes into the parent once they have the desired geometry and materials setup. I would be dubious reading the code that tries to do the 2nd or 3rd option :)
All the examples are identical other than how youd prefer to write it. If I would write that code, I would pick the first one and only put meshes into the parent once they have the desired geometry and materials setup.
.
Any async operation creating a Mesh chops up the graph creation of a branch.
.
I don't see how...?
Conflict :)
So we agree that it "chops up" the graph creation. I'm not sure how else to call the multiple points when a graph is appended.
Afaik the renderer is smart enough to upload it once to the GPU and reference it in each mesh draw call. (correct me if I'm wrong @mrdoob).
From what i remember, Meshes just reference a geometry, that has a uuid. Internally WebGLRenderer holds another structure that it uses for GPU communication? That's why the merge needs to be more elaborate, setting on a bunch of flags etc. While i'm not 100% sure what happens when you just switch the reference, i think the render()
figures out that it's a different geometry, a uuid it has not uploaded to the GPU and has a pointer to hence creates it.
In effect, not at all different then just calling new Mesh( downloadedGeom )
, your app will stutter.
I think this is what has the final say before the gpu takes over?:
https://github.com/mrdoob/three.js/blob/dev/src/renderers/webgl/WebGLGeometries.js#L7
If it fails to get a geometry, this is where Geometry gets converted to BufferGeometry.
I guess that depends what you mean by chopping up, I would not call it that :) It's just normal three.js code to us.
I'm not sure what you are arguing for and want me to agree on. You wish to change the core mechanics of three.js with this issue?
What about the people who don't consider this normal three.js code?
I.e you have to give your object chain to someone ( a branch of the graph) that is set up to pop in at many different times, and they cant figure out where to look for it?
The common question "why is this undefined"?
Which then goes on to, "i need to wait for N geometries, in order to work on [O3d]->[mesh]->[o3d]-[MYMESH]->[anotherMesh]...
"
Nothing gets uploaded on the stop in Mesh constructor. Your just passing the same JS object (geometry) reference to all of the meshes. The upload happens on next render()
if the mesh is to be rendered (part of the scene, not hidden or frustum culled out).
You app will stutter exactly as much in all of your examples. There is no difference.
the stutter is besides the point :)
i was trying to illustrate that the same thing happens on
existingMesh.geometry = someOtherGeometry
and
new Mesh( someOtherGeometry )
which gets added where existingMesh would already been
Those people should then get familiar with the library and learn how it should be used via examples etc. or pick another lib I guess :)
I can't follow your line of thought anymore. I'm sure there is question somewhere in there :)
(I don't represent three.js, just a random guy on the internet trying to help)
uh huh, i disagree,
There is already so much abstraction going on, this is a great example , the stutter. If you had "uploadToGPU" instead of renderer.render
taking care of that, it would be much more obvious that you can control this in a different way, as simple as making sure that it's only one thing added at a time with a distance of a few frames etc.
My lines of thought are that you couple loading logic to a branch creation, and that it's "chopped up", meaning you have to rely on that logic to make sure that your entire branch is created before you can go on doing something with a parent.child[a].child[b].child[....
This further means that when creating a Mesh from THREE.BoxGeometry and using a JSONLoader you have to have a completely different concept of creating that branch. Asynchronous, relying on a loader etc.
wheel = new THREE.Object3D();
for( var i = 0 ; i < 5 ; i++)
wheel.add( new THREE.Mesh() );
//why does this need to happen asynchronously?
wheel.userData.wheelOption = 0;
wheel.toggleOptions = function(){
var o = this.userData.wheelOption;
o = ++ o < this.children.length ? o : 0;
for( var i = 0 ; i < this.children.length ; i ++){
this.children.visible = i == o;
}
}.bind(wheel);
The abstraction and ease of use is by design. This is the first line in the readme:
The aim of the project is to create a lightweight 3D library with a very low level of complexity — in other words, for dummies.
Low level of complexity means for the end user, not necessarily the internals and how its written. If you want to manage and have strict control over GPU uploads and all that yourself I think you need to write your own renderer or check at other alternatives. three.js also has DirectGeometry
and friends that might get you close but I think you cant control the GPU uploads, but can reduce the work it needs to do before uploads.
again, the gpu upload is not the issue, the graph construction, which is pretty high level and basic can get confusing, that is the point. In the example above, it's a pretty high level concern - toggle some meshes visible. These meshes can be declared ahead of any asset management, or after. This logic can be known, i.e. "there are 5 options" "switch current on, others off". No need for loaders nor procedural geometry anywhere.
My lines of thought are that you couple loading logic to a branch creation, and that it's "chopped up", meaning you have to rely on that logic to make sure that your entire branch is created before you can go on doing something with a
parent.child[a].child[b].child[....
If you just want to have a scene graph that doesn't change, why don't you just wait until the loading is done before rendering anything?
Well, it was fully the point/issue of your previous post, which I was replying on. But I guess you changed your mind already what we are disagreeing on :) Now its back to scene graph construction being confusing.
But you mentioned the gpu first, i didnt think it was relevant. Apologies for the digression. I think we all agree that there are a few ways to do the same thing.
Can we close this then? 😛
the chopped up graph is not a concern, and the ambigouity between mesh and just objectd?
wheel = new THREE.Group();
for( var i = 0 ; i < 5 ; i++)
wheel.add( new THREE.Group() );
wheel.userData.wheelOption = 0;
wheel.toggleOptions = function(){
var o = this.userData.wheelOption;
o = ++ o < this.children.length ? o : 0;
for( var i = 0 ; i < this.children.length ; i ++){
this.children[0].children[0].visible = i == o;
}
}.bind(wheel);
would be the correct way to set up a graph with some logic, and later attach a mesh to it?
I don't understand what that code is supposed to do...
Set up a branch synchronously, that expects 5 meshes to be added, could be THREE.BoxGeometry, or JSONLoader that give Geometry to them, but this code block doesn't care. It knows it can set visibility on a Mesh, doesn't care what the mesh contains or when it obtained it.
maybe visibility is confusing, lets say you just want to move one child along some axis on some event, you don't care what geometry it has.
You would be using the Object3D methods of a Mesh. But you don't have a Mesh if you are loading some geometry, but you can have it if you construct procedural geometry.
But if instead of a mesh you say, "on event, parent moves child[ E ] up" and use Object3D you can define that logic within that branch (parent) , completely agnostic of the geometry, texture, material etc.
Well, create a container for it then (THREE.Group
). Move the container and add the meshes to it as they load.
I think toggleOptions
should not be tied into the three.js scene/objects. That is application logic and belongs to you app (if I were to write that). I would keep app state and rendering separate.
But thats one way of writing it of course, it's up to you as a developer how you design it.
var car = new THREE.Object3D();
car.position.set(0, 20, 0);
scene.add(car);
for( var i = 0 ; i < 5 ; i++) {
var wheel = new THREE.Mesh(wheelGeom, wheelMaterial);
wheel.position.set(math here to offset wheels from car center);
car.add(wheel);
}
var setWheelVisible = function(index, visible) {
if (car.children[index])
car.children[index].visible = visible;
};
var rotateWheels = function(deg) {
for( var i = 0 ; i < 5 ; i++)
car.children[i].rotation.x += THREE.Math.degToRad(deg);
}
var update = function() {
rotateWheels(45 * frametimeAndStuff);
requestAnimationFrame(update);
};
requestAnimationFrame(update);
Not sure if I understood you right but setWheelVisible
or rotateWheels
do not touch the geomery or need to know about it. You app does and you do.
But I think this is better suited for stack overflow help or philosophical discussion :) Not a bug report.
I realize visible is a bad example. Material also has the same interface, and is involving rendering on many levels.
Let's say it is something that is entirely in the realm of THREE and an exclusively Object3D. Like .translateX( value )
.
I'm trying to say that this, in addition to this:
Well, create a container for it then (THREE.Group). Move the container and add the meshes to it as they load.
Makes the Mesh pretty redundant. I.e. if it's most likely always at the end of that chain, you dont need to apply any TRS to it.
Well i just learned that an undocumented class is supposed to be used as the backbone for graph creation in three.js :) , is it still a stack overflow thing? Maybe the issue is "document Group ASAP"
Well i just learned that an undocumented class is supposed to be used as the backbone for graph creation in three.js, is it still a stack overflow thing?
What undocumented class? Oh! Group
is not documented? We should fix that.
"use for positioning things" could actually be the solution?
Makes the Mesh pretty redundant. I.e. if it's most likely always at the end of that chain, you dont need to apply any TRS to it.
In that case, set mesh.matrixAutoUpdate
to false
.
I think Object3D is the back bone still. Group is a slight optimization as said. You probably need a huge scenes before you see the perf hit of Object3D and Group. But do agree it should be documented (better).
For big scenes matrixAutoUpdate
is a good idea.
@mrdoob Maybe put the pros and cons (if any) perf wise of using Group vs. Object3D in the docs. Would be nice to have the steps you save in render explicitly stated there.
Not sure if I understood you right but setWheelVisible or rotateWheels do not touch the geomery or need to know about it. You app does and you do. @jonnenauha
I'm trying to write everything in es6 and class as much as it is convenient. In this particular case i would argue that the setWheelVisible
or rotateWheels
should not be part of the app either, but part of the car.
Which could be built from MeshToggler
and ObjectRotator
etc.
This use case becomes pretty complicated when you have to consider wiring callbacks at this level.
But designing a class:
class MultiWheel extends THREE.Group{
constructor( geometryArray){
super();
//logic
}
}
can be over-engineering at some point when you just need a 3d scope, to group some things and set up some spatial logic.
/me leaves conversation
:(
http://threejs.org/examples/#webgl_geometry_hierarchy2
how much does this change if instead of one box, it's the stanford bunny, the blender monkey, and a few other async options.
close it as not an issue
Description of the problem
I'm a little bit confused with the concept of creating a scene graph with three.js, not sure if this should be posted to stack overflow.
Object3D
Scene
These two make sense for constructing scene graphs, but where does
Group
fit in, or how is it different thanObject3D
?But then
Mesh
also extendsobject3d
and adds flavor to the mix.So a minimal graph needed to draw something is
[Scene]->[Mesh]->[Geometry,Material]
But where do object and group fit in
[Scene]->[Group]->[Object3D]->[Mesh]
If object3D has only one child mesh, then that seems reduntant, since mesh is already an object3d and can be positioned. If it has multiple children then it should be a group, if it doesnt have any what is the use case scenario?
Then this concept makes constructing a graph a bit confusing when it comes to loading meshes.
Lets say you want to "draw a box", and look at the documentation:
All nice and synchronous, geometry and material declared ahead of a mesh, and then a mesh is constructed with them.
But then if you want to load geometry, and you look at the documentation:
Since scene already exists, the graph exists. But some time in the future, depending on when a
Geometry
class gets it's data, we continue creating the graph.I'm not sure if it was always possible to instantiate a
Mesh
without providing an instance ofGeometry
, it is now so it is possible to add this to a mesh that has already been created.I'm not sure exactly how to explain the ambiguity here but i think this example illustrates it well. I'm also trying to relate to how Unity handles the scene graph. Their
GameObject
can have a mesh/rendering component attached, but this entity is not a 3d object.[Scene]->[Object3D]->[Object3D]->[Objec3D]->[Mesh]
does the mesh here really need to have TRS, and it's own matrix, i.e. be an object3d? ...
Three.js version
Browser
OS
Hardware Requirements (graphics card, VR Device, ...)