Ooops
November 25, 2020, 08:10:10 AM

### Author Topic: [bb] Smooth Movement for Entities by Rob the Great [ 1+ years ago ]  (Read 616 times)

#### BlitzBot

• Jr. Member
• Posts: 1
##### [bb] Smooth Movement for Entities by Rob the Great [ 1+ years ago ]
« on: June 29, 2017, 12:28:39 AM »
Title : Smooth Movement for Entities
Author : Rob the Great
Posted : 1+ years ago

Description : Please see the comments in my code for the complete description. Essentially, you can include this code to gradually move an entity from point A to point B, including rotation. The entity starts off with zero velocity, picks up speed to 100% velocity (based on distance and time traveled), and then slows down back to zero velocity at the end of the movement. The math is contained in a single function, you just simply need to create a starting pivot and an ending pivot oriented to the beginning and final position for the entity to be moved.

I designed this for my purposes, but it turned out well, so I decided to share it with the community. Let me know if any major bugs or glitches occur (I did some testing, but not thorough testing).

Enjoy!

Code was last edited on 06-10-2013 (removed a small bug).

Code :
Code: BlitzBasic
1. ;====================================================================
2. ; Project: Smooth Movement Function
3. ; Version: 1.0
4. ; Author: Rob the Great
5. ; Email: robmerrick181@gmail.com
6. ; Copyright: No Copyright, free for public use (commercial and non-commericial)
7. ; Description:    This is a single function that will apply smooth 3D movement
8. ;                                 on an entity from point A to point B. The function can loop
9. ;                                 on its own until the movement is complete, or it can be
10. ;                                 iterated within an external loop. The function returns 0 if
11. ;                                 the movement is still going, or 1 if the movement is complete.
12. ;====================================================================
13.
14. ;Everything in this block needs to be copied in order to use it in your project.
15. ;BEGIN BLOCK
16. ;===============================================================================
17. ;I chose to use a type for ease and flow. This also allows for more than one entity to move at the same time, but looping must be disabled for that to work.
18.
19. Type smoothmovement
20.
21.         Field entity
22.         Field movevalue#
23.         Field amplitude#
24.         Field pitchamplitude#
25.         Field yawamplitude#
26.         Field rollamplitude#
27.         Field snapped
28.
29. End Type
30.
31. ;This function will move an entity from point A to point B in a smooth manner.
32. ;It will begin with a velocity of 0 and will accelerate until it is half-way
33. ;to the end point, and then decelerate until it reaches the end point at a
34. ;velocity of 0. This is just like PositionEntity, only without the snapping.
35. ;This function will also rotate that same entity from rotation A to rotation
36. ;B in a smooth manner.
37. ;
38. ;This function requires the creation of two pivots, one for point A and the
39. ;other for point B. I used pivots to cut back on the number of parameters
40. ;needed to make this function work. Simply create the two pivots and position
41. ;and rotate them to represent the starting and ending points for the entity
42. ;to be moved. Don't forget to free the pivots once you are done, because the
43. ;function won't do this for you (unless you add it in yourself above the
44. ;Return 1 line in the SmoothMove() function.)
45. ;
46. ;The function has two modes: internal loop or external loop. The internal loop
47. ;is more for my purposes, but I went ahead and included it in this demo. It will
48. ;loop on its own and automatically end when it reaches its destination. This is
49. ;especially useful if you wish to move the camera, but not so much for other
50. ;entities. The other mode is the external loop, which just means that you have
51. ;to continously call this function in a loop until it returns 1. The second
52. ;option is more likely what you want out of this function, and is thus the default.
53. ;
54. ;Parameters: entity
55. ;                                       -the handle of the entity to be moved from point A to point B.
56. ;                        startpiv
57. ;                                       -the handle of a pivot created to store the starting point of
58. ;                                       -the entity to be moved. Make sure it is positioned at the
59. ;                                       -starting coordinates and that it is rotated to the starting values.
60. ;                                       -Note that the entity will automatically snap to the startpiv's
61. ;                                       -beginning position and rotation to ensure the math is right.
62. ;                        endpiv
63. ;                                       -the handle of a pivot created to store the ending point of the
64. ;                                       -entity to be moved. Make sure that it is positioned at the
65. ;                                       -ending coordinates and that it is rotated to the ending values.
66. ;                        framecount# = 120.0
67. ;                                       -the number of iterations to make until the entity has moved from
68. ;                                       -point A to point B. The default is 120 frames (a floating point
69. ;                                       -number is used for math reasons, but this is untested with values
70. ;                                       -other than integers for arguments). If needed, set this to 0 or 1 to
71. ;                                       -instantly snap the entity to the new position and rotation. This is
72. ;                                       -the same thing as calling PoisitionEntity() and RotateEntity()
73. ;                                       -except that the math will be a little more intense on the
74. ;                                       -processor using this method. It's your call.
75. ;                        loop = 0
76. ;                                       -set this parameter to 0 to return after each iteration, or set it
77. ;                                       -to 1 to have the function loop on its own until it is finished. The
78. ;                                       -default is set to 0
79. ;
80. ;Returns 0 if the entity is not done moving (and loop is off), or 1 if the movement is
81. ;complete.
82. ;
83. ;NOTES: This function will rotate an entity as well as position it. The rotation works for
84. ;all Blitz rotation values, but keep in mind that if it passes 180 degrees, it will rotate
85. ;more than once before reaching the final destination. This function will snap the entity
86. ;to the starting rotation of the startpiv to ensure the math works as it should. Because
87. ;of this, the entity can accidently snap into it's starting position if this is different
88. ;from its current position. Also note that this function is only designed to handle Global
89. ;position and rotation values, so keep than in mind when you set up your start and end pivots.
90.
91. Function SmoothMove(entity,startpiv,endpiv,framecount# = 120.0,loop = 0)
92.
93.
94.         If entity = 0 Or startpiv = 0 Or endpiv = 0 ;check for valid handles
95.                 RuntimeError "Please specificy a valid handle for entity, startpiv, and endpiv."
96.         EndIf
97.         If EntityDistance(entity,endpiv) <= 0.01 ;bail if entity is already at the end point (useful for multiple entity movements)
98.                 If Abs(EntityPitch(entity,True) - EntityPitch(endpiv,True)) <= 0.01
99.                         If Abs(EntityYaw(entity,True) - EntityYaw(endpiv,True)) <= 0.01
100.                                 If Abs(EntityRoll(entity,True) - EntityRoll(endpiv,True)) <= 0.01
101.                                         Return 1
102.                                 EndIf
103.                         EndIf
104.                 EndIf
105.         EndIf
106.         ;make variables to store the entity's current rotation
107.         Local temppitch#
108.         Local tempyaw#
109.         Local temproll#
110.
111.         ;define a name for the smoothmovement type
112.         Local s.smoothmovement
113.
114.         ;make a variable to know whether or not the type has already been created (only applies if looping is enabled)
116.
117.         ;even though this is a loop, the function will return if looping is disabled
118.         Repeat
119.
120.                 ;here's a panic button. remove if needed.
121.                 If KeyDown(1) Then End
122.
123.                 If (typemade) Or (Not loop) ;if the type has already been made or looping is disabled
124.                         For s.smoothmovement = Each smoothmovement
125.                                 If sentity = entity ;find the type with the entity to be moved. Note that this function can handle more than one movement if looping is disabled.
126.                                         temppitch# = EntityPitch#(sentity,True) ;store the rotation values
127.                                         tempyaw# = EntityYaw#(sentity,True) ;store the rotation values
128.                                         temproll# = EntityRoll#(sentity,True) ;store the rotation values
129.                                         PointEntity sentity,endpiv ;aim the entity at the end point
130.                                         Local newvalue#
131.                                         newvalue = Sin#(smovevalue#/framecount#) ;This only needs to be calculated once per iteration, but is used multiple times.
132.                                         ;the next line is a very simplified form of a very complex equation. See the comments at the end for complete details.
137.                                         RotateEntity sentity,temppitch#+newvalue#*spitchamplitude#,tempyaw#+newvalue#*syawamplitude,temproll#+newvalue#*s
138. ollamplitude#,True
139.                                         ;turn the entity back to how it was facing and then apply a gradual turn to slowly orient toward the final rotation.
140.                                         smovevalue# = smovevalue# + 180.0 ;this increases by 1*180 each iteration. Think of it as counting the number of frames that have passed
141.                                                                                                                 ;the *180 comes from simplifying the equation above.
142.                                         If smovevalue# >= (framecount#*180) ;If we go above the frame count (*180 from the simplified equation above), we are done!
143.                                                 PositionEntity sentity,EntityX#(endpiv,True),EntityY#(endpiv,True),EntityZ#(endpiv,True),True ;should be there, but let's be smart
144.                                                 RotateEntity sentity,EntityPitch#(endpiv,True),EntityYaw(endpiv,True),EntityRoll#(endpiv,True),True ;Same as above
145.                                                 Delete s ;get ride of the smoothmovement type
146.                                                 Return 1 ;and we're done!
147.                                         EndIf
148.                                         If Not loop ;if we're not looping
149.                                                 Return 0 ;we're not done
150.                                         Else ;if we are looping
151.                                                 Exit ;get out of the For...Next loop (no need to cycle through everything once we have our type modified)
152.                                         EndIf
153.                                 EndIf
154.                         Next
155.                 EndIf
156.                 If Not typemade ;if this is the first iteration...
157.                                                 ;note that typemade changes to 1 after this block and will stay that way if looping is enabled. If looping is disabled,
158.                                                 ;the function won't make it to this point anyway because it returns before it hits this point if the type has already been made.
159.                         PositionEntity entity,EntityX(startpiv,True),EntityY(startpiv,True),EntityZ(startpiv,True),True ;Make sure we are starting at the right spot
160.                         RotateEntity entity,EntityPitch(startpiv,True),EntityYaw(startpiv,True),EntityRoll(startpiv,True),True ;same as above
161.                         s.smoothmovement = New smoothmovement ;make a new smoothmovement type
162.                         sentity = entity ;inheret the entity
163.                         smovevalue# = 0.0 ;initialize the movement value (think of this as a counter for the number of iterations. It increases by 1*180 each iteration).
164.                         ;integraldistance = ((2*framecount)/Pi)*(amplitude of sin wave), yay calculus! See below for more info on how I got this equation.
165.                         samplitude# = ((EntityDistance#(startpiv,endpiv))*Pi)/(2.0*framecount#) ;amplitude is solved from the integral, using entitydistance for the distance
166.                         spitchamplitude# = (EntityPitch#(endpiv,True) - EntityPitch#(startpiv,True))*Pi/(2.0*framecount#) ;This tells us how fast to rotate on the x-axis
167.                         syawamplitude# = (EntityYaw#(endpiv,True) - EntityYaw#(startpiv,True))*Pi/(2.0*framecount#) ;This tells us how fast to rotate on the y-axis
168.                         s
169. ollamplitude# = (EntityRoll#(endpiv,True) - EntityRoll#(startpiv,True))*Pi/(2.0*framecount#) ;This tells us how fast to rotate on the z-axis
170.                         typemade = 1 ;let the program know we have made the smoothmovement type so we don't make it again (only applies if looping is enabled)
171.                         If Not loop ;if we are not looping
172.                                 Return 0 ;we're not done at this point
173.                         EndIf
174.                 EndIf
175.
176.                 UpdateWorld ;on the loop, we need these lines, feel free to modify as you wish
177.                 RenderWorld
178.                 Flip
179.
180.         Forever ;Don't freak out about this line. There is a panic button (ESC) to get out if needed.
181.
182.
183. End Function
184. ;END BLOCK
185. ;============================================================================================================================================
186.
187. ;3D Example - This shows how the process works. I won't comment this part as strongly.
188. ;Essentially, I am making two spheres and a start and end point for each sphere.
189. Graphics3D 1024,768,0,2
190.
191. SetBuffer BackBuffer()
192.
193. Global camera = CreateCamera()
194. MoveEntity camera,0,20,-15
195. RotateEntity camera,60,0,0
196.
197. Local light = CreateLight(2)
198. PositionEntity light,80,30,-5
199.
200. Local sphere = CreateSphere()
201. MoveEntity sphere,-15,0,0
202. Local front = CreateCube()
203. PositionEntity front,-15,0,1.5
204. ScaleEntity front,0.75,0.75,0.75
205. EntityParent front,sphere
206. Local pointA
207. Local pointB
208. pointA = CreateCube()
209. pointB = CreateCube()
210. PositionEntity pointA,EntityX(sphere),EntityY(sphere),EntityZ(sphere)
211. PositionEntity pointB,EntityX(sphere) + 30,EntityY(sphere),EntityZ(sphere)
212. ScaleEntity pointA,0.1,0.1,0.1
213. ScaleEntity pointB,0.1,0.1,0.1
214. RotateEntity pointA,0,0,0
215. RotateEntity pointB,0,90,0
216. EntityColor pointA,255,0,0
217. EntityColor pointB,0,0,255.0
218.
219. Local sphere2 = CreateSphere()
220. MoveEntity sphere2,0,0,-15
221. Local front2 = CreateCube()
222. PositionEntity front2,0,0,1.5-15
223. ScaleEntity front2,0.75,0.75,0.75
224. EntityParent front2,sphere2
225. Local pointC
226. Local pointD
227. pointC = CreateCube()
228. pointD = CreateCube()
229. PositionEntity pointC,EntityX(sphere2),EntityY(sphere2),EntityZ(sphere2)
230. PositionEntity pointD,EntityX(sphere2),EntityY(sphere2),EntityZ(sphere2) + 30
231. ScaleEntity pointC,0.1,0.1,0.1
232. ScaleEntity pointD,0.1,0.1,0.1
233. RotateEntity pointC,90,180,180
234. RotateEntity pointD,-90,-180,-90
235. EntityColor pointC,0,255.0,0
236. EntityColor pointD,255.0,255.0,0
237.
238. Local atpoint\$ = "A"
239. Local framevalue = 120
240. Local option = 0
241. Local moved = 0
242. Local moved2 = 0
243.
244. While Not KeyDown(1)
245.
246.         If KeyHit(57) ;Spacebar
247.                 ;Option 0 (EXTERNAL LOOP, DEFAULT)
248.                 If Not option
249.
250.                         While (moved = 0) Or (moved2 = 0)
251.
252.                                 If KeyDown(1) Then End
253.
254.                                 If atpoint\$ = "A"
255.                                         moved = SmoothMove(sphere,pointA,pointB,framevalue,option) ;Here is the SmoothMove() Function for the first sphere, and the second is below.
256.                                         moved2 = SmoothMove(sphere2,pointC,pointD,120,option) ;I left the framevalue constant so you can see different rates of movement.
257.                                 Else
258.                                         moved = SmoothMove(sphere,pointB,pointA,framevalue,option)
259.                                         moved2 = SmoothMove(sphere2,pointD,pointC,120,option) ;same as above
260.                                 EndIf
261.
262.                                 UpdateWorld
263.                                 RenderWorld
264.                                 Text 0,0,"MOVING! Note that this text will not appear if option is set to 1."
265.                                 Flip
266.
267.                         Wend
268.
269.                 ;Option 1 (INTERNAL LOOP)
270.                 Else
271.                         If atpoint\$ = "A"
272.                                 SmoothMove(sphere,pointA,pointB,framevalue,option)
273.                                 SmoothMove(sphere2,pointC,pointD,120,option)
274.                         Else
275.                                 SmoothMove(sphere,pointB,pointA,framevalue,option)
276.                                 SmoothMove(sphere2,pointD,pointC,120,option)
277.                         EndIf
278.                 EndIf
279.
280.                 ;Reset from above, doesn't demonstrate the concepts of this function.
281.                 FlushKeys()
282.                 If atpoint\$ = "A"
283.                         atpoint\$ = "B"
284.                 Else
285.                         atpoint\$ = "A"
286.                 EndIf
287.                 moved = 0
288.                 moved2 = 0
289.         EndIf
290.
291.         If KeyHit(28) ;Enter
292.                 option = Not option
293.         EndIf
294.
295.         If KeyDown(200) ;Up arrow
296.                 framevalue = framevalue + 1
297.         EndIf
298.
299.         If KeyDown(208) ;Down arrow
300.                 framevalue = framevalue - 1
301.         EndIf
302.
303.         UpdateWorld
304.         RenderWorld
305.         Text 0,0,"Hit the space bar to move the spheres."
306.         Text 0,15,"The first sphere is currently at point " + atpoint\$ + "."
307.         Text 0,30,"It will take " + framevalue + " frames to move from one point to the next. More frames makes the effect more pronounced."
308.         Text 0,45,"Push the up and down arrows to increase and decrease the number of frames required for movement. More frames takes longer."
309.         Text 0,60,"Push Enter to change the method of the function. Currently, it is set to option " + option + "."
310.         Text 0,75,"Option 0 (default) will not loop on its own, while option 1 will loop on its own."
311.         Text 0,90,"Option 0 supports multiple entity movement at the same time, but option 1 does not."
312.         Flip
313.
314. Wend
315.
316. End
317. ;The essentials to this equation:
318. ;
319. ;If you're wondering how I got the equation MoveEntity sentity,0,0,(Sin#(smovevalue#/framecount#))*(samplitude#), read this.
320. ;
321. ;I used a Sin function because it's periodic and because the values gradually go from 0 to 1 and back to 0.
322. ;A typical Sin(x) function has a period of 2pi, but I didn't want it to go backwards, so I cut the period
323. ;in half to make it pi. This means that I needed to limit my domain to be from 0 to pi*time. I ended up with
324. ;a very basic function of Sin(pi*elapsedframes) for values to move the entity "forward". I needed this curve to
325. ;stretch based on the total number of frames for the whole function, and the math makes this turn into a reciprocal.
326. ;My function became Sin((pi/framecount)*(elapsedframe)). During testing, I saw that these values weren't giving
327. ;me the right distances, so I needed to modify the amplitude of the sin wave in order to increase or decrease
328. ;total distance travelled. The problem was that I had no idea what to set the amplitude to in order to get the
329. ;exact distance. Looking at a Sin graph, I could then see that I had to use Calculus to get this answer.
330. ;So, I let f(x) = Sin((pi/framecount)*(elapsedframe))*amplitude. EntityDistance would have to give me the
331. ;same thing as the integral of this equation, so I went ahead and set up an integral.
332. ;
333. ;Let x = elapsedframes, c = framecount, amplitude = a, d = Total Distance Travelled
334. ;Then the integral from 0 to c of ( (sin(pi*x/c)*a dx ) = d
335. ;Let u = -cos(pi*x/c)
336. ;du = sin(pi*x/c)*pi/c dx
337. ;Producing (c/pi) times the integral from -1 to 1 of ( a*du ) = d
338. ;Evaluating to 2*a*c/pi = d.
339. ;
340. ;From here, I solved for a (amplitude) to get
341. ;a = d*pi/(2*c)
342. ;Meaning
343. ;amplitude = EntityDistance(startpiv,endpiv)*pi/(2*framecount)
344. ;
345. ;From here, I needed to convert from Radians to Degrees because the Blitz Sin function only handles degrees.
346. ;This is what ended up happening:
348. ;radianvalue# = (Pi/framecount#)*(smovevalue#) ;smovevalue# would increase by 1 each iteration, representing the passage of 1 frame
349. ;RADIAN_TO_DEGREE# = 180/pi ;a constant to convert from radians to degrees
351. ;
352. ;This equation was so nasty that I went to clean it up a little bit. Notice that the pis cancel, making the
353. ;equation of MoveEntity sentity,0,0,(Sin#(180*smovevalue#/framecount#))*(samplitude#).
354. ;
355. ;Rather than mutliply by 180 each iteration, I felt that it would be slightly better to add 180 to smovevalue instead.
356. ;I could then disregard the radian to degree conversion, making the final equation
357. ;MoveEntity sentity,0,0,(Sin#(smovevalue#/framecount#))*(samplitude#)
358. ;
359. ;I applied the same technique to the rotation values.
360. ;
361. ;I hope that was more helpful that confusing. If none if it made sense, just trust that it works.