Refactor to use polymorphism?
See original GitHub issueI hope this will be received in the spirit of positivity and contribution.
Have you considered refactoring threejs to use more polymorphism?
this kind of code
if ( object.isGroup ) {
groupOrder = object.renderOrder;
} else if ( object.isLOD ) {
if ( object.autoUpdate === true ) object.update( camera );
} else if ( object.isLight ) {
currentRenderState.pushLight( object );
if ( object.castShadow ) {
currentRenderState.pushShadow( object );
}
} else if ( object.isSprite ) {
if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {
if ( sortObjects ) {
_vector3.setFromMatrixPosition( object.matrixWorld )
.applyMatrix4( _projScreenMatrix );
}
const geometry = objects.update( object );
const material = object.material;
if ( material.visible ) {
currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
}
}
} else if ( object.isImmediateRenderObject ) {
if ( sortObjects ) {
_vector3.setFromMatrixPosition( object.matrixWorld )
.applyMatrix4( _projScreenMatrix );
}
currentRenderList.push( object, null, object.material, groupOrder, _vector3.z, null );
} else if ( object.isMesh || object.isLine || object.isPoints ) {
if ( object.isSkinnedMesh ) {
// update skeleton only once in a frame
if ( object.skeleton.frame !== info.render.frame ) {
object.skeleton.update();
object.skeleton.frame = info.render.frame;
}
}
I was always taught in computer programming classes, tutorials, etc. is kind of considered bad practice. Basically checking for a type of class is a sign that the code should be using polymorphism.
Instead each class would have some function that is appropriate for calling there. Let’s call it handleProjection
since the code above is from WebGLRenderer.projectObject
The code above could change to just this
object.handleProjection( currentRenderState, camera, ... );
Inside Group
it might have
class Group {
handleProject( currentRenderState, camera ) {
currentRenderState.setGroupOrderer( this.groupOrder );
}
}
for other classes, just pseudo code
class LOD {
handleProject( currentRenderState, camera ) {
if ( this.autoUpdate === true ) this.update( camera );
}
}
class Light {
handleProject( currentRenderState, camera ) {
currentRenderState.pushLight( this );
if ( this.castShadow ) {
currentRenderState.pushShadow( this );
}
}
}
class Sprite {
handleProject( currentRenderState ) {
if ( ! this.frustumCulled || currentRenderState.frustum.intersectsSprite( this ) ) {
if ( currentRenderState.sortObjects ) {
_vector3.setFromMatrixPosition( this.matrixWorld )
.applyMatrix4( currentRenderState.projScreenMatrix );
}
const geometry = this.update( this );
const material = this.material;
if ( material.visible ) {
currentRenderState.currentRenderList.push( this, geometry, material, currentRenderState.groupOrder, _vector3.z, null );
}
}
}
}
class ImmediateRenderObject {
handleRender( currentRenderState ) {
if ( currentRenderState.sortObjects ) {
_vector3.setFromMatrixPosition( this.matrixWorld )
.applyMatrix4( currentRenderState.projScreenMatrix );
}
currentRenderList.push( this, null, this.material, currentRenderState.groupOrder, _vector3.z, null );
}
}
class SkinnedMesh {
handleProject( currentRenderState )
// update skeleton only once in a frame
const info = currentRenderState.info;
if ( this.skeleton.frame !== info.render.frame ) {
this.skeleton.update();
this.skeleton.frame = info.render.frame;
}
}
}
etc.
Similarly, any renderer specific things should arguably be also moved into polymorphic classes so that the same code works with a WebGL renderer vs a WebGPU renderer without any if (type-of-class-is-x)
statements.
AFAIK this is how Unity, Unreal, and most AAA 3D engines work. Not this exact way. Only in that they aren’t littered with if statements checking for various classes.
Issue Analytics
- State:
- Created 3 years ago
- Comments:7 (2 by maintainers)
Top GitHub Comments
@looeee
Sorry to budge in. Not to tell anybody how the code should be structured, but to correct what seems like a misunderstanding.
The code above is, in fact, not entirely monomorphic. More specifically all
if
-conditions are polymorphic (or most likely megamorphic) because they observe objects of different shapes. The code within if-blocks is monomorphic.If you were to use virtual dispatch you would move from multiple polymorphic property loads to a single polymorphic call, which most likely would be a performance win. (but that depends on some factors).
Yeah, looking at the code a bit more deeply I was thrown off by this being referred to as a mono/polymorphism issue. @mraleph, thanks for the correction, and @samanthajo2 sorry for being dismissive.
It’s probably better to frame this as conditionals vs dynamic dispatch, and yes, plenty of tutorials do indeed tell you to prefer the latter. The disadvantage is less readable code but the performance increase might be worth it (or might not, I’m not familiar enough with the highlighted section of code to make a guess).
However, as @mrdoob says, we have bigger fish to fry right now with the conversion to classes. Maybe we can reconsider this once that task is done?