1 module prova.core.entity;
2 
3 import prova;
4 
5 ///
6 class Entity
7 {
8   ///
9   Vector3 position;
10   ///
11   Quaternion rotation;
12   ///
13   Vector3 scale = Vector3(1, 1, 1);
14   ///
15   Vector3 velocity;
16   /// Velocity is multiplied by (1 - friction) every tick
17   float friction = 0;
18   package bool isSetup = false;
19   package Scene _scene;
20   package LinkedList!(Entity) children;
21   package LinkedList!(Collider2D) colliders2d;
22   package LinkedList!(AudioSource) audioSources;
23   private Entity _parent;
24   private LinkedList!(int) tags;
25 
26   ///
27   this()
28   {
29     children = new LinkedList!(Entity);
30     colliders2d = new LinkedList!(Collider2D);
31     audioSources = new LinkedList!(AudioSource);
32     tags = new LinkedList!(int);
33   }
34 
35   ///
36   final @property Scene scene()
37   {
38     if(!_scene)
39       throw new Exception("Entity is not attached to a scene");
40     return _scene;
41   }
42 
43   /// Shortcut for scene.game
44   final @property Game game()
45   {
46     return scene.game;
47   }
48 
49   ///
50   final @property Entity parent()
51   {
52     return _parent;
53   }
54 
55   package(prova) final @property void parent(Entity parent)
56   {
57     _parent = parent;
58   }
59 
60   /// Shortcut for scene.game.input
61   final @property Input input()
62   {
63     return scene.game.input;
64   }
65 
66   ///
67   final void addTag(int tag)
68   {
69     tags.insertBack(tag);
70   }
71 
72   ///
73   final void removeTag(int tag)
74   {
75     tags.remove(tag);
76   }
77 
78   ///
79   final bool hasTag(int tag)
80   {
81     return tags.contains(tag);
82   }
83 
84   /// Makes the passed entity a child of this entity
85   final void attach(Entity entity)
86   {
87     if(entity.parent || entity.parent == this)
88       throw new Exception("Entity already attached");
89 
90     if(_scene)
91       _scene.addEntity(entity);
92 
93     children.insertBack(entity);
94     entity.parent = this;
95   }
96 
97   /// Detach a child entity or the parent entity
98   final void detach(Entity entity)
99   {
100     if(parent == entity) {
101       parent.detach(this);
102       return;
103     }
104 
105     if(entity.parent != this)
106       throw new Exception("Entity was not attached");
107 
108     if(_scene && entity._scene)
109       _scene.removeEntity(entity);
110 
111     children.remove(entity);
112     entity.parent = null;
113   }
114 
115   ///
116   final void attach(Collider2D collider)
117   {
118     if(collider.entity || collider.entity == this)
119       throw new Exception("Collider already attached");
120 
121     if(_scene)
122       _scene.collider2DMap.add(collider);
123 
124     colliders2d.insertBack(collider);
125     collider.entity = this;
126   }
127 
128   ///
129   final void detach(Collider2D collider)
130   {
131     if(collider.entity != this)
132       throw new Exception("Collider was not attached");
133 
134     if(_scene)
135       _scene.collider2DMap.remove(collider);
136 
137     colliders2d.remove(collider);
138     collider.entity = null;
139   }
140 
141   ///
142   final void attach(AudioSource source)
143   {
144     if(source.entity || source.entity == this)
145       throw new Exception("AudioSource already attached");
146 
147     if(source.channels == 2)
148       throw new Exception("Source must use a mono format");
149 
150     if(_scene)
151       _scene.audioSources.insertBack(source);
152 
153     audioSources.insertBack(source);
154     source.entity = this;
155   }
156 
157   ///
158   final void detach(AudioSource source)
159   {
160     if(source.entity != this)
161       throw new Exception("Collider was not attached");
162 
163     if(_scene)
164       _scene.audioSources.remove(source);
165 
166     audioSources.remove(source);
167     source.entity = null;
168   }
169 
170   ///
171   final Matrix getLocalTransformMatrix()
172   {
173     Matrix transform = Matrix.identity;
174     transform = transform.scale(scale);
175     transform = transform.rotate(rotation);
176     transform = transform.translate(position);
177 
178     return transform;
179   }
180 
181   ///
182   final Matrix getWorldTransformMatrix()
183   {
184     Matrix transform = getLocalTransformMatrix();
185 
186     if(parent)
187       transform = parent.getWorldTransformMatrix() * transform;
188 
189     return transform;
190   }
191 
192   ///
193   final Vector3 getWorldPosition()
194   {
195     Matrix transform = getWorldTransformMatrix();
196 
197     return transform.transpose() * Vector3();
198   }
199 
200   ///
201   final Quaternion getWorldRotation()
202   {
203     Quaternion worldRotation;
204 
205     if(parent)
206       worldRotation = parent.getWorldRotation();
207 
208     return rotation * worldRotation;
209   }
210 
211   ///
212   final Vector3 getWorldScale()
213   {
214     Vector3 worldScale = Vector3(1, 1, 1);
215 
216     if(parent)
217       worldScale = parent.getWorldScale();
218 
219     return scale * worldScale;
220   }
221 
222   /// Called every draw tick (skipped if update loop is behind)
223   void draw(RenderTarget renderTarget, Matrix transform)
224   {
225     foreach(Entity child; children)
226       child.draw(renderTarget, transform * child.getLocalTransformMatrix());
227   }
228 
229   /// Called when first attached to a scene
230   void setup(){}
231   /// Called when attached to a scene
232   void start(){}
233   /// Called every update tick
234   void update(){}
235 
236   ///
237   void onCollisionEnter2D(Collider2D collider, Collider2D other){}
238   ///
239   void onCollision2D(Collider2D collider, Collider2D other){}
240   ///
241   void onCollisionExit2D(Collider2D collider, Collider2D other){}
242 }