Compare commits
8 Commits
0b570b5fd6
...
46a5403511
| Author | SHA1 | Date | |
|---|---|---|---|
| 46a5403511 | |||
| 87bfb92576 | |||
| c7a3e5601c | |||
| 4009130f6e | |||
| c91243f6e9 | |||
| 0e3871ac26 | |||
| 55baafec8a | |||
| b5a66993ca |
@@ -69,6 +69,7 @@ add_subdirectory(error)
|
||||
add_subdirectory(event)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(physics)
|
||||
add_subdirectory(scene)
|
||||
add_subdirectory(script)
|
||||
add_subdirectory(time)
|
||||
|
||||
@@ -85,7 +85,7 @@ void capsuleBuffer(
|
||||
const float_t lxz1 = radius * cosf(phi1);
|
||||
const float_t lxz2 = radius * cosf(phi2);
|
||||
|
||||
/* UV: top cap occupies v in [0.5 + halfHeightFrac .. 1.0] — we use a
|
||||
/* UV: top cap occupies v in [0.5 + halfHeightFrac .. 1.0]: we use a
|
||||
* simple per-band normalisation against the full height. */
|
||||
const float_t v1 = 1.0f - (float_t)i / (float_t)(2 * capRings + 1);
|
||||
const float_t v2 = 1.0f - (float_t)(i + 1) / (float_t)(2 * capRings + 1);
|
||||
|
||||
@@ -62,7 +62,7 @@ void cubeBuffer(
|
||||
const float_t x0 = min[0], y0 = min[1], z0 = min[2];
|
||||
const float_t x1 = max[0], y1 = max[1], z1 = max[2];
|
||||
|
||||
// Front face (+Z normal) — CCW when viewed from +Z
|
||||
// Front face (+Z normal): CCW when viewed from +Z
|
||||
CUBE_VERT( 0, x0, y0, z1, 0.0f, 0.0f);
|
||||
CUBE_VERT( 1, x1, y0, z1, 1.0f, 0.0f);
|
||||
CUBE_VERT( 2, x1, y1, z1, 1.0f, 1.0f);
|
||||
@@ -70,7 +70,7 @@ void cubeBuffer(
|
||||
CUBE_VERT( 4, x1, y1, z1, 1.0f, 1.0f);
|
||||
CUBE_VERT( 5, x0, y1, z1, 0.0f, 1.0f);
|
||||
|
||||
// Back face (-Z normal) — CCW when viewed from -Z
|
||||
// Back face (-Z normal): CCW when viewed from -Z
|
||||
CUBE_VERT( 6, x1, y0, z0, 0.0f, 0.0f);
|
||||
CUBE_VERT( 7, x0, y0, z0, 1.0f, 0.0f);
|
||||
CUBE_VERT( 8, x0, y1, z0, 1.0f, 1.0f);
|
||||
@@ -78,7 +78,7 @@ void cubeBuffer(
|
||||
CUBE_VERT(10, x0, y1, z0, 1.0f, 1.0f);
|
||||
CUBE_VERT(11, x1, y1, z0, 0.0f, 1.0f);
|
||||
|
||||
// Right face (+X normal) — CCW when viewed from +X
|
||||
// Right face (+X normal): CCW when viewed from +X
|
||||
CUBE_VERT(12, x1, y0, z1, 0.0f, 0.0f);
|
||||
CUBE_VERT(13, x1, y0, z0, 1.0f, 0.0f);
|
||||
CUBE_VERT(14, x1, y1, z0, 1.0f, 1.0f);
|
||||
@@ -86,7 +86,7 @@ void cubeBuffer(
|
||||
CUBE_VERT(16, x1, y1, z0, 1.0f, 1.0f);
|
||||
CUBE_VERT(17, x1, y1, z1, 0.0f, 1.0f);
|
||||
|
||||
// Left face (-X normal) — CCW when viewed from -X
|
||||
// Left face (-X normal): CCW when viewed from -X
|
||||
CUBE_VERT(18, x0, y0, z0, 0.0f, 0.0f);
|
||||
CUBE_VERT(19, x0, y0, z1, 1.0f, 0.0f);
|
||||
CUBE_VERT(20, x0, y1, z1, 1.0f, 1.0f);
|
||||
@@ -94,7 +94,7 @@ void cubeBuffer(
|
||||
CUBE_VERT(22, x0, y1, z1, 1.0f, 1.0f);
|
||||
CUBE_VERT(23, x0, y1, z0, 0.0f, 1.0f);
|
||||
|
||||
// Top face (+Y normal) — CCW when viewed from +Y
|
||||
// Top face (+Y normal): CCW when viewed from +Y
|
||||
CUBE_VERT(24, x0, y1, z1, 0.0f, 0.0f);
|
||||
CUBE_VERT(25, x1, y1, z1, 1.0f, 0.0f);
|
||||
CUBE_VERT(26, x1, y1, z0, 1.0f, 1.0f);
|
||||
@@ -102,7 +102,7 @@ void cubeBuffer(
|
||||
CUBE_VERT(28, x1, y1, z0, 1.0f, 1.0f);
|
||||
CUBE_VERT(29, x0, y1, z0, 0.0f, 1.0f);
|
||||
|
||||
// Bottom face (-Y normal) — CCW when viewed from -Y
|
||||
// Bottom face (-Y normal): CCW when viewed from -Y
|
||||
CUBE_VERT(30, x0, y0, z0, 0.0f, 0.0f);
|
||||
CUBE_VERT(31, x1, y0, z0, 1.0f, 0.0f);
|
||||
CUBE_VERT(32, x1, y0, z1, 1.0f, 1.0f);
|
||||
|
||||
@@ -36,9 +36,9 @@ errorret_t planeInit();
|
||||
*
|
||||
* The min/max corners fully describe the plane in 3D space. The axis enum
|
||||
* controls which dimension is treated as the "depth" (normal) axis:
|
||||
* PLANE_AXIS_XY — spans X and Y, depth from min[2]/max[2] (uses min[2])
|
||||
* PLANE_AXIS_XZ — spans X and Z, depth from min[1]/max[1] (uses min[1])
|
||||
* PLANE_AXIS_YZ — spans Y and Z, depth from min[0]/max[0] (uses min[0])
|
||||
* PLANE_AXIS_XY: spans X and Y, depth from min[2]/max[2] (uses min[2])
|
||||
* PLANE_AXIS_XZ: spans X and Z, depth from min[1]/max[1] (uses min[1])
|
||||
* PLANE_AXIS_YZ: spans Y and Z, depth from min[0]/max[0] (uses min[0])
|
||||
*
|
||||
* @param vertices Vertex array to write into (must hold PLANE_VERTEX_COUNT).
|
||||
* @param axis Which axis the plane's normal points along.
|
||||
|
||||
+57
-50
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
@@ -17,20 +17,17 @@
|
||||
#include "script/scriptmanager.h"
|
||||
#include "assert/assert.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "game/game.h"
|
||||
|
||||
#include "display/mesh/quad.h"
|
||||
#include "display/mesh/capsule.h"
|
||||
#include "asset/loader/display/assetmeshloader.h"
|
||||
#include "physics/physicsmanager.h"
|
||||
#include "display/mesh/cube.h"
|
||||
#include "display/mesh/plane.h"
|
||||
|
||||
engine_t ENGINE;
|
||||
entityid_t ent1;
|
||||
componentid_t ent1Pos;
|
||||
componentid_t ent1Mesh;
|
||||
componentid_t ent1Mat;
|
||||
|
||||
mesh_t loadedMesh;
|
||||
meshvertex_t *loadedVertices;
|
||||
/* Kept module-level only because engineUpdate needs them for the reset. */
|
||||
static entityid_t phBoxEnt;
|
||||
static componentid_t phBoxPhys;
|
||||
|
||||
errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
memoryZero(&ENGINE, sizeof(engine_t));
|
||||
@@ -48,46 +45,57 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
errorChain(uiInit());
|
||||
errorChain(sceneInit());
|
||||
entityManagerInit();
|
||||
physicsManagerInit();
|
||||
errorChain(gameInit());
|
||||
|
||||
// FOF
|
||||
/* ---- Camera ---- */
|
||||
entityid_t cam = entityManagerAdd();
|
||||
componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION);
|
||||
float_t distance = 1.5f;
|
||||
float_t up = distance / 2.0f;
|
||||
float_t distance = 6.0f;
|
||||
entityPositionLookAt(
|
||||
cam,
|
||||
camPos,
|
||||
(vec3){ 0.0f, up, 0.0f },
|
||||
cam, camPos,
|
||||
(vec3){ 0.0f, 1.0f, 0.0f },
|
||||
(vec3){ distance, distance + up, distance }
|
||||
(vec3){ 0.0f, 1.0f, 0.0f },
|
||||
(vec3){ distance, distance, distance }
|
||||
);
|
||||
componentid_t camCam = entityAddComponent(cam, COMPONENT_TYPE_CAMERA);
|
||||
entityCameraSetZFar(cam, camCam, distance * 5.0f);
|
||||
entityCameraSetZFar(cam, camCam, distance * 6.0f);
|
||||
|
||||
ent1 = entityManagerAdd();
|
||||
ent1Pos = entityAddComponent(ent1, COMPONENT_TYPE_POSITION);
|
||||
ent1Mesh = entityAddComponent(ent1, COMPONENT_TYPE_MESH);
|
||||
ent1Mat = entityAddComponent(ent1, COMPONENT_TYPE_MATERIAL);
|
||||
/* ---- Static floor (visual + physics) ---- */
|
||||
entityid_t floorEnt = entityManagerAdd();
|
||||
componentid_t floorPos = entityAddComponent(floorEnt, COMPONENT_TYPE_POSITION);
|
||||
componentid_t floorMesh = entityAddComponent(floorEnt, COMPONENT_TYPE_MESH);
|
||||
componentid_t floorMat = entityAddComponent(floorEnt, COMPONENT_TYPE_MATERIAL);
|
||||
componentid_t floorPhys = entityAddComponent(floorEnt, COMPONENT_TYPE_PHYSICS);
|
||||
|
||||
errorChain(assetMeshLoad(
|
||||
"test/Mei.stl",
|
||||
&loadedMesh,
|
||||
&loadedVertices,
|
||||
MESH_INPUT_AXIS_Y_UP
|
||||
));
|
||||
entityMeshSetMesh(ent1, ent1Mesh, &loadedMesh);
|
||||
entityPositionSetPosition(floorEnt, floorPos, (vec3){ -5.0f, 0.0f, -5.0f });
|
||||
entityPositionSetScale(floorEnt, floorPos, (vec3){ 10.0f, 1.0f, 10.0f });
|
||||
entityMeshSetMesh(floorEnt, floorMesh, &PLANE_MESH_SIMPLE);
|
||||
entityMaterialGetShaderMaterial(floorEnt, floorMat)->unlit.color = COLOR_GREEN;
|
||||
|
||||
vec3 min, max;
|
||||
meshGetBounds(&loadedMesh, min, max);
|
||||
printf("Mesh bounds: min(%f, %f, %f), max(%f, %f, %f)\n", min[0], min[1], min[2], max[0], max[1], max[2]);
|
||||
entityphysics_t *floorPhysData = entityPhysicsGet(floorEnt, floorPhys);
|
||||
floorPhysData->type = PHYSICS_BODY_STATIC;
|
||||
floorPhysData->shape.type = PHYSICS_SHAPE_PLANE;
|
||||
floorPhysData->shape.data.plane.normal[0] = 0.0f;
|
||||
floorPhysData->shape.data.plane.normal[1] = 1.0f;
|
||||
floorPhysData->shape.data.plane.normal[2] = 0.0f;
|
||||
floorPhysData->shape.data.plane.distance = 0.0f;
|
||||
|
||||
shadermaterial_t *mat = entityMaterialGetShaderMaterial(ent1, ent1Mat);
|
||||
mat->unlit.color = COLOR_WHITE;
|
||||
/* ---- Dynamic box ---- */
|
||||
phBoxEnt = entityManagerAdd();
|
||||
componentid_t boxPos = entityAddComponent(phBoxEnt, COMPONENT_TYPE_POSITION);
|
||||
componentid_t boxMesh = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MESH);
|
||||
componentid_t boxMat = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MATERIAL);
|
||||
phBoxPhys = entityAddComponent(phBoxEnt, COMPONENT_TYPE_PHYSICS);
|
||||
|
||||
// EOF
|
||||
entityMeshSetMesh(phBoxEnt, boxMesh, &CUBE_MESH_SIMPLE);
|
||||
entityMaterialGetShaderMaterial(phBoxEnt, boxMat)->unlit.color = COLOR_RED;
|
||||
|
||||
// Run the init script.
|
||||
/* Physics position lives in the POSITION component. CUBE_MESH_SIMPLE is
|
||||
* centred at origin (-0.5..0.5), so entity position == physics centre. */
|
||||
entityPositionSetPosition(phBoxEnt, boxPos, (vec3){ 0.0f, 4.0f, 0.0f });
|
||||
|
||||
/* Run the init script. */
|
||||
scriptcontext_t ctx;
|
||||
errorChain(scriptContextInit(&ctx));
|
||||
errorChain(scriptContextExecFile(&ctx, "init.lua"));
|
||||
@@ -100,17 +108,19 @@ errorret_t engineUpdate(void) {
|
||||
timeUpdate();
|
||||
inputUpdate();
|
||||
|
||||
vec3 rotation;
|
||||
entityPositionGetRotation(ent1, ent1Pos, rotation);
|
||||
#if DUSK_TIME_DYNAMIC
|
||||
rotation[1] += 1.0f * TIME.dynamicDelta;
|
||||
#else
|
||||
rotation[1] += 1.0f * TIME.delta;
|
||||
#endif
|
||||
entityPositionSetRotation(ent1, ent1Pos, rotation);
|
||||
|
||||
uiUpdate();
|
||||
errorChain(sceneUpdate());
|
||||
|
||||
/* Reset the box to its start position on demand. */
|
||||
if(inputIsDown(INPUT_ACTION_ACCEPT)) {
|
||||
componentid_t posComp = entityGetComponent(phBoxEnt, COMPONENT_TYPE_POSITION);
|
||||
entityPositionSetPosition(phBoxEnt, posComp, (vec3){ 0.0f, 4.0f, 0.0f });
|
||||
entityPhysicsSetVelocity(phBoxEnt, phBoxPhys, (vec3){ 0.0f, 0.0f, 0.0f });
|
||||
}
|
||||
|
||||
/* Step physics: positions are updated directly on POSITION components. */
|
||||
physicsManagerUpdate();
|
||||
|
||||
errorChain(gameUpdate());
|
||||
errorChain(displayUpdate());
|
||||
|
||||
@@ -124,9 +134,6 @@ void engineExit(void) {
|
||||
}
|
||||
|
||||
errorret_t engineDispose(void) {
|
||||
errorChain(meshDispose(&loadedMesh));
|
||||
memoryFree(loadedVertices);
|
||||
|
||||
sceneDispose();
|
||||
errorChain(gameDispose());
|
||||
entityManagerDispose();
|
||||
@@ -135,4 +142,4 @@ errorret_t engineDispose(void) {
|
||||
errorChain(displayDispose());
|
||||
errorChain(assetDispose());
|
||||
errorOk();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,4 +3,5 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(physics)
|
||||
@@ -115,6 +115,15 @@ void entityPositionSetScale(
|
||||
entityPositionRebuild(pos);
|
||||
}
|
||||
|
||||
entityposition_t *entityPositionGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
return componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
}
|
||||
|
||||
void entityPositionRebuild(entityposition_t *pos) {
|
||||
glm_mat4_identity(pos->transform);
|
||||
glm_translate(pos->transform, pos->position);
|
||||
|
||||
@@ -135,6 +135,20 @@ void entityPositionSetScale(
|
||||
vec3 scale
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a direct pointer to the entity position component data.
|
||||
* After modifying position, rotation, or scale directly, call
|
||||
* entityPositionRebuild() to update the transform matrix.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @return Pointer to the entity position component data.
|
||||
*/
|
||||
entityposition_t *entityPositionGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Internal function to rebuild the transform matrix of the entity position
|
||||
* component based on the current position, rotation, and scale.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
entityphysics.c
|
||||
)
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entityphysics.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "physics/physicsmanager.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
void entityPhysicsInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
|
||||
memoryZero(phys, sizeof(entityphysics_t));
|
||||
|
||||
// Default to cube
|
||||
phys->type = PHYSICS_BODY_DYNAMIC;
|
||||
phys->shape.type = PHYSICS_SHAPE_CUBE;
|
||||
phys->shape.data.cube.halfExtents[0] = 0.5f;
|
||||
phys->shape.data.cube.halfExtents[1] = 0.5f;
|
||||
phys->shape.data.cube.halfExtents[2] = 0.5f;
|
||||
phys->gravityScale = 1.0f;
|
||||
phys->onGround = false;
|
||||
}
|
||||
|
||||
entityphysics_t *entityPhysicsGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
return componentGetData(entityId, componentId, COMPONENT_TYPE_PHYSICS);
|
||||
}
|
||||
|
||||
void entityPhysicsSetShape(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const physicsshape_t shape
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
phys->shape = shape;
|
||||
// TODO: Do I need to reset the state for ground/active?
|
||||
}
|
||||
|
||||
physicsshape_t entityPhysicsGetShape(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
return phys->shape;
|
||||
}
|
||||
|
||||
void entityPhysicsGetVelocity(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 dest
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
|
||||
glm_vec3_copy(phys->velocity, dest);
|
||||
}
|
||||
|
||||
void entityPhysicsSetVelocity(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 velocity
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
glm_vec3_copy(velocity, phys->velocity);
|
||||
}
|
||||
|
||||
void entityPhysicsApplyImpulse(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 impulse
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
|
||||
if(phys->type == PHYSICS_BODY_STATIC) return;
|
||||
glm_vec3_add(phys->velocity, impulse, phys->velocity);
|
||||
}
|
||||
|
||||
bool_t entityPhysicsIsOnGround(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
return phys->onGround;
|
||||
}
|
||||
|
||||
void entityPhysicsDispose(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity/entitybase.h"
|
||||
#include "physics/physicsshape.h"
|
||||
#include "physics/physicsbodytype.h"
|
||||
|
||||
typedef struct {
|
||||
physicsbodytype_t type;
|
||||
physicsshape_t shape;
|
||||
vec3 velocity;
|
||||
float_t gravityScale;
|
||||
bool_t onGround;
|
||||
} entityphysics_t;
|
||||
|
||||
/**
|
||||
* Initializes the physics component: allocates a body in PHYSICS_WORLD.
|
||||
* Asserts if the world body limit is reached.
|
||||
*/
|
||||
void entityPhysicsInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the underlying physics structure (temporarily) for the given entity.
|
||||
* This is really just intended for doing operations faster than using the
|
||||
* getters and setters, but it is preferred that you use those.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @return The physics component data for the given entity and component ID.
|
||||
*/
|
||||
entityphysics_t *entityPhysicsGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the shape of the entity's physics body. This will not reset the body
|
||||
* state, so if you change from a cube to a sphere, it will keep the same
|
||||
* velocity and onGround state.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param shape The new shape to set on the physics body.
|
||||
*/
|
||||
void entityPhysicsSetShape(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const physicsshape_t shape
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the shape of the entity's physics body.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @return The shape of the physics body.
|
||||
*/
|
||||
physicsshape_t entityPhysicsGetShape(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the velocity of the entity's physics body.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param dest The destination vec3 to write the velocity to.
|
||||
*/
|
||||
void entityPhysicsGetVelocity(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 dest
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the velocity of the entity's physics body. This is not an impulse, so
|
||||
* it will be affected by mass and drag.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param velocity The new velocity to set on the physics body.
|
||||
*/
|
||||
void entityPhysicsSetVelocity(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 velocity
|
||||
);
|
||||
|
||||
/**
|
||||
* Applies an impulse to the entity's physics body. This is an immediate
|
||||
* velocity change that is not affected by mass or drag. No-op on STATIC bodies.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param impulse The impulse to apply to the physics body.
|
||||
*/
|
||||
void entityPhysicsApplyImpulse(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 impulse
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns true if the entity's physics body rested on a surface during the last
|
||||
* step or move.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @return True if the body is on the ground, false otherwise.
|
||||
*/
|
||||
bool_t entityPhysicsIsOnGround(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Releases the body slot back to PHYSICS_WORLD. Called automatically when
|
||||
* the component is disposed via the component system.
|
||||
*/
|
||||
void entityPhysicsDispose(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
@@ -9,8 +9,10 @@
|
||||
#include "entity/component/display/entitycamera.h"
|
||||
#include "entity/component/display/entitymesh.h"
|
||||
#include "entity/component/display/entitymaterial.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
|
||||
X(POSITION, entityposition_t, position, entityPositionInit, NULL)
|
||||
X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL)
|
||||
X(MESH, entitymesh_t, mesh, entityMeshInit, NULL)
|
||||
X(MATERIAL, entitymaterial_t, material, entityMaterialInit, NULL)
|
||||
X(MATERIAL, entitymaterial_t, material, entityMaterialInit, NULL)
|
||||
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose)
|
||||
@@ -34,7 +34,8 @@ componentid_t entityAddComponent(
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the ID of the component of the given type on the entity with the given ID.
|
||||
* Gets the ID of the component of the given type on the entity with the given
|
||||
* ID, or 0xFF if the entity lacks the component.
|
||||
*
|
||||
* @param entityId The ID of the entity to get the component from.
|
||||
* @param type The type of the component to get.
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
#define ENTITY_COUNT_MAX 128
|
||||
#define ENTITY_COMPONENT_COUNT_MAX 24
|
||||
#define ENTITY_COUNT_MAX 64
|
||||
#define ENTITY_COMPONENT_COUNT_MAX 16
|
||||
|
||||
typedef uint8_t entityid_t;
|
||||
typedef uint8_t componentid_t;
|
||||
|
||||
@@ -17,6 +17,12 @@ void entityManagerInit(void) {
|
||||
ENTITY_MANAGER.entitiesWithComponent, 0xFF,
|
||||
sizeof(entityid_t) * COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX
|
||||
);
|
||||
|
||||
printf(
|
||||
"Entity Manager size is currently: %zu bytes (%.2f KB)\n",
|
||||
sizeof(entitymanager_t),
|
||||
sizeof(entitymanager_t) / 1024.0f
|
||||
);
|
||||
}
|
||||
|
||||
entityid_t entityManagerAdd() {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
physicsmanager.c
|
||||
physicsworld.c
|
||||
physicstest.c
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
/**
|
||||
* Never moves. Acts as an immovable collision surface.
|
||||
*/
|
||||
PHYSICS_BODY_STATIC,
|
||||
|
||||
/**
|
||||
* Simulated by the world step: gravity, forces, and collision response.
|
||||
*/
|
||||
PHYSICS_BODY_DYNAMIC,
|
||||
|
||||
/**
|
||||
* Moved programmatically via physicsWorldMoveBody; collides but is not
|
||||
* driven by the simulation. Typical use: player character controller.
|
||||
*/
|
||||
PHYSICS_BODY_KINEMATIC
|
||||
} physicsbodytype_t;
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "physicsmanager.h"
|
||||
#include "time/time.h"
|
||||
|
||||
void physicsManagerInit(void) {
|
||||
physicsWorldInit();
|
||||
}
|
||||
|
||||
void physicsManagerUpdate() {
|
||||
#if DUSK_TIME_DYNAMIC
|
||||
if(TIME.dynamicUpdate) return; // Don't update on dynamic updates.
|
||||
#endif
|
||||
|
||||
physicsWorldStep(TIME.delta);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "physicsworld.h"
|
||||
|
||||
/**
|
||||
* Initializes the physics manager.
|
||||
*/
|
||||
void physicsManagerInit(void);
|
||||
|
||||
/**
|
||||
* Advances the physics simulation.
|
||||
*/
|
||||
void physicsManagerUpdate();
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
PHYSICS_SHAPE_CUBE,
|
||||
PHYSICS_SHAPE_SPHERE,
|
||||
PHYSICS_SHAPE_CAPSULE,
|
||||
PHYSICS_SHAPE_PLANE
|
||||
} physicshapetype_t;
|
||||
|
||||
typedef struct {
|
||||
vec3 halfExtents;
|
||||
} physicsshapecube_t;
|
||||
|
||||
typedef struct {
|
||||
float_t radius;
|
||||
} physicsshapesphere_t;
|
||||
|
||||
typedef struct {
|
||||
float_t radius;
|
||||
float_t halfHeight;
|
||||
} physicsshapecapsule_t;
|
||||
|
||||
typedef struct {
|
||||
vec3 normal;
|
||||
float_t distance;
|
||||
} physicsshapeplane_t;
|
||||
|
||||
typedef union {
|
||||
physicsshapecube_t cube;
|
||||
physicsshapesphere_t sphere;
|
||||
physicsshapecapsule_t capsule;
|
||||
physicsshapeplane_t plane;
|
||||
} physicsshapedata_t;
|
||||
|
||||
typedef struct {
|
||||
physicshapetype_t type;
|
||||
physicsshapedata_t data;
|
||||
} physicsshape_t;
|
||||
@@ -0,0 +1,402 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "physicstest.h"
|
||||
|
||||
bool physicsTestAabbVsAabb(
|
||||
const vec3 ac, const vec3 ah,
|
||||
const vec3 bc, const vec3 bh,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
float_t dx = ac[0] - bc[0];
|
||||
float_t dy = ac[1] - bc[1];
|
||||
float_t dz = ac[2] - bc[2];
|
||||
|
||||
float_t px = (ah[0] + bh[0]) - fabsf(dx);
|
||||
float_t py = (ah[1] + bh[1]) - fabsf(dy);
|
||||
float_t pz = (ah[2] + bh[2]) - fabsf(dz);
|
||||
|
||||
if(px <= 0.0f || py <= 0.0f || pz <= 0.0f) return false;
|
||||
|
||||
outNormal[0] = outNormal[1] = outNormal[2] = 0.0f;
|
||||
if(px < py && px < pz) {
|
||||
*outDepth = px;
|
||||
outNormal[0] = dx >= 0.0f ? 1.0f : -1.0f;
|
||||
} else if(py < pz) {
|
||||
*outDepth = py;
|
||||
outNormal[1] = dy >= 0.0f ? 1.0f : -1.0f;
|
||||
} else {
|
||||
*outDepth = pz;
|
||||
outNormal[2] = dz >= 0.0f ? 1.0f : -1.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool physicsTestSphereVsSphere(
|
||||
const vec3 ac, const float_t ar,
|
||||
const vec3 bc, const float_t br,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 diff;
|
||||
glm_vec3_sub((float_t *)ac, (float_t *)bc, diff);
|
||||
float_t dist2 = glm_vec3_norm2(diff);
|
||||
float_t sumR = ar + br;
|
||||
|
||||
if(dist2 >= sumR * sumR) return false;
|
||||
|
||||
float_t dist = sqrtf(dist2);
|
||||
*outDepth = sumR - dist;
|
||||
|
||||
if(dist > 1e-6f) {
|
||||
glm_vec3_scale(diff, 1.0f / dist, outNormal);
|
||||
} else {
|
||||
outNormal[0] = 0.0f;
|
||||
outNormal[1] = 1.0f;
|
||||
outNormal[2] = 0.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool physicsTestSphereVsAabb(
|
||||
const vec3 sc, const float_t sr,
|
||||
const vec3 ac, const vec3 ah,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 closest = {
|
||||
glm_clamp(sc[0], ac[0] - ah[0], ac[0] + ah[0]),
|
||||
glm_clamp(sc[1], ac[1] - ah[1], ac[1] + ah[1]),
|
||||
glm_clamp(sc[2], ac[2] - ah[2], ac[2] + ah[2])
|
||||
};
|
||||
|
||||
vec3 diff;
|
||||
glm_vec3_sub((float_t *)sc, closest, diff);
|
||||
float_t dist2 = glm_vec3_norm2(diff);
|
||||
|
||||
bool inside = (dist2 < 1e-10f);
|
||||
if(!inside && dist2 >= sr * sr) return false;
|
||||
|
||||
if(!inside) {
|
||||
float_t dist = sqrtf(dist2);
|
||||
*outDepth = sr - dist;
|
||||
glm_vec3_scale(diff, 1.0f / dist, outNormal);
|
||||
} else {
|
||||
float_t faces[6] = {
|
||||
(ac[0] + ah[0]) - sc[0],
|
||||
sc[0] - (ac[0] - ah[0]),
|
||||
(ac[1] + ah[1]) - sc[1],
|
||||
sc[1] - (ac[1] - ah[1]),
|
||||
(ac[2] + ah[2]) - sc[2],
|
||||
sc[2] - (ac[2] - ah[2])
|
||||
};
|
||||
const float_t normals[6][3] = {
|
||||
{1,0,0},{-1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}
|
||||
};
|
||||
int mi = 0;
|
||||
for(int k = 1; k < 6; k++) {
|
||||
if(faces[k] < faces[mi]) mi = k;
|
||||
}
|
||||
*outDepth = sr + faces[mi];
|
||||
outNormal[0] = normals[mi][0];
|
||||
outNormal[1] = normals[mi][1];
|
||||
outNormal[2] = normals[mi][2];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool physicsTestSphereVsPlane(
|
||||
const vec3 sc, const float_t sr,
|
||||
const vec3 pn, const float_t pd,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
float_t signedDist = glm_vec3_dot((float_t *)pn, (float_t *)sc) - pd;
|
||||
*outDepth = sr - signedDist;
|
||||
if(*outDepth <= 0.0f) return false;
|
||||
glm_vec3_copy((float_t *)pn, outNormal);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool physicsTestAabbVsPlane(
|
||||
const vec3 ac, const vec3 ah,
|
||||
const vec3 pn, const float_t pd,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
float_t proj = fabsf(pn[0] * ah[0])
|
||||
+ fabsf(pn[1] * ah[1])
|
||||
+ fabsf(pn[2] * ah[2]);
|
||||
float_t signedDist = glm_vec3_dot((float_t *)pn, (float_t *)ac) - pd;
|
||||
*outDepth = proj - signedDist;
|
||||
if(*outDepth <= 0.0f) return false;
|
||||
glm_vec3_copy((float_t *)pn, outNormal);
|
||||
return true;
|
||||
}
|
||||
|
||||
void physicsTestClosestPointOnSegment(
|
||||
const vec3 a, const vec3 b, const vec3 p, vec3 out
|
||||
) {
|
||||
vec3 ab, ap;
|
||||
glm_vec3_sub((float_t *)b, (float_t *)a, ab);
|
||||
glm_vec3_sub((float_t *)p, (float_t *)a, ap);
|
||||
float_t denom = glm_vec3_dot(ab, ab);
|
||||
float_t t = (denom > 1e-10f)
|
||||
? glm_clamp(glm_vec3_dot(ap, ab) / denom, 0.0f, 1.0f)
|
||||
: 0.0f;
|
||||
glm_vec3_lerp((float_t *)a, (float_t *)b, t, out);
|
||||
}
|
||||
|
||||
void physicsTestClosestPointsBetweenSegments(
|
||||
const vec3 a1, const vec3 b1,
|
||||
const vec3 a2, const vec3 b2,
|
||||
vec3 outP1, vec3 outP2
|
||||
) {
|
||||
vec3 d1, d2, r;
|
||||
glm_vec3_sub((float_t *)b1, (float_t *)a1, d1);
|
||||
glm_vec3_sub((float_t *)b2, (float_t *)a2, d2);
|
||||
glm_vec3_sub((float_t *)a1, (float_t *)a2, r);
|
||||
|
||||
float_t a = glm_vec3_dot(d1, d1);
|
||||
float_t e = glm_vec3_dot(d2, d2);
|
||||
float_t f = glm_vec3_dot(d2, r);
|
||||
float_t s, t;
|
||||
|
||||
if(a <= 1e-10f && e <= 1e-10f) {
|
||||
glm_vec3_copy((float_t *)a1, outP1);
|
||||
glm_vec3_copy((float_t *)a2, outP2);
|
||||
return;
|
||||
}
|
||||
if(a <= 1e-10f) {
|
||||
t = 0.0f;
|
||||
s = glm_clamp(f / e, 0.0f, 1.0f);
|
||||
} else {
|
||||
float_t c = glm_vec3_dot(d1, r);
|
||||
if(e <= 1e-10f) {
|
||||
s = 0.0f;
|
||||
t = glm_clamp(-c / a, 0.0f, 1.0f);
|
||||
} else {
|
||||
float_t b = glm_vec3_dot(d1, d2);
|
||||
float_t denom = a * e - b * b;
|
||||
t = (fabsf(denom) > 1e-10f)
|
||||
? glm_clamp((b * f - c * e) / denom, 0.0f, 1.0f)
|
||||
: 0.0f;
|
||||
s = glm_clamp((b * t + f) / e, 0.0f, 1.0f);
|
||||
t = glm_clamp((b * s - c) / a, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
glm_vec3_lerp((float_t *)a1, (float_t *)b1, t, outP1);
|
||||
glm_vec3_lerp((float_t *)a2, (float_t *)b2, s, outP2);
|
||||
}
|
||||
|
||||
bool physicsTestCapsuleVsSphere(
|
||||
const vec3 cc, const float_t cr, const float_t chh,
|
||||
const vec3 sc, const float_t sr,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 capA = { cc[0], cc[1] - chh, cc[2] };
|
||||
vec3 capB = { cc[0], cc[1] + chh, cc[2] };
|
||||
vec3 closest;
|
||||
physicsTestClosestPointOnSegment(capA, capB, sc, closest);
|
||||
return physicsTestSphereVsSphere(
|
||||
closest, cr, sc, sr, outNormal, outDepth
|
||||
);
|
||||
}
|
||||
|
||||
bool physicsTestCapsuleVsAabb(
|
||||
const vec3 cc, const float_t cr, const float_t chh,
|
||||
const vec3 ac, const vec3 ah,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 capA = { cc[0], cc[1] - chh, cc[2] };
|
||||
vec3 capB = { cc[0], cc[1] + chh, cc[2] };
|
||||
vec3 closest;
|
||||
physicsTestClosestPointOnSegment(capA, capB, ac, closest);
|
||||
return physicsTestSphereVsAabb(
|
||||
closest, cr, ac, ah, outNormal, outDepth
|
||||
);
|
||||
}
|
||||
|
||||
bool physicsTestCapsuleVsPlane(
|
||||
const vec3 cc, const float_t cr, const float_t chh,
|
||||
const vec3 pn, const float_t pd,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 capA = { cc[0], cc[1] - chh, cc[2] };
|
||||
vec3 capB = { cc[0], cc[1] + chh, cc[2] };
|
||||
float_t da = glm_vec3_dot((float_t *)pn, capA) - pd;
|
||||
float_t db = glm_vec3_dot((float_t *)pn, capB) - pd;
|
||||
float_t minDist = (da < db) ? da : db;
|
||||
*outDepth = cr - minDist;
|
||||
if(*outDepth <= 0.0f) return false;
|
||||
glm_vec3_copy((float_t *)pn, outNormal);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool physicsTestCapsuleVsCapsule(
|
||||
const vec3 c1, const float_t r1, const float_t hh1,
|
||||
const vec3 c2, const float_t r2, const float_t hh2,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 a1 = { c1[0], c1[1] - hh1, c1[2] };
|
||||
vec3 b1 = { c1[0], c1[1] + hh1, c1[2] };
|
||||
vec3 a2 = { c2[0], c2[1] - hh2, c2[2] };
|
||||
vec3 b2 = { c2[0], c2[1] + hh2, c2[2] };
|
||||
vec3 p1, p2;
|
||||
physicsTestClosestPointsBetweenSegments(a1, b1, a2, b2, p1, p2);
|
||||
return physicsTestSphereVsSphere(p1, r1, p2, r2, outNormal, outDepth);
|
||||
}
|
||||
|
||||
bool physicsTestDispatch(
|
||||
const vec3 aPos, const physicsshape_t aShape,
|
||||
const vec3 bPos, const physicsshape_t bShape,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
physicshapetype_t ta = aShape.type;
|
||||
physicshapetype_t tb = bShape.type;
|
||||
|
||||
if(tb == PHYSICS_SHAPE_PLANE) {
|
||||
const float_t *pn = bShape.data.plane.normal;
|
||||
const float_t pd = bShape.data.plane.distance;
|
||||
switch (ta) {
|
||||
case PHYSICS_SHAPE_CUBE:
|
||||
return physicsTestAabbVsPlane(
|
||||
aPos, aShape.data.cube.halfExtents,
|
||||
pn, pd, outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_SPHERE:
|
||||
return physicsTestSphereVsPlane(
|
||||
aPos, aShape.data.sphere.radius,
|
||||
pn, pd, outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_CAPSULE:
|
||||
return physicsTestCapsuleVsPlane(
|
||||
aPos,
|
||||
aShape.data.capsule.radius,
|
||||
aShape.data.capsule.halfHeight,
|
||||
pn, pd, outNormal, outDepth
|
||||
);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(ta == PHYSICS_SHAPE_PLANE) {
|
||||
vec3 tmp; float_t d;
|
||||
if(!physicsTestDispatch(
|
||||
bPos, bShape, aPos, aShape, tmp, &d
|
||||
)) return false;
|
||||
glm_vec3_scale(tmp, -1.0f, outNormal);
|
||||
*outDepth = d;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (ta) {
|
||||
case PHYSICS_SHAPE_CUBE: {
|
||||
const float_t *ac = aPos;
|
||||
const float_t *ah = aShape.data.cube.halfExtents;
|
||||
switch (tb) {
|
||||
case PHYSICS_SHAPE_CUBE:
|
||||
return physicsTestAabbVsAabb(
|
||||
ac, ah,
|
||||
bPos, bShape.data.cube.halfExtents,
|
||||
outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_SPHERE: {
|
||||
vec3 tmp; float_t d;
|
||||
if(!physicsTestSphereVsAabb(
|
||||
bPos, bShape.data.sphere.radius,
|
||||
ac, ah, tmp, &d
|
||||
)) return false;
|
||||
glm_vec3_scale(tmp, -1.0f, outNormal);
|
||||
*outDepth = d;
|
||||
return true;
|
||||
}
|
||||
case PHYSICS_SHAPE_CAPSULE: {
|
||||
vec3 tmp; float_t d;
|
||||
if(!physicsTestCapsuleVsAabb(
|
||||
bPos,
|
||||
bShape.data.capsule.radius,
|
||||
bShape.data.capsule.halfHeight,
|
||||
ac, ah, tmp, &d
|
||||
)) return false;
|
||||
glm_vec3_scale(tmp, -1.0f, outNormal);
|
||||
*outDepth = d;
|
||||
return true;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
case PHYSICS_SHAPE_SPHERE: {
|
||||
const float_t sr = aShape.data.sphere.radius;
|
||||
switch (tb) {
|
||||
case PHYSICS_SHAPE_CUBE:
|
||||
return physicsTestSphereVsAabb(
|
||||
aPos, sr,
|
||||
bPos, bShape.data.cube.halfExtents,
|
||||
outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_SPHERE:
|
||||
return physicsTestSphereVsSphere(
|
||||
aPos, sr,
|
||||
bPos, bShape.data.sphere.radius,
|
||||
outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_CAPSULE: {
|
||||
vec3 tmp; float_t d;
|
||||
if(!physicsTestCapsuleVsSphere(
|
||||
bPos,
|
||||
bShape.data.capsule.radius,
|
||||
bShape.data.capsule.halfHeight,
|
||||
aPos, sr, tmp, &d
|
||||
)) return false;
|
||||
glm_vec3_scale(tmp, -1.0f, outNormal);
|
||||
*outDepth = d;
|
||||
return true;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
case PHYSICS_SHAPE_CAPSULE: {
|
||||
const float_t cr = aShape.data.capsule.radius;
|
||||
const float_t chh = aShape.data.capsule.halfHeight;
|
||||
switch (tb) {
|
||||
case PHYSICS_SHAPE_CUBE:
|
||||
return physicsTestCapsuleVsAabb(
|
||||
aPos, cr, chh,
|
||||
bPos, bShape.data.cube.halfExtents,
|
||||
outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_SPHERE:
|
||||
return physicsTestCapsuleVsSphere(
|
||||
aPos, cr, chh,
|
||||
bPos, bShape.data.sphere.radius,
|
||||
outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_CAPSULE:
|
||||
return physicsTestCapsuleVsCapsule(
|
||||
aPos, cr, chh,
|
||||
bPos,
|
||||
bShape.data.capsule.radius,
|
||||
bShape.data.capsule.halfHeight,
|
||||
outNormal, outDepth
|
||||
);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool_t physicsTestShapeVsShape(
|
||||
const vec3 aPos, const physicsshape_t aShape,
|
||||
const vec3 bPos, const physicsshape_t bShape,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
return physicsTestDispatch(
|
||||
aPos, aShape, bPos, bShape, outNormal, outDepth
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "physicsshape.h"
|
||||
|
||||
/**
|
||||
* Tests overlap between two axis-aligned bounding boxes.
|
||||
* outNormal points from B toward A.
|
||||
*
|
||||
* @param ac Center of AABB A.
|
||||
* @param ah Half-extents of AABB A.
|
||||
* @param bc Center of AABB B.
|
||||
* @param bh Half-extents of AABB B.
|
||||
* @param outNormal Push-out normal (B toward A).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestAabbVsAabb(
|
||||
const vec3 ac, const vec3 ah,
|
||||
const vec3 bc, const vec3 bh,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between two spheres.
|
||||
* outNormal points from B toward A.
|
||||
*
|
||||
* @param ac Center of sphere A.
|
||||
* @param ar Radius of sphere A.
|
||||
* @param bc Center of sphere B.
|
||||
* @param br Radius of sphere B.
|
||||
* @param outNormal Push-out normal (B toward A).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestSphereVsSphere(
|
||||
const vec3 ac, const float_t ar,
|
||||
const vec3 bc, const float_t br,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between a sphere and an axis-aligned bounding box.
|
||||
* outNormal points from the AABB toward the sphere.
|
||||
*
|
||||
* @param sc Center of the sphere.
|
||||
* @param sr Radius of the sphere.
|
||||
* @param ac Center of the AABB.
|
||||
* @param ah Half-extents of the AABB.
|
||||
* @param outNormal Push-out normal (AABB toward sphere).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestSphereVsAabb(
|
||||
const vec3 sc, const float_t sr,
|
||||
const vec3 ac, const vec3 ah,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between a sphere and an infinite plane.
|
||||
* outNormal equals the plane normal (pointing away from the surface).
|
||||
*
|
||||
* @param sc Center of the sphere.
|
||||
* @param sr Radius of the sphere.
|
||||
* @param pn Plane normal (unit vector, world-space).
|
||||
* @param pd Plane offset: dot(pn, surfacePoint) == pd.
|
||||
* @param outNormal Push-out normal (equals pn).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestSphereVsPlane(
|
||||
const vec3 sc, const float_t sr,
|
||||
const vec3 pn, const float_t pd,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between an AABB and an infinite plane.
|
||||
* outNormal equals the plane normal.
|
||||
*
|
||||
* @param ac Center of the AABB.
|
||||
* @param ah Half-extents of the AABB.
|
||||
* @param pn Plane normal (unit vector, world-space).
|
||||
* @param pd Plane offset (see physicsTestSphereVsPlane).
|
||||
* @param outNormal Push-out normal (equals pn).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestAabbVsPlane(
|
||||
const vec3 ac, const vec3 ah,
|
||||
const vec3 pn, const float_t pd,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Finds the closest point on segment [a, b] to query point p.
|
||||
*
|
||||
* @param a Start of the segment.
|
||||
* @param b End of the segment.
|
||||
* @param p Query point.
|
||||
* @param out Receives the closest point on [a, b] to p.
|
||||
*/
|
||||
void physicsTestClosestPointOnSegment(
|
||||
const vec3 a, const vec3 b, const vec3 p, vec3 out
|
||||
);
|
||||
|
||||
/**
|
||||
* Finds the closest points between two line segments.
|
||||
*
|
||||
* @param a1 Start of segment 1.
|
||||
* @param b1 End of segment 1.
|
||||
* @param a2 Start of segment 2.
|
||||
* @param b2 End of segment 2.
|
||||
* @param outP1 Receives the closest point on segment 1.
|
||||
* @param outP2 Receives the closest point on segment 2.
|
||||
*/
|
||||
void physicsTestClosestPointsBetweenSegments(
|
||||
const vec3 a1, const vec3 b1,
|
||||
const vec3 a2, const vec3 b2,
|
||||
vec3 outP1, vec3 outP2
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between a Y-axis-aligned capsule and a sphere.
|
||||
* outNormal points from the sphere toward the capsule.
|
||||
*
|
||||
* @param cc Center of the capsule.
|
||||
* @param cr Radius of the capsule.
|
||||
* @param chh Half-height of the capsule's cylindrical segment.
|
||||
* @param sc Center of the sphere.
|
||||
* @param sr Radius of the sphere.
|
||||
* @param outNormal Push-out normal (sphere toward capsule).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestCapsuleVsSphere(
|
||||
const vec3 cc, const float_t cr, const float_t chh,
|
||||
const vec3 sc, const float_t sr,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between a Y-axis-aligned capsule and an AABB.
|
||||
* outNormal points from the AABB toward the capsule.
|
||||
*
|
||||
* @param cc Center of the capsule.
|
||||
* @param cr Radius of the capsule.
|
||||
* @param chh Half-height of the capsule's cylindrical segment.
|
||||
* @param ac Center of the AABB.
|
||||
* @param ah Half-extents of the AABB.
|
||||
* @param outNormal Push-out normal (AABB toward capsule).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestCapsuleVsAabb(
|
||||
const vec3 cc, const float_t cr, const float_t chh,
|
||||
const vec3 ac, const vec3 ah,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between a Y-axis-aligned capsule and an infinite plane.
|
||||
* outNormal equals the plane normal.
|
||||
*
|
||||
* @param cc Center of the capsule.
|
||||
* @param cr Radius of the capsule.
|
||||
* @param chh Half-height of the capsule's cylindrical segment.
|
||||
* @param pn Plane normal (unit vector, world-space).
|
||||
* @param pd Plane offset (see physicsTestSphereVsPlane).
|
||||
* @param outNormal Push-out normal (equals pn).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestCapsuleVsPlane(
|
||||
const vec3 cc, const float_t cr, const float_t chh,
|
||||
const vec3 pn, const float_t pd,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between two Y-axis-aligned capsules.
|
||||
* outNormal points from capsule B toward capsule A.
|
||||
*
|
||||
* @param c1 Center of capsule A.
|
||||
* @param r1 Radius of capsule A.
|
||||
* @param hh1 Half-height of capsule A's cylindrical segment.
|
||||
* @param c2 Center of capsule B.
|
||||
* @param r2 Radius of capsule B.
|
||||
* @param hh2 Half-height of capsule B's cylindrical segment.
|
||||
* @param outNormal Push-out normal (B toward A).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestCapsuleVsCapsule(
|
||||
const vec3 c1, const float_t r1, const float_t hh1,
|
||||
const vec3 c2, const float_t r2, const float_t hh2,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Routes a shape-pair collision test to the correct primitive.
|
||||
* When A is a plane, delegates with swapped arguments and negates
|
||||
* the resulting normal. outNormal points from B toward A.
|
||||
*
|
||||
* @param aPos Position of shape A.
|
||||
* @param aShape Shape descriptor of A.
|
||||
* @param bPos Position of shape B.
|
||||
* @param bShape Shape descriptor of B.
|
||||
* @param outNormal Push-out normal (B toward A).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestDispatch(
|
||||
const vec3 aPos, const physicsshape_t aShape,
|
||||
const vec3 bPos, const physicsshape_t bShape,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests for collision between two shapes. Returns true if they
|
||||
* overlap, and if so, outputs the push-out normal and depth.
|
||||
*
|
||||
* outNormal always points from shape B toward shape A, so adding
|
||||
* (outNormal * outDepth) to A's position separates the two shapes.
|
||||
*
|
||||
* @param aPos Position of shape A.
|
||||
* @param aShape Shape descriptor of A.
|
||||
* @param bPos Position of shape B.
|
||||
* @param bShape Shape descriptor of B.
|
||||
* @param outNormal Push-out normal, pointing from B toward A.
|
||||
* @param outDepth Penetration depth (positive when overlapping).
|
||||
* @return true if the shapes overlap, false otherwise.
|
||||
*/
|
||||
bool_t physicsTestShapeVsShape(
|
||||
const vec3 aPos,
|
||||
const physicsshape_t aShape,
|
||||
const vec3 bPos,
|
||||
const physicsshape_t bShape,
|
||||
vec3 outNormal,
|
||||
float_t *outDepth
|
||||
);
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "physicsworld.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component.h"
|
||||
#include "physicstest.h"
|
||||
|
||||
physicsworld_t PHYSICS_WORLD;
|
||||
|
||||
void physicsWorldInit() {
|
||||
memoryZero(&PHYSICS_WORLD, sizeof(physicsworld_t));
|
||||
|
||||
PHYSICS_WORLD.gravity[0] = 0.0f;
|
||||
PHYSICS_WORLD.gravity[1] = -9.81f;
|
||||
PHYSICS_WORLD.gravity[2] = 0.0f;
|
||||
}
|
||||
|
||||
void physicsWorldStep(const float_t dt) {
|
||||
assertTrue(dt > 0.0f, "Delta time must be positive");
|
||||
|
||||
entityid_t physEnts[ENTITY_COUNT_MAX];
|
||||
componentid_t physComps[ENTITY_COUNT_MAX];
|
||||
entityid_t physCount = componentGetEntitiesWithComponent(
|
||||
COMPONENT_TYPE_PHYSICS, physEnts, physComps
|
||||
);
|
||||
|
||||
/* Pre-fetch all position and physics pointers once. */
|
||||
entityposition_t *positions[ENTITY_COUNT_MAX];
|
||||
entityphysics_t *physBodies[ENTITY_COUNT_MAX];
|
||||
for(entityid_t i = 0; i < physCount; i++) {
|
||||
componentid_t posComp = entityGetComponent(
|
||||
physEnts[i], COMPONENT_TYPE_POSITION
|
||||
);
|
||||
positions[i] = (posComp != 0xFF)
|
||||
? entityPositionGet(physEnts[i], posComp)
|
||||
: NULL;
|
||||
physBodies[i] = entityPhysicsGet(physEnts[i], physComps[i]);
|
||||
}
|
||||
|
||||
/* Phase 1: integrate dynamic bodies (gravity + velocity → position).
|
||||
* Writes directly to pos->position — matrix rebuilt at end. */
|
||||
for(entityid_t i = 0; i < physCount; i++) {
|
||||
if(!positions[i]) continue;
|
||||
entityphysics_t *phys = physBodies[i];
|
||||
if(phys->type != PHYSICS_BODY_DYNAMIC) continue;
|
||||
|
||||
phys->onGround = false;
|
||||
|
||||
phys->velocity[0] += PHYSICS_WORLD.gravity[0] * phys->gravityScale * dt;
|
||||
phys->velocity[1] += PHYSICS_WORLD.gravity[1] * phys->gravityScale * dt;
|
||||
phys->velocity[2] += PHYSICS_WORLD.gravity[2] * phys->gravityScale * dt;
|
||||
|
||||
float_t *pos = positions[i]->position;
|
||||
pos[0] += phys->velocity[0] * dt;
|
||||
pos[1] += phys->velocity[1] * dt;
|
||||
pos[2] += phys->velocity[2] * dt;
|
||||
}
|
||||
|
||||
/* Phase 2: dynamic vs static/kinematic. */
|
||||
for(entityid_t i = 0; i < physCount; i++) {
|
||||
if(!positions[i]) continue;
|
||||
entityphysics_t *phys = physBodies[i];
|
||||
if(phys->type != PHYSICS_BODY_DYNAMIC) continue;
|
||||
|
||||
float_t *pos = positions[i]->position;
|
||||
|
||||
for(entityid_t j = 0; j < physCount; j++) {
|
||||
if(i == j || !positions[j]) continue;
|
||||
entityphysics_t *otherPhys = physBodies[j];
|
||||
if(otherPhys->type == PHYSICS_BODY_DYNAMIC) continue;
|
||||
|
||||
vec3 normal; float_t depth;
|
||||
if(!physicsTestShapeVsShape(
|
||||
pos, phys->shape,
|
||||
positions[j]->position, otherPhys->shape,
|
||||
normal, &depth
|
||||
)) continue;
|
||||
|
||||
pos[0] += normal[0] * depth;
|
||||
pos[1] += normal[1] * depth;
|
||||
pos[2] += normal[2] * depth;
|
||||
|
||||
float_t vn = glm_vec3_dot(phys->velocity, normal);
|
||||
if(vn < 0.0f) {
|
||||
phys->velocity[0] -= vn * normal[0];
|
||||
phys->velocity[1] -= vn * normal[1];
|
||||
phys->velocity[2] -= vn * normal[2];
|
||||
}
|
||||
|
||||
if(normal[1] > PHYSICS_GROUND_THRESHOLD) phys->onGround = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Phase 3: dynamic vs dynamic. */
|
||||
for(entityid_t i = 0; i < physCount; i++) {
|
||||
if(!positions[i]) continue;
|
||||
entityphysics_t *physA = physBodies[i];
|
||||
if(physA->type != PHYSICS_BODY_DYNAMIC) continue;
|
||||
|
||||
float_t *posA = positions[i]->position;
|
||||
|
||||
for(entityid_t j = i + 1; j < physCount; j++) {
|
||||
if(!positions[j]) continue;
|
||||
entityphysics_t *physB = physBodies[j];
|
||||
if(physB->type != PHYSICS_BODY_DYNAMIC) continue;
|
||||
|
||||
float_t *posB = positions[j]->position;
|
||||
|
||||
vec3 normal; float_t depth;
|
||||
if(!physicsTestShapeVsShape(
|
||||
posA, physA->shape, posB, physB->shape, normal, &depth
|
||||
)) continue;
|
||||
|
||||
posA[0] += normal[0] * depth * 0.5f;
|
||||
posA[1] += normal[1] * depth * 0.5f;
|
||||
posA[2] += normal[2] * depth * 0.5f;
|
||||
|
||||
posB[0] -= normal[0] * depth * 0.5f;
|
||||
posB[1] -= normal[1] * depth * 0.5f;
|
||||
posB[2] -= normal[2] * depth * 0.5f;
|
||||
|
||||
float_t v_rel = glm_vec3_dot(physA->velocity, normal)
|
||||
- glm_vec3_dot(physB->velocity, normal);
|
||||
if(v_rel < 0.0f) {
|
||||
physA->velocity[0] -= v_rel * normal[0];
|
||||
physA->velocity[1] -= v_rel * normal[1];
|
||||
physA->velocity[2] -= v_rel * normal[2];
|
||||
physB->velocity[0] += v_rel * normal[0];
|
||||
physB->velocity[1] += v_rel * normal[1];
|
||||
physB->velocity[2] += v_rel * normal[2];
|
||||
}
|
||||
|
||||
if( normal[1] > PHYSICS_GROUND_THRESHOLD) physA->onGround = true;
|
||||
if(-normal[1] > PHYSICS_GROUND_THRESHOLD) physB->onGround = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Rebuild transforms for all dynamic bodies once, after all phases. */
|
||||
for(entityid_t i = 0; i < physCount; i++) {
|
||||
if(!positions[i]) continue;
|
||||
if(physBodies[i]->type != PHYSICS_BODY_DYNAMIC) continue;
|
||||
entityPositionRebuild(positions[i]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "physics/physicsshape.h"
|
||||
#include "physics/physicsbodytype.h"
|
||||
|
||||
#define PHYSICS_GROUND_THRESHOLD 0.707f
|
||||
|
||||
typedef struct {
|
||||
vec3 gravity;
|
||||
} physicsworld_t;
|
||||
|
||||
extern physicsworld_t PHYSICS_WORLD;
|
||||
|
||||
/**
|
||||
* Initializes the physics world.
|
||||
*/
|
||||
void physicsWorldInit(void);
|
||||
|
||||
/**
|
||||
* Steps the physics simulation forward.
|
||||
*
|
||||
* @param dt The time delta in seconds since the last step.
|
||||
*/
|
||||
void physicsWorldStep(const float_t dt);
|
||||
@@ -76,7 +76,7 @@ errorret_t displayInitDolphin(void) {
|
||||
);
|
||||
|
||||
// Setup cull modes
|
||||
GX_SetCullMode(GX_CULL_FRONT);
|
||||
GX_SetCullMode(GX_CULL_BACK);
|
||||
GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
|
||||
GX_SetZMode(GX_TRUE, GX_ALWAYS, GX_FALSE);
|
||||
GX_SetDispCopyGamma(GX_GM_1_0);
|
||||
|
||||
@@ -229,6 +229,7 @@ errorret_t shaderSetTextureGL(
|
||||
if(texture == NULL) {
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
errorChain(errorGLCheck());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -312,6 +313,14 @@ errorret_t shaderSetColorGL(
|
||||
// glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
|
||||
// errorChain(errorGLCheck());
|
||||
|
||||
glColor4f(
|
||||
(float_t)color.r / 255.0f,
|
||||
(float_t)color.g / 255.0f,
|
||||
(float_t)color.b / 255.0f,
|
||||
(float_t)color.a / 255.0f
|
||||
);
|
||||
errorChain(errorGLCheck());
|
||||
|
||||
#else
|
||||
GLint location;
|
||||
errorChain(shaderParamGetLocationGL(shader, name, &location));
|
||||
|
||||
@@ -30,7 +30,7 @@ with open(args.csv, newline="", encoding="utf-8") as f:
|
||||
item_types.append(item_type)
|
||||
rows[item_id] = row
|
||||
|
||||
# Assign enum values — types and IDs share a single counter so values never collide
|
||||
# Assign enum values: types and IDs share a single counter so values never collide
|
||||
count = 0
|
||||
type_values = {}
|
||||
id_values = {}
|
||||
|
||||
Reference in New Issue
Block a user