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 }