├── LICENSE ├── Makefile ├── README.md ├── common.h ├── controller.c ├── damper.c ├── deadblending.c ├── doublespring.c ├── extrapolation.c ├── inertialization.c ├── interpolation.c ├── resonance.c ├── smoothing.c ├── springdamper.c ├── timedspring.c ├── tracking.c └── velocityspring.c /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Daniel Holden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PLATFORM ?= PLATFORM_DESKTOP 2 | 3 | RAYLIB_DIR = C:/raylib 4 | INCLUDE_DIR = -I ./ -I $(RAYLIB_DIR)/raylib/src -I $(RAYLIB_DIR)/raygui/src 5 | LIBRARY_DIR = -L $(RAYLIB_DIR)/raylib/src 6 | DEFINES = -D _DEFAULT_SOURCE -D RAYLIB_BUILD_MODE=RELEASE -D $(PLATFORM) 7 | 8 | ifeq ($(PLATFORM),PLATFORM_DESKTOP) 9 | CC = g++ 10 | EXT = .exe 11 | CFLAGS ?= $(DEFINES) -O3 $(RAYLIB_DIR)/raylib/src/raylib.rc.data $(INCLUDE_DIR) $(LIBRARY_DIR) 12 | LIBS = -lraylib -lopengl32 -lgdi32 -lwinmm 13 | endif 14 | 15 | .PHONY: all 16 | 17 | SOURCE = \ 18 | damper.c \ 19 | springdamper.c \ 20 | smoothing.c \ 21 | controller.c \ 22 | inertialization.c \ 23 | deadblending.c \ 24 | extrapolation.c \ 25 | tracking.c \ 26 | interpolation.c \ 27 | resonance.c \ 28 | timedspring.c \ 29 | velocityspring.c \ 30 | doublespring.c 31 | 32 | EXECUTABLE = $(SOURCE:.c=$(EXT)) 33 | 34 | all: $(EXECUTABLE) 35 | 36 | %$(EXT): %.c 37 | $(CC) -o $@ $< $(CFLAGS) $(LIBS) 38 | 39 | clean: 40 | rm $(EXECUTABLE) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Spring-It-On: The Game Developer's Spring-Roll-Call 2 | =================================================== 3 | 4 | This repo contains the source code for all the demos from [this article](http://theorangeduck.com/page/spring-roll-call). 5 | 6 | It uses [raylib](https://www.raylib.com/) or more specifically [raygui](https://github.com/raysan5/raygui) so if you have that installed it should be easy to play around and try them out. 7 | 8 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #include "raylib.h" 4 | #define RAYGUI_IMPLEMENTATION 5 | #include "raygui.h" 6 | } 7 | 8 | #include 9 | 10 | //-------------------------------------- 11 | 12 | float lerp(float x, float y, float a) 13 | { 14 | return (1.0f - a) * x + a * y; 15 | } 16 | 17 | float clamp(float x, float minimum, float maximum) 18 | { 19 | return x > maximum ? maximum : x < minimum ? minimum : x; 20 | } 21 | 22 | float max(float x, float y) 23 | { 24 | return x > y ? x : y; 25 | } 26 | 27 | float min(float x, float y) 28 | { 29 | return x < y ? x : y; 30 | } 31 | 32 | //-------------------------------------- 33 | 34 | float damper(float x, float g, float factor) 35 | { 36 | return lerp(x, g, factor); 37 | } 38 | 39 | float damper_bad(float x, float g, float damping, float dt) 40 | { 41 | return lerp(x, g, damping * dt); 42 | } 43 | 44 | /* 45 | float damper_exponential( 46 | float x, 47 | float g, 48 | float damping, 49 | float dt, 50 | float ft = 1.0f / 60.0f) 51 | { 52 | return lerp(x, g, 1.0f - powf(1.0 - ft * damping, dt / ft)); 53 | } 54 | */ 55 | 56 | float damper_exponential( 57 | float x, 58 | float g, 59 | float damping, 60 | float dt, 61 | float ft = 1.0f / 60.0f) 62 | { 63 | return lerp(x, g, 1.0f - powf(1.0 / (1.0 - ft * damping), -dt / ft)); 64 | } 65 | 66 | /* 67 | float damper_exact(float x, float g, float halflife, float dt) 68 | { 69 | return lerp(x, g, 1.0f - powf(2, -dt / halflife)); 70 | } 71 | */ 72 | 73 | /* 74 | float damper_exact(float x, float g, float halflife, float dt, float eps=1e-5f) 75 | { 76 | return lerp(x, g, 1.0f - expf(-(0.69314718056f * dt) / (halflife + eps))); 77 | } 78 | */ 79 | 80 | float fast_negexp(float x) 81 | { 82 | return 1.0f / (1.0f + x + 0.48f*x*x + 0.235f*x*x*x); 83 | } 84 | 85 | float damper_exact(float x, float g, float halflife, float dt, float eps=1e-5f) 86 | { 87 | return lerp(x, g, 1.0f - fast_negexp((0.69314718056f * dt) / (halflife + eps))); 88 | } 89 | 90 | float damper_decay_exact(float x, float halflife, float dt, float eps=1e-5f) 91 | { 92 | return x * fast_negexp((0.69314718056f * dt) / (halflife + eps)); 93 | } 94 | 95 | //-------------------------------------- 96 | 97 | void spring_damper_bad( 98 | float& x, 99 | float& v, 100 | float g, 101 | float q, 102 | float stiffness, 103 | float damping, 104 | float dt) 105 | { 106 | v += dt * stiffness * (g - x) + dt * damping * (q - v); 107 | x += dt * v; 108 | } 109 | 110 | float fast_atan(float x) 111 | { 112 | float z = fabs(x); 113 | float w = z > 1.0f ? 1.0f / z : z; 114 | float y = (M_PI / 4.0f)*w - w*(w - 1)*(0.2447f + 0.0663f*w); 115 | return copysign(z > 1.0f ? M_PI / 2.0 - y : y, x); 116 | } 117 | 118 | float squaref(float x) 119 | { 120 | return x*x; 121 | } 122 | 123 | /* 124 | void spring_damper_exact( 125 | float& x, 126 | float& v, 127 | float x_goal, 128 | float v_goal, 129 | float stiffness, 130 | float damping, 131 | float dt, 132 | float eps = 1e-5f) 133 | { 134 | float g = x_goal; 135 | float q = v_goal; 136 | float s = stiffness; 137 | float d = damping; 138 | float c = g + (d*q) / (s + eps); 139 | float y = d / 2.0f; 140 | float w = sqrtf(s - (d*d)/4.0f); 141 | float j = sqrtf(squaref(v + y*(x - c)) / (w*w + eps) + squaref(x - c)); 142 | float p = fast_atan((v + (x - c) * y) / (-(x - c)*w + eps)); 143 | 144 | j = (x - c) > 0.0f ? j : -j; 145 | 146 | float eydt = fast_negexp(y*dt); 147 | 148 | x = j*eydt*cosf(w*dt + p) + c; 149 | v = -y*j*eydt*cosf(w*dt + p) - w*j*eydt*sinf(w*dt + p); 150 | } 151 | */ 152 | 153 | void spring_damper_exact_stiffness_damping( 154 | float& x, 155 | float& v, 156 | float x_goal, 157 | float v_goal, 158 | float stiffness, 159 | float damping, 160 | float dt, 161 | float eps = 1e-5f) 162 | { 163 | float g = x_goal; 164 | float q = v_goal; 165 | float s = stiffness; 166 | float d = damping; 167 | float c = g + (d*q) / (s + eps); 168 | float y = d / 2.0f; 169 | 170 | if (fabs(s - (d*d) / 4.0f) < eps) // Critically Damped 171 | { 172 | float j0 = x - c; 173 | float j1 = v + j0*y; 174 | 175 | float eydt = fast_negexp(y*dt); 176 | 177 | x = j0*eydt + dt*j1*eydt + c; 178 | v = -y*j0*eydt - y*dt*j1*eydt + j1*eydt; 179 | } 180 | else if (s - (d*d) / 4.0f > 0.0) // Under Damped 181 | { 182 | float w = sqrtf(s - (d*d)/4.0f); 183 | float j = sqrtf(squaref(v + y*(x - c)) / (w*w + eps) + squaref(x - c)); 184 | float p = fast_atan((v + (x - c) * y) / (-(x - c)*w + eps)); 185 | 186 | j = (x - c) > 0.0f ? j : -j; 187 | 188 | float eydt = fast_negexp(y*dt); 189 | 190 | x = j*eydt*cosf(w*dt + p) + c; 191 | v = -y*j*eydt*cosf(w*dt + p) - w*j*eydt*sinf(w*dt + p); 192 | } 193 | else if (s - (d*d) / 4.0f < 0.0) // Over Damped 194 | { 195 | float y0 = (d + sqrtf(d*d - 4*s)) / 2.0f; 196 | float y1 = (d - sqrtf(d*d - 4*s)) / 2.0f; 197 | float j1 = (c*y0 - x*y0 - v) / (y1 - y0); 198 | float j0 = x - j1 - c; 199 | 200 | float ey0dt = fast_negexp(y0*dt); 201 | float ey1dt = fast_negexp(y1*dt); 202 | 203 | x = j0*ey0dt + j1*ey1dt + c; 204 | v = -y0*j0*ey0dt - y1*j1*ey1dt; 205 | } 206 | } 207 | 208 | float halflife_to_damping(float halflife, float eps = 1e-5f) 209 | { 210 | return (4.0f * 0.69314718056f) / (halflife + eps); 211 | } 212 | 213 | float damping_to_halflife(float damping, float eps = 1e-5f) 214 | { 215 | return (4.0f * 0.69314718056f) / (damping + eps); 216 | } 217 | 218 | float frequency_to_stiffness(float frequency) 219 | { 220 | return squaref(2.0f * M_PI * frequency); 221 | } 222 | 223 | float stiffness_to_frequency(float stiffness) 224 | { 225 | return sqrtf(stiffness) / (2.0f * M_PI); 226 | } 227 | 228 | float critical_halflife(float frequency) 229 | { 230 | return damping_to_halflife(sqrtf(frequency_to_stiffness(frequency) * 4.0f)); 231 | } 232 | 233 | float critical_frequency(float halflife) 234 | { 235 | return stiffness_to_frequency(squaref(halflife_to_damping(halflife)) / 4.0f); 236 | } 237 | 238 | void spring_damper_exact( 239 | float& x, 240 | float& v, 241 | float x_goal, 242 | float v_goal, 243 | float frequency, 244 | float halflife, 245 | float dt, 246 | float eps = 1e-5f) 247 | { 248 | float g = x_goal; 249 | float q = v_goal; 250 | float s = frequency_to_stiffness(frequency); 251 | float d = halflife_to_damping(halflife); 252 | float c = g + (d*q) / (s + eps); 253 | float y = d / 2.0f; 254 | 255 | if (fabs(s - (d*d) / 4.0f) < eps) // Critically Damped 256 | { 257 | float j0 = x - c; 258 | float j1 = v + j0*y; 259 | 260 | float eydt = fast_negexp(y*dt); 261 | 262 | x = j0*eydt + dt*j1*eydt + c; 263 | v = -y*j0*eydt - y*dt*j1*eydt + j1*eydt; 264 | } 265 | else if (s - (d*d) / 4.0f > 0.0) // Under Damped 266 | { 267 | float w = sqrtf(s - (d*d)/4.0f); 268 | float j = sqrtf(squaref(v + y*(x - c)) / (w*w + eps) + squaref(x - c)); 269 | float p = fast_atan((v + (x - c) * y) / (-(x - c)*w + eps)); 270 | 271 | j = (x - c) > 0.0f ? j : -j; 272 | 273 | float eydt = fast_negexp(y*dt); 274 | 275 | x = j*eydt*cosf(w*dt + p) + c; 276 | v = -y*j*eydt*cosf(w*dt + p) - w*j*eydt*sinf(w*dt + p); 277 | } 278 | else if (s - (d*d) / 4.0f < 0.0) // Over Damped 279 | { 280 | float y0 = (d + sqrtf(d*d - 4*s)) / 2.0f; 281 | float y1 = (d - sqrtf(d*d - 4*s)) / 2.0f; 282 | float j1 = (c*y0 - x*y0 - v) / (y1 - y0); 283 | float j0 = x - j1 - c; 284 | 285 | float ey0dt = fast_negexp(y0*dt); 286 | float ey1dt = fast_negexp(y1*dt); 287 | 288 | x = j0*ey0dt + j1*ey1dt + c; 289 | v = -y0*j0*ey0dt - y1*j1*ey1dt; 290 | } 291 | } 292 | 293 | float damping_ratio_to_stiffness(float ratio, float damping) 294 | { 295 | return squaref(damping / (ratio * 2.0f)); 296 | } 297 | 298 | float damping_ratio_to_damping(float ratio, float stiffness) 299 | { 300 | return ratio * 2.0f * sqrtf(stiffness); 301 | } 302 | 303 | void spring_damper_exact_ratio( 304 | float& x, 305 | float& v, 306 | float x_goal, 307 | float v_goal, 308 | float damping_ratio, 309 | float halflife, 310 | float dt, 311 | float eps = 1e-5f) 312 | { 313 | float g = x_goal; 314 | float q = v_goal; 315 | float d = halflife_to_damping(halflife); 316 | float s = damping_ratio_to_stiffness(damping_ratio, d); 317 | float c = g + (d*q) / (s + eps); 318 | float y = d / 2.0f; 319 | 320 | if (fabs(s - (d*d) / 4.0f) < eps) // Critically Damped 321 | { 322 | float j0 = x - c; 323 | float j1 = v + j0*y; 324 | 325 | float eydt = fast_negexp(y*dt); 326 | 327 | x = j0*eydt + dt*j1*eydt + c; 328 | v = -y*j0*eydt - y*dt*j1*eydt + j1*eydt; 329 | } 330 | else if (s - (d*d) / 4.0f > 0.0) // Under Damped 331 | { 332 | float w = sqrtf(s - (d*d)/4.0f); 333 | float j = sqrtf(squaref(v + y*(x - c)) / (w*w + eps) + squaref(x - c)); 334 | float p = fast_atan((v + (x - c) * y) / (-(x - c)*w + eps)); 335 | 336 | j = (x - c) > 0.0f ? j : -j; 337 | 338 | float eydt = fast_negexp(y*dt); 339 | 340 | x = j*eydt*cosf(w*dt + p) + c; 341 | v = -y*j*eydt*cosf(w*dt + p) - w*j*eydt*sinf(w*dt + p); 342 | } 343 | else if (s - (d*d) / 4.0f < 0.0) // Over Damped 344 | { 345 | float y0 = (d + sqrtf(d*d - 4*s)) / 2.0f; 346 | float y1 = (d - sqrtf(d*d - 4*s)) / 2.0f; 347 | float j1 = (c*y0 - x*y0 - v) / (y1 - y0); 348 | float j0 = x - j1 - c; 349 | 350 | float ey0dt = fast_negexp(y0*dt); 351 | float ey1dt = fast_negexp(y1*dt); 352 | 353 | x = j0*ey0dt + j1*ey1dt + c; 354 | v = -y0*j0*ey0dt - y1*j1*ey1dt; 355 | } 356 | } 357 | 358 | 359 | //-------------------------------------- 360 | 361 | void critical_spring_damper_exact( 362 | float& x, 363 | float& v, 364 | float x_goal, 365 | float v_goal, 366 | float halflife, 367 | float dt) 368 | { 369 | float g = x_goal; 370 | float q = v_goal; 371 | float d = halflife_to_damping(halflife); 372 | float c = g + (d*q) / ((d*d) / 4.0f); 373 | float y = d / 2.0f; 374 | float j0 = x - c; 375 | float j1 = v + j0*y; 376 | float eydt = fast_negexp(y*dt); 377 | 378 | x = eydt*(j0 + j1*dt) + c; 379 | v = eydt*(v - j1*y*dt); 380 | } 381 | 382 | void simple_spring_damper_exact( 383 | float& x, 384 | float& v, 385 | float x_goal, 386 | float halflife, 387 | float dt) 388 | { 389 | float y = halflife_to_damping(halflife) / 2.0f; 390 | float j0 = x - x_goal; 391 | float j1 = v + j0*y; 392 | float eydt = fast_negexp(y*dt); 393 | 394 | x = eydt*(j0 + j1*dt) + x_goal; 395 | v = eydt*(v - j1*y*dt); 396 | } 397 | 398 | void decay_spring_damper_exact( 399 | float& x, 400 | float& v, 401 | float halflife, 402 | float dt) 403 | { 404 | float y = halflife_to_damping(halflife) / 2.0f; 405 | float j1 = v + x*y; 406 | float eydt = fast_negexp(y*dt); 407 | 408 | x = eydt*(x + j1*dt); 409 | v = eydt*(v - j1*y*dt); 410 | } 411 | 412 | //-------------------------------------- 413 | 414 | float halflife_to_lag(float halflife) 415 | { 416 | return halflife / 0.69314718056f; 417 | } 418 | 419 | float lag_to_halflife(float lag) 420 | { 421 | return lag * 0.69314718056f; 422 | } 423 | 424 | //-------------------------------------- 425 | -------------------------------------------------------------------------------- /controller.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | //-------------------------------------- 4 | 5 | void spring_character_update( 6 | float& x, 7 | float& v, 8 | float& a, 9 | float v_goal, 10 | float halflife, 11 | float dt) 12 | { 13 | float y = halflife_to_damping(halflife) / 2.0f; 14 | float j0 = v - v_goal; 15 | float j1 = a + j0*y; 16 | float eydt = fast_negexp(y*dt); 17 | 18 | x = eydt*(((-j1)/(y*y)) + ((-j0 - j1*dt)/y)) + 19 | (j1/(y*y)) + j0/y + v_goal * dt + x; 20 | v = eydt*(j0 + j1*dt) + v_goal; 21 | a = eydt*(a - j1*y*dt); 22 | } 23 | 24 | void spring_character_predict( 25 | float px[], 26 | float pv[], 27 | float pa[], 28 | int count, 29 | float x, 30 | float v, 31 | float a, 32 | float v_goal, 33 | float halflife, 34 | float dt) 35 | { 36 | for (int i = 0; i < count; i++) 37 | { 38 | px[i] = x; 39 | pv[i] = v; 40 | pa[i] = a; 41 | } 42 | 43 | for (int i = 0; i < count; i++) 44 | { 45 | spring_character_update(px[i], pv[i], pa[i], v_goal, halflife, i * dt); 46 | } 47 | } 48 | 49 | //-------------------------------------- 50 | 51 | enum 52 | { 53 | TRAJ_MAX = 32, 54 | TRAJ_SUB = 4, 55 | PRED_MAX = 4, 56 | PRED_SUB = 4, 57 | }; 58 | 59 | float trajx_prev[TRAJ_MAX]; 60 | float trajy_prev[TRAJ_MAX]; 61 | 62 | float predx[PRED_MAX], predy[PRED_MAX]; 63 | float predxv[PRED_MAX], predyv[PRED_MAX]; 64 | float predxa[PRED_MAX], predya[PRED_MAX]; 65 | 66 | int main(void) 67 | { 68 | // Init Window 69 | 70 | const int screenWidth = 640; 71 | const int screenHeight = 360; 72 | 73 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - controller"); 74 | 75 | // Init Variables 76 | 77 | float halflife = 0.1f; 78 | float dt = 1.0 / 60.0f; 79 | float timescale = 240.0f; 80 | 81 | SetTargetFPS(1.0f / dt); 82 | 83 | // Trajectory 84 | 85 | float trajx = screenWidth / 2.0f; 86 | float trajy = screenHeight / 2.0f; 87 | float trajxv = 0.0, trajyv = 0.0; 88 | float trajxa = 0.0, trajya = 0.0; 89 | float traj_xv_goal = 0.0; 90 | float traj_yv_goal = 0.0; 91 | 92 | for (int i = 0; i < TRAJ_MAX; i++) 93 | { 94 | trajx_prev[i] = screenWidth / 2.0f; 95 | trajy_prev[i] = screenHeight / 2.0f; 96 | } 97 | 98 | while (!WindowShouldClose()) 99 | { 100 | // Shift History 101 | 102 | for (int i = TRAJ_MAX - 1; i > 0; i--) 103 | { 104 | trajx_prev[i] = trajx_prev[i - 1]; 105 | trajy_prev[i] = trajy_prev[i - 1]; 106 | } 107 | 108 | // Controller 109 | 110 | GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "halflife", TextFormat("%5.3f", halflife), &halflife, 0.0f, 1.0f); 111 | 112 | // Update Spring 113 | 114 | float gamepadx = GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X); 115 | float gamepady = GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y); 116 | float gamepadmag = sqrtf(gamepadx*gamepadx + gamepady*gamepady); 117 | 118 | if (gamepadmag > 0.2f) 119 | { 120 | float gamepaddirx = gamepadx / gamepadmag; 121 | float gamepaddiry = gamepady / gamepadmag; 122 | float gamepadclippedmag = gamepadmag > 1.0f ? 1.0f : gamepadmag*gamepadmag; 123 | gamepadx = gamepaddirx * gamepadclippedmag; 124 | gamepady = gamepaddiry * gamepadclippedmag; 125 | } 126 | else 127 | { 128 | gamepadx = 0.0f; 129 | gamepady = 0.0f; 130 | } 131 | 132 | traj_xv_goal = 250.0f * gamepadx; 133 | traj_yv_goal = 250.0f * gamepady; 134 | 135 | spring_character_update(trajx, trajxv, trajxa, traj_xv_goal, halflife, dt); 136 | spring_character_update(trajy, trajyv, trajya, traj_yv_goal, halflife, dt); 137 | 138 | spring_character_predict(predx, predxv, predxa, PRED_MAX, trajx, trajxv, trajxa, traj_xv_goal, halflife, dt * PRED_SUB); 139 | spring_character_predict(predy, predyv, predya, PRED_MAX, trajy, trajyv, trajya, traj_yv_goal, halflife, dt * PRED_SUB); 140 | 141 | trajx_prev[0] = trajx; 142 | trajy_prev[0] = trajy; 143 | 144 | BeginDrawing(); 145 | 146 | ClearBackground(RAYWHITE); 147 | 148 | for (int i = 0; i < TRAJ_MAX - TRAJ_SUB; i += TRAJ_SUB) 149 | { 150 | Vector2 start = {trajx_prev[i + 0], trajy_prev[i + 0]}; 151 | Vector2 stop = {trajx_prev[i + TRAJ_SUB], trajy_prev[i + TRAJ_SUB]}; 152 | 153 | DrawLineV(start, stop, BLUE); 154 | DrawCircleV(start, 3, BLUE); 155 | } 156 | 157 | for (int i = 1; i < PRED_MAX; i ++) 158 | { 159 | Vector2 start = {predx[i + 0], predy[i + 0]}; 160 | Vector2 stop = {predx[i - 1], predy[i - 1]}; 161 | 162 | DrawLineV(start, stop, MAROON); 163 | DrawCircleV(start, 3, MAROON); 164 | } 165 | 166 | DrawCircleV((Vector2){trajx, trajy}, 4, DARKBLUE); 167 | 168 | Vector2 gamepadPosition = {60, 300}; 169 | Vector2 gamepadStickPosition = {gamepadPosition.x + gamepadx * 25, gamepadPosition.y + gamepady * 25}; 170 | DrawCircleLines(gamepadPosition.x, gamepadPosition.y, 25, DARKPURPLE); 171 | DrawCircleV(gamepadPosition, 3, DARKPURPLE); 172 | DrawCircleV(gamepadStickPosition, 3, DARKPURPLE); 173 | DrawLineV(gamepadPosition, gamepadStickPosition, DARKPURPLE); 174 | 175 | EndDrawing(); 176 | 177 | } 178 | 179 | CloseWindow(); 180 | 181 | return 0; 182 | } -------------------------------------------------------------------------------- /damper.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | enum 4 | { 5 | HISTORY_MAX = 256 6 | }; 7 | 8 | float x_prev[HISTORY_MAX]; 9 | float t_prev[HISTORY_MAX]; 10 | 11 | int main(void) 12 | { 13 | // Init Window 14 | 15 | const int screenWidth = 640; 16 | const int screenHeight = 360; 17 | 18 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - damper"); 19 | 20 | // Init Variables 21 | 22 | float t = 0.0; 23 | float x = screenHeight / 2.0f; 24 | float g = x; 25 | float goalOffset = 600; 26 | //float factor = 0.1; 27 | 28 | //float damping = 10.0f; 29 | float halflife = 0.1f; 30 | float dt = 1.0 / 60.0f; 31 | float timescale = 240.0f; 32 | 33 | SetTargetFPS(1.0f / dt); 34 | 35 | for (int i = 0; i < HISTORY_MAX; i++) 36 | { 37 | x_prev[i] = x; 38 | t_prev[i] = t; 39 | } 40 | 41 | while (!WindowShouldClose()) 42 | { 43 | // Shift History 44 | 45 | for (int i = HISTORY_MAX - 1; i > 0; i--) 46 | { 47 | x_prev[i] = x_prev[i - 1]; 48 | t_prev[i] = t_prev[i - 1]; 49 | } 50 | 51 | // Get Goal 52 | 53 | if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)) 54 | { 55 | g = GetMousePosition().y; 56 | } 57 | 58 | // Damper 59 | 60 | //GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "factor", TextFormat("%5.3f", factor), &factor, 0.0f, 1.0f); 61 | //GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "damping", TextFormat("%5.3f", damping), &damping, 0.01f, 30.0f); 62 | GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "halflife", TextFormat("%5.3f", halflife), &halflife, 0.0f, 1.0f); 63 | GuiSliderBar((Rectangle){ 100, 45, 120, 20 }, "dt", TextFormat("%5.3f", dt), &dt, 1.0 / 60.0f, 0.1f); 64 | 65 | // Update Spring 66 | 67 | SetTargetFPS(1.0f / dt); 68 | 69 | t += dt; 70 | 71 | //x = damper(x, g, factor); 72 | //x = damper_bad(x, g, damping, dt); 73 | //x = damper_exponential(x, g, damping, dt); 74 | x = damper_exact(x, g, halflife, dt); 75 | 76 | x_prev[0] = x; 77 | t_prev[0] = t; 78 | 79 | BeginDrawing(); 80 | 81 | ClearBackground(RAYWHITE); 82 | 83 | DrawCircleV((Vector2){goalOffset, g}, 5, MAROON); 84 | DrawCircleV((Vector2){goalOffset, x}, 5, DARKBLUE); 85 | 86 | for (int i = 0; i < HISTORY_MAX - 1; i++) 87 | { 88 | Vector2 x_start = {goalOffset - (t - t_prev[i + 0]) * timescale, x_prev[i + 0]}; 89 | Vector2 x_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, x_prev[i + 1]}; 90 | 91 | DrawLineV(x_start, x_stop, BLUE); 92 | DrawCircleV(x_start, 2, BLUE); 93 | } 94 | 95 | 96 | EndDrawing(); 97 | 98 | } 99 | 100 | CloseWindow(); 101 | 102 | return 0; 103 | } -------------------------------------------------------------------------------- /deadblending.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | #include 4 | 5 | //-------------------------------------- 6 | 7 | void dead_blending_transition( 8 | float& ext_x, // Extrapolated position 9 | float& ext_v, // Extrapolated velocity 10 | float& ext_t, // Time since transition 11 | float src_x, // Current position 12 | float src_v) // Current velocity 13 | { 14 | ext_x = src_x; 15 | ext_v = src_v; 16 | ext_t = 0.0f; 17 | } 18 | 19 | static inline float smoothstep(float x) 20 | { 21 | x = clamp(x, 0.0f, 1.0f); 22 | return x * x * (3.0f - 2.0f * x); 23 | } 24 | 25 | void dead_blending_update( 26 | float& out_x, // Output position 27 | float& out_v, // Output velocity 28 | float& ext_x, // Extrapolated position 29 | float& ext_v, // Extrapolated velocity 30 | float& ext_t, // Time since transition 31 | float in_x, // Input position 32 | float in_v, // Input velocity 33 | float blendtime, // Blend time 34 | float dt, // Delta time 35 | float eps=1e-8f) 36 | { 37 | if (ext_t < blendtime) 38 | { 39 | ext_x += ext_v * dt; 40 | ext_t += dt; 41 | 42 | float alpha = smoothstep(ext_t / max(blendtime, eps)); 43 | out_x = lerp(ext_x, in_x, alpha); 44 | out_v = lerp(ext_v, in_v, alpha); 45 | } 46 | else 47 | { 48 | out_x = in_x; 49 | out_v = in_v; 50 | ext_t = FLT_MAX; 51 | } 52 | } 53 | 54 | void dead_blending_update_decay( 55 | float& out_x, // Output position 56 | float& out_v, // Output velocity 57 | float& ext_x, // Extrapolated position 58 | float& ext_v, // Extrapolated velocity 59 | float& ext_t, // Time since transition 60 | float in_x, // Input position 61 | float in_v, // Input velocity 62 | float blendtime, // Blend time 63 | float decay_halflife, // Decay Halflife 64 | float dt, // Delta time 65 | float eps=1e-8f) 66 | { 67 | if (ext_t < blendtime) 68 | { 69 | ext_v = damper_decay_exact(ext_v, decay_halflife, dt); 70 | ext_x += ext_v * dt; 71 | ext_t += dt; 72 | 73 | float alpha = smoothstep(ext_t / max(blendtime, eps)); 74 | out_x = lerp(ext_x, in_x, alpha); 75 | out_v = lerp(ext_v, in_v, alpha); 76 | } 77 | else 78 | { 79 | out_x = in_x; 80 | out_v = in_v; 81 | ext_t = FLT_MAX; 82 | } 83 | } 84 | 85 | void inertialize_function(float& g, float& gv, float t, float freq, float amp, float phase, float off) 86 | { 87 | g = amp * sin(t * freq + phase) + off; 88 | gv = amp * freq * cos(t * freq + phase); 89 | } 90 | 91 | void inertialize_function1(float& g, float& gv, float t) 92 | { 93 | inertialize_function(g, gv, t, 2.0f * M_PI * 1.25, 74.0, 23.213123, 254); 94 | } 95 | 96 | void inertialize_function2(float& g, float& gv, float t) 97 | { 98 | inertialize_function(g, gv, t, 2.0f * M_PI * 3.4, 28.0, 912.2381, 113); 99 | } 100 | 101 | //-------------------------------------- 102 | 103 | enum 104 | { 105 | HISTORY_MAX = 256 106 | }; 107 | 108 | float x_prev[HISTORY_MAX]; 109 | float v_prev[HISTORY_MAX]; 110 | float t_prev[HISTORY_MAX]; 111 | float g_prev[HISTORY_MAX]; 112 | 113 | int main(void) 114 | { 115 | // Init Window 116 | 117 | const int screenWidth = 640; 118 | const int screenHeight = 360; 119 | 120 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - inertialization"); 121 | 122 | // Init Variables 123 | 124 | float t = 0.0; 125 | float x = screenHeight / 2.0f; 126 | float v = 0.0; 127 | float g = x; 128 | float goalOffset = 600; 129 | 130 | float blendtime = 0.5f; 131 | float decay_halflife = 0.25f; 132 | float dt = 1.0 / 60.0f; 133 | float timescale = 240.0f; 134 | 135 | float ext_x = 0.0; 136 | float ext_v = 0.0; 137 | float ext_t = FLT_MAX; 138 | bool inertialize_toggle = false; 139 | 140 | SetTargetFPS(1.0f / dt); 141 | 142 | for (int i = 0; i < HISTORY_MAX; i++) 143 | { 144 | x_prev[i] = x; 145 | v_prev[i] = v; 146 | t_prev[i] = t; 147 | g_prev[i] = x; 148 | } 149 | 150 | while (!WindowShouldClose()) 151 | { 152 | // Shift History 153 | 154 | for (int i = HISTORY_MAX - 1; i > 0; i--) 155 | { 156 | x_prev[i] = x_prev[i - 1]; 157 | v_prev[i] = v_prev[i - 1]; 158 | t_prev[i] = t_prev[i - 1]; 159 | g_prev[i] = g_prev[i - 1]; 160 | } 161 | 162 | //if (GuiButton((Rectangle){ 100, 75, 120, 20 }, "Transition")) 163 | if (GuiButton((Rectangle){ 100, 45, 120, 20 }, "Transition")) 164 | { 165 | inertialize_toggle = !inertialize_toggle; 166 | 167 | float src_x = x_prev[1]; 168 | float src_v = (x_prev[1] - x_prev[2]) / (t_prev[1] - t_prev[2]); 169 | 170 | dead_blending_transition( 171 | ext_x, ext_v, ext_t, 172 | src_x, src_v); 173 | } 174 | 175 | GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "blendtime", TextFormat("%5.3f", blendtime), &blendtime, 0.0f, 1.0f); 176 | //GuiSliderBar((Rectangle){ 100, 45, 120, 20 }, "dt", TextFormat("%5.3f", dt), &dt, 1.0 / 60.0f, 0.1f); 177 | 178 | // Update Spring 179 | 180 | //SetTargetFPS(1.0f / dt); 181 | 182 | t += dt; 183 | 184 | float gv = 0.0f; 185 | if (inertialize_toggle) 186 | { 187 | inertialize_function1(g, gv, t); 188 | } 189 | else 190 | { 191 | inertialize_function2(g, gv, t); 192 | } 193 | 194 | dead_blending_update(x, v, ext_x, ext_v, ext_t, g, gv, blendtime, dt); 195 | //dead_blending_update_decay(x, v, ext_x, ext_v, ext_t, g, gv, blendtime, decay_halflife, dt); 196 | 197 | x_prev[0] = x; 198 | v_prev[0] = v; 199 | t_prev[0] = t; 200 | g_prev[0] = g; 201 | 202 | BeginDrawing(); 203 | 204 | ClearBackground(RAYWHITE); 205 | 206 | DrawCircleV((Vector2){goalOffset, g}, 5, MAROON); 207 | DrawCircleV((Vector2){goalOffset, x}, 5, DARKBLUE); 208 | 209 | for (int i = 0; i < HISTORY_MAX - 1; i++) 210 | { 211 | Vector2 x_start = {goalOffset - (t - t_prev[i + 0]) * timescale, x_prev[i + 0]}; 212 | Vector2 x_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, x_prev[i + 1]}; 213 | 214 | DrawLineV(x_start, x_stop, BLUE); 215 | DrawCircleV(x_start, 2, BLUE); 216 | } 217 | 218 | for (int i = 0; i < HISTORY_MAX - 1; i++) 219 | { 220 | Vector2 g_start = {goalOffset - (t - t_prev[i + 0]) * timescale, g_prev[i + 0]}; 221 | Vector2 g_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, g_prev[i + 1]}; 222 | 223 | DrawLineV(g_start, g_stop, MAROON); 224 | DrawCircleV(g_start, 2, MAROON); 225 | } 226 | 227 | EndDrawing(); 228 | 229 | } 230 | 231 | CloseWindow(); 232 | 233 | return 0; 234 | } -------------------------------------------------------------------------------- /doublespring.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | //-------------------------------------- 4 | 5 | void double_spring_damper_exact( 6 | float& x, 7 | float& v, 8 | float& xi, 9 | float& vi, 10 | float x_goal, 11 | float halflife, 12 | float dt) 13 | { 14 | simple_spring_damper_exact(xi, vi, x_goal, 0.5f * halflife, dt); 15 | simple_spring_damper_exact(x, v, xi, 0.5f * halflife, dt); 16 | } 17 | 18 | //-------------------------------------- 19 | 20 | enum 21 | { 22 | HISTORY_MAX = 256 23 | }; 24 | 25 | float x_prev[HISTORY_MAX]; 26 | float v_prev[HISTORY_MAX]; 27 | float t_prev[HISTORY_MAX]; 28 | 29 | float xi_prev[HISTORY_MAX]; 30 | float vi_prev[HISTORY_MAX]; 31 | 32 | int main(void) 33 | { 34 | // Init Window 35 | 36 | const int screenWidth = 640; 37 | const int screenHeight = 360; 38 | 39 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - doublespring"); 40 | 41 | // Init Variables 42 | 43 | float t = 0.0; 44 | float x = screenHeight / 2.0f; 45 | float v = 0.0; 46 | float g = x; 47 | float goalOffset = 600; 48 | 49 | float halflife = 0.1f; 50 | float dt = 1.0 / 60.0f; 51 | float timescale = 240.0f; 52 | 53 | float xi = x; 54 | float vi = v; 55 | 56 | SetTargetFPS(1.0f / dt); 57 | 58 | for (int i = 0; i < HISTORY_MAX; i++) 59 | { 60 | x_prev[i] = x; 61 | v_prev[i] = v; 62 | t_prev[i] = t; 63 | 64 | xi_prev[i] = x; 65 | vi_prev[i] = v; 66 | } 67 | 68 | while (!WindowShouldClose()) 69 | { 70 | // Shift History 71 | 72 | for (int i = HISTORY_MAX - 1; i > 0; i--) 73 | { 74 | x_prev[i] = x_prev[i - 1]; 75 | v_prev[i] = v_prev[i - 1]; 76 | t_prev[i] = t_prev[i - 1]; 77 | 78 | xi_prev[i] = xi_prev[i - 1]; 79 | vi_prev[i] = vi_prev[i - 1]; 80 | } 81 | 82 | // Get Goal 83 | 84 | if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)) 85 | { 86 | g = GetMousePosition().y; 87 | } 88 | 89 | // Double Spring 90 | 91 | GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "halflife", TextFormat("%5.3f", halflife), &halflife, 0.0f, 1.0f); 92 | GuiSliderBar((Rectangle){ 100, 45, 120, 20 }, "dt", TextFormat("%5.3f", dt), &dt, 1.0 / 60.0f, 0.1f); 93 | 94 | // Update Spring 95 | 96 | SetTargetFPS(1.0f / dt); 97 | 98 | t += dt; 99 | 100 | double_spring_damper_exact(x, v, xi, vi, g, halflife, dt); 101 | 102 | x_prev[0] = x; 103 | v_prev[0] = v; 104 | t_prev[0] = t; 105 | 106 | xi_prev[0] = xi; 107 | vi_prev[0] = vi; 108 | 109 | BeginDrawing(); 110 | 111 | ClearBackground(RAYWHITE); 112 | 113 | DrawCircleV((Vector2){goalOffset, g}, 5, MAROON); 114 | DrawCircleV((Vector2){goalOffset, x}, 5, DARKBLUE); 115 | 116 | for (int i = 0; i < HISTORY_MAX - 1; i++) 117 | { 118 | Vector2 g_start = {goalOffset - (t - t_prev[i + 0]) * timescale, xi_prev[i + 0]}; 119 | Vector2 g_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, xi_prev[i + 1]}; 120 | 121 | DrawLineV(g_start, g_stop, MAROON); 122 | DrawCircleV(g_start, 2, MAROON); 123 | } 124 | 125 | for (int i = 0; i < HISTORY_MAX - 1; i++) 126 | { 127 | Vector2 x_start = {goalOffset - (t - t_prev[i + 0]) * timescale, x_prev[i + 0]}; 128 | Vector2 x_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, x_prev[i + 1]}; 129 | 130 | DrawLineV(x_start, x_stop, BLUE); 131 | DrawCircleV(x_start, 2, BLUE); 132 | } 133 | 134 | EndDrawing(); 135 | 136 | } 137 | 138 | CloseWindow(); 139 | 140 | return 0; 141 | } -------------------------------------------------------------------------------- /extrapolation.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | #include 4 | 5 | //-------------------------------------- 6 | 7 | void extrapolate( 8 | float& x, 9 | float& v, 10 | float dt, 11 | float halflife, 12 | float eps = 1e-5f) 13 | { 14 | float y = 0.69314718056f / (halflife + eps); 15 | x = x + (v / (y + eps)) * (1.0f - fast_negexp(y * dt)); 16 | v = v * fast_negexp(y * dt); 17 | } 18 | 19 | void extrapolate_function(float& g, float& gv, float t, float freq, float amp, float phase, float off) 20 | { 21 | g = amp * sin(t * freq + phase) + off; 22 | gv = amp * freq * cos(t * freq + phase); 23 | } 24 | 25 | void extrapolate_function1(float& g, float& gv, float t) 26 | { 27 | float g0, gv0, g1, gv1, g2, gv2; 28 | 29 | extrapolate_function(g0, gv0, t, 2.0f * M_PI * 1.5, 40.0, 23.213123, 0); 30 | extrapolate_function(g1, gv1, t, 2.0f * M_PI * 3.4, 14.0, 912.2381, 0); 31 | extrapolate_function(g2, gv2, t, 2.0f * M_PI * 0.4, 21.0, 452.2381, 0); 32 | 33 | g = 200 + g0 + g1 + g2; 34 | gv = gv0 + gv1 + gv2; 35 | } 36 | 37 | //-------------------------------------- 38 | 39 | enum 40 | { 41 | HISTORY_MAX = 256 42 | }; 43 | 44 | float x_prev[HISTORY_MAX]; 45 | float v_prev[HISTORY_MAX]; 46 | float t_prev[HISTORY_MAX]; 47 | float g_prev[HISTORY_MAX]; 48 | 49 | int main(void) 50 | { 51 | // Init Window 52 | 53 | const int screenWidth = 640; 54 | const int screenHeight = 360; 55 | 56 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - extrapolation"); 57 | 58 | // Init Variables 59 | 60 | float t = 0.0; 61 | float x = screenHeight / 2.0f; 62 | float v = 0.0; 63 | float g = x; 64 | float goalOffset = 600; 65 | 66 | float halflife = 0.2f; 67 | float dt = 1.0 / 60.0f; 68 | float timescale = 240.0f; 69 | 70 | bool extrapolation_toggle = false; 71 | 72 | SetTargetFPS(1.0f / dt); 73 | 74 | for (int i = 0; i < HISTORY_MAX; i++) 75 | { 76 | x_prev[i] = x; 77 | v_prev[i] = v; 78 | t_prev[i] = t; 79 | g_prev[i] = x; 80 | } 81 | 82 | while (!WindowShouldClose()) 83 | { 84 | // Shift History 85 | 86 | for (int i = HISTORY_MAX - 1; i > 0; i--) 87 | { 88 | x_prev[i] = x_prev[i - 1]; 89 | v_prev[i] = v_prev[i - 1]; 90 | t_prev[i] = t_prev[i - 1]; 91 | g_prev[i] = g_prev[i - 1]; 92 | } 93 | 94 | //if (GuiButton((Rectangle){ 100, 75, 120, 20 }, "Extrapolate")) 95 | if (GuiButton((Rectangle){ 100, 45, 120, 20 }, "Extrapolate")) 96 | { 97 | extrapolation_toggle = !extrapolation_toggle; 98 | } 99 | 100 | GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "halflife", TextFormat("%5.3f", halflife), &halflife, 0.0f, 0.5f); 101 | //GuiSliderBar((Rectangle){ 100, 45, 120, 20 }, "dt", TextFormat("%5.3f", dt), &dt, 1.0 / 60.0f, 0.1f); 102 | 103 | // Update Spring 104 | 105 | //SetTargetFPS(1.0f / dt); 106 | 107 | t += dt; 108 | 109 | float gv = 0.0f; 110 | 111 | extrapolate_function1(g, gv, t); 112 | 113 | if (extrapolation_toggle) 114 | { 115 | extrapolate(x, v, dt, halflife); 116 | } 117 | else 118 | { 119 | x = g; 120 | v = gv; 121 | } 122 | 123 | x_prev[0] = x; 124 | v_prev[0] = v; 125 | t_prev[0] = t; 126 | g_prev[0] = g; 127 | 128 | BeginDrawing(); 129 | 130 | ClearBackground(RAYWHITE); 131 | 132 | DrawCircleV((Vector2){goalOffset, g}, 5, MAROON); 133 | DrawCircleV((Vector2){goalOffset, x}, 5, DARKBLUE); 134 | 135 | for (int i = 0; i < HISTORY_MAX - 1; i++) 136 | { 137 | Vector2 x_start = {goalOffset - (t - t_prev[i + 0]) * timescale, x_prev[i + 0]}; 138 | Vector2 x_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, x_prev[i + 1]}; 139 | 140 | DrawLineV(x_start, x_stop, BLUE); 141 | DrawCircleV(x_start, 2, BLUE); 142 | } 143 | 144 | for (int i = 0; i < HISTORY_MAX - 1; i++) 145 | { 146 | Vector2 g_start = {goalOffset - (t - t_prev[i + 0]) * timescale, g_prev[i + 0]}; 147 | Vector2 g_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, g_prev[i + 1]}; 148 | 149 | DrawLineV(g_start, g_stop, MAROON); 150 | DrawCircleV(g_start, 2, MAROON); 151 | } 152 | 153 | EndDrawing(); 154 | 155 | } 156 | 157 | CloseWindow(); 158 | 159 | return 0; 160 | } -------------------------------------------------------------------------------- /inertialization.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | //-------------------------------------- 4 | 5 | void inertialize_transition( 6 | float& off_x, float& off_v, 7 | float src_x, float src_v, 8 | float dst_x, float dst_v) 9 | { 10 | off_x = (src_x + off_x) - dst_x; 11 | off_v = (src_v + off_v) - dst_v; 12 | } 13 | 14 | void inertialize_update( 15 | float& out_x, float& out_v, 16 | float& off_x, float& off_v, 17 | float in_x, float in_v, 18 | float halflife, 19 | float dt) 20 | { 21 | decay_spring_damper_exact(off_x, off_v, halflife, dt); 22 | out_x = in_x + off_x; 23 | out_v = in_v + off_v; 24 | } 25 | 26 | void inertialize_function(float& g, float& gv, float t, float freq, float amp, float phase, float off) 27 | { 28 | g = amp * sin(t * freq + phase) + off; 29 | gv = amp * freq * cos(t * freq + phase); 30 | } 31 | 32 | void inertialize_function1(float& g, float& gv, float t) 33 | { 34 | inertialize_function(g, gv, t, 2.0f * M_PI * 1.25, 74.0, 23.213123, 254); 35 | } 36 | 37 | void inertialize_function2(float& g, float& gv, float t) 38 | { 39 | inertialize_function(g, gv, t, 2.0f * M_PI * 3.4, 28.0, 912.2381, 113); 40 | } 41 | 42 | //-------------------------------------- 43 | 44 | enum 45 | { 46 | HISTORY_MAX = 256 47 | }; 48 | 49 | float x_prev[HISTORY_MAX]; 50 | float v_prev[HISTORY_MAX]; 51 | float t_prev[HISTORY_MAX]; 52 | float g_prev[HISTORY_MAX]; 53 | 54 | int main(void) 55 | { 56 | // Init Window 57 | 58 | const int screenWidth = 640; 59 | const int screenHeight = 360; 60 | 61 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - inertialization"); 62 | 63 | // Init Variables 64 | 65 | float t = 0.0; 66 | float x = screenHeight / 2.0f; 67 | float v = 0.0; 68 | float g = x; 69 | float goalOffset = 600; 70 | 71 | float halflife = 0.1f; 72 | float dt = 1.0 / 60.0f; 73 | float timescale = 240.0f; 74 | 75 | float off_x = 0.0; 76 | float off_v = 0.0; 77 | bool inertialize_toggle = false; 78 | 79 | SetTargetFPS(1.0f / dt); 80 | 81 | for (int i = 0; i < HISTORY_MAX; i++) 82 | { 83 | x_prev[i] = x; 84 | v_prev[i] = v; 85 | t_prev[i] = t; 86 | g_prev[i] = x; 87 | } 88 | 89 | while (!WindowShouldClose()) 90 | { 91 | // Shift History 92 | 93 | for (int i = HISTORY_MAX - 1; i > 0; i--) 94 | { 95 | x_prev[i] = x_prev[i - 1]; 96 | v_prev[i] = v_prev[i - 1]; 97 | t_prev[i] = t_prev[i - 1]; 98 | g_prev[i] = g_prev[i - 1]; 99 | } 100 | 101 | if (GuiButton((Rectangle){ 100, 75, 120, 20 }, "Transition")) 102 | { 103 | inertialize_toggle = !inertialize_toggle; 104 | 105 | float src_x = g_prev[1]; 106 | float src_v = (g_prev[1] - g_prev[2]) / (t_prev[1] - t_prev[2]); 107 | float dst_x, dst_v; 108 | 109 | if (inertialize_toggle) 110 | { 111 | inertialize_function1(dst_x, dst_v, t); 112 | } 113 | else 114 | { 115 | inertialize_function2(dst_x, dst_v, t); 116 | } 117 | 118 | inertialize_transition( 119 | off_x, off_v, 120 | src_x, src_v, 121 | dst_x, dst_v); 122 | } 123 | 124 | GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "halflife", TextFormat("%5.3f", halflife), &halflife, 0.0f, 1.0f); 125 | GuiSliderBar((Rectangle){ 100, 45, 120, 20 }, "dt", TextFormat("%5.3f", dt), &dt, 1.0 / 60.0f, 0.1f); 126 | 127 | // Update Spring 128 | 129 | SetTargetFPS(1.0f / dt); 130 | 131 | t += dt; 132 | 133 | float gv = 0.0f; 134 | if (inertialize_toggle) 135 | { 136 | inertialize_function1(g, gv, t); 137 | } 138 | else 139 | { 140 | inertialize_function2(g, gv, t); 141 | } 142 | 143 | inertialize_update(x, v, off_x, off_v, g, gv, halflife, dt); 144 | 145 | x_prev[0] = x; 146 | v_prev[0] = v; 147 | t_prev[0] = t; 148 | g_prev[0] = g; 149 | 150 | BeginDrawing(); 151 | 152 | ClearBackground(RAYWHITE); 153 | 154 | DrawCircleV((Vector2){goalOffset, g}, 5, MAROON); 155 | DrawCircleV((Vector2){goalOffset, x}, 5, DARKBLUE); 156 | 157 | for (int i = 0; i < HISTORY_MAX - 1; i++) 158 | { 159 | Vector2 x_start = {goalOffset - (t - t_prev[i + 0]) * timescale, x_prev[i + 0]}; 160 | Vector2 x_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, x_prev[i + 1]}; 161 | 162 | DrawLineV(x_start, x_stop, BLUE); 163 | DrawCircleV(x_start, 2, BLUE); 164 | } 165 | 166 | for (int i = 0; i < HISTORY_MAX - 1; i++) 167 | { 168 | Vector2 g_start = {goalOffset - (t - t_prev[i + 0]) * timescale, g_prev[i + 0]}; 169 | Vector2 g_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, g_prev[i + 1]}; 170 | 171 | DrawLineV(g_start, g_stop, MAROON); 172 | DrawCircleV(g_start, 2, MAROON); 173 | } 174 | 175 | EndDrawing(); 176 | 177 | } 178 | 179 | CloseWindow(); 180 | 181 | return 0; 182 | } -------------------------------------------------------------------------------- /interpolation.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | 4 | //-------------------------------------- 5 | 6 | void piecewise_interpolation( 7 | float& x, 8 | float& v, 9 | float t, 10 | float pnts[], 11 | int npnts) 12 | { 13 | t = t * (npnts - 1); 14 | int i0 = floorf(t); 15 | int i1 = i0 + 1; 16 | i0 = i0 > npnts - 1 ? npnts - 1 : i0; 17 | i1 = i1 > npnts - 1 ? npnts - 1 : i1; 18 | float alpha = fmod(t, 1.0f); 19 | 20 | x = lerp(pnts[i0], pnts[i1], alpha); 21 | v = (pnts[i0] - pnts[i1]) / npnts; 22 | } 23 | 24 | //-------------------------------------- 25 | 26 | enum 27 | { 28 | CTRL_MAX = 8, 29 | }; 30 | 31 | float ctrlx[CTRL_MAX]; 32 | float ctrly[CTRL_MAX]; 33 | 34 | int main(void) 35 | { 36 | // Init Window 37 | 38 | const int screenWidth = 640; 39 | const int screenHeight = 360; 40 | 41 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - interpolation"); 42 | 43 | // Init Variables 44 | 45 | float halflife = 0.5f; 46 | float frequency = 1.5f; 47 | int ctrl_selected = -1; 48 | 49 | for (int i = 0; i < CTRL_MAX; i++) 50 | { 51 | ctrlx[i] = ((float)i / CTRL_MAX) * 600 + 20; 52 | ctrly[i] = sinf((float)i) * 100 + 100; 53 | } 54 | 55 | while (!WindowShouldClose()) 56 | { 57 | if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) 58 | { 59 | float best_dist = FLT_MAX; 60 | for (int i = 0; i < CTRL_MAX; i++) 61 | { 62 | float dist = squaref(ctrlx[i] - GetMousePosition().x) + squaref(ctrly[i] - GetMousePosition().y); 63 | if (dist < best_dist) 64 | { 65 | best_dist = dist; 66 | ctrl_selected = i; 67 | } 68 | } 69 | } 70 | 71 | if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)) 72 | { 73 | ctrlx[ctrl_selected] = GetMousePosition().x; 74 | ctrly[ctrl_selected] = GetMousePosition().y; 75 | } 76 | 77 | // Interpolation 78 | 79 | GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "frequency", TextFormat("%5.3f", frequency), &frequency, 0.0f, 3.0f); 80 | GuiSliderBar((Rectangle){ 100, 45, 120, 20 }, "halflife", TextFormat("%5.3f", halflife), &halflife, 0.0f, 1.0f); 81 | 82 | BeginDrawing(); 83 | 84 | ClearBackground(RAYWHITE); 85 | 86 | for (int i = 0; i < CTRL_MAX; i++) 87 | { 88 | DrawCircleV((Vector2){ctrlx[i], ctrly[i]}, 4, MAROON); 89 | } 90 | 91 | for (int i = 0; i < CTRL_MAX - 1; i++) 92 | { 93 | DrawLineV( 94 | (Vector2){ctrlx[i], ctrly[i]}, 95 | (Vector2){ctrlx[i + 1], ctrly[i + 1]}, RED); 96 | } 97 | 98 | float sx = ctrlx[0]; 99 | float sy = ctrly[0]; 100 | float svx = (ctrlx[1] - ctrlx[0]) / CTRL_MAX; 101 | float svy = (ctrly[1] - ctrly[0]) / CTRL_MAX; 102 | 103 | DrawCircleV((Vector2){sx, sy}, 2, BLUE); 104 | 105 | int subsamples = 100; 106 | for (int i = 0; i < subsamples; i++) 107 | { 108 | Vector2 start = {sx, sy}; 109 | 110 | float goalx, goaly; 111 | float goalvx, goalvy; 112 | 113 | piecewise_interpolation(goalx, goalvx, (float)i / (subsamples - 1), ctrlx, CTRL_MAX); 114 | piecewise_interpolation(goaly, goalvy, (float)i / (subsamples - 1), ctrly, CTRL_MAX); 115 | 116 | spring_damper_exact(sx, svx, goalx, goalvx, halflife, frequency, (float)CTRL_MAX / subsamples); 117 | spring_damper_exact(sy, svy, goaly, goalvy, halflife, frequency, (float)CTRL_MAX / subsamples); 118 | 119 | Vector2 stop = {sx, sy}; 120 | 121 | DrawLineV(start, stop, DARKBLUE); 122 | 123 | DrawCircleV(stop, 2, BLUE); 124 | } 125 | 126 | EndDrawing(); 127 | 128 | } 129 | 130 | CloseWindow(); 131 | 132 | return 0; 133 | } -------------------------------------------------------------------------------- /resonance.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | //-------------------------------------- 4 | 5 | float spring_energy( 6 | float x, 7 | float v, 8 | float frequency, 9 | float x_rest = 0.0f, 10 | float v_rest = 0.0f, 11 | float scale = 1.0f) 12 | { 13 | float s = frequency_to_stiffness(frequency); 14 | 15 | return ( 16 | squaref(scale * (v - v_rest)) + s * 17 | squaref(scale * (x - x_rest))) / 2.0f; 18 | } 19 | 20 | float resonant_frequency(float goal_frequency, float halflife) 21 | { 22 | float d = halflife_to_damping(halflife); 23 | float goal_stiffness = frequency_to_stiffness(goal_frequency); 24 | float resonant_stiffness = goal_stiffness - (d*d)/4.0f; 25 | return stiffness_to_frequency(resonant_stiffness); 26 | } 27 | 28 | //-------------------------------------- 29 | 30 | enum 31 | { 32 | HISTORY_MAX = 256 33 | }; 34 | 35 | float x_prev[HISTORY_MAX]; 36 | float v_prev[HISTORY_MAX]; 37 | float t_prev[HISTORY_MAX]; 38 | float g_prev[HISTORY_MAX]; 39 | 40 | int main(void) 41 | { 42 | // Init Window 43 | 44 | const int screenWidth = 640; 45 | const int screenHeight = 360; 46 | 47 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - resonance"); 48 | 49 | // Init Variables 50 | 51 | float t = 0.0; 52 | float x = screenHeight / 2.0f; 53 | float v = 0.0; 54 | float g = x; 55 | float goalOffset = 600; 56 | 57 | float frequency = 2.0f; 58 | float halflife = 2.0f; 59 | float dt = 1.0 / 60.0f; 60 | float timescale = 240.0f; 61 | 62 | float goal_frequency = 2.5f; 63 | float res_frequency = 1.5f; 64 | float energy = 0.0; 65 | 66 | SetTargetFPS(1.0f / dt); 67 | 68 | for (int i = 0; i < HISTORY_MAX; i++) 69 | { 70 | x_prev[i] = x; 71 | v_prev[i] = v; 72 | t_prev[i] = t; 73 | g_prev[i] = x; 74 | } 75 | 76 | while (!WindowShouldClose()) 77 | { 78 | // Shift History 79 | 80 | for (int i = HISTORY_MAX - 1; i > 0; i--) 81 | { 82 | x_prev[i] = x_prev[i - 1]; 83 | v_prev[i] = v_prev[i - 1]; 84 | t_prev[i] = t_prev[i - 1]; 85 | g_prev[i] = g_prev[i - 1]; 86 | } 87 | 88 | // Get Goal 89 | 90 | g = screenHeight / 2.0f + 10.0 * sinf(t * 2.0f * M_PI * goal_frequency); 91 | 92 | // Resonance 93 | 94 | if (GuiButton((Rectangle){ 125, 95, 120, 20 }, "Resonant Frequency")) 95 | { 96 | frequency = resonant_frequency(goal_frequency, halflife); 97 | } 98 | 99 | GuiSliderBar((Rectangle){ 125, 20, 120, 20 }, "halflife", TextFormat("%5.3f", halflife), &halflife, 0.0f, 4.0f); 100 | GuiSliderBar((Rectangle){ 125, 45, 120, 20 }, "frequency", TextFormat("%5.3f", frequency), &frequency, 0.0f, 5.0f); 101 | GuiSliderBar((Rectangle){ 125, 70, 120, 20 }, "goal frequency", TextFormat("%5.3f", goal_frequency), &goal_frequency, 0.0f, 5.0f); 102 | 103 | energy = spring_energy(x, v, frequency, screenHeight/2.0f, 0.0f, 0.01f); 104 | 105 | GuiSliderBar((Rectangle){ 400, 20, 120, 20 }, "energy", TextFormat("%4.1f", energy), &energy, 0.0f, 160.0f); 106 | 107 | // Update Spring 108 | 109 | SetTargetFPS(1.0f / dt); 110 | 111 | t += dt; 112 | 113 | spring_damper_exact(x, v, g, 0.0f, frequency, halflife, dt); 114 | 115 | x_prev[0] = x; 116 | v_prev[0] = v; 117 | t_prev[0] = t; 118 | g_prev[0] = g; 119 | 120 | BeginDrawing(); 121 | 122 | ClearBackground(RAYWHITE); 123 | 124 | DrawCircleV((Vector2){goalOffset, g}, 5, MAROON); 125 | DrawCircleV((Vector2){goalOffset, x}, 5, DARKBLUE); 126 | 127 | for (int i = 0; i < HISTORY_MAX - 1; i++) 128 | { 129 | Vector2 x_start = {goalOffset - (t - t_prev[i + 0]) * timescale, x_prev[i + 0]}; 130 | Vector2 x_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, x_prev[i + 1]}; 131 | 132 | DrawLineV(x_start, x_stop, BLUE); 133 | DrawCircleV(x_start, 2, BLUE); 134 | } 135 | 136 | for (int i = 0; i < HISTORY_MAX - 1; i++) 137 | { 138 | Vector2 g_start = {goalOffset - (t - t_prev[i + 0]) * timescale, g_prev[i + 0]}; 139 | Vector2 g_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, g_prev[i + 1]}; 140 | 141 | DrawLineV(g_start, g_stop, MAROON); 142 | DrawCircleV(g_start, 2, MAROON); 143 | } 144 | 145 | EndDrawing(); 146 | 147 | } 148 | 149 | CloseWindow(); 150 | 151 | return 0; 152 | } -------------------------------------------------------------------------------- /smoothing.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | enum 4 | { 5 | HISTORY_MAX = 256 6 | }; 7 | 8 | float x_prev[HISTORY_MAX]; 9 | float v_prev[HISTORY_MAX]; 10 | float t_prev[HISTORY_MAX]; 11 | 12 | int main(void) 13 | { 14 | // Init Window 15 | 16 | const int screenWidth = 640; 17 | const int screenHeight = 360; 18 | 19 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - smoothing"); 20 | 21 | // Init Variables 22 | 23 | float t = 0.0; 24 | float x = screenHeight / 2.0f; 25 | float v = 0.0; 26 | float g = x; 27 | float goalOffset = 600; 28 | 29 | float halflife = 0.1f; 30 | float dt = 1.0 / 60.0f; 31 | float timescale = 240.0f; 32 | 33 | float noise = 0.0f; 34 | float jitter = 0.0f; 35 | 36 | SetTargetFPS(1.0f / dt); 37 | 38 | for (int i = 0; i < HISTORY_MAX; i++) 39 | { 40 | x_prev[i] = x; 41 | v_prev[i] = v; 42 | t_prev[i] = t; 43 | } 44 | 45 | while (!WindowShouldClose()) 46 | { 47 | // Shift History 48 | 49 | for (int i = HISTORY_MAX - 1; i > 0; i--) 50 | { 51 | x_prev[i] = x_prev[i - 1]; 52 | v_prev[i] = v_prev[i - 1]; 53 | t_prev[i] = t_prev[i - 1]; 54 | } 55 | 56 | // Get Goal 57 | 58 | if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)) 59 | { 60 | g = GetMousePosition().y; 61 | } 62 | 63 | g += noise * (((float)rand() / RAND_MAX) * 2.0f - 1.0); 64 | 65 | if (jitter) 66 | { 67 | g -= jitter; 68 | jitter = 0.0; 69 | } 70 | else if (rand() % (int)(0.5 / dt) == 0) 71 | { 72 | jitter = noise * 10.0f * (((float)rand() / RAND_MAX) * 2.0f - 1.0); 73 | g += jitter; 74 | } 75 | 76 | // Spring Damper 77 | 78 | GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "halflife", TextFormat("%5.3f", halflife), &halflife, 0.0f, 1.0f); 79 | GuiSliderBar((Rectangle){ 100, 45, 120, 20 }, "dt", TextFormat("%5.3f", dt), &dt, 1.0 / 60.0f, 0.1f); 80 | GuiSliderBar((Rectangle){ 100, 70, 120, 20 }, "noise", TextFormat("%5.3f", noise), &noise, 0.0f, 20.0f); 81 | 82 | // Update Spring 83 | 84 | SetTargetFPS(1.0f / dt); 85 | 86 | t += dt; 87 | 88 | simple_spring_damper_exact(x, v, g, halflife, dt); 89 | 90 | x_prev[0] = x; 91 | v_prev[0] = v; 92 | t_prev[0] = t; 93 | 94 | BeginDrawing(); 95 | 96 | ClearBackground(RAYWHITE); 97 | 98 | DrawCircleV((Vector2){goalOffset, g}, 5, MAROON); 99 | DrawCircleV((Vector2){goalOffset, x}, 5, DARKBLUE); 100 | 101 | for (int i = 0; i < HISTORY_MAX - 1; i++) 102 | { 103 | Vector2 x_start = {goalOffset - (t - t_prev[i + 0]) * timescale, x_prev[i + 0]}; 104 | Vector2 x_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, x_prev[i + 1]}; 105 | 106 | DrawLineV(x_start, x_stop, BLUE); 107 | DrawCircleV(x_start, 2, BLUE); 108 | } 109 | 110 | EndDrawing(); 111 | 112 | } 113 | 114 | CloseWindow(); 115 | 116 | return 0; 117 | } -------------------------------------------------------------------------------- /springdamper.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | enum 4 | { 5 | HISTORY_MAX = 256 6 | }; 7 | 8 | float x_prev[HISTORY_MAX]; 9 | float v_prev[HISTORY_MAX]; 10 | float t_prev[HISTORY_MAX]; 11 | 12 | int main(void) 13 | { 14 | // Init Window 15 | 16 | const int screenWidth = 640; 17 | const int screenHeight = 360; 18 | 19 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - springdamper"); 20 | 21 | // Init Variables 22 | 23 | float t = 0.0; 24 | float x = screenHeight / 2.0f; 25 | float v = 0.0; 26 | float g = x; 27 | float goalOffset = 600; 28 | 29 | //float stiffness = 15.0f; 30 | //float damping = 2.0f; 31 | //float frequency = 2.0f; 32 | float damping_ratio = 1.0f; 33 | float halflife = 0.1f; 34 | float dt = 1.0 / 60.0f; 35 | float timescale = 240.0f; 36 | 37 | SetTargetFPS(1.0f / dt); 38 | 39 | for (int i = 0; i < HISTORY_MAX; i++) 40 | { 41 | x_prev[i] = x; 42 | v_prev[i] = v; 43 | t_prev[i] = t; 44 | } 45 | 46 | while (!WindowShouldClose()) 47 | { 48 | // Shift History 49 | 50 | for (int i = HISTORY_MAX - 1; i > 0; i--) 51 | { 52 | x_prev[i] = x_prev[i - 1]; 53 | v_prev[i] = v_prev[i - 1]; 54 | t_prev[i] = t_prev[i - 1]; 55 | } 56 | 57 | // Get Goal 58 | 59 | if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)) 60 | { 61 | g = GetMousePosition().y; 62 | } 63 | 64 | // Spring Damper 65 | 66 | //GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "stiffness", TextFormat("%5.3f", stiffness), &stiffness, 0.01f, 30.0f); 67 | //GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "frequency", TextFormat("%5.3f", frequency), &frequency, 0.0f, 3.0f); 68 | GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "damping ratio", TextFormat("%5.3f", damping_ratio), &damping_ratio, 0.0f, 2.0f); 69 | //GuiSliderBar((Rectangle){ 100, 45, 120, 20 }, "damping", TextFormat("%5.3f", damping), &damping, 0.01f, 30.0f); 70 | GuiSliderBar((Rectangle){ 100, 45, 120, 20 }, "halflife", TextFormat("%5.3f", halflife), &halflife, 0.0f, 1.0f); 71 | GuiSliderBar((Rectangle){ 100, 70, 120, 20 }, "dt", TextFormat("%5.3f", dt), &dt, 1.0 / 60.0f, 0.1f); 72 | 73 | // Update Spring 74 | 75 | SetTargetFPS(1.0f / dt); 76 | 77 | t += dt; 78 | //spring_damper_bad(x, v, g, 0.0f, stiffness, damping, dt); 79 | //spring_damper_exact(x, v, g, 0.0f, stiffness, damping, dt); 80 | //spring_damper_exact(x, v, g, 0.0f, frequency, halflife, dt); 81 | spring_damper_exact_ratio(x, v, g, 0.0f, damping_ratio, halflife, dt); 82 | //critical_spring_damper_exact(x, v, g, 0.0f, halflife, dt); 83 | //simple_spring_damper_exact(x, v, g, halflife, dt); 84 | //decay_spring_damper_exact(x, v, halflife, dt); 85 | 86 | x_prev[0] = x; 87 | v_prev[0] = v; 88 | t_prev[0] = t; 89 | 90 | BeginDrawing(); 91 | 92 | ClearBackground(RAYWHITE); 93 | 94 | 95 | DrawCircleV((Vector2){goalOffset, g}, 5, MAROON); 96 | DrawCircleV((Vector2){goalOffset, x}, 5, DARKBLUE); 97 | 98 | for (int i = 0; i < HISTORY_MAX - 1; i++) 99 | { 100 | Vector2 x_start = {goalOffset - (t - t_prev[i + 0]) * timescale, x_prev[i + 0]}; 101 | Vector2 x_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, x_prev[i + 1]}; 102 | 103 | DrawLineV(x_start, x_stop, BLUE); 104 | DrawCircleV(x_start, 2, BLUE); 105 | } 106 | 107 | EndDrawing(); 108 | 109 | } 110 | 111 | CloseWindow(); 112 | 113 | return 0; 114 | } -------------------------------------------------------------------------------- /timedspring.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | //-------------------------------------- 4 | 5 | void timed_spring_damper_exact( 6 | float& x, 7 | float& v, 8 | float& xi, 9 | float x_goal, 10 | float t_goal, 11 | float halflife, 12 | float dt) 13 | { 14 | float min_time = t_goal > dt ? t_goal : dt; 15 | 16 | float v_goal = (x_goal - xi) / min_time; 17 | 18 | float t_goal_future = halflife_to_lag(halflife); 19 | float x_goal_future = t_goal_future < t_goal ? 20 | xi + v_goal * t_goal_future : x_goal; 21 | 22 | simple_spring_damper_exact(x, v, x_goal_future, halflife, dt); 23 | 24 | xi += v_goal * dt; 25 | } 26 | 27 | //-------------------------------------- 28 | 29 | enum 30 | { 31 | HISTORY_MAX = 256 32 | }; 33 | 34 | float x_prev[HISTORY_MAX]; 35 | float v_prev[HISTORY_MAX]; 36 | float t_prev[HISTORY_MAX]; 37 | 38 | float xi_prev[HISTORY_MAX]; 39 | 40 | int main(void) 41 | { 42 | // Init Window 43 | 44 | const int screenWidth = 640; 45 | const int screenHeight = 360; 46 | 47 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - timedspring"); 48 | 49 | // Init Variables 50 | 51 | float t = 0.0; 52 | float x = screenHeight / 2.0f; 53 | float v = 0.0; 54 | float g = x; 55 | float goalOffset = 600; 56 | 57 | float halflife = 0.1f; 58 | float dt = 1.0 / 60.0f; 59 | float timescale = 240.0f; 60 | 61 | float goal_time = 1.0f; 62 | float ti = 0.0; 63 | 64 | float xi = x; 65 | 66 | SetTargetFPS(1.0f / dt); 67 | 68 | for (int i = 0; i < HISTORY_MAX; i++) 69 | { 70 | x_prev[i] = x; 71 | v_prev[i] = v; 72 | t_prev[i] = t; 73 | 74 | xi_prev[i] = x; 75 | } 76 | 77 | while (!WindowShouldClose()) 78 | { 79 | // Shift History 80 | 81 | for (int i = HISTORY_MAX - 1; i > 0; i--) 82 | { 83 | x_prev[i] = x_prev[i - 1]; 84 | v_prev[i] = v_prev[i - 1]; 85 | t_prev[i] = t_prev[i - 1]; 86 | 87 | xi_prev[i] = xi_prev[i - 1]; 88 | } 89 | 90 | // Get Goal 91 | 92 | if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)) 93 | { 94 | g = GetMousePosition().y; 95 | 96 | ti = goal_time; 97 | } 98 | 99 | // Timed Spring 100 | 101 | GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "halflife", TextFormat("%5.3f", halflife), &halflife, 0.0f, 1.0f); 102 | GuiSliderBar((Rectangle){ 100, 45, 120, 20 }, "timer reset", TextFormat("%5.3f", goal_time), &goal_time, 0.0f, 3.0f); 103 | 104 | GuiLabel((Rectangle){ 525, 20, 120, 20 }, TextFormat("Timer: %4.2f", ti)); 105 | 106 | // Update Spring 107 | 108 | SetTargetFPS(1.0f / dt); 109 | 110 | t += dt; 111 | 112 | ti -= dt; 113 | ti = ti < 0.0f ? 0.0f : ti; 114 | 115 | timed_spring_damper_exact(x, v, xi, g, ti, halflife, dt); 116 | 117 | x_prev[0] = x; 118 | v_prev[0] = v; 119 | t_prev[0] = t; 120 | 121 | xi_prev[0] = xi; 122 | 123 | BeginDrawing(); 124 | 125 | ClearBackground(RAYWHITE); 126 | 127 | DrawCircleV((Vector2){goalOffset, g}, 5, MAROON); 128 | DrawCircleV((Vector2){goalOffset, x}, 5, DARKBLUE); 129 | 130 | for (int i = 0; i < HISTORY_MAX - 1; i++) 131 | { 132 | Vector2 g_start = {goalOffset - (t - t_prev[i + 0]) * timescale, xi_prev[i + 0]}; 133 | Vector2 g_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, xi_prev[i + 1]}; 134 | 135 | DrawLineV(g_start, g_stop, MAROON); 136 | DrawCircleV(g_start, 2, MAROON); 137 | } 138 | 139 | for (int i = 0; i < HISTORY_MAX - 1; i++) 140 | { 141 | Vector2 x_start = {goalOffset - (t - t_prev[i + 0]) * timescale, x_prev[i + 0]}; 142 | Vector2 x_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, x_prev[i + 1]}; 143 | 144 | DrawLineV(x_start, x_stop, BLUE); 145 | DrawCircleV(x_start, 2, BLUE); 146 | } 147 | 148 | EndDrawing(); 149 | 150 | } 151 | 152 | CloseWindow(); 153 | 154 | return 0; 155 | } -------------------------------------------------------------------------------- /tracking.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | //-------------------------------------- 4 | 5 | void tracking_function(float& g, float& gv, float t, float freq, float amp, float phase, float off) 6 | { 7 | g = amp * sin(t * freq + phase) + off; 8 | gv = amp * freq * cos(t * freq + phase); 9 | } 10 | 11 | void tracking_function1(float& g, float& gv, float t) 12 | { 13 | tracking_function(g, gv, t, 2.0f * M_PI * 1.25, 74.0, 23.213123, 254); 14 | } 15 | 16 | void tracking_function2(float& g, float& gv, float t) 17 | { 18 | tracking_function(g, gv, t, 2.0f * M_PI * 3.4, 28.0, 912.2381, 113); 19 | } 20 | 21 | //-------------------------------------- 22 | 23 | void tracking_spring_update( 24 | float& x, 25 | float& v, 26 | float x_goal, 27 | float v_goal, 28 | float a_goal, 29 | float x_gain, 30 | float v_gain, 31 | float a_gain, 32 | float dt) 33 | { 34 | v = lerp(v, v + a_goal * dt, a_gain); 35 | v = lerp(v, v_goal, v_gain); 36 | v = lerp(v, (x_goal - x) / dt, x_gain); 37 | x = x + dt * v; 38 | } 39 | 40 | void tracking_spring_update_no_acceleration( 41 | float& x, 42 | float& v, 43 | float x_goal, 44 | float v_goal, 45 | float x_gain, 46 | float v_gain, 47 | float dt) 48 | { 49 | v = lerp(v, v_goal, v_gain); 50 | v = lerp(v, (x_goal - x) / dt, x_gain); 51 | x = x + dt * v; 52 | } 53 | 54 | void tracking_spring_update_no_velocity_acceleration( 55 | float& x, 56 | float& v, 57 | float x_goal, 58 | float x_gain, 59 | float dt) 60 | { 61 | v = lerp(v, (x_goal - x) / dt, x_gain); 62 | x = x + dt * v; 63 | } 64 | 65 | //-------------------------------------- 66 | 67 | void tracking_spring_update_improved( 68 | float& x, 69 | float& v, 70 | float x_goal, 71 | float v_goal, 72 | float a_goal, 73 | float x_halflife, 74 | float v_halflife, 75 | float a_halflife, 76 | float dt) 77 | { 78 | v = damper_exact(v, v + a_goal * dt, a_halflife, dt); 79 | v = damper_exact(v, v_goal, v_halflife, dt); 80 | v = damper_exact(v, (x_goal - x) / dt, x_halflife, dt); 81 | x = x + dt * v; 82 | } 83 | 84 | void tracking_spring_update_no_acceleration_improved( 85 | float& x, 86 | float& v, 87 | float x_goal, 88 | float v_goal, 89 | float x_halflife, 90 | float v_halflife, 91 | float dt) 92 | { 93 | v = damper_exact(v, v_goal, v_halflife, dt); 94 | v = damper_exact(v, (x_goal - x) / dt, x_halflife, dt); 95 | x = x + dt * v; 96 | } 97 | 98 | void tracking_spring_update_no_velocity_acceleration_improved( 99 | float& x, 100 | float& v, 101 | float x_goal, 102 | float x_halflife, 103 | float dt) 104 | { 105 | v = damper_exact(v, (x_goal - x) / dt, x_halflife, dt); 106 | x = x + dt * v; 107 | } 108 | 109 | //-------------------------------------- 110 | 111 | void tracking_spring_update_exact( 112 | float& x, 113 | float& v, 114 | float x_goal, 115 | float v_goal, 116 | float a_goal, 117 | float x_gain, 118 | float v_gain, 119 | float a_gain, 120 | float dt, 121 | float gain_dt) 122 | { 123 | float t0 = (1.0f - v_gain) * (1.0f - x_gain); 124 | float t1 = a_gain * (1.0f - v_gain) * (1.0f - x_gain); 125 | float t2 = (v_gain * (1.0f - x_gain)) / gain_dt; 126 | float t3 = x_gain / (gain_dt*gain_dt); 127 | 128 | float stiffness = t3; 129 | float damping = (1.0f - t0) / gain_dt; 130 | float spring_x_goal = x_goal; 131 | float spring_v_goal = (t2*v_goal + t1*a_goal) / ((1.0f - t0) / gain_dt); 132 | 133 | spring_damper_exact_stiffness_damping( 134 | x, 135 | v, 136 | spring_x_goal, 137 | spring_v_goal, 138 | stiffness, 139 | damping, 140 | dt); 141 | } 142 | 143 | void tracking_spring_update_no_acceleration_exact( 144 | float& x, 145 | float& v, 146 | float x_goal, 147 | float v_goal, 148 | float x_gain, 149 | float v_gain, 150 | float dt, 151 | float gain_dt) 152 | { 153 | float t0 = (1.0f - v_gain) * (1.0f - x_gain); 154 | float t2 = (v_gain * (1.0f - x_gain)) / gain_dt; 155 | float t3 = x_gain / (gain_dt*gain_dt); 156 | 157 | float stiffness = t3; 158 | float damping = (1.0f - t0) / gain_dt; 159 | float spring_x_goal = x_goal; 160 | float spring_v_goal = t2*v_goal / ((1.0f - t0) / gain_dt); 161 | 162 | spring_damper_exact_stiffness_damping( 163 | x, 164 | v, 165 | spring_x_goal, 166 | spring_v_goal, 167 | stiffness, 168 | damping, 169 | dt); 170 | } 171 | 172 | void tracking_spring_update_no_velocity_acceleration_exact( 173 | float& x, 174 | float& v, 175 | float x_goal, 176 | float x_gain, 177 | float dt, 178 | float gain_dt) 179 | { 180 | float t0 = 1.0f - x_gain; 181 | float t3 = x_gain / (gain_dt*gain_dt); 182 | 183 | float stiffness = t3; 184 | float damping = (1.0f - t0) / gain_dt; 185 | float spring_x_goal = x_goal; 186 | float spring_v_goal = 0.0f; 187 | 188 | spring_damper_exact_stiffness_damping( 189 | x, 190 | v, 191 | spring_x_goal, 192 | spring_v_goal, 193 | stiffness, 194 | damping, 195 | dt); 196 | } 197 | 198 | //-------------------------------------- 199 | 200 | float tracking_target_acceleration( 201 | float x_next, 202 | float x_curr, 203 | float x_prev, 204 | float dt) 205 | { 206 | return (((x_next - x_curr) / dt) - ((x_curr - x_prev) / dt)) / dt; 207 | } 208 | 209 | float tracking_target_velocity( 210 | float x_next, 211 | float x_curr, 212 | float dt) 213 | { 214 | return (x_next - x_curr) / dt; 215 | } 216 | 217 | //-------------------------------------- 218 | 219 | enum 220 | { 221 | HISTORY_MAX = 256 222 | }; 223 | 224 | float x_prev[HISTORY_MAX]; 225 | float v_prev[HISTORY_MAX]; 226 | float t_prev[HISTORY_MAX]; 227 | float g_prev[HISTORY_MAX]; 228 | 229 | int main(void) 230 | { 231 | // Init Window 232 | 233 | const int screenWidth = 640; 234 | const int screenHeight = 360; 235 | 236 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - tracking"); 237 | 238 | // Init Variables 239 | 240 | float t = 0.0; 241 | float x = screenHeight / 2.0f; 242 | float v = 0.0; 243 | float g = x; 244 | float goalOffset = 600; 245 | 246 | float halflife = 0.1f; 247 | float dt = 1.0 / 60.0f; 248 | float timescale = 240.0f; 249 | 250 | float x_gain = 0.01f; 251 | float v_gain = 0.2f; 252 | float a_gain = 1.0f; 253 | 254 | float x_halflife = 1.0f; 255 | float v_halflife = 0.05f; 256 | float a_halflife = 0.0f; 257 | 258 | float v_max = 750.0f; 259 | float a_max = 12500.0f; 260 | 261 | bool tracking_toggle = true; 262 | int time_since_switch = 0; 263 | bool clamping = false; 264 | bool improved = true; 265 | bool exact = true; 266 | 267 | SetTargetFPS(1.0f / dt); 268 | 269 | for (int i = 0; i < HISTORY_MAX; i++) 270 | { 271 | x_prev[i] = x; 272 | v_prev[i] = v; 273 | t_prev[i] = t; 274 | g_prev[i] = x; 275 | } 276 | 277 | while (!WindowShouldClose()) 278 | { 279 | // Shift History 280 | 281 | for (int i = HISTORY_MAX - 1; i > 0; i--) 282 | { 283 | x_prev[i] = x_prev[i - 1]; 284 | v_prev[i] = v_prev[i - 1]; 285 | t_prev[i] = t_prev[i - 1]; 286 | g_prev[i] = g_prev[i - 1]; 287 | } 288 | 289 | if (GuiButton((Rectangle){ 100, 45, 120, 20 }, "Transition")) 290 | { 291 | tracking_toggle = !tracking_toggle; 292 | time_since_switch = 0; 293 | } 294 | else 295 | { 296 | time_since_switch++; 297 | } 298 | 299 | //GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "halflife", TextFormat("%5.3f", halflife), &halflife, 0.0f, 1.0f); 300 | GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "dt", TextFormat("%5.3f", dt), &dt, 1.0 / 60.0f, 0.1f); 301 | 302 | // Update Spring 303 | 304 | SetTargetFPS(1.0f / dt); 305 | 306 | t += dt; 307 | 308 | float gv = 0.0f; 309 | if (tracking_toggle) 310 | { 311 | tracking_function1(g, gv, t); 312 | } 313 | else 314 | { 315 | tracking_function2(g, gv, t); 316 | } 317 | 318 | /* 319 | critical_spring_damper_exact( 320 | x, 321 | v, 322 | g, 323 | gv, 324 | halflife, 325 | dt); 326 | */ 327 | 328 | if (clamping || time_since_switch > 1) 329 | { 330 | float x_goal = g; 331 | float v_goal = tracking_target_velocity(g, g_prev[1], dt); 332 | float a_goal = tracking_target_acceleration(g, g_prev[1], g_prev[2], dt); 333 | 334 | if (clamping) 335 | { 336 | v_goal = clamp(v_goal, -v_max, v_max); 337 | a_goal = clamp(a_goal, -a_max, a_max); 338 | } 339 | 340 | if (exact) 341 | { 342 | tracking_spring_update_exact( 343 | x, v, 344 | x_goal, v_goal, a_goal, 345 | x_gain, v_gain, a_gain, 346 | dt, 347 | 1.0f / 60.0f); 348 | } 349 | else if (improved) 350 | { 351 | tracking_spring_update_improved( 352 | x, v, 353 | x_goal, v_goal, a_goal, 354 | x_halflife, v_halflife, a_halflife, 355 | dt); 356 | } 357 | else 358 | { 359 | tracking_spring_update( 360 | x, v, 361 | x_goal, v_goal, a_goal, 362 | x_gain, v_gain, a_gain, 363 | dt); 364 | } 365 | } 366 | else if (time_since_switch > 0) 367 | { 368 | float x_goal = g; 369 | float v_goal = tracking_target_velocity(g, g_prev[1], dt); 370 | 371 | if (exact) 372 | { 373 | tracking_spring_update_no_acceleration_exact( 374 | x, v, 375 | x_goal, v_goal, 376 | x_gain, v_gain, 377 | dt, 378 | 1.0f / 60.0f); 379 | } 380 | else if (improved) 381 | { 382 | tracking_spring_update_no_acceleration_improved( 383 | x, v, 384 | x_goal, v_goal, 385 | x_halflife, v_halflife, 386 | dt); 387 | } 388 | else 389 | { 390 | tracking_spring_update_no_acceleration( 391 | x, v, 392 | x_goal, v_goal, 393 | x_gain, v_gain, 394 | dt); 395 | } 396 | } 397 | else 398 | { 399 | float x_goal = g; 400 | 401 | if (exact) 402 | { 403 | tracking_spring_update_no_velocity_acceleration_exact( 404 | x, v, 405 | x_goal, 406 | x_gain, 407 | dt, 408 | 1.0f / 60.0f); 409 | } 410 | else if (improved) 411 | { 412 | tracking_spring_update_no_velocity_acceleration_improved( 413 | x, v, 414 | x_goal, 415 | x_halflife, 416 | dt); 417 | } 418 | else 419 | { 420 | tracking_spring_update_no_velocity_acceleration( 421 | x, v, 422 | x_goal, 423 | x_gain, 424 | dt); 425 | } 426 | } 427 | 428 | x_prev[0] = x; 429 | v_prev[0] = v; 430 | t_prev[0] = t; 431 | g_prev[0] = g; 432 | 433 | BeginDrawing(); 434 | 435 | ClearBackground(RAYWHITE); 436 | 437 | DrawCircleV((Vector2){goalOffset, g}, 5, MAROON); 438 | DrawCircleV((Vector2){goalOffset, x}, 5, DARKBLUE); 439 | 440 | for (int i = 0; i < HISTORY_MAX - 1; i++) 441 | { 442 | Vector2 x_start = {goalOffset - (t - t_prev[i + 0]) * timescale, x_prev[i + 0]}; 443 | Vector2 x_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, x_prev[i + 1]}; 444 | 445 | DrawLineV(x_start, x_stop, BLUE); 446 | DrawCircleV(x_start, 2, BLUE); 447 | } 448 | 449 | for (int i = 0; i < HISTORY_MAX - 1; i++) 450 | { 451 | Vector2 g_start = {goalOffset - (t - t_prev[i + 0]) * timescale, g_prev[i + 0]}; 452 | Vector2 g_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, g_prev[i + 1]}; 453 | 454 | DrawLineV(g_start, g_stop, MAROON); 455 | DrawCircleV(g_start, 2, MAROON); 456 | } 457 | 458 | EndDrawing(); 459 | 460 | } 461 | 462 | CloseWindow(); 463 | 464 | return 0; 465 | } -------------------------------------------------------------------------------- /velocityspring.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | //-------------------------------------- 4 | 5 | void velocity_spring_damper_exact( 6 | float& x, 7 | float& v, 8 | float& xi, 9 | float x_goal, 10 | float v_goal, 11 | float halflife, 12 | float dt, 13 | float eps = 1e-5f) 14 | { 15 | float x_diff = ((x_goal - xi) > 0.0f ? 1.0f : -1.0f) * v_goal; 16 | 17 | float t_goal_future = halflife_to_lag(halflife); 18 | float x_goal_future = fabs(x_goal - xi) > t_goal_future * v_goal ? 19 | xi + x_diff * t_goal_future : x_goal; 20 | 21 | simple_spring_damper_exact(x, v, x_goal_future, halflife, dt); 22 | 23 | xi = fabs(x_goal - xi) > dt * v_goal ? xi + x_diff * dt : x_goal; 24 | } 25 | 26 | //-------------------------------------- 27 | 28 | enum 29 | { 30 | HISTORY_MAX = 256 31 | }; 32 | 33 | float x_prev[HISTORY_MAX]; 34 | float v_prev[HISTORY_MAX]; 35 | float t_prev[HISTORY_MAX]; 36 | 37 | float xi_prev[HISTORY_MAX]; 38 | 39 | int main(void) 40 | { 41 | // Init Window 42 | 43 | const int screenWidth = 640; 44 | const int screenHeight = 360; 45 | 46 | InitWindow(screenWidth, screenHeight, "raylib [springs] example - velocityspring"); 47 | 48 | // Init Variables 49 | 50 | float t = 0.0; 51 | float x = screenHeight / 2.0f; 52 | float v = 0.0; 53 | float g = x; 54 | float goalOffset = 600; 55 | 56 | float halflife = 0.1f; 57 | float dt = 1.0 / 60.0f; 58 | float timescale = 240.0f; 59 | 60 | float goal_velocity = 100.0f; 61 | 62 | float xi = x; 63 | 64 | SetTargetFPS(1.0f / dt); 65 | 66 | for (int i = 0; i < HISTORY_MAX; i++) 67 | { 68 | x_prev[i] = x; 69 | v_prev[i] = v; 70 | t_prev[i] = t; 71 | 72 | xi_prev[i] = x; 73 | } 74 | 75 | while (!WindowShouldClose()) 76 | { 77 | // Shift History 78 | 79 | for (int i = HISTORY_MAX - 1; i > 0; i--) 80 | { 81 | x_prev[i] = x_prev[i - 1]; 82 | v_prev[i] = v_prev[i - 1]; 83 | t_prev[i] = t_prev[i - 1]; 84 | 85 | xi_prev[i] = xi_prev[i - 1]; 86 | } 87 | 88 | // Get Goal 89 | 90 | if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)) 91 | { 92 | g = GetMousePosition().y; 93 | } 94 | 95 | // Timed Spring 96 | 97 | GuiSliderBar((Rectangle){ 100, 20, 120, 20 }, "halflife", TextFormat("%5.3f", halflife), &halflife, 0.0f, 1.0f); 98 | GuiSliderBar((Rectangle){ 100, 45, 120, 20 }, "goal velocity", TextFormat("%5.3f", goal_velocity), &goal_velocity, 0.0f, 500.0f); 99 | 100 | // Update Spring 101 | 102 | SetTargetFPS(1.0f / dt); 103 | 104 | t += dt; 105 | 106 | velocity_spring_damper_exact(x, v, xi, g, goal_velocity, halflife, dt); 107 | 108 | x_prev[0] = x; 109 | v_prev[0] = v; 110 | t_prev[0] = t; 111 | 112 | xi_prev[0] = xi; 113 | 114 | BeginDrawing(); 115 | 116 | ClearBackground(RAYWHITE); 117 | 118 | DrawCircleV((Vector2){goalOffset, g}, 5, MAROON); 119 | DrawCircleV((Vector2){goalOffset, x}, 5, DARKBLUE); 120 | 121 | for (int i = 0; i < HISTORY_MAX - 1; i++) 122 | { 123 | Vector2 g_start = {goalOffset - (t - t_prev[i + 0]) * timescale, xi_prev[i + 0]}; 124 | Vector2 g_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, xi_prev[i + 1]}; 125 | 126 | DrawLineV(g_start, g_stop, MAROON); 127 | DrawCircleV(g_start, 2, MAROON); 128 | } 129 | 130 | for (int i = 0; i < HISTORY_MAX - 1; i++) 131 | { 132 | Vector2 x_start = {goalOffset - (t - t_prev[i + 0]) * timescale, x_prev[i + 0]}; 133 | Vector2 x_stop = {goalOffset - (t - t_prev[i + 1]) * timescale, x_prev[i + 1]}; 134 | 135 | DrawLineV(x_start, x_stop, BLUE); 136 | DrawCircleV(x_start, 2, BLUE); 137 | } 138 | 139 | EndDrawing(); 140 | 141 | } 142 | 143 | CloseWindow(); 144 | 145 | return 0; 146 | } --------------------------------------------------------------------------------