Merge: Bounding

This commit is contained in:
2025-11-18 23:50:18 +09:00
parent bb848b0517
commit d0acaada65
5 changed files with 278 additions and 3 deletions

View File

@@ -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;

View File

@@ -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{};

View File

@@ -17,6 +17,7 @@
#include "core/texture_cache.h"
#include "core/ibl_manager.h"
#include "engine_context.h"
#include <vk_types.h>
namespace {

View File

@@ -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

View File

@@ -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;