317 lines
9.4 KiB
C
317 lines
9.4 KiB
C
/**
|
|
* Copyright (c) 2026 Dominic Masters
|
|
*
|
|
* This software is released under the MIT License.
|
|
* https://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
#include "entity/entitymanager.h"
|
|
|
|
// Lazily recompute worldTransform from the parent chain.
|
|
static void entityPositionUpdateWorld(entityposition_t *pos) {
|
|
if(!pos->dirty) return;
|
|
|
|
if(pos->parentEntityId == ENTITY_ID_INVALID) {
|
|
glm_mat4_copy(pos->localTransform, pos->worldTransform);
|
|
} else {
|
|
entityposition_t *parent = componentGetData(
|
|
pos->parentEntityId, pos->parentComponentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
entityPositionUpdateWorld(parent);
|
|
glm_mat4_mul(parent->worldTransform, pos->localTransform, pos->worldTransform);
|
|
}
|
|
|
|
pos->dirty = false;
|
|
}
|
|
|
|
void entityPositionMarkDirty(entityposition_t *pos) {
|
|
pos->dirty = true;
|
|
for(uint8_t i = 0; i < pos->childCount; i++) {
|
|
entityposition_t *child = componentGetData(
|
|
pos->childEntityIds[i], pos->childComponentIds[i], COMPONENT_TYPE_POSITION
|
|
);
|
|
entityPositionMarkDirty(child);
|
|
}
|
|
}
|
|
|
|
void entityPositionInit(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId
|
|
) {
|
|
entityposition_t *pos = componentGetData(
|
|
entityId, componentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
|
|
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->dirty = false;
|
|
pos->parentEntityId = ENTITY_ID_INVALID;
|
|
pos->parentComponentId = COMPONENT_ID_INVALID;
|
|
pos->childCount = 0;
|
|
}
|
|
|
|
void entityPositionLookAt(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId,
|
|
vec3 target,
|
|
vec3 up,
|
|
vec3 eye
|
|
) {
|
|
entityposition_t *pos = componentGetData(
|
|
entityId, componentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
glm_lookat(eye, target, up, pos->localTransform);
|
|
entityPositionDecompose(pos);
|
|
entityPositionMarkDirty(pos);
|
|
}
|
|
|
|
void entityPositionGetTransform(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId,
|
|
mat4 dest
|
|
) {
|
|
entityposition_t *pos = componentGetData(
|
|
entityId, componentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
entityPositionUpdateWorld(pos);
|
|
glm_mat4_copy(pos->worldTransform, dest);
|
|
}
|
|
|
|
void entityPositionGetLocalTransform(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId,
|
|
mat4 dest
|
|
) {
|
|
entityposition_t *pos = componentGetData(
|
|
entityId, componentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
glm_mat4_copy(pos->localTransform, dest);
|
|
}
|
|
|
|
void entityPositionGetPosition(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId,
|
|
vec3 dest
|
|
) {
|
|
entityposition_t *pos = componentGetData(
|
|
entityId, componentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
glm_vec3_copy(pos->position, dest);
|
|
}
|
|
|
|
void entityPositionSetPosition(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId,
|
|
vec3 position
|
|
) {
|
|
entityposition_t *pos = componentGetData(
|
|
entityId, componentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
glm_vec3_copy(position, pos->position);
|
|
entityPositionRebuild(pos);
|
|
}
|
|
|
|
void entityPositionGetRotation(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId,
|
|
vec3 dest
|
|
) {
|
|
entityposition_t *pos = componentGetData(
|
|
entityId, componentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
glm_vec3_copy(pos->rotation, dest);
|
|
}
|
|
|
|
void entityPositionSetRotation(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId,
|
|
vec3 rotation
|
|
) {
|
|
entityposition_t *pos = componentGetData(
|
|
entityId, componentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
glm_vec3_copy(rotation, pos->rotation);
|
|
entityPositionRebuild(pos);
|
|
}
|
|
|
|
void entityPositionGetScale(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId,
|
|
vec3 dest
|
|
) {
|
|
entityposition_t *pos = componentGetData(
|
|
entityId, componentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
glm_vec3_copy(pos->scale, dest);
|
|
}
|
|
|
|
void entityPositionSetScale(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId,
|
|
vec3 scale
|
|
) {
|
|
entityposition_t *pos = componentGetData(
|
|
entityId, componentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
glm_vec3_copy(scale, pos->scale);
|
|
entityPositionRebuild(pos);
|
|
}
|
|
|
|
void entityPositionSetParent(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId,
|
|
const entityid_t parentEntityId,
|
|
const componentid_t parentComponentId
|
|
) {
|
|
entityposition_t *pos = componentGetData(
|
|
entityId, componentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
|
|
// Remove from old parent's child list.
|
|
if(pos->parentEntityId != ENTITY_ID_INVALID) {
|
|
entityposition_t *oldParent = componentGetData(
|
|
pos->parentEntityId, pos->parentComponentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
for(uint8_t i = 0; i < oldParent->childCount; i++) {
|
|
if(
|
|
oldParent->childEntityIds[i] == entityId &&
|
|
oldParent->childComponentIds[i] == componentId
|
|
) {
|
|
oldParent->childCount--;
|
|
for(uint8_t j = i; j < oldParent->childCount; j++) {
|
|
oldParent->childEntityIds[j] = oldParent->childEntityIds[j + 1];
|
|
oldParent->childComponentIds[j] = oldParent->childComponentIds[j + 1];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pos->parentEntityId = parentEntityId;
|
|
pos->parentComponentId = parentComponentId;
|
|
|
|
// Register with new parent.
|
|
if(parentEntityId != ENTITY_ID_INVALID) {
|
|
entityposition_t *parent = componentGetData(
|
|
parentEntityId, parentComponentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
if(parent->childCount < ENTITY_POSITION_CHILDREN_MAX) {
|
|
parent->childEntityIds[parent->childCount] = entityId;
|
|
parent->childComponentIds[parent->childCount] = componentId;
|
|
parent->childCount++;
|
|
}
|
|
}
|
|
|
|
entityPositionMarkDirty(pos);
|
|
}
|
|
|
|
entityposition_t *entityPositionGet(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId
|
|
) {
|
|
return componentGetData(
|
|
entityId, componentId, COMPONENT_TYPE_POSITION
|
|
);
|
|
}
|
|
|
|
void entityPositionRebuild(entityposition_t *pos) {
|
|
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);
|
|
entityPositionMarkDirty(pos);
|
|
}
|
|
|
|
void entityPositionDisposeDeep(
|
|
const entityid_t entityId,
|
|
const componentid_t componentId
|
|
) {
|
|
entityposition_t *pos = entityPositionGet(entityId, componentId);
|
|
|
|
// Detach from parent so the parent's child list stays consistent.
|
|
if(pos->parentEntityId != ENTITY_ID_INVALID) {
|
|
entityPositionSetParent(entityId, componentId, ENTITY_ID_INVALID, COMPONENT_ID_INVALID);
|
|
}
|
|
|
|
// Copy the child list before disposing self (entityDispose invalidates pos).
|
|
uint8_t childCount = pos->childCount;
|
|
entityid_t childEntityIds[ENTITY_POSITION_CHILDREN_MAX];
|
|
componentid_t childComponentIds[ENTITY_POSITION_CHILDREN_MAX];
|
|
for(uint8_t i = 0; i < childCount; i++) {
|
|
childEntityIds[i] = pos->childEntityIds[i];
|
|
childComponentIds[i] = pos->childComponentIds[i];
|
|
// Sever the child's parent link so it won't try to modify our disposed data.
|
|
entityposition_t *child = entityPositionGet(childEntityIds[i], childComponentIds[i]);
|
|
child->parentEntityId = ENTITY_ID_INVALID;
|
|
child->parentComponentId = COMPONENT_ID_INVALID;
|
|
}
|
|
|
|
entityDispose(entityId);
|
|
|
|
for(uint8_t i = 0; i < childCount; i++) {
|
|
entityPositionDisposeDeep(childEntityIds[i], childComponentIds[i]);
|
|
}
|
|
}
|
|
|
|
void entityPositionDecompose(entityposition_t *pos) {
|
|
// Translation: column 3
|
|
pos->position[0] = pos->localTransform[3][0];
|
|
pos->position[1] = pos->localTransform[3][1];
|
|
pos->position[2] = pos->localTransform[3][2];
|
|
|
|
// Scale: length of each basis column (xyz only)
|
|
pos->scale[0] = sqrtf(
|
|
pos->localTransform[0][0] * pos->localTransform[0][0] +
|
|
pos->localTransform[0][1] * pos->localTransform[0][1] +
|
|
pos->localTransform[0][2] * pos->localTransform[0][2]
|
|
);
|
|
pos->scale[1] = sqrtf(
|
|
pos->localTransform[1][0] * pos->localTransform[1][0] +
|
|
pos->localTransform[1][1] * pos->localTransform[1][1] +
|
|
pos->localTransform[1][2] * pos->localTransform[1][2]
|
|
);
|
|
pos->scale[2] = sqrtf(
|
|
pos->localTransform[2][0] * pos->localTransform[2][0] +
|
|
pos->localTransform[2][1] * pos->localTransform[2][1] +
|
|
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;
|
|
|
|
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;
|
|
|
|
// Extract XYZ euler angles (R = Rx * Ry * Rz, column-major)
|
|
float sinBeta = glm_clamp(r[2][0], -1.0f, 1.0f);
|
|
pos->rotation[1] = asinf(sinBeta);
|
|
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]);
|
|
} 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]);
|
|
}
|
|
}
|