1 /**
2  * Defines the GameObject class, to be subclassed by scripts and instantiated for static objects.
3  */
4 module dash.core.gameobject;
5 import dash.core, dash.components, dash.graphics, dash.utility;
6 
7 import yaml;
8 import std.conv, std.variant, std.array, std.algorithm, std.typecons, std.range, std..string, std.math;
9 
10 enum AnonymousName = "__anonymous";
11 
12 /**
13  * Contains flags for all things that could be disabled.
14  */
15 struct ObjectStateFlags
16 {
17     bool updateComponents;
18     bool updateBehaviors;
19     bool updateChildren;
20     bool drawMesh;
21     bool drawLight;
22 
23     /**
24      * Set each member to false.
25      */
26     void pauseAll()
27     {
28         foreach( member; __traits(allMembers, ObjectStateFlags) )
29             static if( __traits(compiles, __traits(getMember, ObjectStateFlags, member) = false) )
30                 __traits(getMember, ObjectStateFlags, member) = false;
31     }
32 
33     /**
34      * Set each member to true.
35      */
36     void resumeAll()
37     {
38         foreach( member; __traits(allMembers, ObjectStateFlags) )
39             static if( __traits(compiles, __traits(getMember, ObjectStateFlags, member) = true) )
40                 __traits(getMember, ObjectStateFlags, member) = true;
41     }
42 }
43 
44 /// A tuple of a resource and a gameobject reference
45 alias GameObjectResource = Tuple!( Resource, "resource", GameObject, "object" );
46 GameObjectResource[][Resource] objectsByResource;
47 
48 /**
49  * Manages all components and transform in the world. Can be overridden.
50  */
51 final class GameObject
52 {
53 private:
54     GameObject _parent;
55     GameObject[] _children;
56     Prefab _prefab;
57     Component[ClassInfo] componentList;
58     string _name;
59     bool canChangeName;
60     static uint nextId = 1;
61 
62     enum componentProperty( Type ) = q{
63         @property $type $property() { return getComponent!$type; }
64         @property void $property( $type v ) { addComponent( v ); }
65     }.replaceMap( [ "$property": Type.stringof.toLower, "$type": Type.stringof ] );
66 
67 package:
68     /// THIS IS ONLY SET IF THIS OBJECT IS SCENE _ROOT
69     Scene scene;
70 
71     /// Searche the parent tree until we find the scene object
72     Scene findScene()
73     {
74         // Get root object
75         GameObject par;
76         for( par = this; par.parent; par = par.parent ) { }
77         
78         return par.scene;
79     }
80 
81 public:
82     /**
83      * The struct that will be directly deserialized from the ddl.
84      */
85     static struct Description
86     {
87         /// The name of the object.
88         @rename( "Name" )
89         string name;
90 
91         /// The name of the prefab to create from. Do you use with $(D prefab).
92         @rename( "InstanceOf" ) @optional
93         string prefabName = null;
94 
95         /// The Prefab to create from.
96         @ignore
97         Prefab prefab;
98 
99         /// The transform of the object.
100         @rename( "Transform" ) @optional
101         Transform.Description transform;
102 
103         /// Children of this object.
104         @rename( "Children" ) @optional
105         Description[] children;
106 
107         @rename( "Components" ) @optional
108         Component.Description[] components;
109     }
110 
111     /// The current transform of the object.
112     Transform transform;
113     /// The light attached to this object.
114     @property void light( Light v ) { addComponent( v ); }
115     /// ditto
116     @property Light light()
117     {
118         enum get( Type ) = q{
119             if( auto l = getComponent!$type )
120                 return l;
121         }.replace( "$type", Type.stringof );
122         mixin( get!AmbientLight );
123         mixin( get!DirectionalLight );
124         mixin( get!PointLight );
125         mixin( get!SpotLight );
126         return null;
127     }
128     /// The Mesh belonging to the object.
129     mixin( componentProperty!Mesh );
130     /// The Material belonging to the object.
131     mixin( componentProperty!Material );
132     /// The animation on the object.
133     mixin( componentProperty!Animation );
134     /// The camera attached to this object.
135     mixin( componentProperty!Camera );
136     /// The emitter attached to this object.
137     mixin( componentProperty!Emitter );
138     /// The object that this object belongs to.
139     mixin( Property!_parent );
140     /// All of the objects which list this as parent
141     mixin( Property!_children );
142     /// The prefab that this object is based on.
143     mixin( Property!_prefab );
144     /// The name of the object.
145     mixin( Property!( _name, AccessModifier.Package ) );
146     /// Change the name of the object.
147     void changeName( string newName )
148     in
149     {
150         assert( newName && newName.length, "Invalid name given." );
151     }
152     body
153     {
154         // Ignore an unchanging name.
155         if( name == newName )
156         {
157             return;
158         }
159         else if( canChangeName || DGame.instance.currentState == EngineState.Editor )
160         {
161             // Update mappings in the scene.
162             if( auto scene = findScene() )
163             {
164                 scene.idByName.remove( name );
165                 scene.idByName[ newName ] = id;
166             }
167 
168             // Change the name.
169             name = newName;
170         }
171         else
172         {
173             throw new Exception( "Unable to rename gameobject at this time." );
174         }
175     }
176     /// The ID of the object.
177     immutable uint id;
178     /// The current update settings
179     ObjectStateFlags* stateFlags;
180     /// Allow setting of state flags directly.
181     //alias stateFlags this;
182 
183     /**
184      * Create a GameObject from a description object.
185      *
186      * Params:
187      *  desc =              The description to pull info from.
188      *
189      * Returns:
190      *  A new game object with components and info pulled from desc.
191      */
192     static GameObject create( const Description desc )
193     {
194         GameObject obj;
195 
196         // Create the object
197         if( desc.prefabName )
198         {
199             auto fab = Prefabs[ desc.prefabName ];
200             obj = fab.createInstance();
201             obj.prefab = fab;
202         }
203         else if( desc.prefab )
204         {
205             obj = desc.prefab.createInstance();
206         }
207         else
208         {
209             obj = new GameObject;
210         }
211 
212         // Set object name
213         obj.name = desc.name;
214 
215         // Init transform
216         obj.transform = desc.transform;
217 
218         // Create children
219         if( desc.children.length > 0 )
220         {
221             foreach( child; desc.children )
222             {
223                 obj.addChild( GameObject.create( child ) );
224             }
225         }
226 
227         // Add components
228         foreach( component; desc.components )
229         {
230             obj.addComponent( component.createInstance() );
231         }
232 
233         // Init components
234         foreach( comp; obj.componentList )
235             comp.initialize();
236 
237         return obj;
238     }
239 
240     /**
241      * Create a description from a GameObject.
242      *
243      * Returns:
244      *  A new description with components and info.
245      */
246     Description toDescription()
247     {
248         Description desc;
249         desc.name = name;
250         desc.prefab = prefab;
251         desc.prefabName = prefab ? prefab.name : null;
252         desc.transform = transform.toDescription();
253         desc.children = children.map!( child => child.toDescription() ).array();
254         desc.components = componentList.values.map!( comp => cast()comp.description ).array();
255         return desc;
256     }
257 
258     /// To complement the descriptions, and make serialization easier.
259     static GameObject fromRepresentation( Description desc )
260     {
261         return GameObject.create( desc );
262     }
263     /// ditto
264     Description toRepresentation()
265     {
266         return toDescription();
267     }
268     static assert( isCustomSerializable!GameObject );
269 
270     /**
271      * Creates basic GameObject with transform and connection to transform's emitter.
272      */
273     this()
274     {
275         transform = Transform( this );
276 
277         // Create default material
278         material = new Material( new MaterialAsset( "default" ) );
279         id = nextId++;
280 
281         stateFlags = new ObjectStateFlags;
282         stateFlags.resumeAll();
283 
284         name = typeid(this).name.split( '.' )[ $-1 ] ~ id.to!string;
285         canChangeName = true;
286     }
287 
288     /**
289      * Allows you to create an object with a set list of components you already have.
290      *
291      * Params:
292      *  newComponents =     The list of components to add.
293      */
294     this( Component[] newComponents... )
295     {
296         this();
297 
298         foreach( comp; newComponents )
299             addComponent( comp );
300     }
301 
302     /**
303      * Called once per frame to update all children and components.
304      */
305     final void update()
306     {
307         if( stateFlags.updateComponents )
308             foreach( ci, component; componentList )
309                 component.update();
310 
311         if( stateFlags.updateChildren )
312             foreach( obj; children )
313                 obj.update();
314     }
315 
316     /**
317      * Called once per frame to draw all children.
318      */
319     final void draw()
320     {
321         foreach( component; componentList )
322             component.draw();
323 
324         foreach( obj; children )
325             obj.draw();
326     }
327 
328     /**
329      * Called when the game is shutting down, to shutdown all children.
330      */
331     final void shutdown()
332     {
333         foreach( component; componentList )
334             component.shutdown();
335 
336         foreach( obj; children )
337             obj.shutdown();
338     }
339 
340     /**
341      * Refreshes the object with the given YAML node.
342      *
343      * Params:
344      *  desc =          The node to refresh the object with.
345      */
346     final void refresh( Description node )
347     {
348         if( name != node.name )
349             changeName( node.name );
350 
351         transform.refresh( node.transform );
352 
353         // Refresh components
354         bool[string] componentExists = zip( StoppingPolicy.shortest, componentList.byKey.map!( k => k.name ), false.repeat ).assocArray();
355         foreach( compDesc; node.components )
356         {
357             // Found it!
358             componentExists[ compDesc.componentType.name ] = true;
359 
360             // Refresh, or add if it's new
361             if( auto comp = compDesc.componentType in componentList )
362                 comp.refresh( compDesc );
363             else
364                 addComponent( compDesc.createInstance() );
365         }
366 
367         // Remove old components
368         foreach( key; componentExists.keys.filter!( k => !componentExists[k] ) )
369             componentList.remove( cast(ClassInfo)ClassInfo.find( key ) );
370 
371         // Refresh children
372         bool[string] childrenExist = zip( StoppingPolicy.shortest, _children.map!( child => child.name ), false.repeat ).assocArray();
373         foreach( childDesc; node.children )
374         {
375             // Found it!
376             childrenExist[ childDesc.name ] = true;
377 
378             // Refresh, or add if it's new
379             if( auto child = _children.filter!( child => child.name == childDesc.name ).front )
380                 child.refresh( childDesc );
381             else
382                 addChild( GameObject.create( childDesc ) );
383         }
384 
385         foreach( key; childrenExist.keys.filter!( k => !childrenExist[k] ) )
386             childrenExist.remove( key );
387     }
388 
389     /**
390      * Refresh the component of the given type.
391      *
392      * Params:
393      *  componentType = The type of teh component to refresh.
394      *  desc =          The new description of the component.
395      */
396     final void refreshComponent( ClassInfo componentType, Component.Description desc )
397     {
398         if( auto comp = componentType in componentList )
399         {
400             comp.refresh( desc );
401         }
402     }
403 
404     /**
405      * Refresh the component of the given type.
406      *
407      * Params:
408      *  ComponentType = The type of teh component to refresh.
409      *  desc =          The new description of the component.
410      */
411     final void refreshComponent( ComponentType )( Component.Description desc )
412     {
413         refreshComponent( typeid(ComponentType), desc );
414     }
415 
416     /**
417      * Refresh the component of the given type.
418      *
419      * Params:
420      *  componentName = The type of teh component to refresh.
421      *  desc =          The new description of the component.
422      */
423     final void refreshComponent( string componentName, Component.Description desc )
424     {
425         refreshComponent( getDescription( componentName ).componentType, desc );
426     }
427 
428     /**
429      * Adds a component to the object.
430      */
431     final void addComponent( Component newComponent )
432     in
433     {
434         assert( newComponent, "Null component added." );
435     }
436     body
437     {
438         componentList[ typeid(newComponent) ] = newComponent;
439         newComponent.owner = this;
440     }
441 
442     /**
443      * Gets a component of the given type.
444      */
445     final T getComponent( T )() if( is( T : Component ) )
446     {
447         if( auto comp = typeid(T) in componentList )
448             return cast(T)*comp;
449         else
450             return null;
451     }
452 
453     /**
454      * Adds object to the children, adds it to the scene graph.
455      *
456      * Params:
457      *  newChild =            The object to add.
458      */
459     final void addChild( GameObject newChild )
460     {
461         // Nothing to see here.
462         if( newChild.parent == this )
463             return;
464         // Remove from current parent
465         else if( newChild.parent )
466             newChild.parent.removeChild( newChild );
467 
468         _children ~= newChild;
469         newChild.parent = this;
470         newChild.canChangeName = false;
471 
472         Scene currentScene = findScene();
473 
474         if( currentScene )
475         {
476             GameObject[] objectChildren;
477             {
478                 GameObject[] objs;
479                 objs ~= newChild;
480 
481                 while( objs.length )
482                 {
483                     auto obj = objs[ 0 ];
484                     objs = objs[ 1..$ ];
485                     objectChildren ~= obj;
486 
487                     foreach( child; obj.children )
488                         objs ~= child;
489                 }
490             }
491 
492             // If adding to the scene, make sure all new children are in.
493             foreach( child; objectChildren )
494             {
495                 currentScene.objectById[ child.id ] = child;
496                 currentScene.idByName[ child.name ] = child.id;
497             }
498         }
499     }
500 
501     /**
502      * Removes the given object as a child from this object.
503      *
504      * Params:
505      *  oldChild =            The object to remove.
506      */
507     final void removeChild( GameObject oldChild )
508     {
509         children = children.remove( children.countUntil( oldChild ) );
510 
511         oldChild.canChangeName = true;
512         oldChild.parent = null;
513 
514         Scene currentScene = findScene();
515 
516         // Remove from scene.
517         if( currentScene )
518         {
519             currentScene.objectById.remove( oldChild.id );
520             currentScene.idByName.remove( oldChild.name );
521         }
522     }
523 }
524 
525 /**
526  * Handles 3D Transformations for an object.
527  * Stores position, rotation, and scale
528  * and can generate a World matrix, worldPosition/Rotation (based on parents' transforms)
529  * as well as forward, up, and right axes based on rotation
530  */
531 struct Transform
532 {
533 private:
534     vec3f _prevPos;
535     quatf _prevRot;
536     vec3f _prevScale;
537     mat4f _matrix;
538 
539     void opAssign( Description desc )
540     {
541         position = vec3f( desc.position[] );
542         rotation = fromEulerAngles( desc.rotation[ 0 ], desc.rotation[ 1 ], desc.rotation[ 2 ] );
543         scale = vec3f( desc.scale[] );
544     }
545 
546     /**
547      * Default constructor, most often created by GameObjects.
548      *
549      * Params:
550      *  obj =            The object the transform belongs to.
551      */
552     this( GameObject obj )
553     {
554         owner = obj;
555         position = vec3f(0,0,0);
556         scale = vec3f(1,1,1);
557         rotation = quatf.identity;
558     }
559 
560 public:
561     /**
562      * The struct that will be directly deserialized from the ddl.
563      */
564     static struct Description
565     {
566         /// The position of the object.
567         @rename( "Position" ) @optional
568         float[3] position = [ 0.0f, 0.0f, 0.0f ];
569 
570         /// The position of the object.
571         @rename( "Rotation" ) @optional
572         float[3] rotation = [ 0.0f, 0.0f, 0.0f ];
573 
574         /// The position of the object.
575         @rename( "Scale" ) @optional
576         float[3] scale = [ 1.0f, 1.0f, 1.0f ];
577     }
578 
579     void refresh( Description desc )
580     {
581         // TODO: Track if the transform actually changed.
582         this = desc;
583     }
584 
585     /**
586      * Create a description from a Transform.
587      *
588      * Returns:
589      *  A new description with components.
590      */
591     Description toDescription()
592     {
593         Description desc;
594         desc.position = position.vector[ 0..3 ];
595         desc.rotation = rotation.toEulerAngles().vector[ 0..3 ];
596         desc.scale = scale.vector[ 0..3 ];
597         return desc;
598     }
599 
600     // these should remain public fields, properties return copies not references
601     /// The position of the object in local space.
602     vec3f position;
603     /// The rotation of the object in local space.
604     quatf rotation;
605     /// The absolute scale of the object. Ignores parent scale.
606     vec3f scale;
607 
608     /// The object which this belongs to.
609     GameObject owner;
610     /// The world matrix of the transform.
611     mixin( Getter!_matrix );
612     //mixin( ThisDirtyGetter!( _matrix, updateMatrix ) );
613 
614     @disable this();
615 
616     /**
617      * This returns the object's position relative to the world origin, not the parent.
618      *
619      * Returns: The object's position relative to the world origin, not the parent.
620      */
621     final @property vec3f worldPosition() @safe pure nothrow
622     {
623         if( owner.parent is null )
624             return position;
625         else
626             return (owner.parent.transform.matrix * vec4f(position, 1.0f)).xyz;
627     }
628 
629     /**
630      * This returns the object's rotation relative to the world origin, not the parent.
631      *
632      * Returns: The object's rotation relative to the world origin, not the parent.
633      */
634     final @property quatf worldRotation() @safe pure nothrow
635     {
636         if( owner.parent is null )
637             return rotation;
638         else
639             return owner.parent.transform.worldRotation * rotation;
640     }
641 
642     /*
643      * Check if current or a parent's matrix needs to be updated.
644      * Called automatically when getting matrix.
645      *
646      * Returns: Whether or not the object is dirty.
647      */
648     final @property bool isDirty() @safe pure nothrow
649     {
650         bool result = position != _prevPos ||
651                       rotation != _prevRot ||
652                       scale != _prevScale;
653 
654         return owner.parent ? (result || owner.parent.transform.isDirty()) : result;
655     }
656 
657     /*
658      * Gets the forward axis of the current transform.
659      *
660      * Returns: The forward axis of the current transform.
661      */
662     final @property const vec3f forward()
663     {
664         return vec3f( -2 * (rotation.x * rotation.z + rotation.w * rotation.y),
665                             -2 * (rotation.y * rotation.z - rotation.w * rotation.x),
666                             -1 + 2 * (rotation.x * rotation.x + rotation.y * rotation.y ));
667     }
668     ///
669     unittest
670     {
671         import std.stdio;
672         writeln( "Dash Transform forward unittest" );
673 
674         auto trans = new Transform( null );
675         auto forward = vec3f( 0.0f, 1.0f, 0.0f );
676         trans.rotation.rotatex( 90.radians );
677         assert( almost_equal( trans.forward, forward ) );
678     }
679 
680     /*
681      * Gets the up axis of the current transform.
682      *
683      * Returns: The up axis of the current transform.
684      */
685     final @property const vec3f up()
686     {
687         return vec3f( 2 * (rotation.x * rotation.y - rotation.w * rotation.z),
688                         1 - 2 * (rotation.x * rotation.x + rotation.z * rotation.z),
689                         2 * (rotation.y * rotation.z + rotation.w * rotation.x));
690     }
691     ///
692     unittest
693     {
694         import std.stdio;
695         writeln( "Dash Transform up unittest" );
696 
697         auto trans = new Transform( null );
698         auto up = vec3f( 0.0f, 0.0f, 1.0f );
699         trans.rotation.rotatex( 90.radians );
700         assert( almost_equal( trans.up, up ) );
701     }
702 
703     /*
704      * Gets the right axis of the current transform.
705      *
706      * Returns: The right axis of the current transform.
707      */
708     final @property const vec3f right()
709     {
710         return vec3f( 1 - 2 * (rotation.y * rotation.y + rotation.z * rotation.z),
711                         2 * (rotation.x * rotation.y + rotation.w * rotation.z),
712                         2 * (rotation.x * rotation.z - rotation.w * rotation.y));
713     }
714     ///
715     unittest
716     {
717         import std.stdio;
718         writeln( "Dash Transform right unittest" );
719 
720         auto trans = new Transform( null );
721         auto right = vec3( 0.0f, 0.0f, -1.0f );
722         trans.rotation.rotatey( 90.radians );
723         assert( almost_equal( trans.right, right ) );
724     }
725 
726     /**
727      * Rebuilds the object's matrix.
728      */
729     final void updateMatrix() @safe pure nothrow
730     {
731         _prevPos = position;
732         _prevRot = rotation;
733         _prevScale = scale;
734 
735         _matrix = mat4f.identity;
736         // Scale
737         _matrix[ 0 ][ 0 ] = scale.x;
738         _matrix[ 1 ][ 1 ] = scale.y;
739         _matrix[ 2 ][ 2 ] = scale.z;
740 
741         // Rotate
742         _matrix = _matrix * rotation.toMatrix!4;
743 
744         // Translate
745         _matrix[ 0 ][ 3 ] = position.x;
746         _matrix[ 1 ][ 3 ] = position.y;
747         _matrix[ 2 ][ 3 ] = position.z;
748 
749         // include parent objects' transforms
750         if( owner.parent )
751             _matrix = owner.parent.transform.matrix * _matrix;
752 
753         // force children to update to reflect changes to this
754         // compensates for children that don't update properly when only parent is dirty
755         foreach( child; owner.children )
756             child.transform.updateMatrix();
757     }
758 }