Merge: Bounding
This commit is contained in:
@@ -111,6 +111,18 @@ std::shared_ptr<MeshAsset> AssetManager::getPrimitive(std::string_view name) con
|
||||
if (auto m = findBy("Sphere")) return m;
|
||||
return {};
|
||||
}
|
||||
if (name == std::string_view("plane") || name == std::string_view("Plane"))
|
||||
{
|
||||
if (auto m = findBy("plane")) return m;
|
||||
if (auto m = findBy("Plane")) return m;
|
||||
return {};
|
||||
}
|
||||
if (name == std::string_view("capsule") || name == std::string_view("Capsule"))
|
||||
{
|
||||
if (auto m = findBy("capsule")) return m;
|
||||
if (auto m = findBy("Capsule")) return m;
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -145,6 +157,16 @@ std::shared_ptr<MeshAsset> AssetManager::createMesh(const MeshCreateInfo &info)
|
||||
vertsSpan = tmpVerts;
|
||||
indsSpan = tmpInds;
|
||||
break;
|
||||
case MeshGeometryDesc::Type::Plane:
|
||||
primitives::buildPlane(tmpVerts, tmpInds);
|
||||
vertsSpan = tmpVerts;
|
||||
indsSpan = tmpInds;
|
||||
break;
|
||||
case MeshGeometryDesc::Type::Capsule:
|
||||
primitives::buildCapsule(tmpVerts, tmpInds);
|
||||
vertsSpan = tmpVerts;
|
||||
indsSpan = tmpInds;
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure tangents exist for primitives (and provided geometry if needed)
|
||||
@@ -246,7 +268,15 @@ std::shared_ptr<MeshAsset> AssetManager::createMesh(const MeshCreateInfo &info)
|
||||
case MeshGeometryDesc::Type::Sphere:
|
||||
surf.bounds.type = BoundsType::Sphere;
|
||||
break;
|
||||
case MeshGeometryDesc::Type::Capsule:
|
||||
surf.bounds.type = BoundsType::Capsule;
|
||||
break;
|
||||
case MeshGeometryDesc::Type::Cube:
|
||||
surf.bounds.type = BoundsType::Box;
|
||||
break;
|
||||
case MeshGeometryDesc::Type::Plane:
|
||||
surf.bounds.type = BoundsType::Box;
|
||||
break;
|
||||
case MeshGeometryDesc::Type::Provided:
|
||||
default:
|
||||
surf.bounds.type = BoundsType::Box;
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
|
||||
struct MeshGeometryDesc
|
||||
{
|
||||
enum class Type { Provided, Cube, Sphere };
|
||||
enum class Type { Provided, Cube, Sphere, Plane, Capsule };
|
||||
|
||||
Type type = Type::Provided;
|
||||
std::span<Vertex> vertices{};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "core/texture_cache.h"
|
||||
#include "core/ibl_manager.h"
|
||||
#include "engine_context.h"
|
||||
#include <vk_types.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#include "core/vk_types.h"
|
||||
#include "glm/gtx/compatibility.hpp"
|
||||
#include "glm/gtx/norm.hpp"
|
||||
|
||||
namespace primitives
|
||||
{
|
||||
// Axis-aligned unit cube centered at origin, size 1 on each side.
|
||||
inline void buildCube(std::vector<Vertex> &vertices, std::vector<uint32_t> &indices)
|
||||
{
|
||||
vertices.clear();
|
||||
@@ -50,6 +55,7 @@ namespace primitives
|
||||
}
|
||||
}
|
||||
|
||||
// Unit sphere centered at origin, radius 0.5.
|
||||
inline void buildSphere(std::vector<Vertex> &vertices, std::vector<uint32_t> &indices, int sectors = 16,
|
||||
int stacks = 16)
|
||||
{
|
||||
@@ -93,5 +99,116 @@ namespace primitives
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace primitives
|
||||
|
||||
// Infinite grid plane trimmed to a unit quad on XZ, centered at origin (Y up).
|
||||
inline void buildPlane(std::vector<Vertex> &vertices, std::vector<uint32_t> &indices)
|
||||
{
|
||||
vertices.clear();
|
||||
indices.clear();
|
||||
|
||||
const glm::vec3 n(0.0f, 1.0f, 0.0f);
|
||||
const float y = 0.0f;
|
||||
|
||||
Vertex v0{{-0.5f, y, -0.5f}, 0.0f, n, 0.0f, glm::vec4(1.0f), glm::vec4(1, 0, 0, 1)};
|
||||
Vertex v1{{ 0.5f, y, -0.5f}, 1.0f, n, 0.0f, glm::vec4(1.0f), glm::vec4(1, 0, 0, 1)};
|
||||
Vertex v2{{ 0.5f, y, 0.5f}, 1.0f, n, 1.0f, glm::vec4(1.0f), glm::vec4(1, 0, 0, 1)};
|
||||
Vertex v3{{-0.5f, y, 0.5f}, 0.0f, n, 1.0f, glm::vec4(1.0f), glm::vec4(1, 0, 0, 1)};
|
||||
|
||||
vertices.push_back(v0);
|
||||
vertices.push_back(v1);
|
||||
vertices.push_back(v2);
|
||||
vertices.push_back(v3);
|
||||
|
||||
indices.push_back(0);
|
||||
indices.push_back(1);
|
||||
indices.push_back(2);
|
||||
indices.push_back(2);
|
||||
indices.push_back(3);
|
||||
indices.push_back(0);
|
||||
}
|
||||
|
||||
// Capsule aligned with local Y axis.
|
||||
// Radius ~0.5, total height ~2.0 (cylinder half-height 0.5).
|
||||
inline void buildCapsule(std::vector<Vertex> &vertices, std::vector<uint32_t> &indices,
|
||||
int radialSegments = 16, int stackSegments = 16)
|
||||
{
|
||||
vertices.clear();
|
||||
indices.clear();
|
||||
|
||||
radialSegments = std::max(radialSegments, 3);
|
||||
stackSegments = std::max(stackSegments, 2);
|
||||
|
||||
const float radius = 0.5f;
|
||||
const float halfHeight = 0.5f; // cylinder half-height
|
||||
const float totalHalf = halfHeight + radius;
|
||||
const float totalHeight = totalHalf * 2.0f;
|
||||
|
||||
// Build a regular grid in (stackSegments+1) x (radialSegments+1)
|
||||
// using an analytical capsule cross-section (capped cylinder).
|
||||
for (int iy = 0; iy <= stackSegments; ++iy)
|
||||
{
|
||||
float v = (float) iy / (float) stackSegments;
|
||||
float y = -totalHalf + totalHeight * v;
|
||||
|
||||
float ay = std::abs(y);
|
||||
float ringRadius;
|
||||
if (ay <= halfHeight)
|
||||
{
|
||||
ringRadius = radius;
|
||||
}
|
||||
else
|
||||
{
|
||||
float dy = ay - halfHeight;
|
||||
float inside = radius * radius - dy * dy;
|
||||
ringRadius = inside > 0.0f ? std::sqrt(inside) : 0.0f;
|
||||
}
|
||||
|
||||
for (int ix = 0; ix <= radialSegments; ++ix)
|
||||
{
|
||||
float u = (float) ix / (float) radialSegments;
|
||||
float theta = u * glm::two_pi<float>();
|
||||
float x = ringRadius * std::cos(theta);
|
||||
float z = ringRadius * std::sin(theta);
|
||||
|
||||
glm::vec3 pos(x, y, z);
|
||||
|
||||
float cy = glm::clamp(y, -halfHeight, halfHeight);
|
||||
glm::vec3 center(0.0f, cy, 0.0f);
|
||||
glm::vec3 n = glm::normalize(pos - center);
|
||||
if (!glm::all(glm::isfinite(n)) || glm::length2(n) < 1e-8f)
|
||||
{
|
||||
n = glm::vec3(0.0f, y >= 0.0f ? 1.0f : -1.0f, 0.0f);
|
||||
}
|
||||
|
||||
Vertex vert{};
|
||||
vert.position = pos;
|
||||
vert.normal = n;
|
||||
vert.uv_x = u;
|
||||
vert.uv_y = v;
|
||||
vert.color = glm::vec4(1.0f);
|
||||
vert.tangent = glm::vec4(1, 0, 0, 1);
|
||||
vertices.push_back(vert);
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t stride = static_cast<uint32_t>(radialSegments + 1);
|
||||
for (int iy = 0; iy < stackSegments; ++iy)
|
||||
{
|
||||
for (int ix = 0; ix < radialSegments; ++ix)
|
||||
{
|
||||
uint32_t i0 = (uint32_t) iy * stride + (uint32_t) ix;
|
||||
uint32_t i1 = i0 + 1;
|
||||
uint32_t i2 = i0 + stride;
|
||||
uint32_t i3 = i2 + 1;
|
||||
|
||||
indices.push_back(i0);
|
||||
indices.push_back(i2);
|
||||
indices.push_back(i1);
|
||||
|
||||
indices.push_back(i1);
|
||||
indices.push_back(i2);
|
||||
indices.push_back(i3);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace primitives
|
||||
|
||||
@@ -137,6 +137,130 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ray / capsule intersection in world space. Capsule is aligned with local Y axis
|
||||
// and reconstructed from Bounds.origin/extents, assuming extents.x/z ~= radius
|
||||
// and extents.y ~= halfHeight + radius (AABB center/half-size convention).
|
||||
bool intersect_ray_capsule(const glm::vec3 &rayOrigin,
|
||||
const glm::vec3 &rayDir,
|
||||
const Bounds &bounds,
|
||||
const glm::mat4 &worldTransform,
|
||||
glm::vec3 &outWorldHit)
|
||||
{
|
||||
if (glm::length2(rayDir) < 1e-8f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Transform ray into object-local space.
|
||||
glm::mat4 invM = glm::inverse(worldTransform);
|
||||
glm::vec3 localOrigin = glm::vec3(invM * glm::vec4(rayOrigin, 1.0f));
|
||||
glm::vec3 localDir = glm::vec3(invM * glm::vec4(rayDir, 0.0f));
|
||||
if (glm::length2(localDir) < 1e-8f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
localDir = glm::normalize(localDir);
|
||||
|
||||
// Work in capsule-local space where Bounds.origin is at (0,0,0).
|
||||
glm::vec3 ro = localOrigin - bounds.origin;
|
||||
glm::vec3 rd = localDir;
|
||||
|
||||
float radius = std::max(bounds.extents.x, bounds.extents.z);
|
||||
if (radius <= 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// extents.y is (halfCylinder + radius) for a symmetric capsule.
|
||||
float halfSegment = std::max(bounds.extents.y - radius, 0.0f);
|
||||
|
||||
float tHit = std::numeric_limits<float>::max();
|
||||
bool hit = false;
|
||||
|
||||
// 1) Cylinder part around Y axis: x^2 + z^2 = r^2, |y| <= halfSegment.
|
||||
float a = rd.x * rd.x + rd.z * rd.z;
|
||||
float b = 2.0f * (ro.x * rd.x + ro.z * rd.z);
|
||||
float c = ro.x * ro.x + ro.z * ro.z - radius * radius;
|
||||
|
||||
if (std::abs(a) > 1e-8f)
|
||||
{
|
||||
float disc = b * b - 4.0f * a * c;
|
||||
if (disc >= 0.0f)
|
||||
{
|
||||
float s = std::sqrt(disc);
|
||||
float invDen = 0.5f / a;
|
||||
float t0 = (-b - s) * invDen;
|
||||
float t1 = (-b + s) * invDen;
|
||||
if (t0 > t1) std::swap(t0, t1);
|
||||
|
||||
auto tryCylHit = [&](float t)
|
||||
{
|
||||
if (t < 0.0f || t >= tHit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
glm::vec3 p = ro + rd * t;
|
||||
if (std::abs(p.y) <= halfSegment + 1e-4f)
|
||||
{
|
||||
tHit = t;
|
||||
hit = true;
|
||||
}
|
||||
};
|
||||
|
||||
tryCylHit(t0);
|
||||
tryCylHit(t1);
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Spherical caps at y = +/- halfSegment.
|
||||
auto intersectCap = [&](float capY)
|
||||
{
|
||||
glm::vec3 center(0.0f, capY, 0.0f);
|
||||
glm::vec3 oc = ro - center;
|
||||
float b2 = glm::dot(oc, rd);
|
||||
float c2 = glm::dot(oc, oc) - radius * radius;
|
||||
float disc = b2 * b2 - c2;
|
||||
if (disc < 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
float s = std::sqrt(disc);
|
||||
float t0 = -b2 - s;
|
||||
float t1 = -b2 + s;
|
||||
|
||||
auto tryCapHit = [&](float t)
|
||||
{
|
||||
if (t < 0.0f || t >= tHit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
tHit = t;
|
||||
hit = true;
|
||||
};
|
||||
|
||||
if (t0 >= 0.0f) tryCapHit(t0);
|
||||
if (t1 >= 0.0f) tryCapHit(t1);
|
||||
};
|
||||
|
||||
intersectCap(+halfSegment);
|
||||
intersectCap(-halfSegment);
|
||||
|
||||
if (!hit)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
glm::vec3 localHit = ro + rd * tHit;
|
||||
glm::vec3 worldHit = glm::vec3(worldTransform * glm::vec4(localHit + bounds.origin, 1.0f));
|
||||
|
||||
if (glm::dot(worldHit - rayOrigin, rayDir) <= 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
outWorldHit = worldHit;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ray / oriented-bounds intersection in world space using object-local shape.
|
||||
// Uses a quick sphere test first; on success refines based on BoundsType.
|
||||
// Returns true when hit; outWorldHit is the closest hit point in world space.
|
||||
@@ -173,9 +297,12 @@ namespace
|
||||
outWorldHit = rayOrigin + rayDir * sphereT;
|
||||
return true;
|
||||
}
|
||||
case BoundsType::Capsule:
|
||||
{
|
||||
return intersect_ray_capsule(rayOrigin, rayDir, bounds, worldTransform, outWorldHit);
|
||||
}
|
||||
case BoundsType::Box:
|
||||
case BoundsType::Mesh: // TODO: replace with BVH/mesh query; box is a safe fallback.
|
||||
case BoundsType::Capsule:
|
||||
default:
|
||||
{
|
||||
// For Capsule and Mesh we currently fall back to the oriented box;
|
||||
|
||||
Reference in New Issue
Block a user