1 /*physics.d by Ruby The Roobster*/
2 /*Version 1.0.0 release*/
3 /*Module for basic physics in the D Programming Language 2.0*/
4 /*This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.*/
16 /** Copyright: 2021, Ruby The Roobster*/
17 /**Author: Ruby The Roobster, rubytheroobster@yandex.com*/
18 /**Date: November 8, 2021*/
19 /** License: GPL-3.0*/
20 
21 ///NOTICE: THIS CODE OBVIOUSLY SUCKS.  IT WILL BE DELETED AND REPLACED WITH SOMETHING ACTUALLY USUABLE.
22 
23 module dutils.physics;
24 version(DLL)
25 {
26 	
27 }
28 else
29 {
30 	public import dutils.skeleton;
31 
32 	///Struct for representing gravity.
33 	public struct Gravity
34 	{
35 		/** The axis which the gravity pulls toward.*/ Axis axis = Axis.y;
36 		/** The strength per frame of the gravity. */ real strength = 0;
37 	}
38 
39 	///Enumeration for representing an axis.
40 	public enum Axis { /**The x-axis.*/ x, /**The y-axis.*/y, /**The z-axis.*/ z}
41 	///Enumeration for representing a plane.
42 	public enum Plane { /**Plane xy*/ xy, /**Plane xz*/ xz, /**Plane zy*/ zy}
43 
44 	package mixin template __mov__general__(string func)
45 	{
46 		void __mov__general__(real accdec = 0, Gravity gravity = Gravity(Axis.y, 0))
47 		{
48 			import core.thread;
49 			Point moveby;
50 			bool b = false;
51 			auto ori = tomove;
52 				debug import std.stdio : writeln;
53 			if(speed > 1 || speed < -1)
54 			{
55 				moveby.x = moveto.x / speed;
56 				moveby.y = moveto.y / speed;
57 				moveby.z = moveto.z / speed;
58 				b = true;
59 			}
60 			else
61 			{
62 				moveby.x = moveto.x * speed;
63 				moveby.y = moveto.y * speed;
64 				moveby.z = moveto.z * speed;
65 			}
66 			while(!((tomove.center.x > moveto.x && moveto.x > 0) ^ (tomove.center.x < moveto.x && moveto.x < 0)))
67 			{
68 				static if(func[0] == 'a')
69 				{
70 					speed += accdec;
71 					if(b)
72 					{
73 						moveby.x = moveto.x / speed;
74 						moveby.y = moveto.y / speed;
75 						moveby.z = moveto.z / speed;
76 					}
77 					else
78 					{
79 						moveby.x = moveto.x * speed;
80 						moveby.y = moveto.y * speed;
81 						moveby.z = moveto.z * speed;
82 					}
83 				}
84 				else static if(func[0] == 'd')
85 				{
86 					speed -= accdec;
87 					if(b)
88 					{
89 						moveby.x = moveto.x / speed;
90 						moveby.y = moveto.y / speed;
91 						moveby.z = moveto.z / speed;
92 					}
93 					else
94 					{
95 						moveby.x = moveto.x * speed;
96 						moveby.y = moveto.y * speed;
97 						moveby.z = moveto.z * speed;
98 					}
99 				}
100 				else
101 				{
102 				}
103 				foreach(i;tomove.faces)
104 				{
105 					foreach(k;i.lines)
106 					{
107 						foreach(j;k.mid_points)
108 						{
109 							j += moveby;
110 						}
111 						k.start += moveby;
112 						k.stop += moveby;
113 					}
114 					i.center += moveby;
115 				}
116 				tomove.center += moveby;
117 				Thread.sleep(dur!"msecs"(tbf));
118 				static if(func[0] == 'a')
119 				{
120 					speed += accdec;
121 				}
122 				else static if(func[0] == 'd')
123 				{
124 					speed -= accdec;
125 				}
126 				else
127 				{
128 				}
129 			}
130 			foreach(i;ori.faces)
131 			{
132 				foreach(j;i.lines)	
133 				{
134 					foreach(k;j.mid_points)
135 					{
136 						k += moveto;
137 					}
138 					j.start += moveto;
139 					j.stop += moveto;
140 				}
141 				i.center += moveto;
142 			}
143 			ori.center += moveto;
144 			tomove = ori;
145 		}
146 	}
147 
148 	  /**move moves all the points in a skeleton to a specified point with a specified time gap between moving the points.
149 	  Params:
150 		moveto =	A point specifying the total amount to move along each axis.
151 		tbf =	The time in miliseconds between 'frames'(a frame is one section of moving points before waiting a bit).  This gives an illusion of continuous motion.
152 		tomove =	The skeleton being moved.
153 		speed =	The speed at which to move the points.
154 	  Returns:
155 	  none*/
156 	pragma(inline, true) public void move(Point moveto, uint tbf, ref shared Skeleton tomove, real speed)
157 	{
158 		mixin __mov__general__!"n";
159 		__mov__general__();
160 	}
161 
162 	  /**accMove moves all the points in a skeleton to a specified point with a specified time gap between movements all while accelerating the speed.
163 	  Params:
164 		moveto = 	A point specifying the total amount to move along each axis.
165 		tbf = 	The time in miliseconds between 'frames'(a frame is one section of moving points before waiting a bit).  This gives an illusion of continuous motion.
166 		tomove = 	The skeleton being moved.
167 		speed  = 	The original speed at which the skeleton moves.
168 		accdec = 	The amount to increment the speed by each frame.
169 	  Returns: none*/
170 	pragma(inline, true) public void accMove(Point moveto, uint tbf, shared ref Skeleton tomove, real speed, real accdec = 0)
171 	{
172 		mixin __mov__general__!"a";
173 		__mov__general__(accdec);
174 	}
175 
176 	  /**decMove moves all the points in a skeleton to a specified point with a specified time gap between movements all while deaccelerating the speed.
177 	  Params:
178 		moveto = 	A point specifying the total amount to move along each axis.
179 		tbf = 	The time in miliseconds between 'frames'(a frame is one section of moving points before waiting a bit).  This gives an illusion of continuous motion.
180 		tomove = 	The skeleton being moved.
181 		speed  = 	The original speed at which the skeleton moves.
182 		accdec = 	The amount to decrement the speed by each frame.
183 	   Returns: none*/
184 	pragma(inline) public void decMove(Point moveto, uint tbf, shared ref Skeleton tomove, real speed, real accdec = 0)
185 	{
186 		mixin __mov__general__!"d";
187 		__mov__general__(accdec);
188 	}
189 
190 	///Collision is a structure representing if a collision happened, and the object collided with.
191 	public struct Collision
192 	{
193 		///True if the collision actually occured.
194 		bool collided;
195 		///The skeleton collided with.
196 		shared Skeleton hitby;
197 	}
198 
199 	  /**detectCollision takes a skeleton, a wait time, and an array of skeletons, and detects collisions, returning true if so.
200 	  Params:
201 			towatch =    A shared array of skeletons that the functions dectects collisions against.
202 			skele =     A skeleton that the function dectects collisions against the array of skeletons with.
203 			time =     The number of miliseconds to wait before exiting.  Infinete when set to real.infinity.
204 	   Returns:
205 	   A collision structure.
206 	  */
207 	public Collision detectCollision(shared Skeleton[] towatch, shared Skeleton skele, in real time = 0)
208 		in	{
209 			auto a = cast(ulong)time;
210 			assert(a == time || time == real.infinity,"Parameter time must always be a whole number or infinity!");
211 		}
212 		do	{
213 			mixin find!(["x", "y", "z"]);
214 			import std.datetime.stopwatch;
215 			auto sw = StopWatch(AutoStart.no);
216 			sw.start();
217 			scope(exit) sw.stop();
218 			while(true)
219 			{
220 				foreach(i;towatch)
221 				{
222 					foreach(j;i.faces)
223 					{
224 						foreach(k;j.lines)
225 						{
226 							foreach(l;k.mid_points)
227 							{
228 								foreach(m;skele.faces)
229 								{
230 									foreach(n;m.lines)
231 									{
232 										for(uint o; o < n.mid_points.length+1; o++)
233 										{
234 												
235 										pragma(inline, true) bool switcho(Point toswitch)
236 										{
237 											switch(o)
238 											{
239 												default:
240 													if(o == n.mid_points.length)
241 													{
242 														find([n.mid_points[o-1], n.stop]);
243 														return Collision(true, i).collided;
244 													}
245 													else
246 													{
247 														find([n.mid_points[o-1], n.mid_points[o]]);
248 													}
249 													if(toswitch.x <= highx && toswitch.x >= lowx && toswitch.y <= highy && toswitch.y >= lowy && toswitch.z <= highz && toswitch.z >= lowz)
250 													{
251 														return Collision(true, i).collided;
252 													}
253 													break;
254 													case 0:
255 													find([n.start, n.mid_points[o]]);
256 													if(toswitch.x <= highx && toswitch.x >= lowx && toswitch.y <= highy && toswitch.y >= lowy && toswitch.z <= highz && toswitch.z >= lowz)
257 													{
258 														return Collision(true, i).collided;
259 													}
260 												}
261 												assert(false, "Hidden function switcho has a bug, file an issue.");
262 										}
263 											if(switcho(l))
264 												return Collision(true, i);
265 											if(switcho(k.start))
266 												return Collision(true, i);
267 											if(switcho(k.stop))
268 												return Collision(true, i);
269 										}
270 									}
271 								}
272 							}
273 						}
274 					}
275 				}
276 				if(!(sw.peek.total!"msecs" <= time) && time != real.infinity)
277 					break;
278 			}
279 			return Collision(false, skele);
280 		}
281 
282 	package mixin template find(string[] tofind)
283 	{
284 		static foreach(i; tofind)	{
285 			mixin("real high" ~ i ~ ";");
286 			mixin("real low" ~ i ~ ";");
287 		}
288 		void find(Point[2] tof)
289 		{
290 			static foreach(i; tofind)
291 			{
292 				mixin("high" ~ i ~ " = tof[0]." ~ i ~ " >= tof[1]." ~ i ~ " ? tof[0]." ~ i ~ " : tof[1]." ~ i ~ ";");
293 				mixin("low" ~ i ~ " = tof[0]." ~ i ~ " <= tof[1]." ~ i ~ " ? tof[0]." ~ i ~ " : tof[1]." ~ i ~ ";");
294 			}
295 		}
296 	}
297 
298 	/**affectByGravity affects a skeleton by a specified gravity struct.  Send any integer to the thread containing the function to terminate it.
299 	Params:
300 	towatch =    A shared array of skeletons that is used for collision checking.
301 	skele =    A shared skeleton that is affected by gravity itself.
302 	tbf =  The wait time between frames in miliseconds, operations not included.  Set to at least 1, as the function spends 1 milisecond waiting for messages.
303 	gravity =    A gravity struct that gives the axis and strength specifications.
304 	Returns: none.*/
305 	pragma(inline, true) public void affectByGravity(shared Skeleton[] towatch, ref shared Skeleton skele, in uint tbf, Gravity gravity)
306 	{
307 			import std.concurrency;
308 		import core.thread;
309 		import std.datetime;
310 		void __dumbswitchiepoo__dumb(ref shared Point l)
311 		{
312 			switch(gravity.axis)
313 			{
314 				case Axis.x:
315 						if((gravity.strength > 0 && l.x > 0) ^ (gravity.strength < 0 && l.x < 0) || detectCollision(towatch, skele, 0).collided)
316 							l.x = l.x - gravity.strength;
317 						break;
318 				case Axis.y:
319 					if((gravity.strength > 0 && l.y > 0) ^ (gravity.strength < 0 && l.y < 0) || detectCollision(towatch, skele, 0).collided)				
320 							l.y = l.y - gravity.strength;
321 					break;
322 				case Axis.z:
323 					if((gravity.strength > 0 && l.z > 0) ^ (gravity.strength < 0 && l.z < 0) || detectCollision(towatch, skele, 0).collided)
324 						l.z = l.z - gravity.strength;
325 					break;
326 				default:
327 					throw new Exception("An error obviously caused by a library developer has occured.  Please file an issue.");
328 			}
329 		}
330 		while(true)
331 		{
332 			if(receiveTimeout(dur!"msecs"(1), function void(int x) { }))
333 			{
334 			  break;
335 			}
336 			foreach(ref i;skele.faces)
337 			{
338 				foreach(ref j;i.lines)
339 				{
340 					foreach(ref k;j.mid_points)
341 					{
342 						__dumbswitchiepoo__dumb(k);
343 					}
344 					__dumbswitchiepoo__dumb(j.start);
345 					__dumbswitchiepoo__dumb(j.stop);
346 				}
347 				__dumbswitchiepoo__dumb(i.center);
348 			}
349 			__dumbswitchiepoo__dumb(skele.center);
350 			Thread.sleep(dur!"msecs"(tbf));
351 		}
352 	}
353 
354 	/**Rotates a skeleton by an amount in radians in anamount of frames with a specifed time between frames.
355 	   Params:
356 		degrees =    The number of radians to rotate the Skeleton by.
357 		plane =    The plane to rotate the Skeleton on.
358 		frames =    The number of frames to rotate the Skeleton in.
359 		rate =    The amount of time in miliseconds to wait between processing each frame.
360 		skele =    The Skeleton to rotate.
361 	*/
362 	public void rotate(in real degrees, in Plane plane, in ulong frames, in ulong rate, ref shared Skeleton skele)
363 	{
364 		ulong fcounter = 0;
365 		assert(frames != 0); //Prevent division by 0.
366 		import std.math.algebraic : abs;
367 		import std.math.trigonometry : sin;
368 		import std.math.trigonometry: cos;
369 		import core.thread;
370 		bruh:
371 		switch(plane)
372 		{
373 			static foreach(a; ["xy", "xz", "zy"])
374 			{
375 				mixin("case Plane." ~ a ~ ":");
376 				while(fcounter <= frames)
377 				{
378 					foreach(ref i; skele.faces)
379 					{
380 						foreach(ref j; i.lines)
381 						{
382 							foreach(ref k; j.mid_points)
383 							{
384 								mixin("real radius = abs(cast(real)(skele.center." ~ [a[0]] ~ " - k." ~ [a[0]] ~ "));"); //Define the radius of the circle of rotation.
385 								//Do the rotation thingy.
386 								mixin("k." ~ [a[0]] ~ " = (cos(degrees/frames) * radius) + skele.center." ~ [a[0]] ~ ";");
387 								//As above
388 								mixin("radius = abs(cast(real)(skele.center." ~ [a[1]] ~ " - k." ~ [a[1]] ~ "));");
389 								mixin("k." ~ [a[1]] ~ " = (sin(degrees/frames) * radius) + skele.center." ~ [a[1]] ~ ";");
390 							}
391 							//Rinse and Repeat!
392 							mixin("real radius = abs(cast(real)(skele.center." ~ [a[0]] ~ " - j.start." ~ [a[0]] ~ "));");
393 							mixin("j.start." ~ [a[0]] ~ " = (cos(degrees/frames) * radius) + skele.center." ~ [a[0]] ~ ";");
394 							mixin("radius = abs(cast(real)(skele.center." ~ [a[1]] ~ " - j.start." ~ [a[1]] ~ "));");
395 							mixin("j.start." ~ [a[1]] ~ " = (sin(degrees/frames) * radius) + skele.center." ~ [a[1]] ~ ";");
396 							mixin("radius = abs(cast(real)(skele.center." ~ [a[0]] ~ " - j.stop." ~ [a[0]] ~ "));");
397 							mixin("j.stop." ~ [a[0]] ~ " = (cos(degrees/frames) * radius) + skele.center." ~ [a[0]] ~ ";");
398 							mixin("radius = abs(cast(real)(skele.center." ~ [a[1]] ~ " - j.stop." ~ [a[1]] ~ "));");
399 							mixin("j.stop." ~ [a[1]] ~ " = (sin(degrees/frames) * radius) + skele.center." ~ [a[1]] ~ ";");
400 						}
401 						mixin("real radius = abs(cast(real)(skele.center." ~ [a[0]] ~ " - i.center." ~ [a[0]] ~ "));");
402 						mixin("i.center." ~ [a[0]] ~ " = (cos(degrees/frames) * radius) + skele.center." ~ [a[0]] ~ ";");
403 						mixin("radius = abs(cast(real)(skele.center." ~ [a[1]] ~ " - i.center." ~ [a[1]] ~ "));");
404 						mixin("i.center." ~ [a[1]] ~ " = (sin(degrees/frames) * radius) + skele.center." ~ [a[1]] ~ ";");
405 					}
406 					Thread.sleep(dur!"msecs"(rate)); //Time between frames.
407 					++fcounter;
408 				}
409 				mixin("break bruh;");
410 			}
411 			default:
412 				throw new Exception("Invalid Plane, somehow.  Either I screwed up or you did.  If you didn't touch the code, open an issue.");
413 		}
414 	}
415 }
416