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 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
49#include "nilorea/n_common.h"
50#include <stdlib.h>
51
62static DR_VEC3 cubic_bezier(DR_VEC3 p0, DR_VEC3 p1, DR_VEC3 p2, DR_VEC3 p3, double t) {
63 double u = 1.0 - t;
64 double u2 = u * u;
65 double u3 = u2 * u;
66 double t2 = t * t;
67 double t3 = t2 * t;
68
69 DR_VEC3 r;
70 r.x = u3 * p0.x + 3.0 * u2 * t * p1.x + 3.0 * u * t2 * p2.x + t3 * p3.x;
71 r.y = u3 * p0.y + 3.0 * u2 * t * p1.y + 3.0 * u * t2 * p2.y + t3 * p3.y;
72 r.z = u3 * p0.z + 3.0 * u2 * t * p1.z + 3.0 * u * t2 * p2.z + t3 * p3.z;
73 return r;
74}
75
84DR_ENTITY* dr_entity_create(DR_ALGO algo, DR_BLEND blend_mode, double pos_threshold, double blend_time) {
85 DR_ENTITY* ent = NULL;
86 Malloc(ent, DR_ENTITY, 1);
87 __n_assert(ent, return NULL);
88
89 ent->algo = algo;
90 ent->blend_mode = blend_mode;
91 ent->pos_threshold = pos_threshold;
92 ent->blend_time = blend_time;
93
97 ent->last_known.time = 0.0;
98
102 ent->prev_predicted.time = 0.0;
103
104 ent->blend_start = 0.0;
105 ent->blending = false;
106
107 ent->bezier_p0 = dr_vec3_zero();
108 ent->bezier_p1 = dr_vec3_zero();
109 ent->bezier_p2 = dr_vec3_zero();
110 ent->bezier_p3 = dr_vec3_zero();
111
112 ent->display_pos = dr_vec3_zero();
113 ent->display_vel = dr_vec3_zero();
114 ent->update_count = 0;
115
116 return ent;
117}
118
123void dr_entity_destroy(DR_ENTITY** entity_ptr) {
124 __n_assert(entity_ptr, return);
125 Free((*entity_ptr));
126}
127
136DR_VEC3 dr_entity_extrapolate(const DR_ENTITY* entity, const DR_STATE* state, double dt) {
137 switch (entity->algo) {
138 case DR_ALGO_STATIC:
139 return state->pos;
140
141 case DR_ALGO_VEL:
142 return dr_vec3_add(state->pos, dr_vec3_scale(state->vel, dt));
143
144 case DR_ALGO_VEL_ACC:
145 /* P(t) = P0 + V0*dt + 0.5*A0*dt^2 */
146 return dr_vec3_add(
147 dr_vec3_add(state->pos, dr_vec3_scale(state->vel, dt)),
148 dr_vec3_scale(state->acc, 0.5 * dt * dt));
149
150 default:
151 return state->pos;
152 }
153}
154
170 const DR_VEC3* pos,
171 const DR_VEC3* vel,
172 const DR_VEC3* acc,
173 double time) {
174 __n_assert(entity, return);
175 __n_assert(pos, return);
176
177 /* Save where we THINK the entity is right now (i.e. extrapolate
178 * the old state to the current time), this becomes the starting
179 * point for convergence blending. */
180 double dt_old = time - entity->last_known.time;
181 entity->prev_predicted.pos = dr_entity_extrapolate(entity, &entity->last_known, dt_old);
182 entity->prev_predicted.vel = entity->last_known.vel;
183 entity->prev_predicted.acc = entity->last_known.acc;
184 entity->prev_predicted.time = time;
185
186 /* Store the new authoritative state */
187 entity->last_known.pos = *pos;
188 entity->last_known.vel = vel ? *vel : dr_vec3_zero();
189 entity->last_known.acc = acc ? *acc : dr_vec3_zero();
190 entity->last_known.time = time;
191
192 /* Activate blending (unless SNAP mode) */
193 if (entity->blend_mode != DR_BLEND_SNAP) {
194 entity->blending = true;
195 entity->blend_start = time;
196
197 /* For cubic Bezier: compute control points */
198 if (entity->blend_mode == DR_BLEND_CUBIC) {
199 /* P0 = where we were (old prediction) */
200 entity->bezier_p0 = entity->prev_predicted.pos;
201 /* P3 = where the new state will be after blend_time */
203 entity, &entity->last_known, entity->blend_time);
204 /* P1 = P0 + (old_velocity * blend_time / 3)
205 * Ensures C1 continuity at the start */
206 entity->bezier_p1 = dr_vec3_add(
207 entity->bezier_p0,
208 dr_vec3_scale(entity->prev_predicted.vel, entity->blend_time / 3.0));
209 /* P2 = P3 - (new_velocity * blend_time / 3)
210 * Ensures C1 continuity at the end */
211 DR_VEC3 end_vel = entity->last_known.vel;
212 if (entity->algo == DR_ALGO_VEL_ACC) {
213 /* Adjust velocity for acceleration over blend_time */
214 end_vel = dr_vec3_add(end_vel,
215 dr_vec3_scale(entity->last_known.acc, entity->blend_time));
216 }
217 entity->bezier_p2 = dr_vec3_sub(
218 entity->bezier_p3,
219 dr_vec3_scale(end_vel, entity->blend_time / 3.0));
220 }
221 }
222
223 entity->update_count++;
224}
225
237void dr_entity_compute(DR_ENTITY* entity, double time, DR_VEC3* out_pos) {
238 __n_assert(entity, return);
239 __n_assert(out_pos, return);
240
241 if (!entity->blending) {
242 /* No blending: pure extrapolation from last known state */
243 double dt = time - entity->last_known.time;
244 *out_pos = dr_entity_extrapolate(entity, &entity->last_known, dt);
245 entity->display_pos = *out_pos;
246 return;
247 }
248
249 /* Compute blend parameter t_hat in [0, 1] */
250 double elapsed = time - entity->blend_start;
251 double t_hat = (entity->blend_time > 0.0) ? elapsed / entity->blend_time : 1.0;
252
253 if (t_hat >= 1.0) {
254 /* Blending is complete, switch to pure extrapolation */
255 entity->blending = false;
256 double dt = time - entity->last_known.time;
257 *out_pos = dr_entity_extrapolate(entity, &entity->last_known, dt);
258 entity->display_pos = *out_pos;
259 return;
260 }
261
262 switch (entity->blend_mode) {
263 case DR_BLEND_PVB: {
264 /* Projective Velocity Blending:
265 * Extrapolate BOTH old and new states forward from blend_start,
266 * then lerp between the two extrapolated positions. */
267
268 /* Where would the OLD prediction be at (time)? */
269 double dt_old = time - entity->prev_predicted.time;
270 DR_VEC3 old_extrap = dr_entity_extrapolate(
271 entity, &entity->prev_predicted, dt_old);
272
273 /* Where does the NEW state predict the entity is at (time)? */
274 double dt_new = time - entity->last_known.time;
275 DR_VEC3 new_extrap = dr_entity_extrapolate(
276 entity, &entity->last_known, dt_new);
277
278 /* Blend */
279 *out_pos = dr_vec3_lerp(old_extrap, new_extrap, t_hat);
280 break;
281 }
282
283 case DR_BLEND_CUBIC:
284 /* Cubic Bezier convergence */
285 *out_pos = cubic_bezier(entity->bezier_p0, entity->bezier_p1,
286 entity->bezier_p2, entity->bezier_p3, t_hat);
287 break;
288
289 case DR_BLEND_SNAP:
290 default: {
291 /* Should not happen (blending is not activated for SNAP), but handle it */
292 double dt = time - entity->last_known.time;
293 *out_pos = dr_entity_extrapolate(entity, &entity->last_known, dt);
294 entity->blending = false;
295 break;
296 }
297 }
298
299 entity->display_pos = *out_pos;
300}
301
317 const DR_VEC3* true_pos,
318 const DR_VEC3* true_vel,
319 const DR_VEC3* true_acc,
320 double time) {
321 (void)true_vel;
322 (void)true_acc;
323
324 __n_assert(entity, return false);
325 __n_assert(true_pos, return false);
326
327 double dt = time - entity->last_known.time;
328 DR_VEC3 predicted = dr_entity_extrapolate(entity, &entity->last_known, dt);
329 double error = dr_vec3_distance(predicted, *true_pos);
330
331 return error > entity->pos_threshold;
332}
333
340 __n_assert(entity, return);
341 entity->algo = algo;
342}
343
349void dr_entity_set_blend_mode(DR_ENTITY* entity, DR_BLEND blend_mode) {
350 __n_assert(entity, return);
351 entity->blend_mode = blend_mode;
352}
353
359void dr_entity_set_threshold(DR_ENTITY* entity, double threshold) {
360 __n_assert(entity, return);
361 entity->pos_threshold = threshold;
362}
363
369void dr_entity_set_blend_time(DR_ENTITY* entity, double blend_time) {
370 __n_assert(entity, return);
371 entity->blend_time = blend_time;
372}
373
384 const DR_VEC3* pos,
385 const DR_VEC3* vel,
386 const DR_VEC3* acc,
387 double time) {
388 __n_assert(entity, return);
389 __n_assert(pos, return);
390
391 entity->last_known.pos = *pos;
392 entity->last_known.vel = vel ? *vel : dr_vec3_zero();
393 entity->last_known.acc = acc ? *acc : dr_vec3_zero();
394 entity->last_known.time = time;
395
396 entity->prev_predicted = entity->last_known;
397 entity->display_pos = *pos;
398 entity->blending = false;
399 entity->update_count = 0;
400}
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
Definition n_common.h:204
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:279
#define Free(__ptr)
Free Handler to get errors.
Definition n_common.h:263
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.