3#include <egg/geom/Sphere.hh>
4#include <egg/math/Math.hh>
14KColData::KColData(
const void *file) {
15 auto addOffset = [](
const void *file, u32 offset) ->
const void * {
16 return reinterpret_cast<const void *
>(
reinterpret_cast<const u8 *
>(file) + offset);
18 EGG::RamStream stream = EGG::RamStream(file,
sizeof(KColHeader));
20 u32 posOffset = stream.read_u32();
21 u32 nrmOffset = stream.read_u32();
22 u32 prismOffset = stream.read_u32();
23 u32 blockOffset = stream.read_u32();
25 m_posData = addOffset(file, posOffset);
26 m_nrmData = addOffset(file, nrmOffset);
27 m_prismData = addOffset(file, prismOffset);
28 m_blockData = addOffset(file, blockOffset);
30 m_prismThickness = stream.read_f32();
31 m_areaMinPos.
read(stream);
44 m_prismIter =
nullptr;
45 m_cachedPrismArray = m_prismCache.data() - 1;
56KColData::~KColData() =
default;
59void KColData::narrowScopeLocal(
const EGG::Vector3f &pos, f32 radius, KCLTypeMask mask) {
60 m_prismCacheTop = m_prismCache.data();
65 m_cachedRadius = radius;
76 m_prismIter = prismArray;
78 while (checkSphereSingle(
nullptr,
nullptr,
nullptr)) {
81 *(m_prismCacheTop++) = *m_prismIter;
83 if (m_prismCacheTop == m_prismCache.end()) {
93 m_bbox.max.set(-999999.0f);
94 m_bbox.min.set(999999.0f);
96 for (
size_t i = 1; i <
m_prisms.size(); ++i) {
107 m_bbox.min = m_bbox.min.
minimize(vtx1);
108 m_bbox.min = m_bbox.min.
minimize(vtx2);
109 m_bbox.min = m_bbox.min.
minimize(vtx3);
110 m_bbox.max = m_bbox.max.
maximize(vtx1);
111 m_bbox.max = m_bbox.max.
maximize(vtx2);
112 m_bbox.max = m_bbox.max.
maximize(vtx3);
117bool KColData::checkPointCollision(f32 *distOut,
EGG::Vector3f *fnrmOut,
u16 *flagsOut) {
118 return std::isfinite(m_prevPos.y) ? checkPointMovement(distOut, fnrmOut, flagsOut) :
119 checkPoint(distOut, fnrmOut, flagsOut);
123bool KColData::checkSphereCollision(f32 *distOut,
EGG::Vector3f *fnrmOut,
u16 *flagsOut) {
141 while (*++m_prismIter != 0) {
149 m_prismIter =
nullptr;
154bool KColData::checkSphereSingle(f32 *distOut,
EGG::Vector3f *fnrmOut,
u16 *flagsOut) {
159 while (*++m_prismIter != 0) {
160 if (m_prismCacheTop != m_prismCache.begin()) {
161 u16 *puVar10 = m_prismCacheTop - 1;
162 while (*m_prismIter != *puVar10) {
163 if (puVar10-- < m_prismCache.begin()) {
168 if (puVar10 >= m_prismCache.begin()) {
173 const KCollisionPrism &prism =
m_prisms[parse<u16>(*m_prismIter)];
179 m_prismIter =
nullptr;
186 KCLTypeMask typeMask) {
190 m_movement = pos - prevPos;
191 m_typeMask = typeMask;
197 KCLTypeMask typeMask) {
201 m_movement = pos - prevPos;
203 m_typeMask = typeMask;
212 if (!sphere1.isInsideOtherSphere(sphere2)) {
217 m_prismIter = m_cachedPrismArray;
222 m_movement = p1 - p2;
223 m_typeMask = typeMask;
233 const int x = point.x - m_areaMinPos.x;
234 const int y = point.y - m_areaMinPos.y;
235 const int z = point.z - m_areaMinPos.z;
245 const u8 *curBlock =
reinterpret_cast<const u8 *
>(m_blockData);
255 offset = parse<u32>(*
reinterpret_cast<const u32 *
>(curBlock + index));
258 if ((offset & 0x80000000) != 0) {
267 u32 x_shift = ((1 * ((u32)x >> shift)) & 1);
268 u32 y_shift = ((2 * ((u32)y >> shift)) & 2);
269 u32 z_shift = ((4 * ((u32)z >> shift)) & 4);
271 index = 4 * (x_shift | y_shift | z_shift);
275 return reinterpret_cast<const u16 *
>(curBlock + (offset & ~0x80000000));
291 f32 dp = cross.
ps_dot(enrm3);
292 cross *= (height / dp);
294 return cross + vertex1;
302 (
reinterpret_cast<uintptr_t
>(m_blockData) -
reinterpret_cast<uintptr_t
>(m_prismData)) /
312 for (
size_t i = 1; i < prismCount; ++i) {
314 prism.height = stream.read_f32();
315 prism.pos_i = stream.read_u16();
316 prism.fnrm_i = stream.read_u16();
317 prism.enrm1_i = stream.read_u16();
318 prism.enrm2_i = stream.read_u16();
319 prism.enrm3_i = stream.read_u16();
320 prism.attribute = stream.read_u16();
328 size_t normalCount = (
reinterpret_cast<uintptr_t
>(m_prismData) +
sizeof(
KCollisionPrism) -
329 reinterpret_cast<uintptr_t
>(m_nrmData)) /
335 for (
auto &nrm : m_nrms) {
345 (
reinterpret_cast<uintptr_t
>(m_nrmData) -
reinterpret_cast<uintptr_t
>(m_posData)) /
351 for (
auto &vert : m_vertices) {
361template <KColData::CollisionCheckType Type>
365 auto out = [&](f32 dist) {
370 *fnrmOut = m_nrms[prism.fnrm_i];
373 *flagsOut = prism.attribute;
381 if (!(attributeMask & m_typeMask)) {
385 const EGG::Vector3f relativePos = m_pos - m_vertices[prism.pos_i];
389 f32 dist_ca = relativePos.
ps_dot(enrm1);
390 if (m_radius <= dist_ca) {
395 f32 dist_ab = relativePos.
ps_dot(enrm2);
396 if (m_radius <= dist_ab) {
401 f32 dist_bc = relativePos.
ps_dot(enrm3) - prism.height;
402 if (m_radius <= dist_bc) {
407 f32 plane_dist = relativePos.
ps_dot(fnrm);
408 f32 dist_in_plane = m_radius - plane_dist;
409 if (dist_in_plane <= 0.0f) {
413 f32 typeDistance = m_prismThickness;
414 if constexpr (Type == CollisionCheckType::Edge) {
415 typeDistance += m_radius;
418 if (dist_in_plane >= typeDistance) {
422 if constexpr (Type == CollisionCheckType::Movement) {
430 if (dist_ab <= 0.0f && dist_bc <= 0.0f && dist_ca <= 0.0f) {
431 if constexpr (Type == CollisionCheckType::Movement) {
434 if (plane_dist < 0.0f && lastPos.
ps_dot(fnrm) < 0.0f) {
438 return out(dist_in_plane);
442 f32 edge_dist, other_edge_dist;
444 bool swapNorms =
false;
446 if (dist_ab >= dist_ca && dist_ab > dist_bc) {
450 if (dist_ca >= dist_bc) {
452 other_edge_nor = enrm1;
453 other_edge_dist = dist_ca;
457 other_edge_nor = enrm3;
458 other_edge_dist = dist_bc;
461 }
else if (dist_bc >= dist_ca) {
465 if (dist_ab >= dist_ca) {
467 other_edge_nor = enrm2;
468 other_edge_dist = dist_ab;
472 other_edge_nor = enrm1;
473 other_edge_dist = dist_ca;
480 if (dist_bc >= dist_ab) {
482 other_edge_nor = enrm3;
483 other_edge_dist = dist_bc;
487 other_edge_nor = enrm2;
488 other_edge_dist = dist_ab;
493 f32 cos = edge_nor.
ps_dot(other_edge_nor);
495 if (cos * edge_dist > other_edge_dist) {
496 if constexpr (Type == CollisionCheckType::Plane) {
497 if (edge_dist > plane_dist) {
501 sq_dist = m_radius * m_radius - edge_dist * edge_dist;
503 f32 sq_sin = cos * cos - 1.0f;
506 std::swap(edge_dist, other_edge_dist);
510 std::swap(edge_nor, other_edge_nor);
513 f32 t = (cos * edge_dist - other_edge_dist) / sq_sin;
514 f32 s = edge_dist - t * cos;
515 const EGG::Vector3f corner_pos = edge_nor * t + other_edge_nor * s;
518 if constexpr (Type == CollisionCheckType::Plane) {
519 if (cornerDot > plane_dist * plane_dist) {
524 sq_dist = m_radius * m_radius - cornerDot;
527 if (sq_dist < plane_dist * plane_dist || sq_dist <= 0.0f) {
531 f32 dist = EGG::Mathf::sqrt(sq_dist) - plane_dist;
536 if constexpr (Type == CollisionCheckType::Movement) {
539 if (lastPos.
ps_dot(fnrm) < 0.0f) {
552 if (!(attrMask & m_typeMask)) {
556 const EGG::Vector3f relativePos = m_pos - m_vertices[prism.pos_i];
559 f32 dist_ca = relativePos.
ps_dot(enrm1);
560 if (dist_ca >= 0.01f) {
565 f32 dist_ab = relativePos.
ps_dot(enrm2);
566 if (dist_ab >= 0.01f) {
571 f32 dist_bc = relativePos.
ps_dot(enrm3) - prism.height;
572 if (dist_bc >= 0.01f) {
577 f32 plane_dist = relativePos.
ps_dot(fnrm);
578 f32 dist_in_plane = 0.01f - plane_dist;
579 if (dist_in_plane <= 0.0f) {
583 if (m_prismThickness <= dist_in_plane && 0.02f + m_prismThickness <= dist_in_plane) {
592 *distOut = dist_in_plane;
600 *flagsOut = prism.attribute;
619 while (*++m_prismIter != 0) {
627 m_prismIter =
nullptr;
632bool KColData::checkPoint(f32 *distOut,
EGG::Vector3f *fnrmOut,
u16 *attributeOut) {
639 while (*++m_prismIter != 0) {
640 const KCollisionPrism &prism =
m_prisms[parse<u16>(*m_prismIter)];
641 if (checkPointCollision(prism, distOut, fnrmOut, attributeOut,
false)) {
647 m_prismIter =
nullptr;
652bool KColData::checkPointMovement(f32 *distOut, EGG::Vector3f *fnrmOut, u16 *attributeOut) {
659 while (*++m_prismIter != 0) {
660 const KCollisionPrism &prism =
m_prisms[parse<u16>(*m_prismIter)];
661 if (checkPointCollision(prism, distOut, fnrmOut, attributeOut,
true)) {
667 m_prismIter =
nullptr;
671KColData::KCollisionPrism::KCollisionPrism() =
default;
673KColData::KCollisionPrism::KCollisionPrism(f32 height, u16 posIndex, u16 faceNormIndex,
674 u16 edge1NormIndex, u16 edge2NormIndex, u16 edge3NormIndex, u16 attribute)
675 : height(height), pos_i(posIndex), fnrm_i(faceNormIndex), enrm1_i(edge1NormIndex),
676 enrm2_i(edge2NormIndex), enrm3_i(edge3NormIndex), attribute(attribute) {}
678void CollisionInfo::update(f32 now_dist,
const EGG::Vector3f &offset,
const EGG::Vector3f &fnrm,
679 u32 kclAttributeTypeBit) {
680 bbox.min = bbox.min.
minimize(offset);
681 bbox.max = bbox.max.
maximize(offset);
684 updateFloor(now_dist, fnrm);
686 if (wallDist > -std::numeric_limits<f32>::min()) {
687 f32 dot = 1.0f - wallNrm.
ps_dot(fnrm);
688 if (dot > perpendicularity) {
689 perpendicularity = std::min(dot, 1.0f);
693 updateWall(now_dist, fnrm);
698void CollisionInfo::transformInfo(CollisionInfo &rhs,
const EGG::Matrix34f &mtx,
699 const EGG::Vector3f &v) {
700 rhs.bbox.min = mtx.ps_multVector33(rhs.bbox.min);
701 rhs.bbox.max = mtx.ps_multVector33(rhs.bbox.max);
703 EGG::Vector3f min = rhs.bbox.min;
705 rhs.bbox.min = min.
minimize(rhs.bbox.max);
706 rhs.bbox.max = min.
maximize(rhs.bbox.max);
708 bbox.min = bbox.min.
minimize(rhs.bbox.min);
709 bbox.max = bbox.max.
maximize(rhs.bbox.max);
711 if (floorDist < rhs.floorDist) {
712 floorDist = rhs.floorDist;
713 floorNrm = mtx.ps_multVector33(rhs.floorNrm);
716 if (wallDist < rhs.wallDist) {
717 wallDist = rhs.wallDist;
718 wallNrm = mtx.ps_multVector33(rhs.wallNrm);
721 if (movingFloorDist < rhs.floorDist) {
722 movingFloorDist = rhs.floorDist;
726 perpendicularity = std::max(perpendicularity, rhs.perpendicularity);
#define KCL_ATTRIBUTE_TYPE_BIT(x)
Given the full 2 byte KCL flag for a triangle, extracts the "Base Type" portion of the flag.
#define KCL_TYPE_FLOOR
0x20E80FFF - Any KCL that the player or items can drive/land on.
#define KCL_TYPE_DIRECTIONAL
0x05070000
#define KCL_TYPE_WALL
0xD010F000
A stream of data stored in memory.
void narrowPolygon_EachBlock(const u16 *prismArray)
owning_span< KCollisionPrism > m_prisms
Optimizes for time by avoiding unnecessary byteswapping. The Wii doesn't have this problem because bi...
void lookupSphere(f32 radius, const EGG::Vector3f &pos, const EGG::Vector3f &prevPos, KCLTypeMask typeMask)
Sets members in preparation of a subsequent sphere collision check call.
u32 m_areaYWidthMask
The y dimension of the octree's bounding box.
const u16 * searchBlock(const EGG::Vector3f &pos)
Finds the data block corresponding to the provided position.
bool checkSphere(f32 *distOut, EGG::Vector3f *fnrmOut, u16 *flagsOut)
Iterates the list of looked-up triangles to see if we are colliding.
u32 m_areaXWidthMask
The x dimension of the octree's bounding box.
void preloadNormals()
Creates a copy of the normals in memory.
bool checkSphereMovement(f32 *distOut, EGG::Vector3f *fnrmOut, u16 *attributeOut)
Iterates the local data block to check for directional collision.
static EGG::Vector3f GetVertex(f32 height, const EGG::Vector3f &vertex1, const EGG::Vector3f &fnrm, const EGG::Vector3f &enrm3, const EGG::Vector3f &enrm)
Computes a prism vertex based off of the triangle's normal vectors.
u32 m_areaXBlocksShift
Used to initialize octree navigation.
u32 m_blockWidthShift
Used to initialize octree navigation.
f32 m_sphereRadius
Clamps the sphere we check collision against.
void preloadPrisms()
Creates a copy of the prisms in memory.
void lookupPoint(const EGG::Vector3f &pos, const EGG::Vector3f &prevPos, KCLTypeMask typeMask)
Sets members in preparation of a subsequent point collision check call.
void computeBBox()
Calculates a EGG::BoundBox3f that describes the boundary of the track's KCL.
void preloadVertices()
Creates a copy of the vertices in memory.
u32 m_areaZWidthMask
The z dimension of the octree's bounding box.
u32 m_areaXYBlocksShift
Used to initialize octree navigation.
bool checkCollision(const KCollisionPrism &prism, f32 *distOut, EGG::Vector3f *fnrmOut, u16 *flagsOut)
This is a combination of the three collision checks in the base game.
A contiguous storage container that manages the lifecycle of a buffer of a given size.
Represents a sphere in 3D space.
void read(Stream &stream)
Initializes a Vector3f by reading 12 bytes from the stream.
constexpr Vector3f minimize(const Vector3f &rhs) const
Returns a vector whose elements are the min of the elements of both vectors.
constexpr f32 ps_dot() const
Paired-singles dot product implementation.
constexpr Vector3f maximize(const Vector3f &rhs) const
Returns a vector whose elements are the max of the elements of both vectors.
constexpr f32 dot(const Vector3f &rhs) const
The dot product between two vectors.
constexpr f32 ps_squareMag() const
Differs from ps_dot due to variation in which operands are fused.