Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_dead_reckoning.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
48#include "nilorea/n_common.h"
49#include <stdlib.h>
50
61static DR_VEC3 cubic_bezier(DR_VEC3 p0, DR_VEC3 p1, DR_VEC3 p2, DR_VEC3 p3, double t) {
62 double u = 1.0 - t;
63 double u2 = u * u;
64 double u3 = u2 * u;
65 double t2 = t * t;
66 double t3 = t2 * t;
67
68 DR_VEC3 r;
69 r.x = u3 * p0.x + 3.0 * u2 * t * p1.x + 3.0 * u * t2 * p2.x + t3 * p3.x;
70 r.y = u3 * p0.y + 3.0 * u2 * t * p1.y + 3.0 * u * t2 * p2.y + t3 * p3.y;
71 r.z = u3 * p0.z + 3.0 * u2 * t * p1.z + 3.0 * u * t2 * p2.z + t3 * p3.z;
72 return r;
73}
74
83DR_ENTITY* dr_entity_create(DR_ALGO algo, DR_BLEND blend_mode, double pos_threshold, double blend_time) {
84 DR_ENTITY* ent = NULL;
85 Malloc(ent, DR_ENTITY, 1);
86 __n_assert(ent, return NULL);
87
88 ent->algo = algo;
89 ent->blend_mode = blend_mode;
90 ent->pos_threshold = pos_threshold;
91 ent->blend_time = blend_time;
92
96 ent->last_known.time = 0.0;
97
101 ent->prev_predicted.time = 0.0;
102
103 ent->blend_start = 0.0;
104 ent->blending = false;
105
106 ent->bezier_p0 = dr_vec3_zero();
107 ent->bezier_p1 = dr_vec3_zero();
108 ent->bezier_p2 = dr_vec3_zero();
109 ent->bezier_p3 = dr_vec3_zero();
110
111 ent->display_pos = dr_vec3_zero();
112 ent->display_vel = dr_vec3_zero();
113 ent->update_count = 0;
114
115 return ent;
116}
117
122void dr_entity_destroy(DR_ENTITY** entity_ptr) {
123 __n_assert(entity_ptr, return);
124 Free((*entity_ptr));
125}
126
135DR_VEC3 dr_entity_extrapolate(const DR_ENTITY* entity, const DR_STATE* state, double dt) {
136 switch (entity->algo) {
137 case DR_ALGO_STATIC:
138 return state->pos;
139
140 case DR_ALGO_VEL:
141 return dr_vec3_add(state->pos, dr_vec3_scale(state->vel, dt));
142
143 case DR_ALGO_VEL_ACC:
144 /* P(t) = P0 + V0*dt + 0.5*A0*dt^2 */
145 return dr_vec3_add(
146 dr_vec3_add(state->pos, dr_vec3_scale(state->vel, dt)),
147 dr_vec3_scale(state->acc, 0.5 * dt * dt));
148
149 default:
150 return state->pos;
151 }
152}
153
169 const DR_VEC3* pos,
170 const DR_VEC3* vel,
171 const DR_VEC3* acc,
172 double time) {
173 __n_assert(entity, return);
174 __n_assert(pos, return);
175
176 /* Save where we THINK the entity is right now (i.e. extrapolate
177 * the old state to the current time), this becomes the starting
178 * point for convergence blending. */
179 double dt_old = time - entity->last_known.time;
180 entity->prev_predicted.pos = dr_entity_extrapolate(entity, &entity->last_known, dt_old);
181 entity->prev_predicted.vel = entity->last_known.vel;
182 entity->prev_predicted.acc = entity->last_known.acc;
183 entity->prev_predicted.time = time;
184
185 /* Store the new authoritative state */
186 entity->last_known.pos = *pos;
187 entity->last_known.vel = vel ? *vel : dr_vec3_zero();
188 entity->last_known.acc = acc ? *acc : dr_vec3_zero();
189 entity->last_known.time = time;
190
191 /* Activate blending (unless SNAP mode) */
192 if (entity->blend_mode != DR_BLEND_SNAP) {
193 entity->blending = true;
194 entity->blend_start = time;
195
196 /* For cubic Bezier: compute control points */
197 if (entity->blend_mode == DR_BLEND_CUBIC) {
198 /* P0 = where we were (old prediction) */
199 entity->bezier_p0 = entity->prev_predicted.pos;
200 /* P3 = where the new state will be after blend_time */
202 entity, &entity->last_known, entity->blend_time);
203 /* P1 = P0 + (old_velocity * blend_time / 3)
204 * Ensures C1 continuity at the start */
205 entity->bezier_p1 = dr_vec3_add(
206 entity->bezier_p0,
207 dr_vec3_scale(entity->prev_predicted.vel, entity->blend_time / 3.0));
208 /* P2 = P3 - (new_velocity * blend_time / 3)
209 * Ensures C1 continuity at the end */
210 DR_VEC3 end_vel = entity->last_known.vel;
211 if (entity->algo == DR_ALGO_VEL_ACC) {
212 /* Adjust velocity for acceleration over blend_time */
213 end_vel = dr_vec3_add(end_vel,
214 dr_vec3_scale(entity->last_known.acc, entity->blend_time));
215 }
216 entity->bezier_p2 = dr_vec3_sub(
217 entity->bezier_p3,
218 dr_vec3_scale(end_vel, entity->blend_time / 3.0));
219 }
220 }
221
222 entity->update_count++;
223}
224
236void dr_entity_compute(DR_ENTITY* entity, double time, DR_VEC3* out_pos) {
237 __n_assert(entity, return);
238 __n_assert(out_pos, return);
239
240 if (!entity->blending) {
241 /* No blending: pure extrapolation from last known state */
242 double dt = time - entity->last_known.time;
243 *out_pos = dr_entity_extrapolate(entity, &entity->last_known, dt);
244 entity->display_pos = *out_pos;
245 return;
246 }
247
248 /* Compute blend parameter t_hat in [0, 1] */
249 double elapsed = time - entity->blend_start;
250 double t_hat = (entity->blend_time > 0.0) ? elapsed / entity->blend_time : 1.0;
251
252 if (t_hat >= 1.0) {
253 /* Blending is complete, switch to pure extrapolation */
254 entity->blending = false;
255 double dt = time - entity->last_known.time;
256 *out_pos = dr_entity_extrapolate(entity, &entity->last_known, dt);
257 entity->display_pos = *out_pos;
258 return;
259 }
260
261 switch (entity->blend_mode) {
262 case DR_BLEND_PVB: {
263 /* Projective Velocity Blending:
264 * Extrapolate BOTH old and new states forward from blend_start,
265 * then lerp between the two extrapolated positions. */
266
267 /* Where would the OLD prediction be at (time)? */
268 double dt_old = time - entity->prev_predicted.time;
269 DR_VEC3 old_extrap = dr_entity_extrapolate(
270 entity, &entity->prev_predicted, dt_old);
271
272 /* Where does the NEW state predict the entity is at (time)? */
273 double dt_new = time - entity->last_known.time;
274 DR_VEC3 new_extrap = dr_entity_extrapolate(
275 entity, &entity->last_known, dt_new);
276
277 /* Blend */
278 *out_pos = dr_vec3_lerp(old_extrap, new_extrap, t_hat);
279 break;
280 }
281
282 case DR_BLEND_CUBIC:
283 /* Cubic Bezier convergence */
284 *out_pos = cubic_bezier(entity->bezier_p0, entity->bezier_p1,
285 entity->bezier_p2, entity->bezier_p3, t_hat);
286 break;
287
288 case DR_BLEND_SNAP:
289 default: {
290 /* Should not happen (blending is not activated for SNAP), but handle it */
291 double dt = time - entity->last_known.time;
292 *out_pos = dr_entity_extrapolate(entity, &entity->last_known, dt);
293 entity->blending = false;
294 break;
295 }
296 }
297
298 entity->display_pos = *out_pos;
299}
300
316 const DR_VEC3* true_pos,
317 const DR_VEC3* true_vel,
318 const DR_VEC3* true_acc,
319 double time) {
320 (void)true_vel;
321 (void)true_acc;
322
323 __n_assert(entity, return false);
324 __n_assert(true_pos, return false);
325
326 double dt = time - entity->last_known.time;
327 DR_VEC3 predicted = dr_entity_extrapolate(entity, &entity->last_known, dt);
328 double error = dr_vec3_distance(predicted, *true_pos);
329
330 return error > entity->pos_threshold;
331}
332
339 __n_assert(entity, return);
340 entity->algo = algo;
341}
342
348void dr_entity_set_blend_mode(DR_ENTITY* entity, DR_BLEND blend_mode) {
349 __n_assert(entity, return);
350 entity->blend_mode = blend_mode;
351}
352
358void dr_entity_set_threshold(DR_ENTITY* entity, double threshold) {
359 __n_assert(entity, return);
360 entity->pos_threshold = threshold;
361}
362
368void dr_entity_set_blend_time(DR_ENTITY* entity, double blend_time) {
369 __n_assert(entity, return);
370 entity->blend_time = blend_time;
371}
372
383 const DR_VEC3* pos,
384 const DR_VEC3* vel,
385 const DR_VEC3* acc,
386 double time) {
387 __n_assert(entity, return);
388 __n_assert(pos, return);
389
390 entity->last_known.pos = *pos;
391 entity->last_known.vel = vel ? *vel : dr_vec3_zero();
392 entity->last_known.acc = acc ? *acc : dr_vec3_zero();
393 entity->last_known.time = time;
394
395 entity->prev_predicted = entity->last_known;
396 entity->display_pos = *pos;
397 entity->blending = false;
398 entity->update_count = 0;
399}
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
Definition n_common.h:203
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:278
#define Free(__ptr)
Free Handler to get errors.
Definition n_common.h:262
DR_VEC3 pos
Position.
double blend_start
Timestamp when blending started.
double x
X component.
DR_VEC3 bezier_p2
Control point 2.
DR_VEC3 bezier_p3
End point (extrapolated last_known at blend_time)
double y
Y component.
DR_ALGO algo
Extrapolation algorithm.
DR_VEC3 bezier_p1
Control point 1.
double blend_time
Duration of convergence blend in seconds.
double z
Z component.
DR_STATE prev_predicted
Where we THOUGHT entity was when update arrived (P0, V0)
DR_VEC3 vel
Velocity.
DR_VEC3 acc
Acceleration.
double time
Timestamp (seconds) when this state was captured.
bool blending
True if currently blending toward last_known.
DR_VEC3 display_vel
Current blended/rendered velocity.
DR_VEC3 bezier_p0
Start point (previous predicted position)
DR_STATE last_known
Most recent authoritative state (P0', V0', A0')
DR_BLEND blend_mode
Convergence blending mode.
double pos_threshold
Position error threshold for sending updates (distance)
DR_VEC3 display_pos
Current blended/rendered position.
int update_count
Number of state updates received.
static DR_VEC3 dr_vec3_scale(DR_VEC3 v, double s)
Scalar multiplication: v * s.
void dr_entity_destroy(DR_ENTITY **entity_ptr)
Destroy a dead reckoning entity and set the pointer to NULL.
static DR_VEC3 dr_vec3_lerp(DR_VEC3 a, DR_VEC3 b, double t)
Linear interpolation: a + (b - a) * t.
void dr_entity_set_threshold(DR_ENTITY *entity, double threshold)
Set the position error threshold for triggering network updates.
void dr_entity_compute(DR_ENTITY *entity, double time, DR_VEC3 *out_pos)
Compute the dead reckoned display position at a given time.
DR_ENTITY * dr_entity_create(DR_ALGO algo, DR_BLEND blend_mode, double pos_threshold, double blend_time)
Create a new dead reckoning entity.
void dr_entity_set_blend_time(DR_ENTITY *entity, double blend_time)
Set the convergence blend duration.
DR_BLEND
Dead reckoning convergence/blending mode.
void dr_entity_set_blend_mode(DR_ENTITY *entity, DR_BLEND blend_mode)
Set the convergence blending mode.
static DR_VEC3 dr_vec3_sub(DR_VEC3 a, DR_VEC3 b)
Vector subtraction: a - b.
bool dr_entity_check_threshold(const DR_ENTITY *entity, const DR_VEC3 *true_pos, const DR_VEC3 *true_vel, const DR_VEC3 *true_acc, double time)
Check whether the owner's true state has diverged from the dead reckoned prediction beyond the config...
DR_ALGO
Dead reckoning extrapolation algorithm.
void dr_entity_set_algo(DR_ENTITY *entity, DR_ALGO algo)
Set the extrapolation algorithm.
static DR_VEC3 dr_vec3_zero(void)
Zero vector.
static DR_VEC3 dr_vec3_add(DR_VEC3 a, DR_VEC3 b)
Vector addition: a + b.
static double dr_vec3_distance(DR_VEC3 a, DR_VEC3 b)
Distance between two points.
void dr_entity_receive_state(DR_ENTITY *entity, const DR_VEC3 *pos, const DR_VEC3 *vel, const DR_VEC3 *acc, double time)
Receive a new authoritative state update from the network.
void dr_entity_set_position(DR_ENTITY *entity, const DR_VEC3 *pos, const DR_VEC3 *vel, const DR_VEC3 *acc, double time)
Force-set entity position without triggering convergence blending.
DR_VEC3 dr_entity_extrapolate(const DR_ENTITY *entity, const DR_STATE *state, double dt)
Extrapolate a kinematic state forward by dt seconds.
@ DR_BLEND_CUBIC
Cubic Bezier spline convergence.
@ DR_BLEND_PVB
Projective Velocity Blending (recommended)
@ DR_BLEND_SNAP
Snap instantly to new state (no smoothing)
@ DR_ALGO_VEL_ACC
Velocity + acceleration: P(t) = P0 + V0*t + 0.5*A0*t^2.
@ DR_ALGO_VEL
Velocity only: P(t) = P0 + V0*t.
@ DR_ALGO_STATIC
No extrapolation; entity stays at last position.
Dead reckoned entity with extrapolation and convergence state.
A snapshot of entity kinematic state at a point in time.
3D vector used for position, velocity, and acceleration
Common headers and low-level functions & define.
static DR_VEC3 cubic_bezier(DR_VEC3 p0, DR_VEC3 p1, DR_VEC3 p2, DR_VEC3 p3, double t)
Evaluate a cubic Bezier curve at parameter t in [0, 1].
Dead Reckoning API for latency hiding in networked games.