From e243f7ceba240b2a004eef6a786bb9dbcfc0b136 Mon Sep 17 00:00:00 2001 From: Cry dsch Date: Tue, 1 Jun 2021 17:35:46 +0200 Subject: [PATCH] review collisions ray-box and ray-sphere --- examples/models/models_mesh_picking.c | 72 ++++++++++------ src/models.c | 118 +++++++++++++++----------- src/raylib.h | 17 ++-- 3 files changed, 120 insertions(+), 87 deletions(-) diff --git a/examples/models/models_mesh_picking.c b/examples/models/models_mesh_picking.c index 5dda8df5c..58371f634 100644 --- a/examples/models/models_mesh_picking.c +++ b/examples/models/models_mesh_picking.c @@ -41,16 +41,18 @@ int main(void) Vector3 towerPos = { 0.0f, 0.0f, 0.0f }; // Set model position BoundingBox towerBBox = GetMeshBoundingBox(tower.meshes[0]); // Get mesh bounding box - bool hitMeshBBox = false; - bool hitTriangle = false; // Test triangle - Vector3 ta = (Vector3){ -25.0, 0.5, 0.0 }; - Vector3 tb = (Vector3){ -4.0, 2.5, 1.0 }; - Vector3 tc = (Vector3){ -8.0, 6.5, 0.0 }; + Vector3 ta = (Vector3){ -25.0f, 0.5f, 0.0f }; + Vector3 tb = (Vector3){ -4.0f, 2.5f, 1.0f }; + Vector3 tc = (Vector3){ -8.0f, 6.5f, 0.0f }; Vector3 bary = { 0.0f, 0.0f, 0.0f }; + // Test sphere + Vector3 sp = (Vector3){ -30.0f, 5.0f, 5.0f }; + float sr = 4.0f; + SetCameraMode(camera, CAMERA_FREE); // Set a free camera mode SetTargetFPS(60); // Set our game to run at 60 frames-per-second @@ -63,59 +65,67 @@ int main(void) UpdateCamera(&camera); // Update camera // Display information about closest hit - RayHitInfo nearestHit = { 0 }; + RayCollisionInfo nearestHit = { 0 }; char *hitObjectName = "None"; nearestHit.distance = FLT_MAX; nearestHit.hit = false; Color cursorColor = WHITE; - // Get ray and test against ground, triangle, and mesh + // Get ray and test against objects ray = GetMouseRay(GetMousePosition(), camera); - // Check ray collision aginst ground plane - RayHitInfo groundHitInfo = GetCollisionRayGround(ray, 0.0f); + // Check ray collision against ground plane + /*RayCollisionInfo groundHitInfo = GetCollisionRayQuad(ray, 0.0f); if ((groundHitInfo.hit) && (groundHitInfo.distance < nearestHit.distance)) { nearestHit = groundHitInfo; cursorColor = GREEN; hitObjectName = "Ground"; - } + }*/ // Check ray collision against test triangle - RayHitInfo triHitInfo = GetCollisionRayTriangle(ray, ta, tb, tc); + RayCollisionInfo triHitInfo = GetCollisionRayTriangle(ray, ta, tb, tc); if ((triHitInfo.hit) && (triHitInfo.distance < nearestHit.distance)) { nearestHit = triHitInfo; - cursorColor = PURPLE; + cursorColor = ORANGE; hitObjectName = "Triangle"; bary = Vector3Barycenter(nearestHit.position, ta, tb, tc); - hitTriangle = true; } - else hitTriangle = false; - - RayHitInfo meshHitInfo = { 0 }; + + // Check ray collision against test sphere + RayCollisionInfo sphHitInfo = GetCollisionRaySphere(ray, sp, sr); + + if ((sphHitInfo.hit) && (sphHitInfo.distance < nearestHit.distance)) { + nearestHit = sphHitInfo; + cursorColor = ORANGE; + hitObjectName = "Sphere"; + } // Check ray collision against bounding box first, before trying the full ray-mesh test - if (CheckCollisionRayBox(ray, towerBBox)) + // Note: distance becomes negative if ray.position is inside the box! + RayCollisionInfo boxHitInfo = GetCollisionRayBox(ray, towerBBox); + + if ((boxHitInfo.hit) && (boxHitInfo.distance < nearestHit.distance)) { - hitMeshBBox = true; + nearestHit = boxHitInfo; + cursorColor = PURPLE; + hitObjectName = "Box"; // Check ray collision against model // NOTE: It considers model.transform matrix! - meshHitInfo = GetCollisionRayModel(ray, tower); + RayCollisionInfo meshHitInfo = GetCollisionRayModel(ray, tower); - if ((meshHitInfo.hit) && (meshHitInfo.distance < nearestHit.distance)) + if (meshHitInfo.hit) { nearestHit = meshHitInfo; cursorColor = ORANGE; hitObjectName = "Mesh"; } } - - hitMeshBBox = false; //---------------------------------------------------------------------------------- // Draw @@ -136,8 +146,11 @@ int main(void) DrawLine3D(tb, tc, PURPLE); DrawLine3D(tc, ta, PURPLE); + // Draw the test sphere + DrawSphereWires(sp, sr, 8, 8, PURPLE); + // Draw the mesh bbox if we hit it - if (hitMeshBBox) DrawBoundingBox(towerBBox, LIME); + if (boxHitInfo.hit) DrawBoundingBox(towerBBox, LIME); // If we hit something, draw the cursor at the hit point if (nearestHit.hit) @@ -152,9 +165,13 @@ int main(void) DrawLine3D(nearestHit.position, normalEnd, RED); } - - DrawRay(ray, MAROON); - + float scale = 10000; + DrawLine3D(Vector3Add(ray.position, Vector3Scale(ray.direction, 10)), Vector3Add(ray.position, Vector3Scale(ray.direction, scale)), RED); + Vector3 p1 = { 0, 0, 0 }; + Vector3 p2 = { 10, 10, 10 }; + DrawLine3D(ray.position, p1, RED); + DrawRay(ray, MAROON); // TODO + DrawGrid(10, 10.0f); EndMode3D(); @@ -167,6 +184,7 @@ int main(void) int ypos = 70; DrawText(TextFormat("Distance: %3.2f", nearestHit.distance), 10, ypos, 10, BLACK); + if (nearestHit.distance < 0) DrawText("(Inside Box)", 100, ypos, 10, BLACK); DrawText(TextFormat("Hit Pos: %3.2f %3.2f %3.2f", nearestHit.position.x, @@ -178,7 +196,7 @@ int main(void) nearestHit.normal.y, nearestHit.normal.z), 10, ypos + 30, 10, BLACK); - if (hitTriangle) DrawText(TextFormat("Barycenter: %3.2f %3.2f %3.2f", bary.x, bary.y, bary.z), 10, ypos + 45, 10, BLACK); + if (triHitInfo.hit) DrawText(TextFormat("Barycenter: %3.2f %3.2f %3.2f", bary.x, bary.y, bary.z), 10, ypos + 45, 10, BLACK); } DrawText("Use Mouse to Move Camera", 10, 430, 10, GRAY); diff --git a/src/models.c b/src/models.c index b9d24e101..c992095f8 100644 --- a/src/models.c +++ b/src/models.c @@ -2980,71 +2980,88 @@ bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius) } // Detect collision between ray and sphere -bool CheckCollisionRaySphere(Ray ray, Vector3 center, float radius) +RayCollisionInfo GetCollisionRaySphere(Ray ray, Vector3 center, float radius) { - bool collision = false; - + RayCollisionInfo result = { 0 }; + Vector3 raySpherePos = Vector3Subtract(center, ray.position); - float distance = Vector3Length(raySpherePos); float vector = Vector3DotProduct(raySpherePos, ray.direction); - float d = radius*radius - (distance*distance - vector*vector); - - if (d >= 0.0f) collision = true; - - return collision; -} - -// Detect collision between ray and sphere with extended parameters and collision point detection -bool CheckCollisionRaySphereEx(Ray ray, Vector3 center, float radius, Vector3 *collisionPoint) -{ - bool collision = false; - - Vector3 raySpherePos = Vector3Subtract(center, ray.position); float distance = Vector3Length(raySpherePos); - float vector = Vector3DotProduct(raySpherePos, ray.direction); - float d = radius*radius - (distance*distance - vector*vector); + float d = radius*radius - (distance * distance - vector*vector); - if (d >= 0.0f) collision = true; + result.hit = d >= 0.0f; // Check if ray origin is inside the sphere to calculate the correct collision point - float collisionDistance = 0; + if (distance < radius) { // inside + result.distance = vector + sqrtf(d); - if (distance < radius) collisionDistance = vector + sqrtf(d); - else collisionDistance = vector - sqrtf(d); + // Calculate collision point + result.position = Vector3Add(ray.position, Vector3Scale(ray.direction, result.distance)); - // Calculate collision point - Vector3 cPoint = Vector3Add(ray.position, Vector3Scale(ray.direction, collisionDistance)); + // Calculate collision normal (pointing outwards) + result.normal = Vector3Negate(Vector3Normalize(Vector3Subtract(result.position, center))); + } else { // outside + result.distance = vector - sqrtf(d); - collisionPoint->x = cPoint.x; - collisionPoint->y = cPoint.y; - collisionPoint->z = cPoint.z; + // Calculate collision point + result.position = Vector3Add(ray.position, Vector3Scale(ray.direction, result.distance)); - return collision; + // Calculate collision normal (pointing inwards) + result.normal = Vector3Normalize(Vector3Subtract(result.position, center)); + } + + return result; } // Detect collision between ray and bounding box -bool CheckCollisionRayBox(Ray ray, BoundingBox box) +// Note: Returns a negative distance if ray.position is inside the box! +RayCollisionInfo GetCollisionRayBox(Ray ray, BoundingBox box) { - bool collision = false; + RayCollisionInfo result = { 0 }; - float t[8] = { 0 }; - t[0] = (box.min.x - ray.position.x)/ray.direction.x; - t[1] = (box.max.x - ray.position.x)/ray.direction.x; - t[2] = (box.min.y - ray.position.y)/ray.direction.y; - t[3] = (box.max.y - ray.position.y)/ray.direction.y; - t[4] = (box.min.z - ray.position.z)/ray.direction.z; - t[5] = (box.max.z - ray.position.z)/ray.direction.z; + float t[11] = { 0 }; + + t[8] = 1.0f / ray.direction.x; + t[9] = 1.0f / ray.direction.y; + t[10] = 1.0f / ray.direction.z; + + t[0] = (box.min.x - ray.position.x) * t[8]; + t[1] = (box.max.x - ray.position.x) * t[8]; + t[2] = (box.min.y - ray.position.y) * t[9]; + t[3] = (box.max.y - ray.position.y) * t[9]; + t[4] = (box.min.z - ray.position.z) * t[10]; + t[5] = (box.max.z - ray.position.z) * t[10]; t[6] = (float)fmax(fmax(fmin(t[0], t[1]), fmin(t[2], t[3])), fmin(t[4], t[5])); t[7] = (float)fmin(fmin(fmax(t[0], t[1]), fmax(t[2], t[3])), fmax(t[4], t[5])); - collision = !(t[7] < 0 || t[6] > t[7]); + result.distance = t[6]; + result.hit = !(t[7] < 0 || t[6] > t[7]); + result.position = Vector3Add(ray.position, Vector3Scale(ray.direction, result.distance)); - return collision; + // get box center point + result.normal = Vector3Lerp(box.min, box.max, 0.5f); + // get vector center point->hit point + result.normal = Vector3Subtract(result.position, result.normal); + // scale vector to unit cube + // we use an additional .01 to fix numerical errors + result.normal = Vector3Scale(result.normal, 2.01f); + result.normal = Vector3Divide(result.normal, Vector3Subtract(box.max, box.min)); + // the relevant elemets of the vector are now slightly larger than 1f (or smaller than -1f) + // and the others are somewhere between -1 and 1. + // casting to int is exactly our wanted normal! + result.normal.x = (int)result.normal.x; + result.normal.y = (int)result.normal.y; + result.normal.z = (int)result.normal.z; + + result.normal = Vector3Normalize(result.normal); + + return result; } + // Get collision info between ray and mesh -RayHitInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform) +RayCollisionInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform) { - RayHitInfo result = { 0 }; + RayCollisionInfo result = { 0 }; // Check if mesh vertex data on CPU for testing if (mesh.vertices != NULL) @@ -3074,7 +3091,7 @@ RayHitInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform) b = Vector3Transform(b, transform); c = Vector3Transform(c, transform); - RayHitInfo triHitInfo = GetCollisionRayTriangle(ray, a, b, c); + RayCollisionInfo triHitInfo = GetCollisionRayTriangle(ray, a, b, c); if (triHitInfo.hit) { @@ -3087,13 +3104,13 @@ RayHitInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform) } // Get collision info between ray and model -RayHitInfo GetCollisionRayModel(Ray ray, Model model) +RayCollisionInfo GetCollisionRayModel(Ray ray, Model model) { - RayHitInfo result = { 0 }; + RayCollisionInfo result = { 0 }; for (int m = 0; m < model.meshCount; m++) { - RayHitInfo meshHitInfo = GetCollisionRayMesh(ray, model.meshes[m], model.transform); + RayCollisionInfo meshHitInfo = GetCollisionRayMesh(ray, model.meshes[m], model.transform); if (meshHitInfo.hit) { @@ -3107,14 +3124,14 @@ RayHitInfo GetCollisionRayModel(Ray ray, Model model) // Get collision info between ray and triangle // NOTE: Based on https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm -RayHitInfo GetCollisionRayTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3) +RayCollisionInfo GetCollisionRayTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3) { #define EPSILON 0.000001 // A small number Vector3 edge1, edge2; Vector3 p, q, tv; float det, invDet, u, v, t; - RayHitInfo result = {0}; + RayCollisionInfo result = {0}; // Find vectors for two edges sharing V1 edge1 = Vector3Subtract(p2, p1); @@ -3156,7 +3173,6 @@ RayHitInfo GetCollisionRayTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3) // Ray hit, get hit point and normal result.hit = true; result.distance = t; - result.hit = true; result.normal = Vector3Normalize(Vector3CrossProduct(edge1, edge2)); result.position = Vector3Add(ray.position, Vector3Scale(ray.direction, t)); } @@ -3165,11 +3181,11 @@ RayHitInfo GetCollisionRayTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3) } // Get collision info between ray and ground plane (Y-normal plane) -RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight) +RayCollisionInfo GetCollisionRayGround(Ray ray, float groundHeight) { #define EPSILON 0.000001 // A small number - RayHitInfo result = { 0 }; + RayCollisionInfo result = { 0 }; if (fabsf(ray.direction.y) > EPSILON) { diff --git a/src/raylib.h b/src/raylib.h index b0b6b9f66..755a581d5 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -403,12 +403,12 @@ typedef struct Ray { } Ray; // Raycast hit information -typedef struct RayHitInfo { +typedef struct RayCollisionInfo { bool hit; // Did the ray hit something? float distance; // Distance to nearest hit Vector3 position; // Position of nearest hit Vector3 normal; // Surface normal of hit -} RayHitInfo; +} RayCollisionInfo; // Bounding box type typedef struct BoundingBox { @@ -1451,13 +1451,12 @@ RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Detect collision between two spheres RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Detect collision between two bounding boxes RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Detect collision between box and sphere -RLAPI bool CheckCollisionRaySphere(Ray ray, Vector3 center, float radius); // Detect collision between ray and sphere -RLAPI bool CheckCollisionRaySphereEx(Ray ray, Vector3 center, float radius, Vector3 *collisionPoint); // Detect collision between ray and sphere, returns collision point -RLAPI bool CheckCollisionRayBox(Ray ray, BoundingBox box); // Detect collision between ray and box -RLAPI RayHitInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh -RLAPI RayHitInfo GetCollisionRayModel(Ray ray, Model model); // Get collision info between ray and model -RLAPI RayHitInfo GetCollisionRayTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle -RLAPI RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight); // Get collision info between ray and ground plane (Y-normal plane) +RLAPI RayCollisionInfo GetCollisionRaySphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere +RLAPI RayCollisionInfo GetCollisionRayBox(Ray ray, BoundingBox box); // Get collision info between ray and box +RLAPI RayCollisionInfo GetCollisionRayModel(Ray ray, Model model); // Get collision info between ray and model +RLAPI RayCollisionInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh +RLAPI RayCollisionInfo GetCollisionRayTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle +RLAPI RayCollisionInfo GetCollisionRayQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad //------------------------------------------------------------------------------------ // Audio Loading and Playing Functions (Module: audio)