From 3230270ae3046ce010f34aaee35389351913bd86 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Tue, 9 Mar 2021 11:39:41 +0900 Subject: [PATCH] [entity] Start work on a new entity library The plan is to have a fully component based entity system. This adds hierarchical transforms. Not particularly useful for quake itself at this stage, but it will allow for much more flexibility later on, especially when QuakeForge becomes more general-purpose. --- include/QF/entity.h | 122 ++++++ libs/Makemodule.am | 1 + libs/entity/Makemodule.am | 13 + libs/entity/hierarchy.c | 444 +++++++++++++++++++ libs/entity/test/Makemodule.am | 19 + libs/entity/test/test-hierarchy.c | 707 ++++++++++++++++++++++++++++++ libs/entity/transform.c | 317 ++++++++++++++ 7 files changed, 1623 insertions(+) create mode 100644 include/QF/entity.h create mode 100644 libs/entity/Makemodule.am create mode 100644 libs/entity/hierarchy.c create mode 100644 libs/entity/test/Makemodule.am create mode 100644 libs/entity/test/test-hierarchy.c create mode 100644 libs/entity/transform.c diff --git a/include/QF/entity.h b/include/QF/entity.h new file mode 100644 index 000000000..9bf676190 --- /dev/null +++ b/include/QF/entity.h @@ -0,0 +1,122 @@ +/* + entity.h + + Entity management + + Copyright (C) 2021 Bill Currie + + Author: Bill Currie + Date: 2021/02/26 + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef __QF_entity_h +#define __QF_entity_h + +#include "QF/darray.h" +#include "QF/mathlib.h" +#include "QF/simd/vec4f.h" +#include "QF/simd/mat4f.h" + +/** \defgroup entity Entity management + \ingroup utils +*/ +///@{ + +typedef struct mat4fset_s DARRAY_TYPE (mat4f_t) mat4fset_t; +typedef struct vec4fset_s DARRAY_TYPE (vec4f_t) vec4fset_t; +typedef struct uint32set_s DARRAY_TYPE (uint32_t) uint32set_t; +typedef struct byteset_s DARRAY_TYPE (byte) byteset_t; +typedef struct stringset_s DARRAY_TYPE (char *) stringset_t; +typedef struct xformset_s DARRAY_TYPE (struct transform_s *) xformset_t; +typedef struct entityset_s DARRAY_TYPE (struct entity_s *) entityset_t; + +#define null_transform (~0u) + +typedef struct hierarchy_s { + xformset_t transform; + entityset_t entity; + uint32set_t childCount; + uint32set_t childIndex; + uint32set_t parentIndex; + stringset_t name; + uint32set_t tag; + byteset_t modified; + mat4fset_t localMatrix; + mat4fset_t localInverse; + mat4fset_t worldMatrix; + mat4fset_t worldInverse; + vec4fset_t localRotation; + vec4fset_t localScale; + vec4fset_t worldRotation; + vec4fset_t worldScale; +} hierarchy_t; + +typedef struct transform_s { + hierarchy_t *hierarchy; + uint32_t index; +} transform_t; + +transform_t *Transform_New (transform_t *parent); +void Transform_Delete (transform_t *transform); +transform_t *Transform_NewNamed (transform_t *parent, const char *name); +uint32_t Transform_ChildCount (const transform_t *transform) __attribute__((pure)); +transform_t *Transform_GetChild (const transform_t *transform, + uint32_t childIndex) __attribute__((pure)); +void Transform_SetParent (transform_t *transform, transform_t *parent); +transform_t *Transform_GetParent (const transform_t *transform) __attribute__((pure)); +void Transform_SetName (transform_t *transform, const char *name); +const char *Transform_GetName (const transform_t *transform) __attribute__((pure)); +void Transform_SetTag (transform_t *transform, uint32_t tag); +uint32_t Transform_GetTag (const transform_t *transform) __attribute__((pure)); +void Transform_GetLocalMatrix (const transform_t *transform, mat4f_t mat); +void Transform_GetLocalInverse (const transform_t *transform, mat4f_t mat); +void Transform_GetWorldMatrix (const transform_t *transform, mat4f_t mat); +void Transform_GetWorldInverse (const transform_t *transform, mat4f_t mat); +vec4f_t Transform_GetLocalPosition (const transform_t *transform) __attribute__((pure)); +void Transform_SetLocalPosition (transform_t *transform_t, vec4f_t position); +vec4f_t Transform_GetLocalRotation (const transform_t *transform) __attribute__((pure)); +void Transform_SetLocalRotation (transform_t *transform_t, vec4f_t rotation); +vec4f_t Transform_GetLocalScale (const transform_t *transform) __attribute__((pure)); +void Transform_SetLocalScale (transform_t *transform_t, vec4f_t scale); +vec4f_t Transform_GetWorldPosition (const transform_t *transform) __attribute__((pure)); +void Transform_SetWorldPosition (transform_t *transform_t, vec4f_t position); +vec4f_t Transform_GetWorldRotation (const transform_t *transform) __attribute__((pure)); +void Transform_SetWorldRotation (transform_t *transform_t, vec4f_t rotation); +vec4f_t Transform_GetWorldScale (const transform_t *transform) __attribute__((pure)); +// NOTE: these use X: right, Y: forward, Z:up +// aslo, not guaranteed to be normalized or even orthogonal +vec4f_t Transform_Forward (const transform_t *transform) __attribute__((pure)); +vec4f_t Transform_Right (const transform_t *transform) __attribute__((pure)); +vec4f_t Transform_Up (const transform_t *transform) __attribute__((pure)); +// no SetWorldScale because after rotations, non uniform scale becomes shear + +hierarchy_t *Hierarchy_New (size_t grow, int createRoot); +hierarchy_t *Hierarchy_Copy (hierarchy_t *src); +void Hierarchy_Delete (hierarchy_t *hierarchy); + +void Hierarchy_UpdateMatrices (hierarchy_t *hierarchy); +uint32_t Hierarchy_InsertHierarchy (hierarchy_t *dst, const hierarchy_t *src, + uint32_t dstParent, uint32_t srcRoot); +void Hierarchy_RemoveHierarchy (hierarchy_t *hierarchy, uint32_t index); +///@} + +#endif//__QF_entity_h diff --git a/libs/Makemodule.am b/libs/Makemodule.am index 5a68c67ba..86fcac7df 100644 --- a/libs/Makemodule.am +++ b/libs/Makemodule.am @@ -7,6 +7,7 @@ include libs/image/Makemodule.am include libs/models/Makemodule.am include libs/video/Makemodule.am include libs/console/Makemodule.am +include libs/entity/Makemodule.am include libs/net/Makemodule.am include libs/client/Makemodule.am diff --git a/libs/entity/Makemodule.am b/libs/entity/Makemodule.am new file mode 100644 index 000000000..d8ac9a7e1 --- /dev/null +++ b/libs/entity/Makemodule.am @@ -0,0 +1,13 @@ +include libs/entity/test/Makemodule.am + +entity_deps=libs/util/libQFutil.la + +lib_LTLIBRARIES += libs/entity/libQFentity.la + +libs_entity_libQFentity_la_LDFLAGS= $(lib_ldflags) +libs_entity_libQFentity_la_LIBADD= $(entity_deps) +libs_entity_libQFentity_la_DEPENDENCIES= $(entity_deps) +libs_entity_libQFentity_la_SOURCES= \ + libs/entity/hierarchy.c \ + libs/entity/transform.c \ + $e diff --git a/libs/entity/hierarchy.c b/libs/entity/hierarchy.c new file mode 100644 index 000000000..cadb3771c --- /dev/null +++ b/libs/entity/hierarchy.c @@ -0,0 +1,444 @@ +/* + hierarchy.c + + General hierarchy handling + + Copyright (C) 2021 Bill Currke + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_STRING_H +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif + +#include "QF/entity.h" + +static void +hierarchy_UpdateTransformIndices (hierarchy_t *hierarchy, uint32_t start, + int offset) +{ + for (size_t i = start; i < hierarchy->transform.size; i++) { + if (hierarchy->transform.a[i]) { + hierarchy->transform.a[i]->index += offset; + } + } +} + +static void +hierarchy_UpdateChildIndices (hierarchy_t *hierarchy, uint32_t start, + int offset) +{ + for (size_t i = start; i < hierarchy->childIndex.size; i++) { + hierarchy->childIndex.a[i] += offset; + } +} + +static void +hierarchy_UpdateParentIndices (hierarchy_t *hierarchy, uint32_t start, + int offset) +{ + for (size_t i = start; i < hierarchy->parentIndex.size; i++) { + hierarchy->parentIndex.a[i] += offset; + } +} + +static void +hierarchy_calcLocalInverse (hierarchy_t *h, uint32_t index) +{ + // This takes advantage of the fact that localMatrix is a simple + // homogenous scale/rotate/translate matrix with no shear + vec4f_t x = h->localMatrix.a[index][0]; + vec4f_t y = h->localMatrix.a[index][1]; + vec4f_t z = h->localMatrix.a[index][2]; + vec4f_t t = h->localMatrix.a[index][3]; + + // "one" is to ensure both the scalar and translation have 1 in their + // forth components + vec4f_t one = { 0, 0, 0, 1 }; + vec4f_t nx = { x[0], y[0], z[0], 0 }; + vec4f_t ny = { x[1], y[1], z[1], 0 }; + vec4f_t nz = { x[2], y[2], z[2], 0 }; + vec4f_t nt = one - t[0] * nx - t[1] * ny - t[2] * nz; + // vertical dot product!!! + vec4f_t s = 1 / (nx * nx + ny * ny + nz * nz + one); + h->localInverse.a[index][0] = nx * s; + h->localInverse.a[index][1] = ny * s; + h->localInverse.a[index][2] = nz * s; + h->localInverse.a[index][3] = nt * s; +} + +void +Hierarchy_UpdateMatrices (hierarchy_t *h) +{ + for (size_t i = 0; i < h->localInverse.size; i++) { + if (h->modified.a[i]) { + hierarchy_calcLocalInverse (h, i); + } + } + if (h->modified.a[0]) { + memcpy (h->worldMatrix.a[0], + h->localMatrix.a[0], sizeof (mat4_t)); + memcpy (h->worldInverse.a[0], + h->localInverse.a[0], sizeof (mat4_t)); + h->worldRotation.a[0] = h->localRotation.a[0]; + h->worldScale.a[0] = h->localScale.a[0]; + } + for (size_t i = 1; i < h->worldMatrix.size; i++) { + uint32_t parent = h->parentIndex.a[i]; + + if (h->modified.a[i] || h->modified.a[parent]) { + mmulf (h->worldMatrix.a[i], + h->worldMatrix.a[parent], h->localMatrix.a[i]); + h->modified.a[i] = 1; + } + } + for (size_t i = 1; i < h->worldInverse.size; i++) { + uint32_t parent = h->parentIndex.a[i]; + + if (h->modified.a[i] || h->modified.a[parent]) { + mmulf (h->worldInverse.a[i], + h->localInverse.a[i], h->worldInverse.a[parent]); + } + } + for (size_t i = 1; i < h->worldRotation.size; i++) { + uint32_t parent = h->parentIndex.a[i]; + if (h->modified.a[i] || h->modified.a[parent]) { + h->worldRotation.a[i] = qmulf (h->worldRotation.a[parent], + h->localRotation.a[i]); + } + } + for (size_t i = 1; i < h->worldScale.size; i++) { + uint32_t parent = h->parentIndex.a[i]; + if (h->modified.a[i] || h->modified.a[parent]) { + h->worldScale.a[i] = m3vmulf (h->worldMatrix.a[parent], + h->localScale.a[i]); + } + } + memset (h->modified.a, 0, h->modified.size); +} + +static void +hierarchy_open (hierarchy_t *hierarchy, uint32_t index, uint32_t count) +{ + DARRAY_OPEN_AT (&hierarchy->transform, index, count); + DARRAY_OPEN_AT (&hierarchy->entity, index, count); + DARRAY_OPEN_AT (&hierarchy->childCount, index, count); + DARRAY_OPEN_AT (&hierarchy->childIndex, index, count); + DARRAY_OPEN_AT (&hierarchy->parentIndex, index, count); + DARRAY_OPEN_AT (&hierarchy->name, index, count); + DARRAY_OPEN_AT (&hierarchy->tag, index, count); + DARRAY_OPEN_AT (&hierarchy->modified, index, count); + DARRAY_OPEN_AT (&hierarchy->localMatrix, index, count); + DARRAY_OPEN_AT (&hierarchy->localInverse, index, count); + DARRAY_OPEN_AT (&hierarchy->worldMatrix, index, count); + DARRAY_OPEN_AT (&hierarchy->worldInverse, index, count); + DARRAY_OPEN_AT (&hierarchy->localRotation, index, count); + DARRAY_OPEN_AT (&hierarchy->localScale, index, count); + DARRAY_OPEN_AT (&hierarchy->worldRotation, index, count); + DARRAY_OPEN_AT (&hierarchy->worldScale, index, count); +} + +static void +hierarchy_close (hierarchy_t *hierarchy, uint32_t index, uint32_t count) +{ + if (count) { + DARRAY_CLOSE_AT (&hierarchy->transform, index, count); + DARRAY_CLOSE_AT (&hierarchy->entity, index, count); + DARRAY_CLOSE_AT (&hierarchy->childCount, index, count); + DARRAY_CLOSE_AT (&hierarchy->childIndex, index, count); + DARRAY_CLOSE_AT (&hierarchy->parentIndex, index, count); + DARRAY_CLOSE_AT (&hierarchy->name, index, count); + DARRAY_CLOSE_AT (&hierarchy->tag, index, count); + DARRAY_CLOSE_AT (&hierarchy->modified, index, count); + DARRAY_CLOSE_AT (&hierarchy->localMatrix, index, count); + DARRAY_CLOSE_AT (&hierarchy->localInverse, index, count); + DARRAY_CLOSE_AT (&hierarchy->worldMatrix, index, count); + DARRAY_CLOSE_AT (&hierarchy->worldInverse, index, count); + DARRAY_CLOSE_AT (&hierarchy->localRotation, index, count); + DARRAY_CLOSE_AT (&hierarchy->localScale, index, count); + DARRAY_CLOSE_AT (&hierarchy->worldRotation, index, count); + DARRAY_CLOSE_AT (&hierarchy->worldScale, index, count); + } +} + +static void +hierarchy_move (hierarchy_t *dst, const hierarchy_t *src, + uint32_t dstIndex, uint32_t srcIndex, uint32_t count) +{ + memcpy (&dst->transform.a[dstIndex], &src->transform.a[srcIndex], + count * sizeof(dst->transform.a[0])); + memset (&src->transform.a[srcIndex], 0, + count * sizeof(dst->transform.a[0])); + memcpy (&dst->entity.a[dstIndex], &src->entity.a[srcIndex], + count * sizeof(dst->entity.a[0])); + memcpy (&dst->name.a[dstIndex], &src->name.a[srcIndex], + count * sizeof(dst->name.a[0])); + memcpy (&dst->tag.a[dstIndex], &src->tag.a[srcIndex], + count * sizeof(dst->tag.a[0])); + memset (&dst->modified.a[dstIndex], 1, count * sizeof(dst->modified.a[0])); + memcpy (&dst->localMatrix.a[dstIndex], &src->localMatrix.a[srcIndex], + count * sizeof(dst->localMatrix.a[0])); + memcpy (&dst->localInverse.a[dstIndex], &src->localInverse.a[srcIndex], + count * sizeof(dst->localInverse.a[0])); + memcpy (&dst->localRotation.a[dstIndex], &src->localRotation.a[srcIndex], + count * sizeof(dst->localRotation.a[0])); + memcpy (&dst->localScale.a[dstIndex], &src->localScale.a[srcIndex], + count * sizeof(dst->localScale.a[0])); + + for (uint32_t i = 0; i < count; i++) { + dst->transform.a[dstIndex + i]->hierarchy = dst; + dst->transform.a[dstIndex + i]->index = dstIndex + i; + } +} + +static void +hierarchy_init (hierarchy_t *dst, uint32_t index, + uint32_t parentIndex, uint32_t childIndex, uint32_t count) +{ + memset (&dst->transform.a[index], 0, + count * sizeof(dst->transform.a[0])); + memset (&dst->entity.a[index], 0, count * sizeof(dst->entity.a[0])); + memset (&dst->name.a[index], 0, count * sizeof(dst->name.a[0])); + memset (&dst->tag.a[index], 0, count * sizeof(dst->tag.a[0])); + memset (&dst->modified.a[index], 1, count * sizeof(dst->modified.a[0])); + + for (uint32_t i = 0; i < count; i++) { + mat4fidentity (dst->localMatrix.a[index]); + mat4fidentity (dst->localInverse.a[index]); + dst->localRotation.a[index] = (vec4f_t) { 0, 0, 0, 1 }; + dst->localScale.a[index] = (vec4f_t) { 1, 1, 1, 1 }; + + dst->parentIndex.a[index + i] = parentIndex; + dst->childCount.a[index + i] = 0; + dst->childIndex.a[index + i] = childIndex; + } +} + +static uint32_t +hierarchy_insert (hierarchy_t *dst, const hierarchy_t *src, + uint32_t dstParent, uint32_t srcRoot, uint32_t count) +{ + uint32_t insertIndex; // where the transforms will be inserted + uint32_t childIndex; // where the transforms' children will inserted + + // The newly added transforms are always last children of the parent + // transform + insertIndex = dst->childIndex.a[dstParent] + dst->childCount.a[dstParent]; + + // By design, all of a transform's children are in one contiguous block, + // and the blocks of children for each transform are ordered by their + // parents. Thus the child index of each transform increases monotonically + // for each child index in the array, regardless of the level of the owning + // transform (higher levels always come before lower levels). + uint32_t neighbor = insertIndex - 1; // insertIndex never zero + childIndex = dst->childIndex.a[neighbor] + dst->childCount.a[neighbor]; + + // Any transforms that come after the inserted transforms need to have + // thier indices adjusted. + hierarchy_UpdateTransformIndices (dst, insertIndex, count); + // The parent transform's child index is not affected, but the child + // indices of all transforms immediately after the parent transform are. + hierarchy_UpdateChildIndices (dst, dstParent + 1, count); + hierarchy_UpdateParentIndices (dst, childIndex, count); + + // The beginning of the block of children for the new transforms was + // computed from the pre-insert indices of the related transforms, thus + // the index must be updated by the number of transforms being inserted + // (it would have been updated thusly if the insert was done before + // updating the indices of the other transforms). + childIndex += count; + + hierarchy_open (dst, insertIndex, count); + if (src) { + hierarchy_move (dst, src, insertIndex, srcRoot, count); + } else { + hierarchy_init (dst, insertIndex, dstParent, childIndex, count); + } + for (uint32_t i = 0; i < count; i++) { + dst->parentIndex.a[insertIndex + i] = dstParent; + dst->childIndex.a[insertIndex + i] = childIndex; + dst->childCount.a[insertIndex + i] = 0; + } + + dst->childCount.a[dstParent] += count; + return insertIndex; +} + +static void +hierarchy_insert_children (hierarchy_t *dst, const hierarchy_t *src, + uint32_t dstParent, uint32_t srcRoot) +{ + uint32_t insertIndex; + uint32_t childIndex = src->childIndex.a[srcRoot]; + uint32_t childCount = src->childCount.a[srcRoot]; + + if (childCount) { + insertIndex = hierarchy_insert (dst, src, dstParent, + childIndex, childCount); + for (uint32_t i = 0; i < childCount; i++) { + hierarchy_insert_children (dst, src, insertIndex + i, + childIndex + i); + } + } +} + +uint32_t +Hierarchy_InsertHierarchy (hierarchy_t *dst, const hierarchy_t *src, + uint32_t dstParent, uint32_t srcRoot) +{ + uint32_t insertIndex; + + if (dstParent == null_transform) { + if (dst->transform.size) { + Sys_Error ("attempt to insert root in non-empty hierarchy"); + } + hierarchy_open (dst, 0, 1); + hierarchy_move (dst, src, 0, srcRoot, 1); + dst->parentIndex.a[0] = null_transform; + dst->childIndex.a[0] = 1; + dst->childCount.a[0] = 0; + insertIndex = 0; + } else { + if (!dst->transform.size) { + Sys_Error ("attempt to insert non-root in empty hierarchy"); + } + insertIndex = hierarchy_insert (dst, src, dstParent, srcRoot, 1); + } + // if src is null, then inserting a new transform which has no children + if (src) { + hierarchy_insert_children (dst, src, insertIndex, srcRoot); + } + Hierarchy_UpdateMatrices (dst); + return insertIndex; +} + +static void +hierarchy_remove_children (hierarchy_t *hierarchy, uint32_t index) +{ + uint32_t childIndex = hierarchy->childIndex.a[index]; + uint32_t childCount = hierarchy->childCount.a[index]; + uint32_t parentIndex = hierarchy->parentIndex.a[index]; + uint32_t nieceIndex = null_transform; + + if (parentIndex != null_transform) { + uint32_t siblingIndex = hierarchy->childIndex.a[parentIndex]; + siblingIndex += hierarchy->childCount.a[parentIndex] - 1; + nieceIndex = hierarchy->childIndex.a[siblingIndex]; + } + for (uint32_t i = childCount; i-- > 0; ) { + hierarchy_remove_children (hierarchy, childIndex + i); + } + hierarchy_close (hierarchy, childIndex, childCount); + hierarchy->childCount.a[index] = 0; + + if (childCount) { + hierarchy_UpdateTransformIndices (hierarchy, childIndex, -childCount); + hierarchy_UpdateChildIndices (hierarchy, index, -childCount); + if (nieceIndex != null_transform) { + hierarchy_UpdateParentIndices (hierarchy, nieceIndex, -childCount); + } + } +} + +void +Hierarchy_RemoveHierarchy (hierarchy_t *hierarchy, uint32_t index) +{ + uint32_t parentIndex = hierarchy->parentIndex.a[index]; + uint32_t childIndex = hierarchy->childIndex.a[index]; + uint32_t siblingIndex = null_transform; + if (parentIndex != null_transform) { + siblingIndex = hierarchy->childIndex.a[parentIndex]; + } + hierarchy_remove_children (hierarchy, index); + hierarchy_close (hierarchy, index, 1); + if (siblingIndex != null_transform) { + hierarchy_UpdateTransformIndices (hierarchy, index, -1); + hierarchy_UpdateChildIndices (hierarchy, siblingIndex, -1); + hierarchy_UpdateParentIndices (hierarchy, childIndex - 1, -1); + } +} + +hierarchy_t * +Hierarchy_New (size_t grow, int createRoot) +{ + if (!grow) { + grow = 16; + } + hierarchy_t *hierarchy = malloc (sizeof (hierarchy_t)); + + DARRAY_INIT (&hierarchy->transform, grow); + DARRAY_INIT (&hierarchy->entity, grow); + DARRAY_INIT (&hierarchy->childCount, grow); + DARRAY_INIT (&hierarchy->childIndex, grow); + DARRAY_INIT (&hierarchy->parentIndex, grow); + DARRAY_INIT (&hierarchy->name, grow); + DARRAY_INIT (&hierarchy->tag, grow); + DARRAY_INIT (&hierarchy->modified, grow); + DARRAY_INIT (&hierarchy->localMatrix, grow); + DARRAY_INIT (&hierarchy->localInverse, grow); + DARRAY_INIT (&hierarchy->worldMatrix, grow); + DARRAY_INIT (&hierarchy->worldInverse, grow); + DARRAY_INIT (&hierarchy->localRotation, grow); + DARRAY_INIT (&hierarchy->localScale, grow); + DARRAY_INIT (&hierarchy->worldRotation, grow); + DARRAY_INIT (&hierarchy->worldScale, grow); + + if (createRoot) { + hierarchy_open (hierarchy, 0, 1); + hierarchy_init (hierarchy, 0, null_transform, 1, 1); + } + + return hierarchy; +} + +void +Hierarchy_Delete (hierarchy_t *hierarchy) +{ + for (size_t i = 0; i < hierarchy->transform.size; i++) { + free (hierarchy->transform.a[i]); + } + for (size_t i = 0; i < hierarchy->name.size; i++) { + free (hierarchy->name.a[i]); + } + DARRAY_CLEAR (&hierarchy->transform); + DARRAY_CLEAR (&hierarchy->entity); + DARRAY_CLEAR (&hierarchy->childCount); + DARRAY_CLEAR (&hierarchy->childIndex); + DARRAY_CLEAR (&hierarchy->parentIndex); + DARRAY_CLEAR (&hierarchy->name); + DARRAY_CLEAR (&hierarchy->tag); + DARRAY_CLEAR (&hierarchy->modified); + DARRAY_CLEAR (&hierarchy->localMatrix); + DARRAY_CLEAR (&hierarchy->localInverse); + DARRAY_CLEAR (&hierarchy->worldMatrix); + DARRAY_CLEAR (&hierarchy->worldInverse); + DARRAY_CLEAR (&hierarchy->localRotation); + DARRAY_CLEAR (&hierarchy->localScale); + DARRAY_CLEAR (&hierarchy->worldRotation); + DARRAY_CLEAR (&hierarchy->worldScale); + free (hierarchy); +} diff --git a/libs/entity/test/Makemodule.am b/libs/entity/test/Makemodule.am new file mode 100644 index 000000000..b0f98ae2b --- /dev/null +++ b/libs/entity/test/Makemodule.am @@ -0,0 +1,19 @@ +libs_entity_tests = \ + libs/entity/test/test-hierarchy \ + $e + +TESTS += $(libs_entity_tests) + +check_PROGRAMS += $(libs_entity_tests) + +libs_entity_test_libs= \ + libs/entity/libQFentity.la \ + libs/util/libQFutil.la + +libs_entity_test_test_hierarchy_SOURCES= \ + libs/entity/test/test-hierarchy.c \ + $e +libs_entity_test_test_hierarchy_LDADD= \ + $(libs_entity_test_libs) +libs_entity_test_test_hierarchy_DEPENDENCIES= \ + $(libs_entity_test_libs) diff --git a/libs/entity/test/test-hierarchy.c b/libs/entity/test/test-hierarchy.c new file mode 100644 index 000000000..513f33fca --- /dev/null +++ b/libs/entity/test/test-hierarchy.c @@ -0,0 +1,707 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "QF/entity.h" + +// NOTE: these are the columns of the matrix! (not that it matters for a +// symmetrical matrix, but...) +mat4f_t identity = { + { 1, 0, 0, 0 }, + { 0, 1, 0, 0 }, + { 0, 0, 1, 0 }, + { 0, 0, 0, 1 }, +}; +vec4f_t one = { 1, 1, 1, 1 }; + +static int +vec4_equal (vec4f_t a, vec4f_t b) +{ + vec4i_t res = a != b; + return !(res[0] || res[1] || res[2] || res[3]); +} + +static int +mat4_equal (const mat4f_t a, const mat4f_t b) +{ + vec4i_t res = {}; + + for (int i = 0; i < 4; i++) { + res |= a[i] != b[i]; + } + return !(res[0] || res[1] || res[2] || res[3]); +} + +static int +check_hierarchy_size (hierarchy_t *h, uint32_t size) +{ + if (h->transform.size != size + || h->entity.size != size + || h->childCount.size != size + || h->childIndex.size != size + || h->parentIndex.size != size + || h->name.size != size + || h->tag.size != size + || h->modified.size != size + || h->localMatrix.size != size + || h->localInverse.size != size + || h->worldMatrix.size != size + || h->worldInverse.size != size + || h->localRotation.size != size + || h->localScale.size != size + || h->worldRotation.size != size + || h->worldScale.size != size) { + printf ("hierarchy does not have exactly %u" + " transform or array sizes are inconsistent\n", size); + return 0; + } + for (size_t i = 0; i < h->transform.size; i++) { + if (h->transform.a[i]->hierarchy != h) { + printf ("transform %zd (%s) does not point to hierarchy\n", + i, h->name.a[i]); + } + } + return 1; +} + +static void +dump_hierarchy (hierarchy_t *h) +{ + for (size_t i = 0; i < h->transform.size; i++) { + printf ("%2zd: %5s %2u %2u %2u %2u\n", i, h->name.a[i], + h->transform.a[i]->index, h->parentIndex.a[i], + h->childIndex.a[i], h->childCount.a[i]); + } + puts (""); +} + +static int +check_indices (transform_t *transform, uint32_t index, uint32_t parentIndex, + uint32_t childIndex, uint32_t childCount) +{ + hierarchy_t *h = transform->hierarchy; + if (transform->index != index) { + printf ("%s/%s index incorrect: expect %u got %u\n", + h->name.a[transform->index], h->name.a[index], + index, transform->index); + return 0; + } + if (h->parentIndex.a[index] != parentIndex) { + printf ("%s parent index incorrect: expect %u got %u\n", + h->name.a[index], parentIndex, h->parentIndex.a[index]); + return 0; + } + if (h->childIndex.a[index] != childIndex) { + printf ("%s child index incorrect: expect %u got %u\n", + h->name.a[index], childIndex, h->childIndex.a[index]); + return 0; + } + if (h->childCount.a[index] != childCount) { + printf ("%s child count incorrect: expect %u got %u\n", + h->name.a[index], childCount, h->childCount.a[index]); + return 0; + } + return 1; +} + +static int +test_single_transform (void) +{ + transform_t *transform = Transform_New (0); + hierarchy_t *h; + + if (!transform) { + printf ("Transform_New returned null\n"); + return 1; + } + if (!(h = transform->hierarchy)) { + printf ("New transform has no hierarchy\n"); + return 1; + } + if (!check_hierarchy_size (h, 1)) { return 1; } + if (!check_indices (transform, 0, null_transform, 1, 0)) { return 1; } + + if (!mat4_equal (h->localMatrix.a[0], identity) + || !mat4_equal (h->localInverse.a[0], identity) + || !mat4_equal (h->worldMatrix.a[0], identity) + || !mat4_equal (h->worldInverse.a[0], identity)) { + printf ("New transform matrices not identity\n"); + return 1; + } + + if (!vec4_equal (h->localRotation.a[0], identity[3]) + || !vec4_equal (h->localScale.a[0], one)) { + printf ("New transform rotation or scale not identity\n"); + return 1; + } + + // Delete the hierarchy directly as setparent isn't fully tested + Hierarchy_Delete (transform->hierarchy); + + return 0; +} + +static int +test_parent_child_init (void) +{ + transform_t *parent = Transform_New (0); + transform_t *child = Transform_New (parent); + + if (parent->hierarchy != child->hierarchy) { + printf ("parent and child transforms have separate hierarchies\n"); + return 1; + } + + if (!check_hierarchy_size (parent->hierarchy, 2)) { return 1; } + + if (!check_indices (parent, 0, null_transform, 1, 1)) { return 1; } + if (!check_indices (child, 1, 0, 2, 0)) { return 1; } + + hierarchy_t *h = parent->hierarchy; + if (!mat4_equal (h->localMatrix.a[0], identity) + || !mat4_equal (h->localInverse.a[0], identity) + || !mat4_equal (h->worldMatrix.a[0], identity) + || !mat4_equal (h->worldInverse.a[0], identity)) { + printf ("Parent transform matrices not identity\n"); + return 1; + } + + if (!vec4_equal (h->localRotation.a[0], identity[3]) + || !vec4_equal (h->localScale.a[0], one)) { + printf ("Parent transform rotation or scale not identity\n"); + return 1; + } + + if (!mat4_equal (h->localMatrix.a[1], identity) + || !mat4_equal (h->localInverse.a[1], identity) + || !mat4_equal (h->worldMatrix.a[1], identity) + || !mat4_equal (h->worldInverse.a[1], identity)) { + printf ("Child transform matrices not identity\n"); + return 1; + } + + if (!vec4_equal (h->localRotation.a[1], identity[3]) + || !vec4_equal (h->localScale.a[1], one)) { + printf ("Child transform rotation or scale not identity\n"); + return 1; + } + + // Delete the hierarchy directly as setparent isn't fully tested + Hierarchy_Delete (parent->hierarchy); + + return 0; +} + +static int +test_parent_child_setparent (void) +{ + transform_t *parent = Transform_New (0); + transform_t *child = Transform_New (0); + + Transform_SetName (parent, "parent"); + Transform_SetName (child, "child"); + + if (!check_indices (parent, 0, null_transform, 1, 0)) { return 1; } + if (!check_indices (child, 0, null_transform, 1, 0)) { return 1; } + + if (parent->hierarchy == child->hierarchy) { + printf ("parent and child transforms have same hierarchy before" + " set paret\n"); + return 1; + } + + Transform_SetParent (child, parent); + + if (parent->hierarchy != child->hierarchy) { + printf ("parent and child transforms have separate hierarchies\n"); + return 1; + } + + if (!check_hierarchy_size (parent->hierarchy, 2)) { return 1; } + + if (!check_indices (parent, 0, null_transform, 1, 1)) { return 1; } + if (!check_indices (child, 1, 0, 2, 0)) { return 1; } + + hierarchy_t *h = parent->hierarchy; + if (!mat4_equal (h->localMatrix.a[0], identity) + || !mat4_equal (h->localInverse.a[0], identity) + || !mat4_equal (h->worldMatrix.a[0], identity) + || !mat4_equal (h->worldInverse.a[0], identity)) { + printf ("Parent transform matrices not identity\n"); + return 1; + } + + if (!vec4_equal (h->localRotation.a[0], identity[3]) + || !vec4_equal (h->localScale.a[0], one)) { + printf ("Parent transform rotation or scale not identity\n"); + return 1; + } + + if (!mat4_equal (h->localMatrix.a[1], identity) + || !mat4_equal (h->localInverse.a[1], identity) + || !mat4_equal (h->worldMatrix.a[1], identity) + || !mat4_equal (h->worldInverse.a[1], identity)) { + printf ("Child transform matrices not identity\n"); + return 1; + } + + if (!vec4_equal (h->localRotation.a[1], identity[3]) + || !vec4_equal (h->localScale.a[1], one)) { + printf ("Child transform rotation or scale not identity\n"); + return 1; + } + + // Delete the hierarchy directly as setparent isn't fully tested + Hierarchy_Delete (parent->hierarchy); + + return 0; +} + +static int +test_build_hierarchy (void) +{ + printf ("test_build_hierarchy\n"); + + transform_t *root = Transform_NewNamed (0, "root"); + transform_t *A = Transform_NewNamed (root, "A"); + transform_t *B = Transform_NewNamed (root, "B"); + transform_t *C = Transform_NewNamed (root, "C"); + + if (!check_indices (root, 0, null_transform, 1, 3)) { return 1; } + if (!check_indices (A, 1, 0, 4, 0)) { return 1; } + if (!check_indices (B, 2, 0, 4, 0)) { return 1; } + if (!check_indices (C, 3, 0, 4, 0)) { return 1; } + + transform_t *B1 = Transform_NewNamed (B, "B1"); + + if (!check_indices (root, 0, null_transform, 1, 3)) { return 1; } + if (!check_indices ( A, 1, 0, 4, 0)) { return 1; } + if (!check_indices ( B, 2, 0, 4, 1)) { return 1; } + if (!check_indices ( C, 3, 0, 5, 0)) { return 1; } + if (!check_indices (B1, 4, 2, 5, 0)) { return 1; } + + transform_t *A1 = Transform_NewNamed (A, "A1"); + + if (!check_indices (root, 0, null_transform, 1, 3)) { return 1; } + if (!check_indices ( A, 1, 0, 4, 1)) { return 1; } + if (!check_indices ( B, 2, 0, 5, 1)) { return 1; } + if (!check_indices ( C, 3, 0, 6, 0)) { return 1; } + if (!check_indices (A1, 4, 1, 6, 0)) { return 1; } + if (!check_indices (B1, 5, 2, 6, 0)) { return 1; } + transform_t *A1a = Transform_NewNamed (A1, "A1a"); + transform_t *B2 = Transform_NewNamed (B, "B2"); + transform_t *A2 = Transform_NewNamed (A, "A2"); + transform_t *B3 = Transform_NewNamed (B, "B3"); + transform_t *B2a = Transform_NewNamed (B2, "B2a"); + + if (!check_hierarchy_size (root->hierarchy, 11)) { return 1; } + + if (!check_indices (root, 0, null_transform, 1, 3)) { return 1; } + if (!check_indices ( A, 1, 0, 4, 2)) { return 1; } + if (!check_indices ( B, 2, 0, 6, 3)) { return 1; } + if (!check_indices ( C, 3, 0, 9, 0)) { return 1; } + if (!check_indices ( A1, 4, 1, 9, 1)) { return 1; } + if (!check_indices ( A2, 5, 1, 10, 0)) { return 1; } + if (!check_indices ( B1, 6, 2, 10, 0)) { return 1; } + if (!check_indices ( B2, 7, 2, 10, 1)) { return 1; } + if (!check_indices ( B3, 8, 2, 11, 0)) { return 1; } + if (!check_indices (A1a, 9, 4, 11, 0)) { return 1; } + if (!check_indices (B2a, 10, 7, 11, 0)) { return 1; } + + transform_t *D = Transform_NewNamed (root, "D"); + + if (!check_hierarchy_size (root->hierarchy, 12)) { return 1; } + + if (!check_indices (root, 0, null_transform, 1, 4)) { return 1; } + if (!check_indices ( A, 1, 0, 5, 2)) { return 1; } + if (!check_indices ( B, 2, 0, 7, 3)) { return 1; } + if (!check_indices ( C, 3, 0, 10, 0)) { return 1; } + if (!check_indices ( D, 4, 0, 10, 0)) { return 1; } + if (!check_indices ( A1, 5, 1, 10, 1)) { return 1; } + if (!check_indices ( A2, 6, 1, 11, 0)) { return 1; } + if (!check_indices ( B1, 7, 2, 11, 0)) { return 1; } + if (!check_indices ( B2, 8, 2, 11, 1)) { return 1; } + if (!check_indices ( B3, 9, 2, 12, 0)) { return 1; } + if (!check_indices (A1a, 10, 5, 12, 0)) { return 1; } + if (!check_indices (B2a, 11, 8, 12, 0)) { return 1; } + + dump_hierarchy (root->hierarchy); + transform_t *C1 = Transform_NewNamed (C, "C1"); + dump_hierarchy (root->hierarchy); + if (!check_hierarchy_size (root->hierarchy, 13)) { return 1; } + + if (!check_indices (root, 0, null_transform, 1, 4)) { return 1; } + if (!check_indices ( A, 1, 0, 5, 2)) { return 1; } + if (!check_indices ( B, 2, 0, 7, 3)) { return 1; } + if (!check_indices ( C, 3, 0, 10, 1)) { return 1; } + if (!check_indices ( D, 4, 0, 11, 0)) { return 1; } + if (!check_indices ( A1, 5, 1, 11, 1)) { return 1; } + if (!check_indices ( A2, 6, 1, 12, 0)) { return 1; } + if (!check_indices ( B1, 7, 2, 12, 0)) { return 1; } + if (!check_indices ( B2, 8, 2, 12, 1)) { return 1; } + if (!check_indices ( B3, 9, 2, 13, 0)) { return 1; } + if (!check_indices ( C1, 10, 3, 13, 0)) { return 1; } + if (!check_indices (A1a, 11, 5, 13, 0)) { return 1; } + if (!check_indices (B2a, 12, 8, 13, 0)) { return 1; } + + // Delete the hierarchy directly as setparent isn't fully tested + Hierarchy_Delete (root->hierarchy); + + return 0; +} + +static int +test_build_hierarchy2 (void) +{ + printf ("test_build_hierarchy2\n"); + + transform_t *root = Transform_NewNamed (0, "root"); + transform_t *A = Transform_NewNamed (root, "A"); + transform_t *B = Transform_NewNamed (root, "B"); + transform_t *C = Transform_NewNamed (root, "C"); + transform_t *B1 = Transform_NewNamed (B, "B1"); + transform_t *A1 = Transform_NewNamed (A, "A1"); + transform_t *A1a = Transform_NewNamed (A1, "A1a"); + transform_t *B2 = Transform_NewNamed (B, "B2"); + transform_t *A2 = Transform_NewNamed (A, "A2"); + transform_t *B3 = Transform_NewNamed (B, "B3"); + transform_t *B2a = Transform_NewNamed (B2, "B2a"); + transform_t *D = Transform_NewNamed (root, "D"); + transform_t *C1 = Transform_NewNamed (C, "C1"); + + if (!check_hierarchy_size (root->hierarchy, 13)) { return 1; } + + if (!check_indices (root, 0, null_transform, 1, 4)) { return 1; } + if (!check_indices ( A, 1, 0, 5, 2)) { return 1; } + if (!check_indices ( B, 2, 0, 7, 3)) { return 1; } + if (!check_indices ( C, 3, 0, 10, 1)) { return 1; } + if (!check_indices ( D, 4, 0, 11, 0)) { return 1; } + if (!check_indices ( A1, 5, 1, 11, 1)) { return 1; } + if (!check_indices ( A2, 6, 1, 12, 0)) { return 1; } + if (!check_indices ( B1, 7, 2, 12, 0)) { return 1; } + if (!check_indices ( B2, 8, 2, 12, 1)) { return 1; } + if (!check_indices ( B3, 9, 2, 13, 0)) { return 1; } + if (!check_indices ( C1, 10, 3, 13, 0)) { return 1; } + if (!check_indices (A1a, 11, 5, 13, 0)) { return 1; } + if (!check_indices (B2a, 12, 8, 13, 0)) { return 1; } + + transform_t *T = Transform_NewNamed (0, "T"); + transform_t *X = Transform_NewNamed (T, "X"); + transform_t *Y = Transform_NewNamed (T, "Y"); + transform_t *Z = Transform_NewNamed (T, "Z"); + transform_t *Y1 = Transform_NewNamed (Y, "Y1"); + transform_t *X1 = Transform_NewNamed (X, "X1"); + transform_t *X1a = Transform_NewNamed (X1, "X1a"); + transform_t *Y2 = Transform_NewNamed (Y, "Y2"); + transform_t *X2 = Transform_NewNamed (X, "X2"); + transform_t *Y3 = Transform_NewNamed (Y, "Y3"); + transform_t *Y2a = Transform_NewNamed (Y2, "Y2a"); + transform_t *Z1 = Transform_NewNamed (Z, "Z1"); + + dump_hierarchy (T->hierarchy); + if (!check_hierarchy_size (T->hierarchy, 12)) { return 1; } + + if (!check_indices ( T, 0, null_transform, 1, 3)) { return 1; } + if (!check_indices ( X, 1, 0, 4, 2)) { return 1; } + if (!check_indices ( Y, 2, 0, 6, 3)) { return 1; } + if (!check_indices ( Z, 3, 0, 9, 1)) { return 1; } + if (!check_indices ( X1, 4, 1, 10, 1)) { return 1; } + if (!check_indices ( X2, 5, 1, 11, 0)) { return 1; } + if (!check_indices ( Y1, 6, 2, 11, 0)) { return 1; } + if (!check_indices ( Y2, 7, 2, 11, 1)) { return 1; } + if (!check_indices ( Y3, 8, 2, 12, 0)) { return 1; } + if (!check_indices ( Z1, 9, 3, 12, 0)) { return 1; } + if (!check_indices (X1a, 10, 4, 12, 0)) { return 1; } + if (!check_indices (Y2a, 11, 7, 12, 0)) { return 1; } + + Transform_SetParent (T, B); + + dump_hierarchy (root->hierarchy); + + if (!check_hierarchy_size (root->hierarchy, 25)) { return 1; } + + if (!check_indices (root, 0, null_transform, 1, 4)) { return 1; } + if (!check_indices ( A, 1, 0, 5, 2)) { return 1; } + if (!check_indices ( B, 2, 0, 7, 4)) { return 1; } + if (!check_indices ( C, 3, 0, 11, 1)) { return 1; } + if (!check_indices ( D, 4, 0, 12, 0)) { return 1; } + if (!check_indices ( A1, 5, 1, 12, 1)) { return 1; } + if (!check_indices ( A2, 6, 1, 13, 0)) { return 1; } + if (!check_indices ( B1, 7, 2, 13, 0)) { return 1; } + if (!check_indices ( B2, 8, 2, 13, 1)) { return 1; } + if (!check_indices ( B3, 9, 2, 14, 0)) { return 1; } + if (!check_indices ( T, 10, 2, 14, 3)) { return 1; } + if (!check_indices ( C1, 11, 3, 17, 0)) { return 1; } + if (!check_indices (A1a, 12, 5, 17, 0)) { return 1; } + if (!check_indices (B2a, 13, 8, 17, 0)) { return 1; } + if (!check_indices ( X, 14, 10, 17, 2)) { return 1; } + if (!check_indices ( Y, 15, 10, 19, 3)) { return 1; } + if (!check_indices ( Z, 16, 10, 22, 1)) { return 1; } + if (!check_indices ( X1, 17, 14, 23, 1)) { return 1; } + if (!check_indices ( X2, 18, 14, 24, 0)) { return 1; } + if (!check_indices ( Y1, 19, 15, 24, 0)) { return 1; } + if (!check_indices ( Y2, 20, 15, 24, 1)) { return 1; } + if (!check_indices ( Y3, 21, 15, 25, 0)) { return 1; } + if (!check_indices ( Z1, 22, 16, 25, 0)) { return 1; } + if (!check_indices (X1a, 23, 17, 25, 0)) { return 1; } + if (!check_indices (Y2a, 24, 20, 25, 0)) { return 1; } + + Transform_SetParent (Y, 0); + + dump_hierarchy (root->hierarchy); + dump_hierarchy (Y->hierarchy); + if (!check_hierarchy_size (root->hierarchy, 20)) { return 1; } + if (!check_hierarchy_size (Y->hierarchy, 5)) { return 1; } + + if (!check_indices (root, 0, null_transform, 1, 4)) { return 1; } + if (!check_indices ( A, 1, 0, 5, 2)) { return 1; } + if (!check_indices ( B, 2, 0, 7, 4)) { return 1; } + if (!check_indices ( C, 3, 0, 11, 1)) { return 1; } + if (!check_indices ( D, 4, 0, 12, 0)) { return 1; } + if (!check_indices ( A1, 5, 1, 12, 1)) { return 1; } + if (!check_indices ( A2, 6, 1, 13, 0)) { return 1; } + if (!check_indices ( B1, 7, 2, 13, 0)) { return 1; } + if (!check_indices ( B2, 8, 2, 13, 1)) { return 1; } + if (!check_indices ( B3, 9, 2, 14, 0)) { return 1; } + if (!check_indices ( T, 10, 2, 14, 3)) { return 1; } + if (!check_indices ( C1, 11, 3, 17, 0)) { return 1; } + if (!check_indices (A1a, 12, 5, 17, 0)) { return 1; } + if (!check_indices (B2a, 13, 8, 17, 0)) { return 1; } + if (!check_indices ( X, 14, 10, 16, 2)) { return 1; } + if (!check_indices ( Z, 15, 10, 18, 1)) { return 1; } + if (!check_indices ( X1, 16, 14, 19, 1)) { return 1; } + if (!check_indices ( X2, 17, 14, 20, 0)) { return 1; } + if (!check_indices ( Z1, 18, 15, 20, 0)) { return 1; } + if (!check_indices (X1a, 19, 16, 20, 0)) { return 1; } + + if (!check_indices ( Y, 0, null_transform, 1, 3)) { return 1; } + if (!check_indices ( Y1, 1, 0, 4, 0)) { return 1; } + if (!check_indices ( Y2, 2, 0, 4, 1)) { return 1; } + if (!check_indices ( Y3, 3, 0, 5, 0)) { return 1; } + if (!check_indices (Y2a, 4, 2, 5, 0)) { return 1; } + + // Delete the hierarchy directly as setparent isn't fully tested + Hierarchy_Delete (root->hierarchy); + Hierarchy_Delete (Y->hierarchy); + + return 0; +} + +static int +check_vector (const transform_t *transform, + vec4f_t (*func) (const transform_t *t), + vec4f_t expect, const char *msg) +{ + vec4f_t res = func(transform); + if (!vec4_equal (res, expect)) { + printf ("%s %s: expected "VEC4F_FMT" got "VEC4F_FMT"\n", + Transform_GetName (transform), msg, + VEC4_EXP (expect), VEC4_EXP (res)); + return 0; + } + return 1; +} + +static int +test_frames (void) +{ + transform_t *root = Transform_NewNamed (0, "root"); + transform_t *A = Transform_NewNamed (root, "A"); + transform_t *B = Transform_NewNamed (root, "B"); + transform_t *A1 = Transform_NewNamed (A, "A1"); + transform_t *B1 = Transform_NewNamed (B, "B1"); + + Transform_SetLocalPosition (root, (vec4f_t) { 0, 0, 1, 1 }); + Transform_SetLocalPosition (A, (vec4f_t) { 1, 0, 0, 1 }); + Transform_SetLocalRotation (A, (vec4f_t) { 0.5, 0.5, 0.5, 0.5 }); + Transform_SetLocalPosition (B, (vec4f_t) { 0, 1, 0, 1 }); + Transform_SetLocalRotation (B, (vec4f_t) { 0.5, -0.5, 0.5, 0.5 }); + Transform_SetLocalPosition (A1, (vec4f_t) { 1, 0, 0, 1 }); + Transform_SetLocalRotation (A1, (vec4f_t) { -0.5, -0.5, -0.5, 0.5 }); + Transform_SetLocalPosition (B1, (vec4f_t) { 0, 1, 0, 1 }); + Transform_SetLocalRotation (B1, (vec4f_t) { -0.5, 0.5, -0.5, 0.5 }); + + hierarchy_t *h = root->hierarchy; + for (size_t i = 0; i < h->transform.size; i++) { + mat4f_t res; + mmulf (res, h->localMatrix.a[i], h->localInverse.a[i]); + if (!mat4_equal (res, identity)) { + printf ("%s: localInverse not inverse of localMatrix\n", + h->name.a[i]); + printf ("l: " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 0)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 1)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 2)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 3)); + printf ("i: " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 0)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 1)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 2)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 3)); + printf ("r: " VEC4F_FMT "\n", MAT4_ROW(res, 0)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(res, 1)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(res, 2)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(res, 3)); + return 1; + } + puts (h->name.a[i]); + printf ("l: " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 0)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 1)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 2)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->localMatrix.a[i], 3)); + printf ("i: " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 0)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 1)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 2)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->localInverse.a[i], 3)); + } + for (size_t i = 0; i < h->transform.size; i++) { + mat4f_t res; + mmulf (res, h->worldMatrix.a[i], h->worldInverse.a[i]); + if (!mat4_equal (res, identity)) { + printf ("%s: worldInverse not inverse of worldMatrix\n", + h->name.a[i]); + printf ("l: " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 0)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 1)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 2)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 3)); + printf ("i: " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 0)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 1)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 2)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 3)); + printf ("r: " VEC4F_FMT "\n", MAT4_ROW(res, 0)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(res, 1)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(res, 2)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(res, 3)); + return 1; + } + puts (h->name.a[i]); + printf ("l: " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 0)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 1)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 2)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->worldMatrix.a[i], 3)); + printf ("i: " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 0)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 1)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 2)); + printf (" " VEC4F_FMT "\n", MAT4_ROW(h->worldInverse.a[i], 3)); + } + + if (!check_vector (root, Transform_GetLocalPosition, + (vec4f_t) { 0, 0, 1, 1 }, "local position")) { + return 1; + } + if (!check_vector (root, Transform_GetWorldPosition, + (vec4f_t) { 0, 0, 1, 1 }, "world position")) { + return 1; + } + if (!check_vector (root, Transform_Right, (vec4f_t) { 1, 0, 0, 0 }, + "right")) { + return 1; + } + if (!check_vector (root, Transform_Forward, (vec4f_t) { 0, 1, 0, 0 }, + "right")) { + return 1; + } + if (!check_vector (root, Transform_Up, (vec4f_t) { 0, 0, 1, 0 }, + "up")) { + return 1; + } + + if (!check_vector (A, Transform_GetLocalPosition, (vec4f_t) { 1, 0, 0, 1 }, + "local position")) { + return 1; + } + if (!check_vector (A, Transform_GetWorldPosition, (vec4f_t) { 1, 0, 1, 1 }, + "world position")) { + return 1; + } + if (!check_vector (A, Transform_Right, (vec4f_t) { 0, 1, 0, 0 }, + "right")) { + return 1; + } + if (!check_vector (A, Transform_Forward, (vec4f_t) { 0, 0, 1, 0 }, + "forward")) { + return 1; + } + if (!check_vector (A, Transform_Up, (vec4f_t) { 1, 0, 0, 0 }, + "up")) { + return 1; + } + if (!check_vector (A1, Transform_GetLocalPosition, (vec4f_t) { 1, 0, 0, 1 }, + "local position")) { + return 1; + } + if (!check_vector (A1, Transform_GetWorldPosition, (vec4f_t) { 1, 1, 1, 1 }, + "world position")) { + return 1; + } + if (!check_vector (A1, Transform_Right, (vec4f_t) { 1, 0, 0, 0 }, + "right")) { + return 1; + } + if (!check_vector (A1, Transform_Forward, (vec4f_t) { 0, 1, 0, 0 }, + "forward")) { + return 1; + } + if (!check_vector (A1, Transform_Up, (vec4f_t) { 0, 0, 1, 0 }, + "up")) { + return 1; + } + + if (!check_vector (B, Transform_GetLocalPosition, (vec4f_t) { 0, 1, 0, 1 }, + "local position")) { + return 1; + } + if (!check_vector (B, Transform_GetWorldPosition, (vec4f_t) { 0, 1, 1, 1 }, + "world position")) { + return 1; + } + if (!check_vector (B, Transform_Right, (vec4f_t) { 0, 0, 1, 0 }, + "right")) { + return 1; + } + if (!check_vector (B, Transform_Forward, (vec4f_t) {-1, 0, 0, 0 }, + "forward")) { + return 1; + } + if (!check_vector (B, Transform_Up, (vec4f_t) { 0,-1, 0, 0 }, + "up")) { + return 1; + } + if (!check_vector (B1, Transform_GetLocalPosition, (vec4f_t) { 0, 1, 0, 1 }, + "local position")) { + return 1; + } + if (!check_vector (B1, Transform_GetWorldPosition, (vec4f_t) {-1, 1, 1, 1 }, + "world position")) { + return 1; + } + if (!check_vector (B1, Transform_Right, (vec4f_t) { 1, 0, 0, 0 }, + "right")) { + return 1; + } + if (!check_vector (B1, Transform_Forward, (vec4f_t) { 0, 1, 0, 0 }, + "forward")) { + return 1; + } + if (!check_vector (B1, Transform_Up, (vec4f_t) { 0, 0, 1, 0 }, + "up")) { + return 1; + } + + return 0; +} + +int +main (void) +{ + if (test_single_transform ()) { return 1; } + if (test_parent_child_init ()) { return 1; } + if (test_parent_child_setparent ()) { return 1; } + if (test_build_hierarchy ()) { return 1; } + if (test_build_hierarchy2 ()) { return 1; } + if (test_frames ()) { return 1; } + + return 0; +} diff --git a/libs/entity/transform.c b/libs/entity/transform.c new file mode 100644 index 000000000..18382e56c --- /dev/null +++ b/libs/entity/transform.c @@ -0,0 +1,317 @@ +/* + transform.c + + General transform handling + + Copyright (C) 2021 Bill Currke + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_STRING_H +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif + +#include "QF/entity.h" +#include "QF/render.h" + +transform_t * +Transform_New (transform_t *parent) +{ + transform_t *transform = malloc (sizeof (transform_t)); + + if (parent) { + transform->hierarchy = parent->hierarchy; + transform->index = Hierarchy_InsertHierarchy (parent->hierarchy, 0, + parent->index, 0); + } else { + transform->hierarchy = Hierarchy_New (16, 1);//FIXME should be config + transform->index = 0; + } + transform->hierarchy->transform.a[transform->index] = transform; + Hierarchy_UpdateMatrices (transform->hierarchy); + return transform; +} + +void +Transform_Delete (transform_t *transform) +{ + if (transform->index != 0) { + // The transform is not the root, so pull it out of its current + // hierarchy so deleting it is easier + Transform_SetParent (transform, 0); + } + Hierarchy_Delete (transform->hierarchy); +} + +transform_t * +Transform_NewNamed (transform_t *parent, const char *name) +{ + transform_t *transform = Transform_New (parent); + Transform_SetName (transform, name); + return transform; +} + +uint32_t +Transform_ChildCount (const transform_t *transform) +{ + hierarchy_t *h = transform->hierarchy; + return h->childCount.a[transform->index]; +} + +transform_t * +Transform_GetChild (const transform_t *transform, uint32_t childIndex) +{ + hierarchy_t *h = transform->hierarchy; + if (childIndex >= h->childCount.a[transform->index]) { + return 0; + } + return h->transform.a[h->childIndex.a[transform->index] + childIndex]; +} + +void +Transform_SetParent (transform_t *transform, transform_t *parent) +{ + if (parent) { + hierarchy_t *hierarchy = transform->hierarchy; + uint32_t index = transform->index; + Hierarchy_InsertHierarchy (parent->hierarchy, hierarchy, + parent->index, index); + Hierarchy_RemoveHierarchy (hierarchy, index); + if (!hierarchy->name.size) { + Hierarchy_Delete (hierarchy); + } + } else { + // null parent -> make transform root + if (!transform->index) { + // already root + return; + } + hierarchy_t *hierarchy = transform->hierarchy; + uint32_t index = transform->index; + + hierarchy_t *new_hierarchy = Hierarchy_New (16, 0); + Hierarchy_InsertHierarchy (new_hierarchy, hierarchy, null_transform, + index); + Hierarchy_RemoveHierarchy (hierarchy, index); + } +} + +transform_t * +Transform_GetParent (const transform_t *transform) +{ + if (transform->index == 0) { + return 0; + } + hierarchy_t *h = transform->hierarchy; + return h->transform.a[h->parentIndex.a[transform->index]]; +} + +void +Transform_SetName (transform_t *transform, const char *name) +{ + hierarchy_t *h = transform->hierarchy; + //FIXME create a string pool (similar to qfcc's, or even move that to util) + if (h->name.a[transform->index]) { + free (h->name.a[transform->index]); + } + h->name.a[transform->index] = strdup (name); +} + +const char * +Transform_GetName (const transform_t *transform) +{ + hierarchy_t *h = transform->hierarchy; + return h->name.a[transform->index]; +} + +void +Transform_SetTag (transform_t *transform, uint32_t tag) +{ + hierarchy_t *h = transform->hierarchy; + h->tag.a[transform->index] = tag; +} + +uint32_t +Transform_GetTag (const transform_t *transform) +{ + hierarchy_t *h = transform->hierarchy; + return h->tag.a[transform->index]; +} + +void +Transform_GetLocalMatrix (const transform_t *transform, mat4f_t mat) +{ + hierarchy_t *h = transform->hierarchy; + memcpy (mat, h->localMatrix.a[transform->index], sizeof (mat4f_t)); +} + +void +Transform_GetLocalInverse (const transform_t *transform, mat4f_t mat) +{ + hierarchy_t *h = transform->hierarchy; + memcpy (mat, h->localInverse.a[transform->index], sizeof (mat4f_t)); +} + +void +Transform_GetWorldMatrix (const transform_t *transform, mat4f_t mat) +{ + hierarchy_t *h = transform->hierarchy; + memcpy (mat, h->worldMatrix.a[transform->index], sizeof (mat4f_t)); +} + +void +Transform_GetWorldInverse (const transform_t *transform, mat4f_t mat) +{ + hierarchy_t *h = transform->hierarchy; + memcpy (mat, h->worldInverse.a[transform->index], sizeof (mat4f_t)); +} + +vec4f_t +Transform_GetLocalPosition (const transform_t *transform) +{ + hierarchy_t *h = transform->hierarchy; + return h->localMatrix.a[transform->index][3]; +} + +void +Transform_SetLocalPosition (transform_t *transform, vec4f_t position) +{ + hierarchy_t *h = transform->hierarchy; + h->localMatrix.a[transform->index][3] = position; + h->modified.a[transform->index] = 1; + Hierarchy_UpdateMatrices (h); +} + +vec4f_t +Transform_GetLocalRotation (const transform_t *transform) +{ + hierarchy_t *h = transform->hierarchy; + return h->localRotation.a[transform->index]; +} + +void +Transform_SetLocalRotation (transform_t *transform, vec4f_t rotation) +{ + hierarchy_t *h = transform->hierarchy; + vec4f_t scale = h->localScale.a[transform->index]; + + mat4f_t mat; + mat4fquat (mat, rotation); + + h->localMatrix.a[transform->index][0] = mat[0] * scale[0]; + h->localMatrix.a[transform->index][1] = mat[1] * scale[1]; + h->localMatrix.a[transform->index][2] = mat[2] * scale[2]; + h->modified.a[transform->index] = 1; + Hierarchy_UpdateMatrices (h); +} + +vec4f_t +Transform_GetLocalScale (const transform_t *transform) +{ + hierarchy_t *h = transform->hierarchy; + return h->localScale.a[transform->index]; +} + +void +Transform_SetLocalScale (transform_t *transform, vec4f_t scale) +{ + hierarchy_t *h = transform->hierarchy; + vec4f_t rotation = h->localRotation.a[transform->index]; + + mat4f_t mat; + mat4fquat (mat, rotation); + + h->localMatrix.a[transform->index][0] = mat[0] * scale[0]; + h->localMatrix.a[transform->index][1] = mat[1] * scale[1]; + h->localMatrix.a[transform->index][2] = mat[2] * scale[2]; + h->modified.a[transform->index] = 1; + Hierarchy_UpdateMatrices (h); +} + +vec4f_t +Transform_GetWorldPosition (const transform_t *transform) +{ + hierarchy_t *h = transform->hierarchy; + return h->worldMatrix.a[transform->index][3]; +} + +void +Transform_SetWorldPosition (transform_t *transform, vec4f_t position) +{ + if (transform->index) { + hierarchy_t *h = transform->hierarchy; + uint32_t parent = h->parentIndex.a[transform->index]; + position = mvmulf (h->worldInverse.a[parent], position); + } + Transform_SetLocalPosition (transform, position); +} + +vec4f_t +Transform_GetWorldRotation (const transform_t *transform) +{ + hierarchy_t *h = transform->hierarchy; + return h->worldRotation.a[transform->index]; +} + +void +Transform_SetWorldRotation (transform_t *transform, vec4f_t rotation) +{ + if (transform->index) { + hierarchy_t *h = transform->hierarchy; + uint32_t parent = h->parentIndex.a[transform->index]; + rotation = qmulf (qconjf (h->worldRotation.a[parent]), rotation); + } + Transform_SetLocalRotation (transform, rotation); +} + +vec4f_t +Transform_GetWorldScale (const transform_t *transform) +{ + hierarchy_t *h = transform->hierarchy; + return h->worldScale.a[transform->index]; +} + +vec4f_t +Transform_Forward (const transform_t *transform) +{ + hierarchy_t *h = transform->hierarchy; + return h->worldMatrix.a[transform->index][1]; +} + +vec4f_t +Transform_Right (const transform_t *transform) +{ + hierarchy_t *h = transform->hierarchy; + return h->worldMatrix.a[transform->index][0]; +} + +vec4f_t +Transform_Up (const transform_t *transform) +{ + hierarchy_t *h = transform->hierarchy; + return h->worldMatrix.a[transform->index][2]; +}