import { AfterViewInit , Directive , EventEmitter , Input , OnChanges , OnDestroy , OnInit , Optional , Output , SimpleChanges , SkipSelf } from '@angular/core' ; import * as THREE from 'three' ; import { RendererService } from '../renderer/renderer.service' ; import { v4 as uuidv4 } from 'uuid' ; @ Directive ( ) export abstract class AbstractObject3D < T extends THREE . Object3D > implements AfterViewInit , OnChanges , OnDestroy , OnInit { /**
     * Rotation in Euler angles (radians) with order X, Y, Z.
     */ @ Input ( ) rotateX : number ; @ Input ( ) rotateY : number ; @ Input ( ) rotateZ : number ; /**
     * Translate the geometry. This is typically done as a one time operation, and not during a loop.
     */ @ Input ( ) translateX : number ; @ Input ( ) translateY : number ; @ Input ( ) translateZ : number ; @ Input ( ) scaleX = 1 ; @ Input ( ) scaleY = 1 ; @ Input ( ) scaleZ = 1 ; @ Input ( ) name : string = uuidv4 ( ) ; @ Input ( ) layer = 0 ; @ Output ( ) changed = new EventEmitter < void > ( ) ; protected childlren : Array < AbstractObject3D < any > > = [ ] ; protected object : T ; constructor ( protected rendererService , @ SkipSelf ( ) @ Optional ( ) protected parent ) { } public ngOnChanges ( changes ) { if ( ! this . object ) { return ; } let modified = false ; if ( [ 'rotateX' , 'rotateY' , 'rotateZ' ] . some ( propName => propName in changes ) ) { this . applyRotation ( ) ; modified = true ; } if ( [ 'translateX' , 'translateY' , 'translateZ' ] . some ( propName => propName in changes ) ) { this . applyTranslation ( ) ; modified = true ; } if ( [ 'scaleX' , 'scaleY' , 'scaleZ' ] . some ( propName => propName in changes ) ) { this . applyScale ( ) ; modified = true ; } if ( modified ) { this . changed . emit ( ) ; this . rendererService . render ( ) ; } } public ngOnDestroy ( ) { if ( this . object && this . object . parent ) { this . parent . removeChild ( this ) ; if ( this . rendererService ) { this . rendererService . render ( ) ; } } } public ngOnInit ( ) { this . object = this . newObject3DInstance ( ) ; if ( this . layer ) { this . object . layers . set ( this . layer ) ; } this . applyTranslation ( ) ; this . applyRotation ( ) ; this . applyScale ( ) ; this . afterInit ( ) ; } public updateParent ( ) { if ( this . parent ) { this . parent . addChild ( this ) ; this . rendererService . render ( ) ; } } public applyRotation ( ) { this . object . rotation . set ( this . rotateX || 0 , this . rotateY || 0 , this . rotateZ || 0 , 'XYZ' ) ; } public applyTranslation ( ) { this . object . position . set ( this . translateX || 0 , this . translateY || 0 , this . translateZ || 0 ) ; } public applyScale ( ) { this . object . scale . set ( this . scaleX || 0 , this . scaleY || 0 , this . scaleZ || 0 ) ; } public addChild ( object ) { if ( this . object ) { this . childlren . push ( object ) ; this . object . add ( object . getObject ( ) ) ; if ( this . rendererService ) { this . rendererService . render ( ) ; } } } protected afterInit ( ) { } public removeChild ( object ) { if ( this . object && object ) { const index = this . childlren . indexOf ( object , 0 ) ; if ( index > - 1 ) { this . childlren . splice ( index , 1 ) ; } this . object . remove ( object . getObject ( ) ) ; } } public getObject ( ) { return this . object ; } protected abstract newObject3DInstance ( ) ; public ngAfterViewInit ( ) { this . updateParent ( ) ; } public findByName ( name ) { const res = this . recursionByName ( this , name ) ; return res ; } protected recursionByName ( currentNode , name ) { if ( currentNode . object && currentNode . name === name ) { return currentNode ; } let node ; currentNode . childlren . some ( child => node = this . recursionByName ( child , name ) ) ; return node ; } public getChildren ( ) { return this . childlren ; } public removeChildByName ( name ) { this . childlren = this . childlren . filter ( i => i . name !== name ) ; } }