Skip to content

Commit acb80a3

Browse files
authored
Add function processLineAgainstMesh (PR #3178)
1 parent f382cfb commit acb80a3

File tree

6 files changed

+197
-146
lines changed

6 files changed

+197
-146
lines changed

Client/game_sa/CWorldSA.cpp

Lines changed: 165 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,164 @@ void ConvertMatrixToEulerAngles(const CMatrix_Padded& matrixPadded, float& fX, f
254254
}
255255
}
256256

257+
258+
auto CWorldSA::ProcessLineAgainstMesh(CEntitySAInterface* targetEntity, CVector start, CVector end) -> SProcessLineOfSightMaterialInfoResult
259+
{
260+
assert(targetEntity);
261+
262+
SProcessLineOfSightMaterialInfoResult ret;
263+
264+
struct Context
265+
{
266+
float minHitDistSq{}; //< [squared] hit distance from the line segment's origin
267+
CVector originOS, endOS, dirOS; //< Line origin, end and dir [in object space]
268+
CMatrix entMat, entInvMat; //< The hit entity's matrix, and it's inverse
269+
RpTriangle* hitTri{}; //< The triangle hit
270+
RpAtomic* hitAtomic{}; //< The atomic of the hit triangle's geometry
271+
RpGeometry* hitGeo{}; //< The geometry of the hit triangle
272+
CVector hitBary{}; //< Barycentric coordinates [on the hit triangle] of the hit
273+
CVector hitPosOS{}; //< Hit position in object space
274+
CEntitySAInterface* entity{}; //< The hit entity
275+
} c = {};
276+
277+
c.entity = targetEntity;
278+
279+
if (!c.entity->m_pRwObject) {
280+
return ret; // isValid will be false in this case
281+
}
282+
283+
// Get matrix, and it's inverse
284+
c.entity->Placeable.matrix->ConvertToMatrix(c.entMat);
285+
c.entInvMat = c.entMat.Inverse();
286+
287+
// ...to transform the line origin and end into object space
288+
c.originOS = c.entInvMat.TransformVector(start);
289+
c.endOS = c.entInvMat.TransformVector(end);
290+
c.dirOS = c.endOS - c.originOS;
291+
c.minHitDistSq = c.dirOS.LengthSquared(); // By setting it to this value we avoid collisions that would be detected after the line segment
292+
// [but are still ont the ray]
293+
294+
// Do raycast against the DFF to get hit position material UV and name
295+
// This is very slow
296+
// Perhaps we could parallelize it somehow? [OpenMP?]
297+
const auto ProcessOneAtomic = [](RpAtomic* a, void* data)
298+
{
299+
Context* const c = static_cast<Context*>(data);
300+
RwFrame* const f = RpAtomicGetFrame(a);
301+
302+
const auto GetFrameCMatrix = [](RwFrame* f)
303+
{
304+
CMatrix out;
305+
pGame->GetRenderWare()->RwMatrixToCMatrix(*RwFrameGetMatrix(f), out);
306+
return out;
307+
};
308+
309+
// Atomic not visible
310+
if (!a->renderCallback || !(a->object.object.flags & 0x04 /*rpATOMICRENDER*/))
311+
{
312+
return true;
313+
}
314+
315+
// Sometimes atomics have no geometry [I don't think that should be possible, but okay]
316+
RpGeometry* const geo = a->geometry;
317+
if (!geo)
318+
{
319+
return true;
320+
}
321+
322+
// Calculate transformation by traversing the hierarchy from the bottom (this frame) -> top (root frame)
323+
CMatrix localToObjTransform{};
324+
for (RwFrame* i = f; i && i != i->root; i = RwFrameGetParent(i))
325+
{
326+
localToObjTransform = GetFrameCMatrix(i) * localToObjTransform;
327+
}
328+
const CMatrix objToLocalTransform = localToObjTransform.Inverse();
329+
330+
const auto ObjectToLocalSpace = [&](const CVector& in) { return objToLocalTransform.TransformVector(in); };
331+
332+
// Transform from object space, into local (the frame's) space
333+
const CVector localOrigin = ObjectToLocalSpace(c->originOS);
334+
const CVector localEnd = ObjectToLocalSpace(c->endOS);
335+
336+
#if 0
337+
if (!CCollisionSA::TestLineSphere(
338+
CColLineSA{localOrigin, 0.f, localEnd, 0.f},
339+
reinterpret_cast<CColSphereSA&>(*RpAtomicGetBoundingSphere(a)) // Fine for now
340+
)) {
341+
return true; // Line segment doesn't touch bsp
342+
}
343+
#endif
344+
const CVector localDir = localEnd - localOrigin;
345+
346+
const CVector* const verts = reinterpret_cast<CVector*>(geo->morph_target->verts); // It's fine, trust me bro
347+
for (auto i = geo->triangles_size; i-- > 0;)
348+
{
349+
RpTriangle* const tri = &geo->triangles[i];
350+
351+
// Process the line against the triangle
352+
CVector hitBary, hitPos;
353+
if (!localOrigin.IntersectsSegmentTriangle(localDir, verts[tri->verts[0]], verts[tri->verts[1]], verts[tri->verts[2]], &hitPos, &hitBary))
354+
{
355+
continue; // No intersection at all
356+
}
357+
358+
// Intersection, check if it's closer than the previous one
359+
const float hitDistSq = (hitPos - localOrigin).LengthSquared();
360+
if (c->minHitDistSq > hitDistSq)
361+
{
362+
c->minHitDistSq = hitDistSq;
363+
c->hitGeo = geo;
364+
c->hitAtomic = a;
365+
c->hitTri = tri;
366+
c->hitBary = hitBary;
367+
c->hitPosOS = localToObjTransform.TransformVector(hitPos); // Transform back into object space
368+
}
369+
}
370+
371+
return true;
372+
};
373+
374+
if (c.entity->m_pRwObject->object.type == 2 /*rpCLUMP*/)
375+
{
376+
RpClumpForAllAtomics(c.entity->m_pRwObject, ProcessOneAtomic, &c);
377+
}
378+
else
379+
{ // Object is a single atomic, so process directly
380+
ProcessOneAtomic(reinterpret_cast<RpAtomic*>(c.entity->m_pRwObject), &c);
381+
}
382+
383+
if (ret.valid = c.hitGeo != nullptr)
384+
{
385+
// Now, calculate texture UV, etc based on the hit [if we've hit anything at all]
386+
// Since we have the barycentric coords of the hit, calculating it is easy
387+
ret.uv = {};
388+
for (int i = 0; i < 3; i++)
389+
{
390+
// UV set index - Usually models only use level 0 indices, so let's stick with that
391+
const int uvSetIdx = 0;
392+
393+
// Vertex's UV position
394+
RwTextureCoordinates* const vtxUV = &c.hitGeo->texcoords[uvSetIdx][c.hitTri->verts[i]];
395+
396+
// Now, just interpolate
397+
ret.uv += CVector2D{vtxUV->u, vtxUV->v} * c.hitBary[i];
398+
}
399+
400+
// Find out material texture name
401+
// For some reason this is sometimes null
402+
RwTexture* const tex = c.hitGeo->materials.materials[c.hitTri->materialId]->texture;
403+
ret.textureName = tex ? tex->name : nullptr;
404+
405+
RwFrame* const hitFrame = RpAtomicGetFrame(c.hitAtomic);
406+
ret.frameName = hitFrame ? hitFrame->szName : nullptr;
407+
408+
// Get hit position in world space
409+
ret.hitPos = c.entMat.TransformVector(c.hitPosOS);
410+
}
411+
412+
return ret;
413+
}
414+
257415
bool CWorldSA::ProcessLineOfSight(const CVector* vecStart, const CVector* vecEnd, CColPoint** colCollision, CEntity** CollisionEntity,
258416
const SLineOfSightFlags flags, SLineOfSightBuildingResult* pBuildingResult, SProcessLineOfSightMaterialInfoResult* outMatInfo)
259417
{
@@ -354,153 +512,15 @@ bool CWorldSA::ProcessLineOfSight(const CVector* vecStart, const CVector* vecEnd
354512
}
355513
}
356514

357-
if (outMatInfo && targetEntity && targetEntity->m_pRwObject)
515+
if (outMatInfo)
358516
{
359-
struct Context
517+
outMatInfo->valid = false;
518+
if (targetEntity)
360519
{
361-
float minHitDistSq{}; //< [squared] hit distance from the line segment's origin
362-
CVector originOS, endOS, dirOS; //< Line origin, end and dir [in object space]
363-
CMatrix entMat, entInvMat; //< The hit entity's matrix, and it's inverse
364-
RpTriangle* hitTri{}; //< The triangle hit
365-
RpAtomic* hitAtomic{}; //< The atomic of the hit triangle's geometry
366-
RpGeometry* hitGeo{}; //< The geometry of the hit triangle
367-
CVector hitBary{}; //< Barycentric coordinates [on the hit triangle] of the hit
368-
CVector hitPosOS{}; //< Hit position in object space
369-
CEntitySAInterface* entity{}; //< The hit entity
370-
} c = {};
371-
372-
c.entity = targetEntity;
373-
374-
// Get matrix, and it's inverse
375-
targetEntity->Placeable.matrix->ConvertToMatrix(c.entMat);
376-
c.entInvMat = c.entMat.Inverse();
377-
378-
// ...to transform the line origin and end into object space
379-
c.originOS = c.entInvMat.TransformVector(*vecStart);
380-
c.endOS = c.entInvMat.TransformVector(*vecEnd);
381-
c.dirOS = c.endOS - c.originOS;
382-
c.minHitDistSq = c.dirOS.LengthSquared(); // By setting it to this value we avoid collisions that would be detected after the line segment
383-
// [but are still ont the ray]
384-
385-
// Do raycast against the DFF to get hit position material UV and name
386-
// This is very slow
387-
// Perhaps we could paralellize it somehow? [OpenMP?]
388-
const auto ProcessOneAtomic = [](RpAtomic* a, void* data)
389-
{
390-
const auto c = (Context*)data;
391-
const auto f = RpAtomicGetFrame(a);
392-
393-
const auto GetFrameCMatrix = [](RwFrame* f)
394-
{
395-
CMatrix out;
396-
pGame->GetRenderWare()->RwMatrixToCMatrix(*RwFrameGetMatrix(f), out);
397-
return out;
398-
};
399-
400-
// Atomic not visible
401-
if (!a->renderCallback || !(a->object.object.flags & 0x04 /*rpATOMICRENDER*/))
402-
{
403-
return true;
404-
}
405-
406-
// Sometimes atomics have no geometry [I don't think that should be possible, but okay]
407-
const auto geo = a->geometry;
408-
if (!geo)
409-
{
410-
return true;
411-
}
412-
413-
// Calculate transformation by traversing the hierarchy from the bottom (this frame) -> top (root frame)
414-
CMatrix localToObjTransform{};
415-
for (auto i = f; i && i != i->root; i = RwFrameGetParent(i))
416-
{
417-
localToObjTransform = GetFrameCMatrix(i) * localToObjTransform;
418-
}
419-
const auto objToLocalTransform = localToObjTransform.Inverse();
420-
421-
const auto ObjectToLocalSpace = [&](const CVector& in) { return objToLocalTransform.TransformVector(in); };
422-
423-
// Transform from object space, into local (the frame's) space
424-
const auto localOrigin = ObjectToLocalSpace(c->originOS);
425-
const auto localEnd = ObjectToLocalSpace(c->endOS);
426-
427-
#if 0
428-
if (!CCollisionSA::TestLineSphere(
429-
CColLineSA{localOrigin, 0.f, localEnd, 0.f},
430-
reinterpret_cast<CColSphereSA&>(*RpAtomicGetBoundingSphere(a)) // Fine for now
431-
)) {
432-
return true; // Line segment doesn't touch bsp
433-
}
434-
#endif
435-
const auto localDir = localEnd - localOrigin;
436-
437-
const auto verts = reinterpret_cast<CVector*>(geo->morph_target->verts); // It's fine, trust me bro
438-
for (auto i = geo->triangles_size; i-- > 0;)
439-
{
440-
const auto tri = &geo->triangles[i];
441-
442-
// Process the line against the triangle
443-
CVector hitBary, hitPos;
444-
if (!localOrigin.IntersectsSegmentTriangle(localDir, verts[tri->verts[0]], verts[tri->verts[1]], verts[tri->verts[2]], &hitPos, &hitBary))
445-
{
446-
continue; // No intersection at all
447-
}
448-
449-
// Intersection, check if it's closer than the previous one
450-
const auto hitDistSq = (hitPos - localOrigin).LengthSquared();
451-
if (c->minHitDistSq > hitDistSq)
452-
{
453-
c->minHitDistSq = hitDistSq;
454-
c->hitGeo = geo;
455-
c->hitAtomic = a;
456-
c->hitTri = tri;
457-
c->hitBary = hitBary;
458-
c->hitPosOS = localToObjTransform.TransformVector(hitPos); // Transform back into object space
459-
}
460-
}
461-
462-
return true;
463-
};
464-
465-
if (targetEntity->m_pRwObject->object.type == 2 /*rpCLUMP*/)
466-
{
467-
RpClumpForAllAtomics(targetEntity->m_pRwObject, ProcessOneAtomic, &c);
468-
}
469-
else
470-
{ // Object is a single atomic, so process directly
471-
ProcessOneAtomic(reinterpret_cast<RpAtomic*>(targetEntity->m_pRwObject), &c);
472-
}
473-
474-
// It might be false if the collision model differs from the clump
475-
// This is completely normal as collisions models are meant to be simplified
476-
// compared to the clump's geometry
477-
if (outMatInfo->valid = c.hitGeo != nullptr)
478-
{
479-
// Now, calculate texture UV, etc based on the hit [if we've hit anything at all]
480-
// Since we have the barycentric coords of the hit, calculating it is easy
481-
outMatInfo->uv = {};
482-
for (auto i = 3u; i-- > 0;)
483-
{
484-
// UV set index - Usually models only use level 0 indices, so let's stick with that
485-
const auto uvSetIdx = 0;
486-
487-
// Vertex's UV position
488-
const auto vtxUV = &c.hitGeo->texcoords[uvSetIdx][c.hitTri->verts[i]];
489-
490-
// Now, just interpolate
491-
outMatInfo->uv += CVector2D{vtxUV->u, vtxUV->v} * c.hitBary[i];
492-
}
493-
494-
// Find out material texture name
495-
// For some reason this is sometimes null
496-
const auto tex = c.hitGeo->materials.materials[c.hitTri->materialId]->texture;
497-
outMatInfo->textureName = tex ? tex->name : nullptr;
498-
499-
const auto frame = RpAtomicGetFrame(c.hitAtomic); // `RpAtomicGetFrame`
500-
outMatInfo->frameName = frame ? frame->szName : nullptr;
501-
502-
// Get hit position in world space
503-
outMatInfo->hitPos = c.entMat.TransformVector(c.hitPosOS);
520+
// There might not be a texture hit info result as the collision model differs from the mesh itself.
521+
// This is completely normal as collisions models are meant to be simplified
522+
// compared to the mesh
523+
*outMatInfo = ProcessLineAgainstMesh(targetEntity, *vecStart, *vecEnd);
504524
}
505525
}
506526

Client/game_sa/CWorldSA.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class CWorldSA : public CWorld
4848
void Remove(CEntity* entity, eDebugCaller CallerId);
4949
void Remove(CEntitySAInterface* entityInterface, eDebugCaller CallerId);
5050
void RemoveReferencesToDeletedObject(CEntitySAInterface* entity);
51+
auto ProcessLineAgainstMesh(CEntitySAInterface* e, CVector start, CVector end) -> SProcessLineOfSightMaterialInfoResult override;
5152
bool ProcessLineOfSight(const CVector* vecStart, const CVector* vecEnd, CColPoint** colCollision, CEntity** CollisionEntity, const SLineOfSightFlags flags,
5253
SLineOfSightBuildingResult* pBuildingResult, SProcessLineOfSightMaterialInfoResult* outMatInfo = nullptr);
5354
void IgnoreEntity(CEntity* entity);

Client/mods/deathmatch/logic/luadefs/CLuaWorldDefs.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ void CLuaWorldDefs::LoadFunctions()
2121
{"getColorFilter", ArgumentParser<GetColorFilter>},
2222
{"getRoofPosition", GetRoofPosition},
2323
{"getGroundPosition", GetGroundPosition},
24+
{"processLineAgainstMesh", ArgumentParser<ProcessLineAgainstMesh>},
2425
{"processLineOfSight", ProcessLineOfSight},
2526
{"getWorldFromScreenPosition", GetWorldFromScreenPosition},
2627
{"getScreenFromWorldPosition", GetScreenFromWorldPosition},
@@ -230,6 +231,32 @@ int CLuaWorldDefs::GetRoofPosition(lua_State* luaVM)
230231
return 1;
231232
}
232233

234+
std::variant<bool, CLuaMultiReturn<bool, float, float, const char*, const char*, float, float, float>> CLuaWorldDefs::ProcessLineAgainstMesh(CClientEntity* e, CVector start, CVector end) {
235+
const auto ge = e->GetGameEntity();
236+
if (!ge) {
237+
// Element likely not streamed in, and such
238+
// Can't process it. This isn't an error per-se, thus we won't raise anything and treat this as a no-hit scenario
239+
return { false };
240+
}
241+
const SProcessLineOfSightMaterialInfoResult matInfo{g_pGame->GetWorld()->ProcessLineAgainstMesh(ge->GetInterface(), start, end)};
242+
if (!matInfo.valid) {
243+
return { false }; // No hit
244+
}
245+
return CLuaMultiReturn<bool, float, float, const char*, const char*, float, float, float>{
246+
true,
247+
248+
matInfo.uv.fX,
249+
matInfo.uv.fY,
250+
251+
matInfo.textureName,
252+
matInfo.frameName,
253+
254+
matInfo.hitPos.fX,
255+
matInfo.hitPos.fY,
256+
matInfo.hitPos.fZ,
257+
};
258+
}
259+
233260
int CLuaWorldDefs::ProcessLineOfSight(lua_State* L)
234261
{
235262
// bool float float float element float float float int int int processLineOfSight ( float startX, float startY, float startZ, float endX, float endY,

Client/mods/deathmatch/logic/luadefs/CLuaWorldDefs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ class CLuaWorldDefs : public CLuaDefs
1515
public:
1616
static void LoadFunctions();
1717

18+
1819
LUA_DECLARE(GetTime);
1920
LUA_DECLARE(GetGroundPosition);
2021
LUA_DECLARE(GetRoofPosition);
22+
static std::variant<bool, CLuaMultiReturn<bool, float, float, const char*, const char*, float, float, float>> ProcessLineAgainstMesh(CClientEntity* e, CVector start, CVector end);
2123
LUA_DECLARE(ProcessLineOfSight);
2224
LUA_DECLARE(IsLineOfSightClear);
2325
LUA_DECLARE(GetWorldFromScreenPosition);

Client/sdk/game/CWorld.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ class CWorld
315315
virtual void Add(CEntity* entity, eDebugCaller CallerId) = 0;
316316
virtual void Remove(CEntity* entity, eDebugCaller CallerId) = 0;
317317
virtual void Remove(CEntitySAInterface* entityInterface, eDebugCaller CallerId) = 0;
318+
virtual auto ProcessLineAgainstMesh(CEntitySAInterface* e, CVector start, CVector end) -> SProcessLineOfSightMaterialInfoResult = 0;
318319
virtual bool ProcessLineOfSight(const CVector* vecStart, const CVector* vecEnd, CColPoint** colCollision, CEntity** CollisionEntity,
319320
const SLineOfSightFlags flags = SLineOfSightFlags(), SLineOfSightBuildingResult* pBuildingResult = NULL, SProcessLineOfSightMaterialInfoResult* outMatInfo = {}) = 0;
320321
virtual void IgnoreEntity(CEntity* entity) = 0;

0 commit comments

Comments
 (0)