1 /**
2  * Copyright:       Copyright (C) 2018 Gabriel Gheorghe, All Rights Reserved
3  * Authors:         $(Gabriel Gheorghe)
4  * License:         $(LINK2 https://www.gnu.org/licenses/gpl-3.0.txt, GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007)
5  * Source:          $(LINK2 https://github.com/GabyForceQ/LibertyEngine/blob/master/source/liberty/scene/impl.d)
6  * Documentation:
7  * Coverage:
8 **/
9 module liberty.scene.impl;
10 
11 import liberty.camera;
12 import liberty.core.engine;
13 import liberty.framework.light.impl;
14 import liberty.graphics.shader.impl;
15 import liberty.math.vector;
16 import liberty.scene.constants;
17 import liberty.scene.entity;
18 import liberty.scene.factory;
19 import liberty.scene.services;
20 import liberty.text.system;
21 import liberty.world.impl;
22 
23 /// A scene is a 3D space where you can place different objects,
24 /// like primitives, terrains, lights and guis.
25 /// It implements $(D ISceneFactory), $(D IStartable) and $(D IUpdateable) services.
26 final class Scene : ISceneFactory, IUpdateable {
27   ///
28   string id;
29   ///
30   bool initialized;
31   ///
32   string relativePath;
33   ///
34   Camera camera;
35   ///
36   Entity[string] entityMap;
37   ///
38   Vector3F startPoint;
39   ///
40   World world;
41   ///
42   Camera[string] cameraMap;
43   ///
44   IStartable[string] startableMap;
45   ///
46   IUpdateable[string] updateableMap;
47   ///
48   Shader[string] shaderMap;
49   ///
50   Light[string] lightMap;
51 
52   /// Create a scene using a unique id.
53   this(string id) {
54     CoreEngine.scene = this;
55 
56     this.id = id;
57     world = new World;
58     camera = spawn!Camera("DefaultCamera");
59   }
60 
61   /// Initialize scene.
62   /// Invoke start for all $(D IStartable) objects that have a start() method.
63   /// Returns reference to this so it can be used in a stream.
64   typeof(this) initialize() {
65     initialized = true;
66     
67     // Start all startable entitys
68     foreach (entity; startableMap)
69       entity.start;
70     
71     return this;
72   }
73 
74   /// Update all entitys that have an update() method.
75   /// These entitys must implement $(D IUpdateable).
76   /// It's called every frame.
77   void update() {
78     foreach (entity; updateableMap)
79       entity.update;
80   }
81 
82   /// Render all renderable systems.
83   /// It's called every frame after $(D Scene.update).
84   void render() {
85     // Apply all lights to the scene
86     applyLights;
87 
88     // Render all shaders to the scene
89     foreach (shader; shaderMap)
90       shader.render(this);
91   }
92 
93   ///
94   T entity(T)(string id)   {
95     return cast(T)entityMap[id];
96   }
97 
98   /// Spawn a scene entity using its reference.
99   /// You can specify where to spawn. By default is set to scene tree.
100   // Returns new entitys reference.
101   ref T spawn(T : Entity, bool STRAT = true)(ref T entity, string id, void delegate(T) initMethod = null) {
102     entity = new T(id, this);
103     insert(entity);
104 
105     static if (is(T == Camera))
106       this.scene.registerCamera(entity);
107 
108     static if (STRAT)
109       entity.start;
110 
111     if (initMethod !is null)
112       initMethod(entity);
113 	
114     return entity;
115   }
116 
117   /// Spawn a scene entity using its ID.
118   /// Second time you call this method for the same id, an assertion is produced.
119   /// Returns new entity reference.
120   T spawn(T : Entity, bool STRAT = true)(string id, void delegate(T) initMethod = null) {
121     T entity = new T(id);
122     entityMap[id] = entity;
123 
124     static if (is(T == Camera))
125       cameraMap[entity.id] = entity;
126 
127     static if (STRAT)
128       entity.start;
129 
130     if (initMethod !is null)
131       initMethod(entity);
132 
133     return entity;
134   }
135   
136   /// Spawn a scene entity using its reference.
137   /// Second time you call this method for the same id, nothing happens.
138   /// Returns old/new entity reference.
139   ref T spawnOnce(T : Entity, bool STRAT = true)(ref T entity, string id, void delegate(T) initMethod = null) {
140     if (id in singletonMap)
141       return cast(T)singletonMap[id];
142 
143     entity = new T(id);
144     entityMap[id] = entity;
145 
146     static if (is(T == Camera))
147       registerCamera(entity);
148     
149     singletonMap[id] = entity;
150 
151     static if (STRAT)
152       entity.start;
153 
154     if (initMethod !is null)
155       initMethod(entity);
156 
157     return entity;
158   }
159 
160   /// Spawn a scene entity using its ID.
161   /// Second time you call this method for the same id, nothing happens.
162   /// Returns old/new entity reference.
163   T spawnOnce(T : Entity, bool STRAT = true)(string id, void delegate(T) initMethod = null) {    
164     if (id in singletonMap)
165       return cast(T)singletonMap[id];
166 
167     T entity = new T(id, this);
168     insert(entity);
169 
170     static if (is(T == Camera))
171       scene.cameraMap[entity.id] = entity;
172 
173     singletonMap[id] = entity;
174 
175     static if (STRAT)
176       entity.start;
177 
178     if (initMethod !is null)
179       initMethod(entity);
180 
181     return entity;
182   }
183 
184   /// Remove a child entity using its reference.
185   /// Returns reference to this so it can be used in a stream.
186   typeof(this) remove(T : Entity)(T entity) {
187     import std.traits : EnumMembers;
188     import liberty.framework.light.impl : Light;
189     import liberty.framework.primitive.impl : Primitive;
190     import liberty.framework.skybox.impl : SkyBox;
191     import liberty.framework.terrain.impl : Terrain;
192     import liberty.framework.gui.impl : Gui;
193     import liberty.text.impl : Text;
194 
195     const id = entity.getId();
196 
197     if (id in childMap) {
198       // Remove entity from scene maps
199       static foreach (e; ["Startable", "Updateable", "Entity"])
200         mixin("scene.get" ~ e ~ "Map.remove(id);");
201 
202       // Remove entity from system
203       static foreach (sys; EnumMembers!SystemType)
204         static if (mixin("is(T : " ~ sys ~ ")"))
205           mixin("scene.get" ~ sys ~ "System.removeElementById(id);");
206       
207       //static if (is(T == Camera))
208       //  scene.safeRemoveCamera(id);
209 
210       // Remove entity from child map
211       childMap[id].destroy;
212       childMap[id] = null;
213       childMap.remove(id);
214       
215       return this;
216     }
217     
218     Logger.warning(
219       "You are trying to remove a null scene entity",
220       typeof(this).stringof
221     );
222 
223     return this;
224   }
225 
226   private void applyLights() {
227     foreach (id; ["Primitive", "Terrain"]) 
228       if (Shader.exists(id)) {
229         Shader
230           .getOrCreate(id)
231           .getProgram
232           .bind;
233 
234         foreach (light; lightMap)
235           if (light.visibility == Visibility.Visible)
236             light.applyTo(id);
237         
238         Shader
239           .getOrCreate(id)
240           .getProgram
241           .unbind;
242       }
243   }
244 }