1 module prova.core.scene;
2 
3 import derelict.openal.al,
4        prova,
5        std.algorithm,
6        std.math;
7 
8 ///
9 class Scene
10 {
11   ///
12   public Camera camera;
13   package LinkedList!(Audio) audioSources;
14   package SpacialMap2D collider2DMap;
15   package Game _game;
16   package bool isSetup;
17   private LinkedList!(Entity) entities;
18 
19   ///
20   this()
21   {
22     camera = new Camera();
23     collider2DMap = new SpacialMap2D();
24     entities = new LinkedList!(Entity);
25     audioSources = new LinkedList!(Audio);
26   }
27 
28   ///
29   final @property Game game()
30   {
31     if(!_game)
32       throw new Exception("Scene is not attached to a game");
33     return _game;
34   }
35 
36   ///
37   final @property Input input()
38   {
39     return game.input;
40   }
41 
42   ///
43   final Entity[] getEntities()
44   {
45     Entity[] clonedArray;
46     clonedArray.length = entities.length;
47     int i = 0;
48 
49     foreach(Node!Entity node; entities)
50       clonedArray[i++] = node.getValue();
51 
52     return clonedArray;
53   }
54 
55   ///
56   final void addEntity(Entity entity)
57   {
58     entities.insertBack(entity);
59     entity._scene = this;
60 
61     foreach(Audio source; entity.audioSources)
62       audioSources.insertBack(source);
63 
64     collider2DMap.add(entity.colliders2d);
65 
66     if(!entity.isSetup) {
67       entity.setup();
68       entity.isSetup = true;
69     }
70 
71     entity.start();
72   }
73 
74   ///
75   final void removeEntity(Entity entity)
76   {
77     entities.remove(entity);
78 
79     if(entity._scene == this)
80       entity._scene = null;
81 
82     foreach(Audio source; entity.audioSources)
83       audioSources.remove(source);
84 
85     collider2DMap.remove(entity.colliders2d);
86   }
87 
88   /// Finds the closest entity to this entity
89   Entity findClosestEntity(Entity entity)
90   {
91     return findClosestEntity(entity, 0, false);
92   }
93 
94   /// Finds the closest entity with the matching tag
95   Entity findClosestEntity(Entity entity, int tag)
96   {
97     return findClosestEntity(entity, tag, true);
98   }
99 
100   private Entity findClosestEntity(Entity entity, int tag, bool needsTag)
101   {
102     float closestDistance = -1;
103     Entity closestEntity = null;
104 
105     foreach(Entity other; entities)
106     {
107       // make sure we aren't matching with self
108       if(other == entity)
109         continue;
110 
111       // make sure tag matches
112       if(needsTag && !other.hasTag(tag))
113         continue;
114 
115       const float distance = other.position.distanceTo(entity.position);
116 
117       if(distance < closestDistance || closestDistance == -1) {
118         closestDistance = distance;
119         closestEntity = other;
120       }
121     }
122 
123     return closestEntity;
124   }
125 
126   /// Called when attached to a scene for the first time
127   void setup() { }
128   /// Called when attached to a scene
129   void start() { }
130 
131   /// Call super.update() to update entities if overridden
132   void update()
133   {
134     updateEntities();
135     updateCollisions();
136   }
137 
138   /// Called by Scene.update()
139   void updateEntities()
140   {
141     foreach(Entity entity; entities) {
142       entity.update();
143       entity.position += entity.velocity;
144       entity.velocity *= 1 - entity.friction;
145     }
146   }
147 
148   /// Called by Scene.update()
149   void updateCollisions()
150   {
151     collider2DMap.mapColliders();
152     collider2DMap.markCollisions();
153     collider2DMap.resolveCollisions();
154   }
155 
156   package void updateAudio()
157   {
158     Vector3 position = camera.position / Audio.scale;
159     Vector3[] orientation = [
160       camera.rotation * Vector3(0, 0, -1), // forward
161       camera.rotation * Vector3(0, 1, 0) // up
162     ];
163 
164     alListener3f(AL_POSITION, position.x, position.y, position.z);
165     alListenerfv(AL_ORIENTATION, cast(float*) orientation.ptr);
166 
167     foreach(Audio source; audioSources)
168       source.update();
169   }
170 
171   /**
172    * All draw operations performed here will be affected by the camera
173    *
174    * Call super.draw(renderTarget) to render entities if overridden
175    */
176   void draw(RenderTarget renderTarget)
177   {
178     Entity[][float] distanceMappedEntities;
179 
180     foreach(Entity entity; entities)
181     {
182       float distance;
183 
184       if(camera.sortingMethod == SortingMethod.Distance)
185         distance = entity.position.distanceTo(camera.position);
186       else
187         distance = camera.position.z - entity.position.z;
188       
189       distanceMappedEntities[distance] ~= entity;
190     }
191 
192     foreach_reverse(float key; sort(distanceMappedEntities.keys))
193       foreach(Entity entity; distanceMappedEntities[key])
194         entity.draw(renderTarget, entity.getLocalTransformMatrix());
195   }
196 
197   /**
198    * All draw operations here will be affected by a static orthographic perspective
199    *
200    * Origin is moved to the bottom left of the window
201    */
202   void drawStatic(RenderTarget renderTarget) { }
203 }