From 23e617ea21c1dbceb990e88bee71dd182d41ba57 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 21 May 2026 13:17:48 -0500 Subject: [PATCH] Optimizing entityposition as much as possible. --- .../entity/component/display/entityposition.c | 130 ++++++++++++------ .../entity/component/display/entityposition.h | 62 ++++++--- 2 files changed, 129 insertions(+), 63 deletions(-) diff --git a/src/dusk/entity/component/display/entityposition.c b/src/dusk/entity/component/display/entityposition.c index 3813fb4b..0716192d 100644 --- a/src/dusk/entity/component/display/entityposition.c +++ b/src/dusk/entity/component/display/entityposition.c @@ -14,32 +14,68 @@ static void entityPositionEnsurePRS(entityposition_t *pos) { pos->flags &= ~ENTITY_POSITION_FLAG_PRS_DIRTY; } -// Rebuild localTransform from the PRS cache. Only called when LOCAL_DIRTY. +// Rebuild localTransform from the PRS cache. Only rebuilds what changed. static void entityPositionEnsureLocal(entityposition_t *pos) { - if(!(pos->flags & ENTITY_POSITION_FLAG_LOCAL_DIRTY)) return; - glm_mat4_identity(pos->localTransform); - glm_translate(pos->localTransform, pos->position); - glm_rotate_x(pos->localTransform, pos->rotation[0], pos->localTransform); - glm_rotate_y(pos->localTransform, pos->rotation[1], pos->localTransform); - glm_rotate_z(pos->localTransform, pos->rotation[2], pos->localTransform); - glm_scale(pos->localTransform, pos->scale); - pos->flags &= ~ENTITY_POSITION_FLAG_LOCAL_DIRTY; + const uint8_t dirty = pos->flags & ( + ENTITY_POSITION_FLAG_ROTATION_DIRTY | ENTITY_POSITION_FLAG_POSITION_DIRTY + ); + if(!dirty) return; + + if(dirty & ENTITY_POSITION_FLAG_ROTATION_DIRTY) { + // Rotation or scale changed: rebuild columns 0-2 analytically (XYZ euler order). + const float c0 = cosf(pos->rotation[0]), s0 = sinf(pos->rotation[0]); + const float c1 = cosf(pos->rotation[1]), s1 = sinf(pos->rotation[1]); + const float c2 = cosf(pos->rotation[2]), s2 = sinf(pos->rotation[2]); + const float s0s1 = s0 * s1; + const float c0s1 = c0 * s1; + + pos->localTransform[0][0] = c1 * c2 * pos->scale[0]; + pos->localTransform[0][1] = (c0 * s2 + s0s1 * c2) * pos->scale[0]; + pos->localTransform[0][2] = (s0 * s2 - c0s1 * c2) * pos->scale[0]; + pos->localTransform[0][3] = 0.0f; + + pos->localTransform[1][0] = -c1 * s2 * pos->scale[1]; + pos->localTransform[1][1] = (c0 * c2 - s0s1 * s2) * pos->scale[1]; + pos->localTransform[1][2] = (s0 * c2 + c0s1 * s2) * pos->scale[1]; + pos->localTransform[1][3] = 0.0f; + + pos->localTransform[2][0] = s1 * pos->scale[2]; + pos->localTransform[2][1] = -s0 * c1 * pos->scale[2]; + pos->localTransform[2][2] = c0 * c1 * pos->scale[2]; + pos->localTransform[2][3] = 0.0f; + } + + if(dirty & ENTITY_POSITION_FLAG_POSITION_DIRTY) { + // Only position changed: update column 3 only (no trig needed). + pos->localTransform[3][0] = pos->position[0]; + pos->localTransform[3][1] = pos->position[1]; + pos->localTransform[3][2] = pos->position[2]; + pos->localTransform[3][3] = 1.0f; + } + + pos->flags &= ~(ENTITY_POSITION_FLAG_ROTATION_DIRTY | ENTITY_POSITION_FLAG_POSITION_DIRTY); } // Recompute worldTransform from the parent chain. Only called when WORLD_DIRTY. static void entityPositionEnsureWorld(entityposition_t *pos) { - entityPositionEnsureLocal(pos); if(!(pos->flags & ENTITY_POSITION_FLAG_WORLD_DIRTY)) return; + entityPositionEnsureLocal(pos); - if(pos->parentEntityId == ENTITY_ID_INVALID) { - glm_mat4_copy(pos->localTransform, pos->worldTransform); - } else { + if(pos->parentEntityId != ENTITY_ID_INVALID) { + // Parented: world = parent.world × local. worldTransform must be written + // because children (and this node's getters) read it. entityposition_t *parent = componentGetData( pos->parentEntityId, pos->parentComponentId, COMPONENT_TYPE_POSITION ); entityPositionEnsureWorld(parent); glm_mat4_mul(parent->worldTransform, pos->localTransform, pos->worldTransform); + } else if(pos->childCount > 0) { + // Parentless root with children: children need a valid worldTransform to + // multiply against, but world == local, so just copy. + glm_mat4_copy(pos->localTransform, pos->worldTransform); } + // Parentless leaf: world == local. Getters read localTransform directly; + // no copy needed. pos->flags &= ~ENTITY_POSITION_FLAG_WORLD_DIRTY; } @@ -63,15 +99,15 @@ void entityPositionInit( entityId, componentId, COMPONENT_TYPE_POSITION ); + pos->flags = 0; + pos->parentEntityId = ENTITY_ID_INVALID; + pos->parentComponentId = COMPONENT_ID_INVALID; + pos->childCount = 0; glm_vec3_zero(pos->position); glm_vec3_zero(pos->rotation); glm_vec3_one(pos->scale); glm_mat4_identity(pos->localTransform); glm_mat4_identity(pos->worldTransform); - pos->flags = 0; - pos->parentEntityId = ENTITY_ID_INVALID; - pos->parentComponentId = COMPONENT_ID_INVALID; - pos->childCount = 0; } void entityPositionLookAt( @@ -87,7 +123,7 @@ void entityPositionLookAt( glm_lookat(eye, target, up, pos->localTransform); // localTransform is now authoritative; PRS cache is stale. pos->flags = (pos->flags | ENTITY_POSITION_FLAG_PRS_DIRTY) - & ~ENTITY_POSITION_FLAG_LOCAL_DIRTY; + & ~(ENTITY_POSITION_FLAG_ROTATION_DIRTY | ENTITY_POSITION_FLAG_POSITION_DIRTY); entityPositionMarkDirty(pos); } @@ -100,7 +136,10 @@ void entityPositionGetTransform( entityId, componentId, COMPONENT_TYPE_POSITION ); entityPositionEnsureWorld(pos); - glm_mat4_copy(pos->worldTransform, dest); + glm_mat4_copy( + pos->parentEntityId == ENTITY_ID_INVALID ? pos->localTransform : pos->worldTransform, + dest + ); } void entityPositionGetLocalTransform( @@ -135,6 +174,12 @@ void entityPositionGetPosition( entityposition_t *pos = componentGetData( entityId, componentId, COMPONENT_TYPE_POSITION ); + // Parentless fast path: world position == pos->position, no matrix needed. + if(pos->parentEntityId == ENTITY_ID_INVALID) { + entityPositionEnsurePRS(pos); + glm_vec3_copy(pos->position, dest); + return; + } entityPositionEnsureWorld(pos); dest[0] = pos->worldTransform[3][0]; dest[1] = pos->worldTransform[3][1]; @@ -150,7 +195,7 @@ void entityPositionSetPosition( entityId, componentId, COMPONENT_TYPE_POSITION ); glm_vec3_copy(position, pos->position); - pos->flags = (pos->flags | ENTITY_POSITION_FLAG_LOCAL_DIRTY) + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_POSITION_DIRTY) & ~ENTITY_POSITION_FLAG_PRS_DIRTY; entityPositionMarkDirty(pos); } @@ -176,7 +221,7 @@ void entityPositionSetRotation( entityId, componentId, COMPONENT_TYPE_POSITION ); glm_vec3_copy(rotation, pos->rotation); - pos->flags = (pos->flags | ENTITY_POSITION_FLAG_LOCAL_DIRTY) + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_ROTATION_DIRTY) & ~ENTITY_POSITION_FLAG_PRS_DIRTY; entityPositionMarkDirty(pos); } @@ -202,7 +247,7 @@ void entityPositionSetScale( entityId, componentId, COMPONENT_TYPE_POSITION ); glm_vec3_copy(scale, pos->scale); - pos->flags = (pos->flags | ENTITY_POSITION_FLAG_LOCAL_DIRTY) + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_ROTATION_DIRTY) & ~ENTITY_POSITION_FLAG_PRS_DIRTY; entityPositionMarkDirty(pos); } @@ -265,7 +310,7 @@ entityposition_t *entityPositionGet( } void entityPositionRebuild(entityposition_t *pos) { - pos->flags = (pos->flags | ENTITY_POSITION_FLAG_LOCAL_DIRTY) + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_ROTATION_DIRTY | ENTITY_POSITION_FLAG_POSITION_DIRTY) & ~ENTITY_POSITION_FLAG_PRS_DIRTY; entityPositionMarkDirty(pos); } @@ -324,36 +369,33 @@ void entityPositionDecompose(entityposition_t *pos) { pos->localTransform[2][2] * pos->localTransform[2][2] ); - // Normalize columns to isolate the rotation matrix. - float invS0 = pos->scale[0] > 0.0f ? 1.0f / pos->scale[0] : 0.0f; - float invS1 = pos->scale[1] > 0.0f ? 1.0f / pos->scale[1] : 0.0f; - float invS2 = pos->scale[2] > 0.0f ? 1.0f / pos->scale[2] : 0.0f; + // Normalize columns to isolate the rotation matrix (9 floats, no mat4 needed). + const float invS0 = pos->scale[0] > 0.0f ? 1.0f / pos->scale[0] : 0.0f; + const float invS1 = pos->scale[1] > 0.0f ? 1.0f / pos->scale[1] : 0.0f; + const float invS2 = pos->scale[2] > 0.0f ? 1.0f / pos->scale[2] : 0.0f; - mat4 r; - glm_mat4_identity(r); - r[0][0] = pos->localTransform[0][0] * invS0; - r[0][1] = pos->localTransform[0][1] * invS0; - r[0][2] = pos->localTransform[0][2] * invS0; - r[1][0] = pos->localTransform[1][0] * invS1; - r[1][1] = pos->localTransform[1][1] * invS1; - r[1][2] = pos->localTransform[1][2] * invS1; - r[2][0] = pos->localTransform[2][0] * invS2; - r[2][1] = pos->localTransform[2][1] * invS2; - r[2][2] = pos->localTransform[2][2] * invS2; + const float r00 = pos->localTransform[0][0] * invS0; + const float r01 = pos->localTransform[0][1] * invS0; + const float r02 = pos->localTransform[0][2] * invS0; + const float r10 = pos->localTransform[1][0] * invS1; + const float r11 = pos->localTransform[1][1] * invS1; + const float r20 = pos->localTransform[2][0] * invS2; + const float r21 = pos->localTransform[2][1] * invS2; + const float r22 = pos->localTransform[2][2] * invS2; // Extract XYZ euler angles (R = Rx * Ry * Rz, column-major) - float sinBeta = glm_clamp(r[2][0], -1.0f, 1.0f); + const float sinBeta = glm_clamp(r20, -1.0f, 1.0f); pos->rotation[1] = asinf(sinBeta); - float cosBeta = cosf(pos->rotation[1]); + const float cosBeta = cosf(pos->rotation[1]); if(fabsf(cosBeta) > 1e-6f) { - pos->rotation[0] = atan2f(-r[2][1], r[2][2]); - pos->rotation[2] = atan2f(-r[1][0], r[0][0]); + pos->rotation[0] = atan2f(-r21, r22); + pos->rotation[2] = atan2f(-r10, r00); } else { // Gimbal lock: pin Z to 0, recover X. pos->rotation[2] = 0.0f; pos->rotation[0] = (sinBeta > 0.0f) - ? atan2f(r[0][1], r[1][1]) - : -atan2f(r[0][1], r[1][1]); + ? atan2f(r01, r11) + : -atan2f(r01, r11); } } diff --git a/src/dusk/entity/component/display/entityposition.h b/src/dusk/entity/component/display/entityposition.h index 99830d5b..bce8a436 100644 --- a/src/dusk/entity/component/display/entityposition.h +++ b/src/dusk/entity/component/display/entityposition.h @@ -15,31 +15,33 @@ * PRS cache is stale. localTransform was written directly (e.g. lookAt) and * position/rotation/scale need to be decomposed before they can be read. */ -#define ENTITY_POSITION_FLAG_PRS_DIRTY (1 << 0) +#define ENTITY_POSITION_FLAG_PRS_DIRTY (1 << 0) /** - * localTransform is stale. PRS was updated via a setter; the matrix needs to - * be rebuilt from position/rotation/scale before it can be used. + * Columns 0-2 of localTransform are stale. Rotation or scale changed; the + * basis vectors need to be rebuilt analytically before the matrix can be used. + * Does not imply column 3 (translation) is stale. */ -#define ENTITY_POSITION_FLAG_LOCAL_DIRTY (1 << 1) +#define ENTITY_POSITION_FLAG_ROTATION_DIRTY (1 << 1) + +/** + * Column 3 of localTransform is stale. Position changed; only the translation + * column needs to be written. Does not imply columns 0-2 are stale. + */ +#define ENTITY_POSITION_FLAG_POSITION_DIRTY (1 << 2) /** * worldTransform is stale. Either the local matrix changed or an ancestor * moved; the full parent-chain multiply must be rerun before world data is read. */ -#define ENTITY_POSITION_FLAG_WORLD_DIRTY (1 << 2) +#define ENTITY_POSITION_FLAG_WORLD_DIRTY (1 << 3) typedef struct { - /** Local transform matrix, rebuilt lazily from position/rotation/scale. */ - mat4 localTransform; - /** World transform matrix, recomputed lazily from the parent chain. */ - mat4 worldTransform; - /** Cached local position (XYZ). May be stale when ENTITY_POSITION_FLAG_PRS_DIRTY is set. */ - vec3 position; - /** Cached local euler rotation (XYZ, radians). May be stale when ENTITY_POSITION_FLAG_PRS_DIRTY is set. */ - vec3 rotation; - /** Cached local scale (XYZ). May be stale when ENTITY_POSITION_FLAG_PRS_DIRTY is set. */ - vec3 scale; + /* + * Hot fields — flag checks and parent/child traversal (markDirty, ensureWorld) + * only touch these. Kept at the front so they share the first cache line. + */ + /** Bitmask of ENTITY_POSITION_FLAG_* values describing which caches are stale. */ uint8_t flags; /** Entity ID of the parent node, or ENTITY_ID_INVALID if none. */ @@ -52,6 +54,27 @@ typedef struct { entityid_t childEntityIds[ENTITY_POSITION_CHILDREN_MAX]; /** Component IDs of child position components. */ componentid_t childComponentIds[ENTITY_POSITION_CHILDREN_MAX]; + + /* + * Warm fields — read/written by PRS getters/setters. + * Accessed more often than the matrices but less often than flags. + */ + + /** Cached local position (XYZ). May be stale when ENTITY_POSITION_FLAG_PRS_DIRTY is set. */ + vec3 position; + /** Cached local euler rotation (XYZ, radians). May be stale when ENTITY_POSITION_FLAG_PRS_DIRTY is set. */ + vec3 rotation; + /** Cached local scale (XYZ). May be stale when ENTITY_POSITION_FLAG_PRS_DIRTY is set. */ + vec3 scale; + + /* + * Cold fields — only touched when actually rebuilding transforms. + */ + + /** Local transform matrix, rebuilt lazily from position/rotation/scale. */ + mat4 localTransform; + /** World transform matrix, recomputed lazily from the parent chain. */ + mat4 worldTransform; } entityposition_t; /** @@ -241,10 +264,11 @@ entityposition_t *entityPositionGet( ); /** - * Signals that the PRS cache was modified externally. Sets - * ENTITY_POSITION_FLAG_LOCAL_DIRTY so localTransform is rebuilt lazily on the - * next read, clears ENTITY_POSITION_FLAG_PRS_DIRTY, and propagates - * ENTITY_POSITION_FLAG_WORLD_DIRTY to self and all descendants. + * Signals that the PRS cache was modified externally. Sets both + * ENTITY_POSITION_FLAG_ROTATION_DIRTY and ENTITY_POSITION_FLAG_POSITION_DIRTY + * so all of localTransform is rebuilt lazily on the next read, clears + * ENTITY_POSITION_FLAG_PRS_DIRTY, and propagates ENTITY_POSITION_FLAG_WORLD_DIRTY + * to self and all descendants. * * @param pos The position component whose PRS was modified. */