1 /**
2  * Defines the GameObject class, to be subclassed by scripts and instantiated for static objects.
3  */
4 module core.gameobject;
5 import core.prefabs, core.properties;
6 import components;
7 import graphics.graphics, graphics.shaders;
8 import utility.config;
9 
10 import yaml;
11 import gl3n.linalg, gl3n.math;
12 
13 import std.signals, std.conv, std.variant;
14 
15 class GameObject
16 {
17 public:
18 	/**
19 	 * The current transform of the object.
20 	 */
21 	mixin Property!( "Transform", "transform", "public" );
22 	/**
23 	 * The Material belonging to the object
24 	 */
25 	mixin Property!( "Material", "material", "public" );
26 	/**
27 	 * The Mesh belonging to the object
28 	 */
29 	mixin Property!( "Mesh", "mesh", "public" );
30 	/**
31 	* The light attached to this object
32 	*/
33 	mixin Property!( "Light", "light", "public" );
34 	/**
35 	* The camera attached to this object
36 	*/
37 	mixin Property!( "Camera", "camera", "public" );
38 	/**
39 	 * The object that this object belongs to
40 	 */
41 	mixin Property!( "GameObject", "parent" );
42 	/**
43 	 * All of the objects which list this as parent
44 	 */
45 	mixin Property!( "GameObject[]", "children" );
46 
47 	mixin Signal!( string, string );
48 
49 	/**
50 	 * Create a GameObject from a Yaml node.
51 	 */
52 	static GameObject createFromYaml( Node yamlObj )
53 	{
54 		GameObject obj;
55 		Variant prop;
56 		Node innerNode;
57 
58 		// Try to get from script
59 		if( Config.tryGet!string( "Script.ClassName", prop, yamlObj ) )
60 		{
61 			const ClassInfo scriptClass = ClassInfo.find( prop.get!string );
62 
63 			if( Config.tryGet!string( "InstanceOf", prop, yamlObj ) )
64 			{
65 				obj = Prefabs[ prop.get!string ].createInstance( scriptClass );
66 			}
67 			else
68 			{
69 				obj = cast(GameObject)scriptClass.create();
70 			}
71 		}
72 
73 		if( Config.tryGet!string( "InstanceOf", prop, yamlObj ) )
74 		{
75 			obj = Prefabs[ prop.get!string ].createInstance();
76 		}
77 		else
78 		{
79 			obj = new GameObject;
80 		}
81 
82 		if( Config.tryGet!string( "Camera", prop, yamlObj ) )
83 		{
84 			auto cam = new Camera;
85 			obj.addComponent( cam );
86 			cam.owner = obj;
87 		}
88 
89 		if( Config.tryGet!string( "Material", prop, yamlObj ) )
90 		{
91 			obj.addComponent( Assets.get!Material( prop.get!string ) );
92 		}
93 
94 		if( Config.tryGet!string( "Mesh", prop, yamlObj ) )
95 		{
96 			obj.addComponent( Assets.get!Mesh( prop.get!string ) );
97 		}
98 
99 		if( Config.tryGet( "Transform", innerNode, yamlObj ) )
100 		{
101 			vec3 transVec;
102 			if( Config.tryGet( "Scale", transVec, innerNode ) )
103 				obj.transform.scale = transVec;
104 			if( Config.tryGet( "Position", transVec, innerNode ) )
105 				obj.transform.position = transVec;
106 			if( Config.tryGet( "Rotation", transVec, innerNode ) )
107 				obj.transform.rotation = quat.euler_rotation( radians(transVec.y), radians(transVec.z), radians(transVec.x) );
108 		}
109 
110 		if( Config.tryGet!Light( "Light", prop, yamlObj ) )
111 		{
112 			obj.addComponent( prop.get!Light );
113 		}
114 
115 		obj.transform.updateMatrix();
116 		return obj;
117 	}
118 
119 	/**
120 	 * Creates basic GameObject with transform and connection to transform's emitter.
121 	 */
122 	this()
123 	{
124 		transform = new Transform( this );
125 		transform.connect( &emit );
126 	}
127 
128 	~this()
129 	{
130 		destroy( transform );
131 	}
132 
133 	/**
134 	 * Called once per frame to update all components.
135 	 */
136 	final void update()
137 	{
138 		onUpdate();
139 
140 		foreach( ci, component; componentList )
141 			component.update();
142 	}
143 
144 	/**
145 	 * Called once per frame to draw all components.
146 	 */
147 	final void draw()
148 	{
149 		onDraw();
150 
151 		if( mesh !is null )
152 		{
153 			Graphics.drawObject( this );
154 		}
155 		if( light !is null )
156 		{
157 			Graphics.addLight( light );
158 		}
159 	}
160 
161 	/**
162 	 * Called when the game is shutting down, to shutdown all components.
163 	 */
164 	final void shutdown()
165 	{
166 		onShutdown();
167 
168 		/*foreach_reverse( ci, component; componentList )
169 		{
170 			component.shutdown();
171 			componentList.remove( ci );
172 		}*/
173 	}
174 
175 	/**
176 	 * Adds a component to the object.
177 	 */
178 	final void addComponent( T )( T newComponent ) if( is( T : Component ) )
179 	{
180 		componentList[ T.classinfo ] = newComponent;
181 
182 		// Add component to proper property
183 		if( typeid( newComponent ) == typeid( Material ) )
184 			material = cast(Material)newComponent;
185 		else if( typeid( newComponent ) == typeid( Mesh ) )
186 			mesh = cast(Mesh)newComponent;
187 		else if( typeid( newComponent ) == typeid( DirectionalLight ) || 
188 				 typeid( newComponent ) == typeid( AmbientLight ) )
189 			light = cast(Light)newComponent;
190 		else if( typeid( newComponent ) == typeid( Camera ) )
191 			camera = cast(Camera)newComponent;
192 	}
193 
194 	/**
195 	 * Gets a component of the given type.
196 	 */
197 	final T getComponent( T )() if( is( T : Component ) )
198 	{
199 		return componentList[ T.classinfo ];
200 	}
201 
202 	final void addChild( GameObject object )
203 	{
204 		object._children ~= object;
205 		object.parent = this;
206 	}
207 
208 	/// Called on the update cycle.
209 	void onUpdate() { }
210 	/// Called on the draw cycle.
211 	void onDraw() { }
212 	/// Called on shutdown.
213 	void onShutdown() { }
214 	/// Called when the object collides with another object.
215 	void onCollision( GameObject other ) { }
216 
217 private:
218 	Component[ClassInfo] componentList;
219 }
220 
221 class Transform
222 {
223 public:
224 	this( GameObject obj = null )
225 	{
226 		owner = obj;
227 		position = vec3(0,0,0);
228 		scale = vec3(1,1,1);
229 		rotation = quat.identity;
230 		updateMatrix();
231 	}
232 
233 	~this()
234 	{
235 		//destroy( position );
236 		//destroy( rotation ); 
237 		//destroy( scale );
238 	}
239 
240 	mixin Property!( "GameObject", "owner" );
241 	vec3 position;
242 	quat rotation;
243 	vec3 scale;
244 	//mixin EmmittingProperty!( "vec3", "position", "public" );
245 	//mixin EmmittingProperty!( "quat", "rotation", "public" );
246 	//mixin EmmittingProperty!( "vec3", "scale", "public" );
247 
248 	/**
249 	* This returns the object's position relative to the world origin, not the parent
250 	*/
251 	final @property vec3 worldPosition()
252 	{
253 		if( owner.parent is null )
254 			return position;
255 		else
256 			return owner.parent.transform.worldPosition + position;
257 	}
258 
259 	/**
260 	* This returns the object's rotation relative to the world origin, not the parent
261 	*/
262 	final @property quat worldRotation()
263 	{
264 		if( owner.parent is null )
265 			return rotation;
266 		else
267 			return owner.parent.transform.worldRotation * rotation;
268 	}
269 
270 	final @property mat4 matrix()
271 	{
272 		if( _matrixIsDirty )
273 			updateMatrix();
274 
275 		if( owner.parent is null )
276 			return _matrix;
277 		else
278 			return owner.parent.transform.matrix * _matrix;
279 	}
280 
281 	mixin Signal!( string, string );
282 
283 	final void updateMatrix()
284 	{
285 		_matrix = mat4.identity;
286 		// Scale
287 		_matrix.scale([scale.x, scale.y, scale.z]);
288 		//Rotate
289 		_matrix.rotation( rotation.to_matrix!( 3, 3 ) );
290 		// Translate
291 		_matrix.translation([position.x, position.y, position.z]);
292 
293 		_matrixIsDirty = false;
294 	}
295 
296 private:
297 	mat4 _matrix;
298 	bool _matrixIsDirty;
299 
300 	final void setMatrixDirty( string prop, string newVal )
301 	{
302 		_matrixIsDirty = true;
303 	}
304 }