Time fixes
This commit is contained in:
@@ -72,6 +72,10 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
entityAddComponent(
|
||||
testEntities, ENGINE.testCubeEntity, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
entityUpdateAdd(
|
||||
testEntities, ENGINE.testCubeEntity, engineTestCubeRotate,
|
||||
ENGINE.testCubePositionComponent, NULL
|
||||
);
|
||||
|
||||
networkRequestConnection(
|
||||
engineNetworkOnConnected,
|
||||
@@ -111,15 +115,6 @@ errorret_t engineUpdate(void) {
|
||||
inputUpdate();
|
||||
consoleUpdate();
|
||||
|
||||
// Test: spin the cube.
|
||||
ENGINE.testCubeRotation += TIME.delta;
|
||||
entityPositionSetLocalRotation(
|
||||
sceneGetEntities(ENGINE.testSceneId),
|
||||
ENGINE.testCubeEntity,
|
||||
ENGINE.testCubePositionComponent,
|
||||
(vec3){ 0.0f, ENGINE.testCubeRotation, 0.0f }
|
||||
);
|
||||
|
||||
errorChain(sceneUpdate());
|
||||
errorChain(assetUpdate());
|
||||
errorChain(uiUpdate());
|
||||
@@ -212,3 +207,16 @@ void engineNetworkOnDisconnect(errorret_t error, void *user) {
|
||||
void engineNetworkDisconnectTestOnComplete(void *user) {
|
||||
consolePrint("Network disconnect test complete");
|
||||
}
|
||||
|
||||
void engineTestCubeRotate(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
) {
|
||||
ENGINE.testCubeRotation += TIME.delta;
|
||||
entityPositionSetLocalRotation(
|
||||
mgr, entityId, componentId,
|
||||
(vec3){ 0.0f, ENGINE.testCubeRotation, 0.0f }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,3 +98,21 @@ void engineNetworkOnDisconnect(errorret_t error, void *user);
|
||||
* @param user Unused.
|
||||
*/
|
||||
void engineNetworkDisconnectTestOnComplete(void *user);
|
||||
|
||||
/**
|
||||
* Entity update callback that spins the test cube around Y. Registered
|
||||
* once in engineInit() via entityUpdateAdd(), so it only runs from
|
||||
* sceneFixedUpdate() -- once per fixed timestep, never once per rendered
|
||||
* frame.
|
||||
*
|
||||
* @param mgr The entity manager that owns the entity.
|
||||
* @param entityId The test cube's entity ID.
|
||||
* @param componentId The test cube's position component ID.
|
||||
* @param user Unused.
|
||||
*/
|
||||
void engineTestCubeRotate(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
);
|
||||
|
||||
@@ -86,7 +86,9 @@ void entityDisposeDeep(entitymanager_t *mgr, const entityid_t entityId) {
|
||||
void entityUpdate(entitymanager_t *mgr, const entityid_t entityId) {
|
||||
entity_t *ent = &mgr->entities[entityId];
|
||||
for(uint8_t i = 0; i < ent->updateCount; i++) {
|
||||
ent->onUpdate[i](entityId, ent->updateComponentId[i], ent->updateUser[i]);
|
||||
ent->onUpdate[i](
|
||||
mgr, entityId, ent->updateComponentId[i], ent->updateUser[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +98,7 @@ void entityDispose(entitymanager_t *mgr, const entityid_t entityId) {
|
||||
|
||||
for(uint8_t i = 0; i < ent->disposeCount; i++) {
|
||||
ent->onDispose[i](
|
||||
entityId, ent->disposeComponentId[i], ent->disposeUser[i]
|
||||
mgr, entityId, ent->disposeComponentId[i], ent->disposeUser[i]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#define ENTITY_DISPOSE_CALLBACK_COUNT_MAX 5
|
||||
|
||||
typedef void (*entitycallback_t)(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
|
||||
@@ -8,4 +8,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
physicsworld.c
|
||||
physicstest.c
|
||||
physicsshapemesh.c
|
||||
)
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "physicsshapemesh.h"
|
||||
#include "physicstest.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void physicsShapeMeshGetVertex(
|
||||
const physicsshapemesh_t *mesh, const uint32_t index, vec3 out
|
||||
) {
|
||||
assertNotNull(mesh, "Mesh cannot be null");
|
||||
assertTrue(index < mesh->triangleCount * 3, "Vertex index OOB");
|
||||
|
||||
const float_t *p = (const float_t *)(mesh->vertices + (size_t)index * mesh->stride);
|
||||
out[0] = p[0];
|
||||
out[1] = p[1];
|
||||
out[2] = p[2];
|
||||
}
|
||||
|
||||
bool_t physicsShapeMeshTestSphere(
|
||||
const physicsshapemesh_t *mesh, const vec3 meshPos,
|
||||
const vec3 sphereCenter, const float_t sphereRadius,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
assertNotNull(mesh, "Mesh cannot be null");
|
||||
|
||||
bool_t found = false;
|
||||
float_t bestDist2 = 0.0f;
|
||||
vec3 bestPoint;
|
||||
|
||||
for(uint32_t i = 0; i < mesh->triangleCount; i++) {
|
||||
vec3 a, b, c;
|
||||
physicsShapeMeshGetVertex(mesh, i * 3 + 0, a);
|
||||
physicsShapeMeshGetVertex(mesh, i * 3 + 1, b);
|
||||
physicsShapeMeshGetVertex(mesh, i * 3 + 2, c);
|
||||
glm_vec3_add(a, (float_t *)meshPos, a);
|
||||
glm_vec3_add(b, (float_t *)meshPos, b);
|
||||
glm_vec3_add(c, (float_t *)meshPos, c);
|
||||
|
||||
vec3 closest;
|
||||
physicsTestClosestPointOnTriangle(sphereCenter, a, b, c, closest);
|
||||
|
||||
vec3 diff;
|
||||
glm_vec3_sub((float_t *)sphereCenter, closest, diff);
|
||||
float_t dist2 = glm_vec3_norm2(diff);
|
||||
if(found && dist2 >= bestDist2) continue;
|
||||
|
||||
found = true;
|
||||
bestDist2 = dist2;
|
||||
glm_vec3_copy(closest, bestPoint);
|
||||
}
|
||||
|
||||
if(!found || bestDist2 >= sphereRadius * sphereRadius) return false;
|
||||
|
||||
float_t dist = sqrtf(bestDist2);
|
||||
*outDepth = sphereRadius - dist;
|
||||
|
||||
vec3 diff;
|
||||
glm_vec3_sub((float_t *)sphereCenter, bestPoint, diff);
|
||||
if(dist > 1e-6f) {
|
||||
glm_vec3_scale(diff, 1.0f / dist, outNormal);
|
||||
} else {
|
||||
outNormal[0] = 0.0f;
|
||||
outNormal[1] = 1.0f;
|
||||
outNormal[2] = 0.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool_t physicsShapeMeshTestCapsule(
|
||||
const physicsshapemesh_t *mesh, const vec3 meshPos,
|
||||
const vec3 capsuleCenter, const float_t capsuleRadius,
|
||||
const float_t capsuleHalfHeight,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 capA = {
|
||||
capsuleCenter[0], capsuleCenter[1] - capsuleHalfHeight, capsuleCenter[2]
|
||||
};
|
||||
vec3 capB = {
|
||||
capsuleCenter[0], capsuleCenter[1] + capsuleHalfHeight, capsuleCenter[2]
|
||||
};
|
||||
|
||||
int32_t sampleCount = capsuleRadius > 1e-6f
|
||||
? (int32_t)ceilf((2.0f * capsuleHalfHeight) / capsuleRadius) + 1
|
||||
: 2;
|
||||
if(sampleCount < 2) sampleCount = 2;
|
||||
if(sampleCount > PHYSICS_SHAPE_MESH_CAPSULE_SAMPLES_MAX) {
|
||||
sampleCount = PHYSICS_SHAPE_MESH_CAPSULE_SAMPLES_MAX;
|
||||
}
|
||||
|
||||
bool_t found = false;
|
||||
vec3 bestNormal = { 0.0f, 1.0f, 0.0f };
|
||||
float_t bestDepth = 0.0f;
|
||||
|
||||
for(int32_t i = 0; i < sampleCount; i++) {
|
||||
float_t t = (float_t)i / (float_t)(sampleCount - 1);
|
||||
vec3 samplePoint;
|
||||
glm_vec3_lerp(capA, capB, t, samplePoint);
|
||||
|
||||
vec3 normal; float_t depth;
|
||||
if(!physicsShapeMeshTestSphere(
|
||||
mesh, meshPos, samplePoint, capsuleRadius, normal, &depth
|
||||
)) continue;
|
||||
|
||||
if(found && depth <= bestDepth) continue;
|
||||
found = true;
|
||||
bestDepth = depth;
|
||||
glm_vec3_copy(normal, bestNormal);
|
||||
}
|
||||
|
||||
if(!found) return false;
|
||||
*outDepth = bestDepth;
|
||||
glm_vec3_copy(bestNormal, outNormal);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool_t physicsShapeMeshTest(
|
||||
const vec3 selfPos, const void *selfData,
|
||||
const vec3 otherPos, const physicsshape_t *otherShape,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
const physicsshapemesh_t *mesh = (const physicsshapemesh_t *)selfData;
|
||||
|
||||
switch(otherShape->type) {
|
||||
case PHYSICS_SHAPE_SPHERE:
|
||||
return physicsShapeMeshTestSphere(
|
||||
mesh, selfPos, otherPos, otherShape->data.sphere.radius,
|
||||
outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_CAPSULE:
|
||||
return physicsShapeMeshTestCapsule(
|
||||
mesh, selfPos, otherPos,
|
||||
otherShape->data.capsule.radius, otherShape->data.capsule.halfHeight,
|
||||
outNormal, outDepth
|
||||
);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
physicsshape_t physicsShapeMeshCreate(const physicsshapemesh_t *mesh) {
|
||||
assertNotNull(mesh, "Mesh cannot be null");
|
||||
return (physicsshape_t){
|
||||
.type = PHYSICS_SHAPE_CUSTOM,
|
||||
.data.custom = { .userData = (void *)mesh, .test = physicsShapeMeshTest }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "physicsshape.h"
|
||||
|
||||
/**
|
||||
* Capsule-vs-mesh is approximated by sampling spheres along the capsule's
|
||||
* axis (see physicsShapeMeshTestCapsule). This bounds how many samples a
|
||||
* single query can take, regardless of how tall/thin the capsule is.
|
||||
*/
|
||||
#define PHYSICS_SHAPE_MESH_CAPSULE_SAMPLES_MAX 8
|
||||
|
||||
/**
|
||||
* Describes an arbitrary triangle mesh (e.g. landscape/terrain geometry)
|
||||
* for use as a PHYSICS_SHAPE_CUSTOM shape via physicsShapeMeshCreate().
|
||||
*
|
||||
* Vertex positions are read directly out of `vertices` at `stride`-byte
|
||||
* intervals, so this can point straight at an existing render vertex
|
||||
* buffer (e.g. `&myMeshVertices[0].pos` with `stride = sizeof(meshvertex_t)`)
|
||||
* with no need to duplicate the geometry for physics -- as long as each
|
||||
* vertex's position is 3 consecutive float_t.
|
||||
*
|
||||
* Not copied or owned: this struct (and the buffer it points into) must
|
||||
* outlive any physicsshape_t built from it, so it's typically a field
|
||||
* alongside whatever owns the mesh (e.g. a terrain chunk).
|
||||
*
|
||||
* Every query scans all triangleCount triangles (no broad-phase/BVH), so
|
||||
* for good performance keep each mesh shape scoped to one reasonably-sized
|
||||
* chunk rather than an entire world, and only add nearby chunks' physics
|
||||
* bodies to the scene.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Pointer to the first triangle vertex's position (3 consecutive float_t). */
|
||||
const uint8_t *vertices;
|
||||
/** Byte stride between consecutive vertex positions. */
|
||||
size_t stride;
|
||||
/** Number of triangles. `vertices` holds triangleCount * 3 positions. */
|
||||
uint32_t triangleCount;
|
||||
} physicsshapemesh_t;
|
||||
|
||||
/**
|
||||
* Reads the world-space position of a single mesh vertex (vertex index,
|
||||
* not triangle index -- pass triangleIndex * 3 + 0/1/2 for a triangle's
|
||||
* corners).
|
||||
*
|
||||
* @param mesh The mesh to read from.
|
||||
* @param index Vertex index, in [0, triangleCount * 3).
|
||||
* @param out Receives the vertex position.
|
||||
*/
|
||||
void physicsShapeMeshGetVertex(
|
||||
const physicsshapemesh_t *mesh, const uint32_t index, vec3 out
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests a sphere against every triangle in the mesh, keeping only the
|
||||
* single closest triangle. outNormal points from the mesh surface toward
|
||||
* the sphere (self toward other, per physicsshapecustomtest_t).
|
||||
*
|
||||
* @param mesh The mesh to test against.
|
||||
* @param meshPos World-space position of the mesh shape's owning entity;
|
||||
* added to every vertex read from mesh.
|
||||
* @param sphereCenter World-space center of the sphere.
|
||||
* @param sphereRadius Radius of the sphere.
|
||||
* @param outNormal Push-out normal (mesh surface toward sphere).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if the sphere overlaps the mesh.
|
||||
*/
|
||||
bool_t physicsShapeMeshTestSphere(
|
||||
const physicsshapemesh_t *mesh, const vec3 meshPos,
|
||||
const vec3 sphereCenter, const float_t sphereRadius,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests a Y-axis-aligned capsule against the mesh by approximating the
|
||||
* capsule as a series of spheres sampled along its axis (see
|
||||
* PHYSICS_SHAPE_MESH_CAPSULE_SAMPLES_MAX) and keeping the deepest overlap
|
||||
* found. An approximation, not an exact capsule-vs-triangle test.
|
||||
*
|
||||
* @param mesh The mesh to test against.
|
||||
* @param meshPos World-space position of the mesh shape's owning entity.
|
||||
* @param capsuleCenter World-space center of the capsule.
|
||||
* @param capsuleRadius Radius of the capsule.
|
||||
* @param capsuleHalfHeight Half-height of the capsule's cylindrical segment.
|
||||
* @param outNormal Push-out normal (mesh surface toward capsule).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if the capsule overlaps the mesh.
|
||||
*/
|
||||
bool_t physicsShapeMeshTestCapsule(
|
||||
const physicsshapemesh_t *mesh, const vec3 meshPos,
|
||||
const vec3 capsuleCenter, const float_t capsuleRadius,
|
||||
const float_t capsuleHalfHeight,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* physicsshapecustomtest_t implementation backing physicsShapeMeshCreate().
|
||||
* Routes to physicsShapeMeshTestSphere/TestCapsule depending on
|
||||
* otherShape's type. Cube (AABB) bodies are not supported yet and never
|
||||
* collide against a mesh shape.
|
||||
*
|
||||
* @param selfPos The mesh shape's position.
|
||||
* @param selfData The physicsshapemesh_t this shape was created from.
|
||||
* @param otherPos The other shape's position.
|
||||
* @param otherShape The other shape's descriptor.
|
||||
* @param outNormal Push-out normal (mesh surface toward other).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if the shapes overlap.
|
||||
*/
|
||||
bool_t physicsShapeMeshTest(
|
||||
const vec3 selfPos, const void *selfData,
|
||||
const vec3 otherPos, const physicsshape_t *otherShape,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Builds a PHYSICS_SHAPE_CUSTOM shape backed by the given triangle mesh.
|
||||
*
|
||||
* @param mesh Mesh descriptor. Must outlive the returned shape (see
|
||||
* physicsshapemesh_t).
|
||||
* @return A physicsshape_t wired to test against this mesh.
|
||||
*/
|
||||
physicsshape_t physicsShapeMeshCreate(const physicsshapemesh_t *mesh);
|
||||
@@ -148,6 +148,78 @@ void physicsTestClosestPointOnSegment(
|
||||
glm_vec3_lerp((float_t *)a, (float_t *)b, t, out);
|
||||
}
|
||||
|
||||
void physicsTestClosestPointOnTriangle(
|
||||
const vec3 p, const vec3 a, const vec3 b, const vec3 c, vec3 out
|
||||
) {
|
||||
vec3 ab, ac, ap;
|
||||
glm_vec3_sub((float_t *)b, (float_t *)a, ab);
|
||||
glm_vec3_sub((float_t *)c, (float_t *)a, ac);
|
||||
glm_vec3_sub((float_t *)p, (float_t *)a, ap);
|
||||
|
||||
float_t d1 = glm_vec3_dot(ab, ap);
|
||||
float_t d2 = glm_vec3_dot(ac, ap);
|
||||
if(d1 <= 0.0f && d2 <= 0.0f) {
|
||||
glm_vec3_copy((float_t *)a, out);
|
||||
return;
|
||||
}
|
||||
|
||||
vec3 bp;
|
||||
glm_vec3_sub((float_t *)p, (float_t *)b, bp);
|
||||
float_t d3 = glm_vec3_dot(ab, bp);
|
||||
float_t d4 = glm_vec3_dot(ac, bp);
|
||||
if(d3 >= 0.0f && d4 <= d3) {
|
||||
glm_vec3_copy((float_t *)b, out);
|
||||
return;
|
||||
}
|
||||
|
||||
float_t vc = d1 * d4 - d3 * d2;
|
||||
if(vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) {
|
||||
float_t v = d1 / (d1 - d3);
|
||||
vec3 scaled;
|
||||
glm_vec3_scale(ab, v, scaled);
|
||||
glm_vec3_add((float_t *)a, scaled, out);
|
||||
return;
|
||||
}
|
||||
|
||||
vec3 cp;
|
||||
glm_vec3_sub((float_t *)p, (float_t *)c, cp);
|
||||
float_t d5 = glm_vec3_dot(ab, cp);
|
||||
float_t d6 = glm_vec3_dot(ac, cp);
|
||||
if(d6 >= 0.0f && d5 <= d6) {
|
||||
glm_vec3_copy((float_t *)c, out);
|
||||
return;
|
||||
}
|
||||
|
||||
float_t vb = d5 * d2 - d1 * d6;
|
||||
if(vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) {
|
||||
float_t w = d2 / (d2 - d6);
|
||||
vec3 scaled;
|
||||
glm_vec3_scale(ac, w, scaled);
|
||||
glm_vec3_add((float_t *)a, scaled, out);
|
||||
return;
|
||||
}
|
||||
|
||||
float_t va = d3 * d6 - d5 * d4;
|
||||
if(va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) {
|
||||
float_t w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
|
||||
vec3 bc, scaled;
|
||||
glm_vec3_sub((float_t *)c, (float_t *)b, bc);
|
||||
glm_vec3_scale(bc, w, scaled);
|
||||
glm_vec3_add((float_t *)b, scaled, out);
|
||||
return;
|
||||
}
|
||||
|
||||
// Interior: barycentric combination of ab and ac from a.
|
||||
float_t denom = 1.0f / (va + vb + vc);
|
||||
float_t v = vb * denom;
|
||||
float_t w = vc * denom;
|
||||
vec3 abScaled, acScaled, sum;
|
||||
glm_vec3_scale(ab, v, abScaled);
|
||||
glm_vec3_scale(ac, w, acScaled);
|
||||
glm_vec3_add(abScaled, acScaled, sum);
|
||||
glm_vec3_add((float_t *)a, sum, out);
|
||||
}
|
||||
|
||||
void physicsTestClosestPointsBetweenSegments(
|
||||
const vec3 a1, const vec3 b1,
|
||||
const vec3 a2, const vec3 b2,
|
||||
|
||||
@@ -110,6 +110,21 @@ void physicsTestClosestPointOnSegment(
|
||||
const vec3 a, const vec3 b, const vec3 p, vec3 out
|
||||
);
|
||||
|
||||
/**
|
||||
* Finds the closest point on triangle (a, b, c) to query point p. Handles
|
||||
* all six Voronoi regions (the three vertices, the three edges, and the
|
||||
* face interior).
|
||||
*
|
||||
* @param p Query point.
|
||||
* @param a First triangle vertex.
|
||||
* @param b Second triangle vertex.
|
||||
* @param c Third triangle vertex.
|
||||
* @param out Receives the closest point on the triangle to p.
|
||||
*/
|
||||
void physicsTestClosestPointOnTriangle(
|
||||
const vec3 p, const vec3 a, const vec3 b, const vec3 c, vec3 out
|
||||
);
|
||||
|
||||
/**
|
||||
* Finds the closest points between two line segments.
|
||||
*
|
||||
|
||||
@@ -72,12 +72,17 @@ physicsworld_t *sceneGetPhysics(const sceneid_t id) {
|
||||
}
|
||||
|
||||
errorret_t sceneUpdate(void) {
|
||||
if(SCENE_MANAGER.active == SCENE_ID_INVALID) errorOk();
|
||||
|
||||
#if DUSK_TIME_DYNAMIC
|
||||
if(!TIME.dynamicUpdate) errorOk();
|
||||
if(TIME.dynamicUpdate) errorOk();
|
||||
#endif
|
||||
|
||||
errorChain(sceneFixedUpdate());
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneFixedUpdate(void) {
|
||||
if(SCENE_MANAGER.active == SCENE_ID_INVALID) errorOk();
|
||||
|
||||
scene_t *scene = &SCENE_MANAGER.scenes[SCENE_MANAGER.active];
|
||||
entityManagerUpdate(&scene->entities);
|
||||
physicsWorldStep(&scene->physics, &scene->entities, TIME.delta);
|
||||
|
||||
+18
-2
@@ -83,13 +83,29 @@ entitymanager_t *sceneGetEntities(const sceneid_t id);
|
||||
physicsworld_t *sceneGetPhysics(const sceneid_t id);
|
||||
|
||||
/**
|
||||
* Ticks the active scene's entities (update callbacks, then a physics
|
||||
* step) on fixed timesteps only, per DUSK_TIME_DYNAMIC.
|
||||
* Called every frame (every call to engineUpdate). On a non-dynamic-time
|
||||
* platform (DUSK_TIME_DYNAMIC undefined) every frame is itself a fixed
|
||||
* timestep, so this calls sceneFixedUpdate() every time. On a
|
||||
* dynamic-time platform, most calls are sub-steps still accumulating
|
||||
* toward the next fixed timestep (TIME.dynamicUpdate is true) and do
|
||||
* nothing here; this calls sceneFixedUpdate() only on the one call where
|
||||
* TIME.dynamicUpdate is false, i.e. exactly once per fixed timestep.
|
||||
*
|
||||
* @return An error if the update failed, or errorOk() if it succeeded.
|
||||
*/
|
||||
errorret_t sceneUpdate(void);
|
||||
|
||||
/**
|
||||
* Ticks the active scene's entities (update callbacks, then a physics
|
||||
* step) by one fixed timestep (TIME.delta). Called by sceneUpdate() at
|
||||
* the fixed-timestep cadence described there -- never once per rendered
|
||||
* frame on a dynamic-time platform, so game logic and physics stay
|
||||
* frame-rate independent and deterministic.
|
||||
*
|
||||
* @return An error if the update failed, or errorOk() if it succeeded.
|
||||
*/
|
||||
errorret_t sceneFixedUpdate(void);
|
||||
|
||||
/**
|
||||
* Renders the active scene (entities, render pipeline, UI).
|
||||
*
|
||||
|
||||
@@ -39,13 +39,26 @@
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
/** Fixed simulation timestep, always DUSK_TIME_STEP. */
|
||||
float_t delta;
|
||||
/** Total elapsed fixed-simulation time, in DUSK_TIME_STEP increments. */
|
||||
float_t time;
|
||||
|
||||
#ifdef DUSK_TIME_DYNAMIC
|
||||
/** dynamicTime at the last fixed-timestep boundary. */
|
||||
float_t lastNonDynamic;
|
||||
/**
|
||||
* False on the one timeUpdate() call per fixed timestep where enough
|
||||
* real time has accumulated to cross a DUSK_TIME_STEP boundary (delta
|
||||
* and time were just advanced) -- true on every other call, still
|
||||
* accumulating toward the next one. Fixed-rate systems (scene
|
||||
* fixed update, physics) should run only when this is false; systems
|
||||
* that want every real frame (e.g. rendering) run unconditionally.
|
||||
*/
|
||||
bool_t dynamicUpdate;
|
||||
/** Real elapsed time since the previous timeUpdate() call. */
|
||||
float_t dynamicDelta;
|
||||
/** Total elapsed real time, accumulated every timeUpdate() call. */
|
||||
float_t dynamicTime;
|
||||
#endif
|
||||
} dusktime_t;
|
||||
|
||||
Reference in New Issue
Block a user