diff --git a/examples/example_basic_6.c b/examples/example_basic_6.c index dddce676..4203405e 100644 --- a/examples/example_basic_6.c +++ b/examples/example_basic_6.c @@ -47,6 +47,7 @@ Index of this file: #include "pl_draw_ext.h" #include "pl_starter_ext.h" #include "pl_collision_ext.h" +#include "pl_gjk_ext.h" #include "pl_screen_log_ext.h" //----------------------------------------------------------------------------- @@ -95,6 +96,7 @@ const plGraphicsI* gptGfx = NULL; const plDrawI* gptDraw = NULL; const plStarterI* gptStarter = NULL; const plCollisionI* gptCollision = NULL; +const plGjkI* gptGjk = NULL; const plScreenLogI* gptScreenLog = NULL; //----------------------------------------------------------------------------- @@ -133,6 +135,7 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) gptDraw = pl_get_api_latest(ptApiRegistry, plDrawI); gptStarter = pl_get_api_latest(ptApiRegistry, plStarterI); gptCollision = pl_get_api_latest(ptApiRegistry, plCollisionI); + gptGjk = pl_get_api_latest(ptApiRegistry, plGjkI); gptScreenLog = pl_get_api_latest(ptApiRegistry, plScreenLogI); return ptAppData; @@ -159,6 +162,7 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) gptDraw = pl_get_api_latest(ptApiRegistry, plDrawI); gptStarter = pl_get_api_latest(ptApiRegistry, plStarterI); gptCollision = pl_get_api_latest(ptApiRegistry, plCollisionI); + gptGjk = pl_get_api_latest(ptApiRegistry, plGjkI); gptScreenLog = pl_get_api_latest(ptApiRegistry, plScreenLogI); // use window API to create a window @@ -275,13 +279,19 @@ pl_app_update(plAppData* ptAppData) // simple variation setting to check things (until gizmo extension is stable) static uint32_t uVariation = 0; - if(gptIO->is_key_pressed(PL_KEY_V, false)) + if(gptIO->is_key_pressed(PL_KEY_V, false) && uVariation < 3) uVariation++; - if(gptIO->is_key_pressed(PL_KEY_B, false)) + if(gptIO->is_key_pressed(PL_KEY_B, false) && uVariation > 0) uVariation--; - // use screen log extension to display variation + // toggle GJK mode + static bool bUseGjk = false; + if(gptIO->is_key_pressed(PL_KEY_G, false)) + bUseGjk = !bUseGjk; + + // use screen log extension to display variation and mode gptScreenLog->add_message_ex(117, 0, PL_COLOR_32_WHITE, 2.0, "Variation: %u", uVariation); + gptScreenLog->add_message_ex(118, 0, PL_COLOR_32_WHITE, 2.0, "Mode: %s (G to toggle)", bUseGjk ? "GJK" : "Collision Ext"); // sphere <-> sphere collision { @@ -303,19 +313,33 @@ pl_app_update(plAppData* ptAppData) uint32_t uColor0 = PL_COLOR_32_RED; uint32_t uColor1 = PL_COLOR_32_RED; - if(gptCollision->sphere_sphere(&tSphere0, &tSphere1)) - uColor0 = PL_COLOR_32_GREEN; - - plCollisionInfo tInfo = {0}; - if(gptCollision->pen_sphere_sphere(&tSphere0, &tSphere1, &tInfo)) + if(bUseGjk) { - uColor1 = PL_COLOR_32_GREEN; - gptDraw->add_3d_cross(ptAppData->pt3dDrawlist, tInfo.tPoint, 0.1f, (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_RED}); - gptDraw->add_3d_line(ptAppData->pt3dDrawlist, tInfo.tPoint, pl_add_vec3(tInfo.tPoint, pl_mul_vec3_scalarf(tInfo.tNormal, tInfo.fPenetration)), (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_GREEN}); + plGjkCollisionInfo tInfo = {0}; + if(gptGjk->pen(pl_gjk_support_sphere, &tSphere0, pl_gjk_support_sphere, &tSphere1, &tInfo)) + { + uColor0 = PL_COLOR_32_GREEN; + uColor1 = PL_COLOR_32_GREEN; + gptDraw->add_3d_cross(ptAppData->pt3dDrawlist, tInfo.tPoint, 0.1f, (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_RED}); + gptDraw->add_3d_line(ptAppData->pt3dDrawlist, tInfo.tPoint, pl_add_vec3(tInfo.tPoint, pl_mul_vec3_scalarf(tInfo.tNormal, tInfo.fPenetration)), (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_GREEN}); + } + } + else + { + if(gptCollision->sphere_sphere(&tSphere0, &tSphere1)) + uColor0 = PL_COLOR_32_GREEN; + + plCollisionInfo tInfo = {0}; + if(gptCollision->pen_sphere_sphere(&tSphere0, &tSphere1, &tInfo)) + { + uColor1 = PL_COLOR_32_GREEN; + gptDraw->add_3d_cross(ptAppData->pt3dDrawlist, tInfo.tPoint, 0.1f, (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_RED}); + gptDraw->add_3d_line(ptAppData->pt3dDrawlist, tInfo.tPoint, pl_add_vec3(tInfo.tPoint, pl_mul_vec3_scalarf(tInfo.tNormal, tInfo.fPenetration)), (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_GREEN}); + } } gptDraw->add_3d_band_xy_filled(ptAppData->pt3dDrawlist, (plVec3){2.0f, 4.0f, 0.0f}, 0.05f, 0.25f, 0, (plDrawSolidOptions){.uColor = uColor0}); - gptDraw->add_3d_band_xy_filled(ptAppData->pt3dDrawlist, (plVec3){2.0f, 4.5f, 0.0f}, 0.05f, 0.25f, 0, (plDrawSolidOptions){.uColor = uColor1}); + gptDraw->add_3d_band_xy_filled(ptAppData->pt3dDrawlist, (plVec3){2.0f, 4.5f, 0.0f}, 0.05f, 0.25f, 0, (plDrawSolidOptions){.uColor = uColor1}); } // box <-> sphere collision @@ -334,26 +358,97 @@ pl_app_update(plAppData* ptAppData) if(uVariation == 2) tSphere1.tCenter = (plVec3){5.2f, 2.9f, 0.0f}; if(uVariation == 3) tSphere1.tCenter = (plVec3){4.3f, 2.0f, 0.0f}; gptDraw->add_3d_sphere(ptAppData->pt3dDrawlist, tSphere1, 0, 0, (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_CYAN}); - + uint32_t uColor0 = PL_COLOR_32_RED; uint32_t uColor1 = PL_COLOR_32_RED; - if(gptCollision->box_sphere(&tBox0, &tSphere1)) - uColor0 = PL_COLOR_32_GREEN; + if(bUseGjk) + { + plGjkCollisionInfo tInfo = {0}; + if(gptGjk->pen(pl_gjk_support_box, &tBox0, pl_gjk_support_sphere, &tSphere1, &tInfo)) + { + uColor0 = PL_COLOR_32_GREEN; + uColor1 = PL_COLOR_32_GREEN; + gptDraw->add_3d_cross(ptAppData->pt3dDrawlist, tInfo.tPoint, 0.1f, (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_RED}); + gptDraw->add_3d_line(ptAppData->pt3dDrawlist, tInfo.tPoint, pl_add_vec3(tInfo.tPoint, pl_mul_vec3_scalarf(tInfo.tNormal, tInfo.fPenetration)), (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_GREEN}); + } + } + else + { + if(gptCollision->box_sphere(&tBox0, &tSphere1)) + uColor0 = PL_COLOR_32_GREEN; + + plCollisionInfo tInfo = {0}; + if(gptCollision->pen_box_sphere(&tBox0, &tSphere1, &tInfo)) + { + uColor1 = PL_COLOR_32_GREEN; + gptDraw->add_3d_cross(ptAppData->pt3dDrawlist, tInfo.tPoint, 0.1f, (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_RED}); + gptDraw->add_3d_line(ptAppData->pt3dDrawlist, tInfo.tPoint, pl_add_vec3(tInfo.tPoint, pl_mul_vec3_scalarf(tInfo.tNormal, tInfo.fPenetration)), (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_GREEN}); + } + } - plCollisionInfo tInfo = {0}; - if(gptCollision->pen_box_sphere(&tBox0, &tSphere1, &tInfo)) + gptDraw->add_3d_band_xy_filled(ptAppData->pt3dDrawlist, (plVec3){5.0f, 4.0f, 0.0f}, 0.05f, 0.25f, 0, (plDrawSolidOptions){.uColor = uColor0}); + gptDraw->add_3d_band_xy_filled(ptAppData->pt3dDrawlist, (plVec3){5.0f, 4.5f, 0.0f}, 0.05f, 0.25f, 0, (plDrawSolidOptions){.uColor = uColor1}); + } + + // convex poly (icosahedron) <-> sphere collision (GJK only) + { + static const float fPhi = 1.618033988f; // golden ratio + static const plVec3 tPolyCenter = {9.0f, 2.0f, 0.0f}; + static const float fScale = 0.5f; + + // icosahedron vertices (centered at origin, then offset) + plVec3 atIcoVerts[12] = { + {-1, fPhi, 0}, { 1, fPhi, 0}, {-1, -fPhi, 0}, { 1, -fPhi, 0}, + { 0, -1, fPhi}, { 0, 1, fPhi}, { 0, -1, -fPhi}, { 0, 1, -fPhi}, + { fPhi, 0, -1}, { fPhi, 0, 1}, {-fPhi, 0, -1}, {-fPhi, 0, 1} + }; + for (int ii = 0; ii < 12; ii++) { + atIcoVerts[ii].x = atIcoVerts[ii].x * fScale + tPolyCenter.x; + atIcoVerts[ii].y = atIcoVerts[ii].y * fScale + tPolyCenter.y; + atIcoVerts[ii].z = atIcoVerts[ii].z * fScale + tPolyCenter.z; + } + + plConvexPoly tPoly = { + .pVertices = atIcoVerts, + .iVertexCount = 12 + }; + + // draw icosahedron edges + static const int aiEdges[][2] = { + {0,1},{0,5},{0,7},{0,10},{0,11},{1,5},{1,7},{1,8},{1,9}, + {2,3},{2,4},{2,6},{2,10},{2,11},{3,4},{3,6},{3,8},{3,9}, + {4,5},{4,9},{4,11},{5,9},{5,11},{6,7},{6,8},{6,10},{7,8}, + {7,10},{8,9},{10,11} + }; + for (int ii = 0; ii < 30; ii++) + gptDraw->add_3d_line(ptAppData->pt3dDrawlist, atIcoVerts[aiEdges[ii][0]], atIcoVerts[aiEdges[ii][1]], (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_WHITE}); + + plSphere tSphere1 = { + .fRadius = 0.5f, + .tCenter = {8.1f, 2.0f, 0.0f} + }; + if(uVariation == 1) tSphere1.tCenter = (plVec3){8.5f, 2.0f, 0.0f}; + if(uVariation == 2) tSphere1.tCenter = (plVec3){9.0f, 2.8f, 0.0f}; + if(uVariation == 3) tSphere1.tCenter = (plVec3){7.5f, 2.0f, 0.0f}; + gptDraw->add_3d_sphere(ptAppData->pt3dDrawlist, tSphere1, 0, 0, (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_CYAN}); + + uint32_t uColor0 = PL_COLOR_32_RED; + uint32_t uColor1 = PL_COLOR_32_RED; + + plGjkCollisionInfo tInfo = {0}; + if(gptGjk->pen(pl_gjk_support_convex_poly, &tPoly, pl_gjk_support_sphere, &tSphere1, &tInfo)) { + uColor0 = PL_COLOR_32_GREEN; uColor1 = PL_COLOR_32_GREEN; gptDraw->add_3d_cross(ptAppData->pt3dDrawlist, tInfo.tPoint, 0.1f, (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_RED}); gptDraw->add_3d_line(ptAppData->pt3dDrawlist, tInfo.tPoint, pl_add_vec3(tInfo.tPoint, pl_mul_vec3_scalarf(tInfo.tNormal, tInfo.fPenetration)), (plDrawLineOptions){.fThickness = 0.01f, .uColor = PL_COLOR_32_GREEN}); } - gptDraw->add_3d_band_xy_filled(ptAppData->pt3dDrawlist, (plVec3){5.0f, 4.0f, 0.0f}, 0.05f, 0.25f, 0, (plDrawSolidOptions){.uColor = uColor0}); - gptDraw->add_3d_band_xy_filled(ptAppData->pt3dDrawlist, (plVec3){5.0f, 4.5f, 0.0f}, 0.05f, 0.25f, 0, (plDrawSolidOptions){.uColor = uColor1}); + gptDraw->add_3d_band_xy_filled(ptAppData->pt3dDrawlist, (plVec3){8.0f, 4.0f, 0.0f}, 0.05f, 0.25f, 0, (plDrawSolidOptions){.uColor = uColor0}); + gptDraw->add_3d_band_xy_filled(ptAppData->pt3dDrawlist, (plVec3){8.0f, 4.5f, 0.0f}, 0.05f, 0.25f, 0, (plDrawSolidOptions){.uColor = uColor1}); } - // start main pass & return the encoder being used plRenderEncoder* ptEncoder = gptStarter->begin_main_pass(); diff --git a/extensions/pl_gjk_ext.c b/extensions/pl_gjk_ext.c new file mode 100644 index 00000000..e14bc5c9 --- /dev/null +++ b/extensions/pl_gjk_ext.c @@ -0,0 +1,456 @@ +/* + pl_gjk_ext.c +*/ + +/* +Index of this file: +// [SECTION] includes +// [SECTION] internal api +// [SECTION] public api implementation +// [SECTION] extension loading +*/ + +//----------------------------------------------------------------------------- +// [SECTION] includes +//----------------------------------------------------------------------------- + +#include +#include +#include "pl.h" +#include "pl_gjk_ext.h" + +#define PL_MATH_INCLUDE_FUNCTIONS +#include "pl_math.h" + +#ifdef PL_UNITY_BUILD + #include "pl_unity_ext.inc" +#else + static const plMemoryI* gptMemory = NULL; + #define PL_ALLOC(x) gptMemory->tracked_realloc(NULL, (x), __FILE__, __LINE__) + #define PL_REALLOC(x, y) gptMemory->tracked_realloc((x), (y), __FILE__, __LINE__) + #define PL_FREE(x) gptMemory->tracked_realloc((x), 0, __FILE__, __LINE__) + + #ifndef PL_DS_ALLOC + #define PL_DS_ALLOC(x) gptMemory->tracked_realloc(NULL, (x), __FILE__, __LINE__) + #define PL_DS_ALLOC_INDIRECT(x, FILE, LINE) gptMemory->tracked_realloc(NULL, (x), FILE, LINE) + #define PL_DS_FREE(x) gptMemory->tracked_realloc((x), 0, __FILE__, __LINE__) + #endif +#endif + +//----------------------------------------------------------------------------- +// [SECTION] internal api +//----------------------------------------------------------------------------- + +typedef struct _plGjkVertex +{ + plVec3 tMinkowski; + plVec3 tShape1; + plVec3 tShape2; +} plGjkVertex; + +typedef struct _plGjkFace +{ + plVec3 tNormal; + float fDist; + int aiIndices[3]; +} plGjkFace; + +static plGjkVertex +pl__support_minkowski_diff(plGjkSupportFunc tFn1, const void* pShape1, plGjkSupportFunc tFn2, const void* pShape2, plVec3 tDir) +{ + plVec3 tNegDir = pl_mul_vec3_scalarf(tDir, -1.0f); + plVec3 tVert1 = tFn1(pShape1, tDir); + plVec3 tVert2 = tFn2(pShape2, tNegDir); + plGjkVertex tResult = { + .tMinkowski = pl_sub_vec3(tVert1, tVert2), + .tShape1 = tVert1, + .tShape2 = tVert2 + }; + return tResult; +} + +static bool +pl__update_simplex(plGjkVertex atSimplex[4], int *piSimplexCount, plVec3 *ptDir) +{ + switch (*piSimplexCount) { + case 2: { // line case + plVec3 tA = atSimplex[1].tMinkowski; + plVec3 tB = atSimplex[0].tMinkowski; + plVec3 tAB = pl_sub_vec3(tB, tA); + plVec3 tAO = pl_mul_vec3_scalarf(tA, -1.0f); + + if (pl_dot_vec3(tAB, tAO) > 0.0f) { + *ptDir = pl_cross_vec3(pl_cross_vec3(tAB, tAO), tAB); + } else { + atSimplex[0] = atSimplex[1]; + *piSimplexCount = 1; + *ptDir = tAO; + } + return false; + } + + case 3: { // triangle case + plGjkVertex tA = atSimplex[2]; + plGjkVertex tB = atSimplex[1]; + plGjkVertex tC = atSimplex[0]; + plVec3 tAB = pl_sub_vec3(tB.tMinkowski, tA.tMinkowski); + plVec3 tAC = pl_sub_vec3(tC.tMinkowski, tA.tMinkowski); + plVec3 tAO = pl_mul_vec3_scalarf(tA.tMinkowski, -1.0f); + plVec3 tABC = pl_cross_vec3(tAB, tAC); + + if (pl_dot_vec3(pl_cross_vec3(tABC, tAC), tAO) > 0.0f) { + if (pl_dot_vec3(tAC, tAO) > 0.0f) { + atSimplex[0] = tC; + atSimplex[1] = tA; + *piSimplexCount = 2; + *ptDir = pl_cross_vec3(pl_cross_vec3(tAC, tAO), tAC); + } else { + atSimplex[0] = tA; + *piSimplexCount = 1; + if (pl_dot_vec3(tAB, tAO) > 0.0f) { + atSimplex[1] = tB; + *piSimplexCount = 2; + *ptDir = pl_cross_vec3(pl_cross_vec3(tAB, tAO), tAB); + } else { + *ptDir = tAO; + } + } + } else { + if (pl_dot_vec3(pl_cross_vec3(tAB, tABC), tAO) > 0.0f) { + atSimplex[0] = tA; + *piSimplexCount = 1; + if (pl_dot_vec3(tAB, tAO) > 0.0f) { + atSimplex[1] = tB; + *piSimplexCount = 2; + *ptDir = pl_cross_vec3(pl_cross_vec3(tAB, tAO), tAB); + } else { + *ptDir = tAO; + } + } else { + if (pl_dot_vec3(tABC, tAO) > 0.0f) { + *ptDir = tABC; + } else { + // flip winding + atSimplex[0] = tB; + atSimplex[1] = tC; + *ptDir = pl_mul_vec3_scalarf(tABC, -1.0f); + } + } + } + return false; + } + + case 4: { // tetrahedron case + plGjkVertex tA = atSimplex[3]; + plGjkVertex tB = atSimplex[2]; + plGjkVertex tC = atSimplex[1]; + plGjkVertex tD = atSimplex[0]; + plVec3 tAB = pl_sub_vec3(tB.tMinkowski, tA.tMinkowski); + plVec3 tAC = pl_sub_vec3(tC.tMinkowski, tA.tMinkowski); + plVec3 tAD = pl_sub_vec3(tD.tMinkowski, tA.tMinkowski); + plVec3 tAO = pl_mul_vec3_scalarf(tA.tMinkowski, -1.0f); + + plVec3 tABC = pl_cross_vec3(tAB, tAC); + plVec3 tACD = pl_cross_vec3(tAC, tAD); + plVec3 tADB = pl_cross_vec3(tAD, tAB); + + if (pl_dot_vec3(tABC, tAO) > 0.0f) { + atSimplex[0] = tC; + atSimplex[1] = tB; + atSimplex[2] = tA; + *piSimplexCount = 3; + *ptDir = tABC; + return false; + } + + if (pl_dot_vec3(tACD, tAO) > 0.0f) { + atSimplex[0] = tD; + atSimplex[1] = tC; + atSimplex[2] = tA; + *piSimplexCount = 3; + *ptDir = tACD; + return false; + } + + if (pl_dot_vec3(tADB, tAO) > 0.0f) { + atSimplex[0] = tB; + atSimplex[1] = tD; + atSimplex[2] = tA; + *piSimplexCount = 3; + *ptDir = tADB; + return false; + } + + // origin is inside the tetrahedron + return true; + } + + default: + break; + } + return false; +} + +static plGjkFace +pl__epa_make_face(const plGjkVertex* atVerts, int iA, int iB, int iC) +{ + plVec3 tAB = pl_sub_vec3(atVerts[iB].tMinkowski, atVerts[iA].tMinkowski); + plVec3 tAC = pl_sub_vec3(atVerts[iC].tMinkowski, atVerts[iA].tMinkowski); + plVec3 tN = pl_norm_vec3(pl_cross_vec3(tAB, tAC)); + float fD = pl_dot_vec3(tN, atVerts[iA].tMinkowski); + + if (fD < 0.0f) + { + tN = pl_mul_vec3_scalarf(tN, -1.0f); + fD = -fD; + int iTemp = iB; + iB = iC; + iC = iTemp; + } + + plGjkFace tFace = { + .tNormal = tN, + .fDist = fD, + .aiIndices = {iA, iB, iC} + }; + return tFace; +} + +static void +pl__epa_compute_contact(const plGjkVertex* atVerts, const plGjkFace* ptFace, plGjkCollisionInfo* ptInfoOut) +{ + // project origin onto the closest face and compute barycentric coordinates + plVec3 tA = atVerts[ptFace->aiIndices[0]].tMinkowski; + plVec3 tB = atVerts[ptFace->aiIndices[1]].tMinkowski; + plVec3 tC = atVerts[ptFace->aiIndices[2]].tMinkowski; + + plVec3 tAB = pl_sub_vec3(tB, tA); + plVec3 tAC = pl_sub_vec3(tC, tA); + + // origin projected onto face plane + plVec3 tP = pl_mul_vec3_scalarf(ptFace->tNormal, ptFace->fDist); + plVec3 tAP = pl_sub_vec3(tP, tA); + + float fD00 = pl_dot_vec3(tAB, tAB); + float fD01 = pl_dot_vec3(tAB, tAC); + float fD11 = pl_dot_vec3(tAC, tAC); + float fD20 = pl_dot_vec3(tAP, tAB); + float fD21 = pl_dot_vec3(tAP, tAC); + + float fDenom = fD00 * fD11 - fD01 * fD01; + if (fabsf(fDenom) < 1e-10f) + { + ptInfoOut->tPoint = atVerts[ptFace->aiIndices[0]].tShape1; + return; + } + + float fV = (fD11 * fD20 - fD01 * fD21) / fDenom; + float fW = (fD00 * fD21 - fD01 * fD20) / fDenom; + float fU = 1.0f - fV - fW; + + // interpolate shape1 support points using barycentric coordinates + plVec3 tS1A = atVerts[ptFace->aiIndices[0]].tShape1; + plVec3 tS1B = atVerts[ptFace->aiIndices[1]].tShape1; + plVec3 tS1C = atVerts[ptFace->aiIndices[2]].tShape1; + + ptInfoOut->tPoint = pl_add_vec3( + pl_add_vec3( + pl_mul_vec3_scalarf(tS1A, fU), + pl_mul_vec3_scalarf(tS1B, fV)), + pl_mul_vec3_scalarf(tS1C, fW)); +} + +static void +pl__epa(plGjkSupportFunc tFn1, const void* pShape1, + plGjkSupportFunc tFn2, const void* pShape2, + plGjkVertex atSimplexEpa[4], plGjkCollisionInfo* ptInfoOut) +{ + plGjkVertex atVerts[64]; + atVerts[0] = atSimplexEpa[0]; + atVerts[1] = atSimplexEpa[1]; + atVerts[2] = atSimplexEpa[2]; + atVerts[3] = atSimplexEpa[3]; + int iVertCount = 4; + + plGjkFace atFaces[128]; + atFaces[0] = pl__epa_make_face(atVerts, 0, 1, 2); + atFaces[1] = pl__epa_make_face(atVerts, 0, 3, 1); + atFaces[2] = pl__epa_make_face(atVerts, 0, 2, 3); + atFaces[3] = pl__epa_make_face(atVerts, 1, 3, 2); + int iFaceCount = 4; + + for (int ii = 0; ii < 64; ii++) + { + // find closest face to origin + int iClosest = 0; + float fMinDist = atFaces[0].fDist; + for (int jj = 1; jj < iFaceCount; jj++) + { + if (atFaces[jj].fDist < fMinDist) + { + fMinDist = atFaces[jj].fDist; + iClosest = jj; + } + } + + plVec3 tNormal = atFaces[iClosest].tNormal; + + // get new support point along closest face normal + plGjkVertex tSupport = pl__support_minkowski_diff(tFn1, pShape1, tFn2, pShape2, tNormal); + float fSupportDist = pl_dot_vec3(tSupport.tMinkowski, tNormal); + + // converged + if (fSupportDist - fMinDist < 1e-4f) + { + ptInfoOut->fPenetration = fMinDist; + ptInfoOut->tNormal = tNormal; + pl__epa_compute_contact(atVerts, &atFaces[iClosest], ptInfoOut); + + // enforce convention: normal points toward shape1 + plVec3 tS2ToS1 = pl_sub_vec3(atVerts[atFaces[iClosest].aiIndices[0]].tShape1, + atVerts[atFaces[iClosest].aiIndices[0]].tShape2); + if (pl_dot_vec3(ptInfoOut->tNormal, tS2ToS1) < 0.0f) + ptInfoOut->tNormal = pl_mul_vec3_scalarf(ptInfoOut->tNormal, -1.0f); + return; + } + + // add new vertex + if (iVertCount >= 64) break; + int iNewVert = iVertCount++; + atVerts[iNewVert] = tSupport; + + // find and remove faces visible from the new point + // collect unique edges from removed faces + int aiEdges[256][2]; + int iEdgeCount = 0; + + for (int jj = iFaceCount - 1; jj >= 0; jj--) + { + plVec3 tToPoint = pl_sub_vec3(tSupport.tMinkowski, atVerts[atFaces[jj].aiIndices[0]].tMinkowski); + if (pl_dot_vec3(atFaces[jj].tNormal, tToPoint) <= 0.0f) + continue; + + // face is visible, collect edges + for (int kk = 0; kk < 3; kk++) + { + int iA = atFaces[jj].aiIndices[kk]; + int iB = atFaces[jj].aiIndices[(kk + 1) % 3]; + + // check if reverse edge already exists (shared edge) + bool bFound = false; + for (int ee = 0; ee < iEdgeCount; ee++) + { + if (aiEdges[ee][0] == iB && aiEdges[ee][1] == iA) + { + aiEdges[ee][0] = aiEdges[iEdgeCount - 1][0]; + aiEdges[ee][1] = aiEdges[iEdgeCount - 1][1]; + iEdgeCount--; + bFound = true; + break; + } + } + if (!bFound && iEdgeCount < 256) + { + aiEdges[iEdgeCount][0] = iA; + aiEdges[iEdgeCount][1] = iB; + iEdgeCount++; + } + } + + // remove face by swapping with last + atFaces[jj] = atFaces[iFaceCount - 1]; + iFaceCount--; + } + + // create new faces from horizon edges to new point + for (int jj = 0; jj < iEdgeCount; jj++) + { + if (iFaceCount >= 128) break; + atFaces[iFaceCount] = pl__epa_make_face(atVerts, aiEdges[jj][0], aiEdges[jj][1], iNewVert); + iFaceCount++; + } + } + + // didn't converge, return best guess + int iClosest = 0; + float fMinDist = atFaces[0].fDist; + for (int jj = 1; jj < iFaceCount; jj++) + { + if (atFaces[jj].fDist < fMinDist) + { + fMinDist = atFaces[jj].fDist; + iClosest = jj; + } + } + ptInfoOut->fPenetration = fMinDist; + ptInfoOut->tNormal = atFaces[iClosest].tNormal; + pl__epa_compute_contact(atVerts, &atFaces[iClosest], ptInfoOut); + + // enforce convention: normal points toward shape1 + plVec3 tS2ToS1 = pl_sub_vec3(atVerts[atFaces[iClosest].aiIndices[0]].tShape1, + atVerts[atFaces[iClosest].aiIndices[0]].tShape2); + if (pl_dot_vec3(ptInfoOut->tNormal, tS2ToS1) < 0.0f) + ptInfoOut->tNormal = pl_mul_vec3_scalarf(ptInfoOut->tNormal, -1.0f); +} + +//----------------------------------------------------------------------------- +// [SECTION] public api implementation +//----------------------------------------------------------------------------- + +bool +pl_pen(plGjkSupportFunc tFn1, const void* pShape1, plGjkSupportFunc tFn2, const void* pShape2, plGjkCollisionInfo* ptInfoOut) +{ + // off-axis initial direction to avoid degenerate collinear simplexes + plVec3 tDir = (plVec3){1.0f, 0.57735f, 0.57735f}; + plGjkVertex atSimplex[4] = {0}; + + atSimplex[0] = pl__support_minkowski_diff(tFn1, pShape1, tFn2, pShape2, tDir); + int iSimplexCount = 1; + tDir = pl_mul_vec3_scalarf(atSimplex[0].tMinkowski, -1.0f); + + const int iMaxIterations = 64; + for (int ii = 0; ii < iMaxIterations; ii++) { + plGjkVertex tNewVert = pl__support_minkowski_diff(tFn1, pShape1, tFn2, pShape2, tDir); + + if (pl_dot_vec3(tNewVert.tMinkowski, tDir) < 0.0f) { + return false; + } + + atSimplex[iSimplexCount++] = tNewVert; + + if (pl__update_simplex(atSimplex, &iSimplexCount, &tDir)) { + if (ptInfoOut) { + pl__epa(tFn1, pShape1, tFn2, pShape2, atSimplex, ptInfoOut); + } + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// [SECTION] extension loading +//----------------------------------------------------------------------------- + +PL_EXPORT void +pl_load_gjk_ext(plApiRegistryI* ptApiRegistry, bool bReload) +{ + const plGjkI tApi = { + .pen = pl_pen, + }; + pl_set_api(ptApiRegistry, plGjkI, &tApi); + + gptMemory = pl_get_api_latest(ptApiRegistry, plMemoryI); +} + +PL_EXPORT void +pl_unload_gjk_ext(plApiRegistryI* ptApiRegistry, bool bReload) +{ + + if (bReload) + return; + + const plGjkI* ptApi = pl_get_api_latest(ptApiRegistry, plGjkI); + ptApiRegistry->remove_api(ptApi); +} diff --git a/extensions/pl_gjk_ext.h b/extensions/pl_gjk_ext.h new file mode 100644 index 00000000..72db425d --- /dev/null +++ b/extensions/pl_gjk_ext.h @@ -0,0 +1,230 @@ +/* + pl_gjk_ext.h +*/ + +/* +Index of this file: +// [SECTION] header mess +// [SECTION] includes +// [SECTION] APIs +// [SECTION] forward declarations +// [SECTION] public api structs +// [SECTION] structs +// [SECTION] inline support functions +*/ + +//----------------------------------------------------------------------------- +// [SECTION] header mess +//----------------------------------------------------------------------------- + +#ifndef PL_GJK_EXT_H +#define PL_GJK_EXT_H + +//----------------------------------------------------------------------------- +// [SECTION] includes +//----------------------------------------------------------------------------- + +#include +#include +#include + +#define PL_MATH_INCLUDE_FUNCTIONS +#include "pl_math.h" + +//----------------------------------------------------------------------------- +// [SECTION] APIs +//----------------------------------------------------------------------------- + +#define plGjkI_version {0, 1, 0} + +//----------------------------------------------------------------------------- +// [SECTION] forward declarations +//----------------------------------------------------------------------------- + +typedef struct _plGjkCollisionInfo plGjkCollisionInfo; +typedef struct _plConvexPoly plConvexPoly; +typedef struct _plFrustum plFrustum; + +// support function signature: given a shape and direction, return the +// farthest point on the shape in that direction. +typedef plVec3 (*plGjkSupportFunc)(const void* pShape, plVec3 tDir); + +//----------------------------------------------------------------------------- +// [SECTION] public api structs +//----------------------------------------------------------------------------- + +typedef struct _plGjkI +{ + bool (*pen)(plGjkSupportFunc tFn1, const void* pShape1, plGjkSupportFunc tFn2, const void* pShape2, plGjkCollisionInfo* ptInfoOut); +} plGjkI; + +//----------------------------------------------------------------------------- +// [SECTION] structs +//----------------------------------------------------------------------------- + +// generic convex polyhedron for arbitrary vertex data +typedef struct _plConvexPoly +{ + plVec3* pVertices; + int iVertexCount; +} plConvexPoly; + +typedef struct _plGjkCollisionInfo +{ + float fPenetration; + plVec3 tNormal; + plVec3 tPoint; +} plGjkCollisionInfo; + +// frustum defined by 6 planes: 0=left, 1=right, 2=top, 3=bottom, 4=near, 5=far +typedef struct _plFrustum +{ + plPlane atPlanes[6]; +} plFrustum; + +//----------------------------------------------------------------------------- +// [SECTION] inline support functions +//----------------------------------------------------------------------------- + +static inline plVec3 +pl_gjk_support_sphere(const void* pShape, plVec3 tDir) +{ + const plSphere* ptSphere = (const plSphere*)pShape; + plVec3 tNormDir = pl_norm_vec3(tDir); + return (plVec3){ + ptSphere->tCenter.x + ptSphere->fRadius * tNormDir.x, + ptSphere->tCenter.y + ptSphere->fRadius * tNormDir.y, + ptSphere->tCenter.z + ptSphere->fRadius * tNormDir.z + }; +} + +static inline plVec3 +pl_gjk_support_aabb(const void* pShape, plVec3 tDir) +{ + const plAABB* ptAABB = (const plAABB*)pShape; + return (plVec3){ + tDir.x >= 0.0f ? ptAABB->tMax.x : ptAABB->tMin.x, + tDir.y >= 0.0f ? ptAABB->tMax.y : ptAABB->tMin.y, + tDir.z >= 0.0f ? ptAABB->tMax.z : ptAABB->tMin.z + }; +} + +static inline plVec3 +pl_gjk_support_box(const void* pShape, plVec3 tDir) +{ + const plBox* ptBox = (const plBox*)pShape; + plMat4 tInv = pl_mat4_invert(&ptBox->tTransform); + plVec3 tLocalDir = pl_mul_mat4_vec4(&tInv, (plVec4){tDir.x, tDir.y, tDir.z, 0.0f}).xyz; + plVec3 tLocalPoint = (plVec3){ + tLocalDir.x >= 0.0f ? ptBox->tHalfSize.x : -ptBox->tHalfSize.x, + tLocalDir.y >= 0.0f ? ptBox->tHalfSize.y : -ptBox->tHalfSize.y, + tLocalDir.z >= 0.0f ? ptBox->tHalfSize.z : -ptBox->tHalfSize.z + }; + return pl_mul_mat4_vec3(&ptBox->tTransform, tLocalPoint); +} + +static inline plVec3 +pl_gjk_support_capsule(const void* pShape, plVec3 tDir) +{ + const plCapsule* ptCapsule = (const plCapsule*)pShape; + plVec3 tNormDir = pl_norm_vec3(tDir); + float fDotBase = pl_dot_vec3(ptCapsule->tBasePos, tDir); + float fDotTip = pl_dot_vec3(ptCapsule->tTipPos, tDir); + plVec3 tCenter = fDotTip >= fDotBase ? ptCapsule->tTipPos : ptCapsule->tBasePos; + return (plVec3){ + tCenter.x + ptCapsule->fRadius * tNormDir.x, + tCenter.y + ptCapsule->fRadius * tNormDir.y, + tCenter.z + ptCapsule->fRadius * tNormDir.z + }; +} + +static inline plVec3 +pl_gjk_support_cylinder(const void* pShape, plVec3 tDir) +{ + const plCylinder* ptCylinder = (const plCylinder*)pShape; + plVec3 tAxis = pl_sub_vec3(ptCylinder->tTipPos, ptCylinder->tBasePos); + float fAxisLen = pl_length_vec3(tAxis); + plVec3 tAxisNorm = pl_mul_vec3_scalarf(tAxis, 1.0f / fAxisLen); + float fDotAxis = pl_dot_vec3(tDir, tAxisNorm); + plVec3 tPerp = pl_sub_vec3(tDir, pl_mul_vec3_scalarf(tAxisNorm, fDotAxis)); + float fPerpLen = pl_length_vec3(tPerp); + plVec3 tCenter = fDotAxis >= 0.0f ? ptCylinder->tTipPos : ptCylinder->tBasePos; + if (fPerpLen > 1e-8f) { + plVec3 tPerpNorm = pl_mul_vec3_scalarf(tPerp, 1.0f / fPerpLen); + return pl_add_vec3(tCenter, pl_mul_vec3_scalarf(tPerpNorm, ptCylinder->fRadius)); + } + return tCenter; +} + +static inline plVec3 +pl_gjk_support_cone(const void* pShape, plVec3 tDir) +{ + const plCone* ptCone = (const plCone*)pShape; + plVec3 tAxis = pl_sub_vec3(ptCone->tTipPos, ptCone->tBasePos); + float fAxisLen = pl_length_vec3(tAxis); + plVec3 tAxisNorm = pl_mul_vec3_scalarf(tAxis, 1.0f / fAxisLen); + float fDotAxis = pl_dot_vec3(tDir, tAxisNorm); + float fSinAngle = ptCone->fRadius / sqrtf(fAxisLen * fAxisLen + ptCone->fRadius * ptCone->fRadius); + if (fDotAxis > pl_length_vec3(tDir) * fSinAngle) + return ptCone->tTipPos; + plVec3 tPerp = pl_sub_vec3(tDir, pl_mul_vec3_scalarf(tAxisNorm, fDotAxis)); + float fPerpLen = pl_length_vec3(tPerp); + if (fPerpLen > 1e-8f) { + plVec3 tPerpNorm = pl_mul_vec3_scalarf(tPerp, 1.0f / fPerpLen); + return pl_add_vec3(ptCone->tBasePos, pl_mul_vec3_scalarf(tPerpNorm, ptCone->fRadius)); + } + return ptCone->tBasePos; +} + +static inline plVec3 +pl_gjk_support_convex_poly(const void* pShape, plVec3 tDir) +{ + const plConvexPoly* ptPoly = (const plConvexPoly*)pShape; + float fMaxDot = -FLT_MAX; + int iBest = 0; + for (int ii = 0; ii < ptPoly->iVertexCount; ii++) { + float fDot = pl_dot_vec3(ptPoly->pVertices[ii], tDir); + if (fDot > fMaxDot) { + fMaxDot = fDot; + iBest = ii; + } + } + return ptPoly->pVertices[iBest]; +} + +static inline plVec3 +pl_gjk_support_frustum(const void* pShape, plVec3 tDir) +{ + const plFrustum* ptFrustum = (const plFrustum*)pShape; + static const int aiCornerPlanes[8][3] = { + {0, 3, 4}, {1, 3, 4}, {1, 2, 4}, {0, 2, 4}, + {0, 3, 5}, {1, 3, 5}, {1, 2, 5}, {0, 2, 5}, + }; + float fMaxDot = -FLT_MAX; + plVec3 tBest = {0}; + for (int ii = 0; ii < 8; ii++) + { + const plPlane* ptP0 = &ptFrustum->atPlanes[aiCornerPlanes[ii][0]]; + const plPlane* ptP1 = &ptFrustum->atPlanes[aiCornerPlanes[ii][1]]; + const plPlane* ptP2 = &ptFrustum->atPlanes[aiCornerPlanes[ii][2]]; + plVec3 tCross = pl_cross_vec3(ptP1->tDirection, ptP2->tDirection); + float fDenom = pl_dot_vec3(ptP0->tDirection, tCross); + if (fabsf(fDenom) < 1e-8f) continue; + plVec3 tPoint = pl_mul_vec3_scalarf( + pl_add_vec3( + pl_add_vec3( + pl_mul_vec3_scalarf(tCross, ptP0->fOffset), + pl_mul_vec3_scalarf(pl_cross_vec3(ptP2->tDirection, ptP0->tDirection), ptP1->fOffset)), + pl_mul_vec3_scalarf(pl_cross_vec3(ptP0->tDirection, ptP1->tDirection), ptP2->fOffset)), + 1.0f / fDenom); + float fDot = pl_dot_vec3(tPoint, tDir); + if (fDot > fMaxDot) + { + fMaxDot = fDot; + tBest = tPoint; + } + } + return tBest; +} + +#endif // PL_GJK_EXT_H diff --git a/extensions/pl_renderer_ext.c b/extensions/pl_renderer_ext.c index baf3603c..62619bc4 100644 --- a/extensions/pl_renderer_ext.c +++ b/extensions/pl_renderer_ext.c @@ -3595,6 +3595,23 @@ pl_renderer_render_view(plView* ptView, plCamera* ptCamera, plCamera* ptCullCame .atDrawables = ptScene->sbtDrawables }; + // precompute frustum corners from inverse VP matrix + if(ptCullCamera) + { + plMat4 tVP = pl_mul_mat4(&ptCullCamera->tProjMat, &ptCullCamera->tViewMat); + plMat4 tInvVP = pl_mat4_invert(&tVP); + + static const plVec3 atNDC[] = { + {-1, -1, 0}, { 1, -1, 0}, {-1, 1, 0}, { 1, 1, 0}, + {-1, -1, 1}, { 1, -1, 1}, {-1, 1, 1}, { 1, 1, 1} + }; + for(int ii = 0; ii < 8; ii++) + { + plVec4 tClip = pl_mul_mat4_vec4(&tInvVP, (plVec4){atNDC[ii].x, atNDC[ii].y, atNDC[ii].z, 1.0f}); + tCullData.atFrustumCorners[ii] = (plVec3){tClip.x / tClip.w, tClip.y / tClip.w, tClip.z / tClip.w}; + } + } + if(ptCullCamera) { plJobDesc tJobDesc = { @@ -4866,6 +4883,7 @@ pl_load_renderer_ext(plApiRegistryI* ptApiRegistry, bool bReload) gptTerrainProcessor = pl_get_api_latest(ptApiRegistry, plTerrainProcessorI); gptStage = pl_get_api_latest(ptApiRegistry, plStageI); gptFreeList = pl_get_api_latest(ptApiRegistry, plFreeListI); + gptGjk = pl_get_api_latest(ptApiRegistry, plGjkI); #endif if(bReload) diff --git a/extensions/pl_renderer_internal.c b/extensions/pl_renderer_internal.c index 4365a28f..e0893d8e 100644 --- a/extensions/pl_renderer_internal.c +++ b/extensions/pl_renderer_internal.c @@ -7,7 +7,6 @@ Index of this file: // [SECTION] includes // [SECTION] job system tasks // [SECTION] resource creation helpers -// [SECTION] culling // [SECTION] scene render helpers */ @@ -40,7 +39,8 @@ pl__renderer_cull_job(plInvocationData tInvoData, void* pData, void* pGroupShare { if(ptCullData->atDrawables[tInvoData.uGlobalIndex].uInstanceCount == 1) // ignore instanced { - if(pl__renderer_sat_visibility_test(ptCullData->ptCullCamera, &ptObject->tAABB)) + plConvexPoly tFrustumPoly = { .pVertices = ptCullData->atFrustumCorners, .iVertexCount = 8 }; + if(gptGjk->pen(pl_gjk_support_convex_poly, &tFrustumPoly, pl_gjk_support_aabb, &ptObject->tAABB, NULL)) { ptCullData->atDrawables[tInvoData.uGlobalIndex].bCulled = false; } @@ -305,257 +305,6 @@ pl__renderer_create_local_buffer(const plBufferDesc* ptDesc, const char* pcName, return tHandle; } -//----------------------------------------------------------------------------- -// [SECTION] culling -//----------------------------------------------------------------------------- - -static bool -pl__renderer_sat_visibility_test(plCamera* ptCamera, const plAABB* ptAABB) -{ - const float fTanFov = tanf(0.5f * ptCamera->fFieldOfView); - - const float fZNear = ptCamera->fNearZ; - const float fZFar = ptCamera->fFarZ; - - // half width, half height - const float fXNear = ptCamera->fAspectRatio * ptCamera->fNearZ * fTanFov; - const float fYNear = ptCamera->fNearZ * fTanFov; - - // consider four adjacent corners of the AABB - plVec3 atCorners[] = { - {ptAABB->tMin.x, ptAABB->tMin.y, ptAABB->tMin.z}, - {ptAABB->tMax.x, ptAABB->tMin.y, ptAABB->tMin.z}, - {ptAABB->tMin.x, ptAABB->tMax.y, ptAABB->tMin.z}, - {ptAABB->tMin.x, ptAABB->tMin.y, ptAABB->tMax.z}, - }; - - // transform corners - for (size_t i = 0; i < 4; i++) - atCorners[i] = pl_mul_mat4_vec3(&ptCamera->tViewMat, atCorners[i]); - - // Use transformed atCorners to calculate center, axes and extents - - plOBB tObb = { - .atAxes = { - pl_sub_vec3(atCorners[1], atCorners[0]), - pl_sub_vec3(atCorners[2], atCorners[0]), - pl_sub_vec3(atCorners[3], atCorners[0]) - }, - }; - - tObb.tCenter = pl_add_vec3(atCorners[0], pl_mul_vec3_scalarf((pl_add_vec3(tObb.atAxes[0], pl_add_vec3(tObb.atAxes[1], tObb.atAxes[2]))), 0.5f)); - tObb.tExtents = (plVec3){ pl_length_vec3(tObb.atAxes[0]), pl_length_vec3(tObb.atAxes[1]), pl_length_vec3(tObb.atAxes[2]) }; - - // normalize - tObb.atAxes[0] = pl_div_vec3_scalarf(tObb.atAxes[0], tObb.tExtents.x); - tObb.atAxes[1] = pl_div_vec3_scalarf(tObb.atAxes[1], tObb.tExtents.y); - tObb.atAxes[2] = pl_div_vec3_scalarf(tObb.atAxes[2], tObb.tExtents.z); - tObb.tExtents = pl_mul_vec3_scalarf(tObb.tExtents, 0.5f); - - // axis along frustum - { - // Projected center of our OBB - const float fMoC = tObb.tCenter.z; - - // Projected size of OBB - float fRadius = 0.0f; - for (size_t i = 0; i < 3; i++) - fRadius += fabsf(tObb.atAxes[i].z) * tObb.tExtents.d[i]; - - const float fObbMin = fMoC - fRadius; - const float fObbMax = fMoC + fRadius; - - if (fObbMin > fZFar || fObbMax < fZNear) - return false; - } - - - // other normals of frustum - { - const plVec3 atM[] = { - { fZNear, 0.0f, fXNear }, // Left Plane - { -fZNear, 0.0f, fXNear }, // Right plane - { 0.0, -fZNear, fYNear }, // Top plane - { 0.0, fZNear, fYNear }, // Bottom plane - }; - for (size_t m = 0; m < 4; m++) - { - const float fMoX = fabsf(atM[m].x); - const float fMoY = fabsf(atM[m].y); - const float fMoZ = atM[m].z; - const float fMoC = pl_dot_vec3(atM[m], tObb.tCenter); - - float fObbRadius = 0.0f; - for (size_t i = 0; i < 3; i++) - fObbRadius += fabsf(pl_dot_vec3(atM[m], tObb.atAxes[i])) * tObb.tExtents.d[i]; - - const float fObbMin = fMoC - fObbRadius; - const float fObbMax = fMoC + fObbRadius; - - const float fP = fXNear * fMoX + fYNear * fMoY; - - float fTau0 = fZNear * fMoZ - fP; - float fTau1 = fZNear * fMoZ + fP; - - if (fTau0 < 0.0f) - fTau0 *= fZFar / fZNear; - - if (fTau1 > 0.0f) - fTau1 *= fZFar / fZNear; - - if (fObbMin > fTau1 || fObbMax < fTau0) - return false; - } - } - - // OBB axes - { - for (size_t m = 0; m < 3; m++) - { - const plVec3* ptM = &tObb.atAxes[m]; - const float fMoX = fabsf(ptM->x); - const float fMoY = fabsf(ptM->y); - const float fMoZ = ptM->z; - const float fMoC = pl_dot_vec3(*ptM, tObb.tCenter); - - const float fObbRadius = tObb.tExtents.d[m]; - - const float fObbMin = fMoC - fObbRadius; - const float fObbMax = fMoC + fObbRadius; - - // frustum projection - const float fP = fXNear * fMoX + fYNear * fMoY; - float fTau0 = fZNear * fMoZ - fP; - float fTau1 = fZNear * fMoZ + fP; - - if (fTau0 < 0.0f) - fTau0 *= fZFar / fZNear; - - if (fTau1 > 0.0f) - fTau1 *= fZFar / fZNear; - - if (fObbMin > fTau1 || fObbMax < fTau0) - return false; - } - } - - // cross products between the edges - // first R x A_i - { - for (size_t m = 0; m < 3; m++) - { - const plVec3 tM = { 0.0f, -tObb.atAxes[m].z, tObb.atAxes[m].y }; - const float fMoX = 0.0f; - const float fMoY = fabsf(tM.y); - const float fMoZ = tM.z; - const float fMoC = tM.y * tObb.tCenter.y + tM.z * tObb.tCenter.z; - - float fObbRadius = 0.0f; - for (size_t i = 0; i < 3; i++) - fObbRadius += fabsf(pl_dot_vec3(tM, tObb.atAxes[i])) * tObb.tExtents.d[i]; - - const float fObbMin = fMoC - fObbRadius; - const float fObbMax = fMoC + fObbRadius; - - // frustum projection - const float fP = fXNear * fMoX + fYNear * fMoY; - float fTau0 = fZNear * fMoZ - fP; - float fTau1 = fZNear * fMoZ + fP; - - if (fTau0 < 0.0f) - fTau0 *= fZFar / fZNear; - - if (fTau1 > 0.0f) - fTau1 *= fZFar / fZNear; - - if (fObbMin > fTau1 || fObbMax < fTau0) - return false; - } - } - - // U x A_i - { - for (size_t m = 0; m < 3; m++) - { - const plVec3 tM = { tObb.atAxes[m].z, 0.0f, -tObb.atAxes[m].x }; - const float fMoX = fabsf(tM.x); - const float fMoY = 0.0f; - const float fMoZ = tM.z; - const float fMoC = tM.x * tObb.tCenter.x + tM.z * tObb.tCenter.z; - - float fObbRadius = 0.0f; - for (size_t i = 0; i < 3; i++) - fObbRadius += fabsf(pl_dot_vec3(tM, tObb.atAxes[i])) * tObb.tExtents.d[i]; - - const float fObbMin = fMoC - fObbRadius; - const float fObbMax = fMoC + fObbRadius; - - // frustum projection - const float fP = fXNear * fMoX + fYNear * fMoY; - float fTau0 = fZNear * fMoZ - fP; - float fTau1 = fZNear * fMoZ + fP; - - if (fTau0 < 0.0f) - fTau0 *= fZFar / fZNear; - - if (fTau1 > 0.0f) - fTau1 *= fZFar / fZNear; - - if (fObbMin > fTau1 || fObbMax < fTau0) - return false; - } - } - - // frustum Edges X Ai - { - for (size_t obb_edge_idx = 0; obb_edge_idx < 3; obb_edge_idx++) - { - const plVec3 atM[] = { - pl_cross_vec3((plVec3){-fXNear, 0.0f, fZNear}, tObb.atAxes[obb_edge_idx]), // Left Plane - pl_cross_vec3((plVec3){ fXNear, 0.0f, fZNear }, tObb.atAxes[obb_edge_idx]), // Right plane - pl_cross_vec3((plVec3){ 0.0f, fYNear, fZNear }, tObb.atAxes[obb_edge_idx]), // Top plane - pl_cross_vec3((plVec3){ 0.0, -fYNear, fZNear }, tObb.atAxes[obb_edge_idx]) // Bottom plane - }; - - for (size_t m = 0; m < 4; m++) - { - const float fMoX = fabsf(atM[m].x); - const float fMoY = fabsf(atM[m].y); - const float fMoZ = atM[m].z; - - const float fEpsilon = 1e-4f; - if (fMoX < fEpsilon && fMoY < fEpsilon && fabsf(fMoZ) < fEpsilon) continue; - - const float fMoC = pl_dot_vec3(atM[m], tObb.tCenter); - - float fObbRadius = 0.0f; - for (size_t i = 0; i < 3; i++) - fObbRadius += fabsf(pl_dot_vec3(atM[m], tObb.atAxes[i])) * tObb.tExtents.d[i]; - - const float fObbMin = fMoC - fObbRadius; - const float fObbMax = fMoC + fObbRadius; - - // frustum projection - const float fP = fXNear * fMoX + fYNear * fMoY; - float fTau0 = fZNear * fMoZ - fP; - float fTau1 = fZNear * fMoZ + fP; - - if (fTau0 < 0.0f) - fTau0 *= fZFar / fZNear; - - if (fTau1 > 0.0f) - fTau1 *= fZFar / fZNear; - - if (fObbMin > fTau1 || fObbMax < fTau0) - return false; - } - } - } - - // no intersections detected - return true; -} - //----------------------------------------------------------------------------- // [SECTION] scene render helpers //----------------------------------------------------------------------------- diff --git a/extensions/pl_renderer_internal.h b/extensions/pl_renderer_internal.h index bc6a8297..65ec9553 100644 --- a/extensions/pl_renderer_internal.h +++ b/extensions/pl_renderer_internal.h @@ -63,6 +63,7 @@ Index of this file: #include "pl_terrain_processor_ext.h" #include "pl_stage_ext.h" #include "pl_freelist_ext.h" +#include "pl_gjk_ext.h" // shader interop #include "pl_shader_interop_renderer.h" @@ -123,7 +124,8 @@ Index of this file: static const plTerrainProcessorI* gptTerrainProcessor = NULL; static const plStageI* gptStage = NULL; static const plFreeListI* gptFreeList = NULL; - + static const plGjkI* gptGjk = NULL; + #endif #include "pl_ds.h" @@ -580,9 +582,10 @@ typedef struct _plRefRendererData typedef struct _plCullData { - plScene* ptScene; - plCamera* ptCullCamera; + plScene* ptScene; + plCamera* ptCullCamera; plDrawable* atDrawables; + plVec3 atFrustumCorners[8]; } plCullData; typedef struct _plMemCpyJobData @@ -614,9 +617,6 @@ static plBufferHandle pl__renderer_create_staging_buffer (const plBufferD static plBufferHandle pl__renderer_create_cached_staging_buffer(const plBufferDesc*, const char* pcName, uint32_t uIdentifier); static plBufferHandle pl__renderer_create_local_buffer (const plBufferDesc*, const char* pcName, uint32_t uIdentifier); -// culling -static bool pl__renderer_sat_visibility_test(plCamera*, const plAABB*); - typedef struct _plCSMInfo { bool bAltMode; diff --git a/extensions/pl_unity_ext.c b/extensions/pl_unity_ext.c index 9d2436e5..2f130cb3 100644 --- a/extensions/pl_unity_ext.c +++ b/extensions/pl_unity_ext.c @@ -42,6 +42,7 @@ Index of this file: #include "pl_screen_log_ext.c" #include "pl_physics_ext.c" #include "pl_collision_ext.c" +#include "pl_gjk_ext.c" #include "pl_bvh_ext.c" #include "pl_config_ext.c" #include "pl_starter_ext.c" @@ -154,6 +155,7 @@ pl_load_ext(plApiRegistryI* ptApiRegistry, bool bReload) pl_load_profile_ext(ptApiRegistry, bReload); pl_load_physics_ext(ptApiRegistry, bReload); pl_load_collision_ext(ptApiRegistry, bReload); + pl_load_gjk_ext(ptApiRegistry, bReload); pl_load_bvh_ext(ptApiRegistry, bReload); pl_load_mesh_ext(ptApiRegistry, bReload); pl_load_config_ext(ptApiRegistry, bReload); @@ -205,6 +207,7 @@ pl_unload_ext(plApiRegistryI* ptApiRegistry, bool bReload) pl_unload_log_ext(ptApiRegistry, bReload); pl_unload_physics_ext(ptApiRegistry, bReload); pl_unload_collision_ext(ptApiRegistry, bReload); + pl_unload_gjk_ext(ptApiRegistry, bReload); pl_unload_bvh_ext(ptApiRegistry, bReload); pl_unload_starter_ext(ptApiRegistry, bReload); pl_unload_config_ext(ptApiRegistry, bReload); diff --git a/extensions/pl_unity_ext.inc b/extensions/pl_unity_ext.inc index e286445a..6831b606 100644 --- a/extensions/pl_unity_ext.inc +++ b/extensions/pl_unity_ext.inc @@ -67,6 +67,7 @@ static const struct _plPathI* gptPath = 0; static const struct _plVoxelI* gptVoxel = 0; static const struct _plAudioI* gptAudio = 0; static const struct _plStageI* gptStage = 0; +static const struct _plGjkI* gptGjk = 0; // context static struct _plIO* gptIO = 0; diff --git a/scripts/gen_distribute.py b/scripts/gen_distribute.py index 208b3fb2..106884e3 100644 --- a/scripts/gen_distribute.py +++ b/scripts/gen_distribute.py @@ -123,6 +123,7 @@ "pl_starter_ext", "pl_physics_ext", "pl_collision_ext", + "pl_gjk_ext", "pl_bvh_ext", "pl_config_ext", "pl_mesh_ext", diff --git a/scripts/package.py b/scripts/package.py index 0f504cc2..4719260f 100644 --- a/scripts/package.py +++ b/scripts/package.py @@ -55,6 +55,7 @@ "pl_ui_ext.h", "pl_physics_ext.h", "pl_collision_ext.h", + "pl_gjk_ext.h", "pl_bvh_ext.h", "pl_config_ext.h", "pl_starter_ext.h", @@ -104,6 +105,7 @@ "pl_string_intern_ext", "pl_physics_ext", "pl_collision_ext", + "pl_gjk_ext", "pl_bvh_ext", "pl_config_ext", "pl_starter_ext",