Starting work on capsules.

This commit is contained in:
2023-03-16 22:30:57 -07:00
parent c6391f1111
commit 11af9d147a
13 changed files with 435 additions and 70 deletions

View File

@ -6,6 +6,7 @@
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
CapsuleMesh.cpp
CubeMesh.cpp
TriangleMesh.cpp
QuadMesh.cpp

View File

@ -0,0 +1,99 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "CapsuleMesh.hpp"
using namespace Dawn;
void CapsuleMesh::calculateRing(
int32_t segments,
float_t height,
float_t radius,
float_t dr,
float_t y,
float_t dy,
std::vector<glm::vec3> *positions
) {
assertNotNull(positions);
float_t segIncr = 1.0f / (float_t)(segments - 1);
for(int32_t s = 0; s < segments; s++ ) {
float_t x = cosf(MATH_PI * 2 * s * segIncr) * dr;
float_t z = sinf(MATH_PI * 2 * s * segIncr) * dr;
positions->emplace_back(glm::vec3(radius * x, radius * y + height * dy, radius * z ));
}
}
void CapsuleMesh::create(
Mesh *mesh,
float_t radius,
float_t height
) {
assertNotNull(mesh);
std::vector<glm::vec3> positions;
std::vector<meshindice_t> indices;
int32_t slices = 12;
int32_t segments = 12;
int32_t ringsBody = slices + 1;
int32_t ringsTotal = slices + ringsBody;
positions.reserve(segments * ringsTotal);
indices.reserve((segments - 1) * (ringsTotal - 1) * 6);
float_t bodyIncr = 1.0f / (float_t)(ringsBody - 1);
float_t ringIncr = 1.0f / (float_t)(slices - 1);
for(int32_t r = 0; r < slices / 2; r++) {
calculateRing(
segments,
height,
radius,
sinf(MATH_PI * r * ringIncr),
sinf(MATH_PI * (r * ringIncr - 0.5f)),
-0.5f,
&positions
);
}
for(int32_t r = 0; r < ringsBody; r++ ) {
calculateRing(
segments,
height,
radius,
1.0f,
0.0f,
r * bodyIncr - 0.5f,
&positions
);
}
for(int32_t r = slices / 2; r < slices; r++) {
calculateRing(
segments,
height,
radius,
sinf(MATH_PI * r * ringIncr),
sinf(MATH_PI * (r * ringIncr - 0.5f)),
0.5f,
&positions
);
}
for(int32_t r = 0; r < ringsTotal - 1; r++ ) {
for(int32_t s = 0; s < segments - 1; s++ ) {
indices.push_back( (uint32_t)(r * segments + ( s + 1 )) );
indices.push_back( (uint32_t)(r * segments + ( s + 0 )) );
indices.push_back( (uint32_t)(( r + 1 ) * segments + ( s + 1 )) );
indices.push_back( (uint32_t)(( r + 1 ) * segments + ( s + 0 )) );
indices.push_back( (uint32_t)(( r + 1 ) * segments + ( s + 1 )) );
indices.push_back( (uint32_t)(r * segments + s) );
}
}
mesh->createBuffers(positions.size(), indices.size());
mesh->bufferPositions(0, positions.data(), positions.size());
mesh->bufferIndices(0, indices.data(), indices.size());
}

View File

@ -0,0 +1,30 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "display/mesh/Mesh.hpp"
#include "util/mathutils.hpp"
namespace Dawn {
class CapsuleMesh {
protected:
static void calculateRing(
int32_t segments,
float_t height,
float_t radius,
float_t dr,
float_t y,
float_t dy,
std::vector<glm::vec3> *positions
);
public:
static void create(
Mesh *mesh,
float_t radius,
float_t height
);
};
}

View File

@ -0,0 +1,71 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "AABB3D.hpp"
using namespace Dawn;
bool_t Dawn::aabb3dIntersect(struct AABB3D cube1, struct AABB3D cube2) {
return (
(cube1.min.x <= cube2.max.x && cube1.max.x >= cube2.min.x) &&
(cube1.min.y <= cube2.max.y && cube1.max.y >= cube2.min.y) &&
(cube1.min.z <= cube2.max.z && cube1.max.z >= cube2.min.z)
);
}
bool aabbSweep(
const glm::vec3& pos1, const glm::vec3& size1, const glm::vec3& vel1,
const glm::vec3& pos2, const glm::vec3& size2, const glm::vec3& vel2,
float& fraction
) {
glm::vec3 relVel = vel1 - vel2;
glm::vec3 relPos = pos1 - pos2;
// If the relative velocity is zero, the cubes are already intersecting
i (glm::length2(relVel) == 0.0f) {
fraction = 0.0f;
return true;
}
// Expand the size of the cubes by the magnitude of the relative velocity
glm::vec3 size1exp = size1 + glm::abs(relVel);
glm::vec3 size2exp = size2 + glm::abs(relVel);
// Compute the times at which the cubes first and last overlap on each axis
float tminx = (size1exp.x + size2exp.x - glm::abs(relPos.x)) / glm::abs(relVel.x);
float tmaxx = (size1.x + size2.x - glm::abs(relPos.x)) / glm::abs(relVel.x);
float tminy = (size1exp.y + size2exp.y - glm::abs(relPos.y)) / glm::abs(relVel.y);
float tmaxy = (size1.y + size2.y - glm::abs(relPos.y)) / glm::abs(relVel.y);
float tminz = (size1exp.z + size2exp.z - glm::abs(relPos.z)) / glm::abs(relVel.z);
float tmaxz = (size1.z + size2.z - glm::abs(relPos.z)) / glm::abs(relVel.z);
// Find the earliest and latest times of overlap
float tmin = glm::max(glm::max(glm::min(tminx, tmaxx), glm::min(tminy, tmaxy)), glm::min(tminz, tmaxz));
float tmax = glm::min(glm::min(glm::max(tminx, tmaxx), glm::max(tminy, tmaxy)), glm::max(tminz, tmaxz));
// If the earliest time of overlap is greater than the length of the timestep, the cubes
// will not collide during the timestep
if (tmin > 1.0f) {
fraction = 1.0f;
return false;
}
// If the latest time of overlap is less than or equal to zero, the cubes are already
// colliding and will collide throughout the timestep
if (tmax <= 0.0f) {
fraction = 0.0f;
return true;
}
// Compute the fraction of the timestep at which the collision occurs
fraction = glm::clamp(tmin, 0.0f, 1.0f);
// Check if the two AABB cubes are intersecting at the time of collision
glm::vec3 min1 = pos1 + vel1 * fraction - size1 / 2.0f;
glm::vec3 max1 = pos1 + vel1 * fraction + size1 / 2.0f;
glm::vec3 min2 = pos2 + vel2 * fraction - size2 / 2.0f;
glm::vec3 max2 = pos2 + vel2 * fraction + size2 / 2.0f;
return aabbIntersect(min1, max1, min2, max2);
}

View File

@ -1,14 +1,25 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "dawnlibs.hpp"
namespace Dawn {
struct AABB3D {
glm::vec3 min;
glm::vec3 max;
};
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "dawnlibs.hpp"
namespace Dawn {
struct AABB3D {
glm::vec3 min;
glm::vec3 max;
};
/**
* Checks whether two 3D AABB are intersecting or not.
*
* @param cube1 First cube.
* @param cube2 Second cube.
* @return True if the two cubes are intersecting, otherwise false.
*/
bool_t aabb3dIntersect(struct AABB3D cube1, struct AABB3D cube2);
}

View File

@ -0,0 +1,15 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "dawnlibs.hpp"
namespace Dawn {
struct PhysicsCapsule {
float_t height;
float_t radius;
glm::vec3 origin;
};
}

View File

@ -200,4 +200,58 @@ bool_t Dawn::raytestQuad(
*normal = glm::normalize(glm::vec3(transform * glm::vec4(0.0f, 0.0f, 1.0f, 0.0f)));
return true; // intersection found
}
bool_t Dawn::raytestCapsule(
struct Ray3D ray,
struct PhysicsCapsule capsule,
glm::vec3 *point,
glm::vec3 *normal,
float_t *distance
) {
// Calculate the axis and length of the capsule
glm::vec3 axis = glm::normalize(glm::vec3(0.0f, capsule.height, 0.0f));
float length = capsule.height - 2 * capsule.radius;
// Calculate the closest point on the capsule axis to the ray origin
glm::vec3 originToCenter = -capsule.origin;
float proj = glm::dot(originToCenter, axis);
glm::vec3 closestPointOnAxis = capsule.origin + axis * proj;
// Calculate the distance between the closest point on the axis and the ray origin
float distToClosestPoint = glm::length(closestPointOnAxis - capsule.origin);
// If the distance is greater than the capsule radius, the ray misses the capsule
if (distToClosestPoint > capsule.radius) {
return false;
}
// Calculate the distance between the closest point on the axis and the capsule end points
float distToCapsuleEnds = glm::length(originToCenter - axis * proj) - length * 0.5f;
// Calculate the distance along the ray to the point of intersection with the capsule
float t1 = glm::dot(-capsule.origin, ray.direction);
float t2 = glm::dot(closestPointOnAxis - capsule.origin, ray.direction);
float t3 = glm::sqrt(capsule.radius * capsule.radius - distToClosestPoint * distToClosestPoint);
float t4 = glm::sqrt(distToCapsuleEnds * distToCapsuleEnds + t3 * t3);
float tEnter = t1 + (t2 - t4);
float tExit = t1 + (t2 + t4);
// If the intersection point is behind the ray origin or beyond the ray endpoint, the ray misses the capsule
if (tEnter < 0 || tEnter > tExit) {
return false;
}
// Set the output parameters
*distance = tEnter;
// Calculate the hit point and normal
*point = capsule.origin + ray.direction * tEnter;
if (tEnter < 0 || tEnter > tExit) {
*normal = glm::normalize(*point);
} else {
*normal = glm::normalize(*point - closestPointOnAxis);
}
return true;
}

View File

@ -8,6 +8,7 @@
#include "assert/assert.hpp"
#include "PhysicsTriangle.hpp"
#include "PhysicsSphere.hpp"
#include "PhysicsCapsule.hpp"
#include "AABB3D.hpp"
namespace Dawn {
@ -57,4 +58,12 @@ namespace Dawn {
glm::vec3 *normal,
float_t *distance
);
bool_t raytestCapsule(
struct Ray3D ray,
struct PhysicsCapsule capsule,
glm::vec3 *point,
glm::vec3 *normal,
float_t *distance
);
}

View File

@ -8,4 +8,5 @@ target_sources(${DAWN_TARGET_NAME}
PRIVATE
Collider3D.cpp
CubeCollider.cpp
CapsuleCollider.cpp
)

View File

@ -0,0 +1,35 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "CapsuleCollider.hpp"
using namespace Dawn;
CapsuleCollider::CapsuleCollider(SceneItem *item) : Collider3D(item) {
}
bool_t CapsuleCollider::performRaycast(
struct Collider3DRayResult *result,
struct Ray3D ray
) {
assertNotNull(result);
return raytestCapsule(
ray,
{
.height = this->height,
.radius = this->radius,
.origin = this->transform->getWorldPosition()
},
&result->point,
&result->normal,
&result->distance
);
}
enum Collider3DType CapsuleCollider::getColliderType() {
return COLLIDER3D_TYPE_CAPSULE;
}

View File

@ -0,0 +1,26 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "Collider3D.hpp"
#include "physics/3d/Ray3D.hpp"
namespace Dawn {
class CapsuleCollider : public Collider3D {
protected:
bool_t performRaycast(
struct Collider3DRayResult *result,
struct Ray3D ray
) override;
public:
float_t height = 1;
float_t radius = 0.5f;
CapsuleCollider(SceneItem *item);
enum Collider3DType getColliderType() override;
};
}

View File

@ -1,53 +1,54 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "scene/SceneItemComponent.hpp"
#include "physics/3d/Ray3D.hpp"
namespace Dawn {
struct Collider3DRayResult {
glm::vec3 point;
float_t distance;
glm::vec3 normal;
Collider3D *collider;
};
enum Collider3DType {
COLLIDER3D_TYPE_CUBE
};
class Collider3D : public SceneItemComponent {
protected:
/**
* Internal per-collider raycast implementation. Same arguments as
* Collider3D::raycast()
*/
virtual bool_t performRaycast(
struct Collider3DRayResult *result,
struct Ray3D ray
) = 0;
public:
Collider3D(SceneItem *item);
/**
* Perform a raycast against this collider.
*
* @param result Where to store the result of the raycast collision
* @param ray The ray to cast against.
* @return True if the ray intercepts this collider, otherwise false.
*/
bool_t raycast(struct Collider3DRayResult *result, struct Ray3D ray);
/**
* Returns which type of collider this is. Useful for performing dynamic
* casting in your event listeners.
*
* @return The collider type this is.
*/
virtual enum Collider3DType getColliderType() = 0;
};
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "scene/SceneItemComponent.hpp"
#include "physics/3d/Ray3D.hpp"
namespace Dawn {
struct Collider3DRayResult {
glm::vec3 point;
float_t distance;
glm::vec3 normal;
Collider3D *collider;
};
enum Collider3DType {
COLLIDER3D_TYPE_CUBE,
COLLIDER3D_TYPE_CAPSULE
};
class Collider3D : public SceneItemComponent {
protected:
/**
* Internal per-collider raycast implementation. Same arguments as
* Collider3D::raycast()
*/
virtual bool_t performRaycast(
struct Collider3DRayResult *result,
struct Ray3D ray
) = 0;
public:
Collider3D(SceneItem *item);
/**
* Perform a raycast against this collider.
*
* @param result Where to store the result of the raycast collision
* @param ray The ray to cast against.
* @return True if the ray intercepts this collider, otherwise false.
*/
bool_t raycast(struct Collider3DRayResult *result, struct Ray3D ray);
/**
* Returns which type of collider this is. Useful for performing dynamic
* casting in your event listeners.
*
* @return The collider type this is.
*/
virtual enum Collider3DType getColliderType() = 0;
};
}

View File

@ -8,7 +8,13 @@
#include "scene/components/PlayerController.hpp"
#include "scene/components/GameCamera.hpp"
#include "scene/components/physics/3d/CubeCollider.hpp"
#include "scene/components/physics/3d/CapsuleCollider.hpp"
#include "scene/components/GameCamera.hpp"
#include "scene/components/display/MeshRenderer.hpp"
#include "scene/components/display/MeshHost.hpp"
#include "scene/components/display/material/SimpleTexturedMaterial.hpp"
#include "display/mesh/CapsuleMesh.hpp"
#include "scene/components/example/ExampleSpin.hpp"
namespace Dawn {
class HelloWorldScene : public Scene {
@ -16,20 +22,26 @@ namespace Dawn {
Camera *camera;
void stage() override {
camera = Camera::create(this);
camera->transform->lookAt(glm::vec3(0, 0, 8), glm::vec3(0, 0, 0));
auto gameCamera = camera->item->addComponent<GameCamera>();
auto playerItem = this->createSceneItem();
auto player = playerItem->addComponent<PlayerController>();
auto hitbox = playerItem->addComponent<CubeCollider>();
auto hitbox = playerItem->addComponent<CapsuleCollider>();
playerItem->addComponent<MeshRenderer>();
auto meshHost = playerItem->addComponent<MeshHost>();
playerItem->addComponent<SimpleTexturedMaterial>();
CapsuleMesh::create(&meshHost->mesh, hitbox->radius, hitbox->height);
auto wall = this->createSceneItem();
auto wallHitbox = wall->addComponent<CubeCollider>();
wall->transform.setLocalPosition(glm::vec3(-10, 0, 0));
wallHitbox->min = -(wallHitbox->max = glm::vec3(1, 1, 5));
gameCamera->player = player;
camera = Camera::create(this);
// camera->transform->lookAt(glm::vec3(0, 0, 8), glm::vec3(0, 0, 0));
// auto gameCamera = camera->item->addComponent<GameCamera>();
camera->transform->lookAt(glm::vec3(4, 4, 4), glm::vec3(0, 0, 0));
// gameCamera->player = player;
}
std::vector<Asset*> getRequiredAssets() override {