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 }