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]; +}