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 }