Optimizing entityposition as much as possible.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user