Files
tech5/source/engine/cm/jobs/collisionmerge.cpp
T
2026-08-09 01:29:43 -07:00

538 lines
19 KiB
C++

#include "cm/jobs/collisionmerge.h"
#include <algorithm>
#include <cmath>
#include <cstring>
namespace {
const queryResults_t* ResultAt(const queryResults_t* results,
const int index, const int resultSize) {
return reinterpret_cast<const queryResults_t*>(
reinterpret_cast<const std::uint8_t*>(results) +
static_cast<std::size_t>(index) * resultSize);
}
bool SameVertex(const idVec3& left, const idVec3& right) {
return std::fabs(left.x - right.x) <= 0.1f &&
std::fabs(left.y - right.y) <= 0.1f &&
std::fabs(left.z - right.z) <= 0.1f;
}
bool SameTriangleCyclic(const std::int16_t* left,
const std::int16_t* right) {
return (left[0] == right[0] && left[1] == right[1] &&
left[2] == right[2]) ||
(left[0] == right[1] && left[1] == right[2] &&
left[2] == right[0]) ||
(left[0] == right[2] && left[1] == right[0] &&
left[2] == right[1]);
}
} // namespace
void idCollisionDetectionMerge::MergeContentsResults(
queryResults_t* const finalResult,
const queryResults_t* const mergeResults, const int numMergeResults,
const int resultSize) {
if (numMergeResults <= 0) {
return;
}
trace_t* const finalTrace =
reinterpret_cast<trace_t*>(finalResult->data);
*finalTrace = *reinterpret_cast<const trace_t*>(mergeResults->data);
for (int index = 1; index < numMergeResults; ++index) {
const trace_t& candidate = *reinterpret_cast<const trace_t*>(
ResultAt(mergeResults, index, resultSize)->data);
if (candidate.c.contentFlags != 0 &&
finalTrace->c.contentFlags == 0) {
*finalTrace = candidate;
} else {
finalTrace->c.contentFlags |= candidate.c.contentFlags;
}
}
}
void idCollisionDetectionMerge::MergeMotionResults(trace_t* const result,
const trace_t* const rotation, const trace_t* const translation) {
const idVec3 rotationEndPosition = rotation->endpos;
const idMat3 rotationEndAxis = rotation->endAxis;
const float fraction =
(rotation->fraction + translation->fraction) * 0.5f;
*result = rotation->fraction < 1.0f ? *rotation : *translation;
result->fraction = fraction;
result->endpos = rotationEndPosition;
result->endAxis = rotationEndAxis;
}
void idCollisionDetectionMerge::MergeStepMoveResults(
trace_t* const result, const trace_t* const down,
const trace_t* const forward2, const trace_t* const forward1,
const bool slideMove) {
std::uint8_t flags = CONTACT_FLAG_NONE;
if (down->fraction < 1.0f) {
flags = CONTACT_FLAG_STEPMOVE_ONSOLID;
if (down->c.normal.z > 0.70710677f) {
flags |= CONTACT_FLAG_STEPMOVE_ONGROUND;
}
}
if (slideMove &&
(flags & CONTACT_FLAG_STEPMOVE_ONSOLID) != 0 &&
(flags & CONTACT_FLAG_STEPMOVE_ONGROUND) == 0) {
*result = *forward1;
result->c.separation = 0.0f;
return;
}
if (forward1->fraction >= 1.0f || forward2->fraction >= 1.0f) {
result->fraction = 1.0f;
} else {
result->fraction =
(1.0f - forward1->fraction) * forward2->fraction +
forward1->fraction;
}
result->endpos = down->endpos;
result->endAxis = down->endAxis;
result->c = forward2->c;
result->c.flags |= flags;
result->c.separation = down->endpos.z - forward1->endpos.z;
}
bool ClipVelocity(idVec3& velocity, const idVec3* const planes,
const int numPlanes) {
for (int first = 0; first < numPlanes; ++first) {
float into = velocity.Dot(planes[first]);
if (into >= 0.05f) {
continue;
}
const float firstScale = into >= 0.0f ? 0.99900097f : 1.001f;
velocity = velocity - planes[first] * (into * firstScale);
for (int second = 0; second < numPlanes; ++second) {
if (second == first) {
continue;
}
into = velocity.Dot(planes[second]);
if (into >= 0.05f) {
continue;
}
const float secondScale =
into >= 0.0f ? 0.99900097f : 1.001f;
velocity = velocity - planes[second] * (into * secondScale);
if (velocity.Dot(planes[first]) >= 0.0f) {
continue;
}
idVec3 crease = planes[first].Cross(planes[second]);
if (crease.NormalizeFast() == 0.0f) {
velocity.Zero();
return true;
}
velocity = crease * velocity.Dot(crease);
bool blocked = false;
for (int third = 0; third < numPlanes; ++third) {
if (third != first && third != second &&
velocity.Dot(planes[third]) < 0.05f) {
blocked = true;
break;
}
}
if (blocked) {
velocity.Zero();
return true;
}
}
}
return false;
}
void idCollisionDetectionMerge::InitSlideMoveState(
slideMoveState_t* const state, const idVec3& velocity,
const idVec3& gravityVector) {
state->velocity = velocity;
state->endVelocity.Zero();
std::memset(&state->firstContact, 0, sizeof(state->firstContact));
state->fractionRemaining = 1.0f;
state->steppedUp = 0.0f;
state->startNormal = velocity;
state->startNormal.NormalizeFast();
state->numPlanes = 0;
state->pad = 0;
const float gravityLength = gravityVector.Length();
if (gravityLength > 1.01f) {
const float gravityScale = (gravityLength - 1.0f) / gravityLength;
state->endVelocity = velocity + gravityVector * gravityScale;
state->velocity = (velocity + state->endVelocity) * 0.5f;
}
}
bool idCollisionDetectionMerge::UpdateSlideMoveState(
slideMoveState_t* const state, trace_t* const trace) {
state->fractionRemaining *= 1.0f - trace->fraction;
state->steppedUp += trace->c.separation;
if (trace->fraction >= 1.0f) {
state->fractionRemaining = 0.0f;
trace->c.flags |= CONTACT_FLAG_SLIDEMOVE_FINISHED;
return true;
}
if (state->firstContact.type == CONTACT_NONE) {
state->firstContact = trace->c;
}
bool duplicatePlane = false;
for (int index = 0; index < state->numPlanes; ++index) {
if (state->planes[index].Dot(trace->c.normal) > 0.99900001f) {
const float into = state->velocity.Dot(trace->c.normal);
const float scale = into >= 0.0f ? 0.99900097f : 1.001f;
state->velocity = state->velocity -
trace->c.normal * (into * scale);
duplicatePlane = true;
break;
}
}
if (!duplicatePlane) {
if (state->numPlanes >= 4) {
state->fractionRemaining = 0.0f;
state->velocity.Zero();
trace->c.flags |= CONTACT_FLAG_SLIDEMOVE_FINISHED;
return true;
}
state->planes[state->numPlanes++] = trace->c.normal;
if (ClipVelocity(state->velocity, state->planes,
state->numPlanes)) {
state->fractionRemaining = 0.0f;
state->velocity.Zero();
trace->c.flags |= CONTACT_FLAG_SLIDEMOVE_FINISHED;
return true;
}
if (state->startNormal.Dot(state->velocity) < 0.01f) {
state->velocity = state->velocity - state->startNormal *
state->velocity.Dot(state->startNormal);
}
}
trace->c.normal = state->velocity;
trace->c.dist = state->fractionRemaining;
return false;
}
void idCollisionDetectionMerge::FinishSlideMoveState(
slideMoveState_t* const state, trace_t* const trace) {
if (state->velocity.LengthSqr() != 0.0f &&
state->endVelocity.LengthSqr() != 0.0f) {
ClipVelocity(state->endVelocity, state->planes,
state->numPlanes);
state->velocity = state->endVelocity;
}
trace->c = state->firstContact;
trace->c.normal = state->velocity;
trace->c.dist = state->fractionRemaining;
trace->c.separation = state->steppedUp;
}
void idCollisionDetectionMerge::MergeTraceResults(
queryResults_t* const finalResult,
const queryResults_t* const mergeResults, const int numMergeResults,
const int resultSize) {
if (numMergeResults <= 0) {
return;
}
trace_t* const finalTrace =
reinterpret_cast<trace_t*>(finalResult->data);
*finalTrace = *reinterpret_cast<const trace_t*>(mergeResults->data);
for (int index = 1; index < numMergeResults; ++index) {
const trace_t& candidate = *reinterpret_cast<const trace_t*>(
ResultAt(mergeResults, index, resultSize)->data);
if (candidate.fraction < finalTrace->fraction) {
*finalTrace = candidate;
}
}
}
void idCollisionDetectionMerge::MergeContactsResults(
queryResults_t* const finalResult,
const queryResults_t* const mergeResults, const int numMergeResults,
const int resultSize) {
struct normalGroup_t {
const contactInfo_t* contacts[12];
int numContacts;
} groups[12] = {};
int numGroups = 0;
for (int resultIndex = 0; resultIndex < numMergeResults;
++resultIndex) {
const contactsResult_t& source =
*reinterpret_cast<const contactsResult_t*>(
ResultAt(mergeResults, resultIndex, resultSize)->data);
const int sourceCount = (std::min)(source.numContacts, 12);
for (int contactIndex = 0; contactIndex < sourceCount;
++contactIndex) {
const contactInfo_t* const contact =
&source.contacts[contactIndex];
int groupIndex = 0;
for (; groupIndex < numGroups; ++groupIndex) {
if (groups[groupIndex].contacts[0]->normal.Dot(
contact->normal) > 0.90630776f) {
break;
}
}
if (groupIndex == numGroups) {
if (numGroups >= 12) {
continue;
}
groups[numGroups++].numContacts = 0;
}
normalGroup_t& group = groups[groupIndex];
if (group.numContacts < 12) {
group.contacts[group.numContacts++] = contact;
}
}
}
contactsResult_t& destination =
*reinterpret_cast<contactsResult_t*>(finalResult->data);
destination.numContacts = 0;
for (int groupIndex = 0; groupIndex < numGroups; ++groupIndex) {
const normalGroup_t& group = groups[groupIndex];
const contactInfo_t* unique[12];
int numUnique = 0;
for (int contactIndex = 0; contactIndex < group.numContacts;
++contactIndex) {
const contactInfo_t* const candidate =
group.contacts[contactIndex];
bool duplicate = false;
for (int uniqueIndex = 0; uniqueIndex < numUnique;
++uniqueIndex) {
if ((candidate->point - unique[uniqueIndex]->point)
.LengthSqr() < 1.0f) {
duplicate = true;
break;
}
}
if (!duplicate) {
unique[numUnique++] = candidate;
}
}
int selected[3] = {0, 1, 2};
int numSelected = numUnique;
if (numUnique > 3) {
float largestAreaSqr = 0.0f;
for (int first = 0; first < numUnique - 2; ++first) {
for (int second = first + 1; second < numUnique - 1;
++second) {
for (int third = second + 1; third < numUnique;
++third) {
const idVec3 side1 =
unique[second]->point - unique[first]->point;
const idVec3 side2 =
unique[third]->point - unique[first]->point;
const float areaSqr =
side1.Cross(side2).LengthSqr();
if (areaSqr > largestAreaSqr) {
largestAreaSqr = areaSqr;
selected[0] = first;
selected[1] = second;
selected[2] = third;
}
}
}
}
numSelected = 3;
}
for (int uniqueIndex = 0; uniqueIndex < numUnique &&
destination.numContacts < 12; ++uniqueIndex) {
bool useContact = numUnique <= 3;
if (numUnique > 3) {
for (int selection = 0; selection < numSelected;
++selection) {
useContact |= selected[selection] == uniqueIndex;
}
}
if (useContact) {
destination.contacts[destination.numContacts++] =
*unique[uniqueIndex];
}
}
}
}
void idCollisionDetectionMerge::MergeClipResults(
queryResults_t* const finalResult,
const queryResults_t* const mergeResults, const int numMergeResults,
const int resultSize) {
if (numMergeResults <= 0) {
return;
}
clipResult_t* const finalClip =
reinterpret_cast<clipResult_t*>(finalResult->data);
*finalClip = *reinterpret_cast<const clipResult_t*>(mergeResults->data);
for (int resultIndex = 1; resultIndex < numMergeResults; ++resultIndex) {
const clipResult_t& source =
*reinterpret_cast<const clipResult_t*>(
ResultAt(mergeResults, resultIndex, resultSize)->data);
std::int16_t vertexRemap[32];
for (int vertex = 0; vertex < source.numVerts && vertex < 32;
++vertex) {
vertexRemap[vertex] = -1;
for (int existing = 0; existing < finalClip->numVerts;
++existing) {
if (SameVertex(source.verts[vertex],
finalClip->verts[existing])) {
vertexRemap[vertex] = static_cast<std::int16_t>(existing);
break;
}
}
if (vertexRemap[vertex] == -1 && finalClip->numVerts < 32) {
vertexRemap[vertex] =
static_cast<std::int16_t>(finalClip->numVerts);
finalClip->verts[finalClip->numVerts++] =
source.verts[vertex];
}
}
for (int sourceIndex = 0;
sourceIndex + 2 < source.numIndices && sourceIndex + 2 < 264;
sourceIndex += 3) {
const int sourceA = source.indices[sourceIndex];
const int sourceB = source.indices[sourceIndex + 1];
const int sourceC = source.indices[sourceIndex + 2];
if (sourceA < 0 || sourceA >= 32 || sourceB < 0 ||
sourceB >= 32 || sourceC < 0 || sourceC >= 32) {
continue;
}
const std::int16_t candidate[3] = {
vertexRemap[sourceA], vertexRemap[sourceB],
vertexRemap[sourceC]};
if (candidate[0] == -1 || candidate[1] == -1 ||
candidate[2] == -1) {
continue;
}
bool duplicate = false;
for (int existing = 0; existing + 2 < finalClip->numIndices;
existing += 3) {
if (SameTriangleCyclic(candidate,
&finalClip->indices[existing])) {
duplicate = true;
break;
}
}
if (!duplicate && finalClip->numIndices + 3 <= 264) {
finalClip->indices[finalClip->numIndices++] = candidate[0];
finalClip->indices[finalClip->numIndices++] = candidate[1];
finalClip->indices[finalClip->numIndices++] = candidate[2];
}
}
}
}
void idCollisionDetectionMerge::MergeQueryResults(
queryResults_t* const finalResult, const int resultSize,
const traceType_t type, const queryResults_t* const mergeResults,
const int numMergeResults, slideMoveState_t* const slideMoveState,
const dependencyType_t dependencyType,
const queryResults_t* const dependency1,
const queryResults_t* const dependency2) {
switch (type) {
case TRACE_CONTENTS:
case TRACE_CONTENTS_POINT:
MergeContentsResults(finalResult, mergeResults, numMergeResults,
resultSize);
break;
case TRACE_CONTACTS_UNI_DIR:
case TRACE_CONTACTS_OMNI_DIR:
MergeContactsResults(finalResult, mergeResults, numMergeResults,
resultSize);
break;
case TRACE_TRANSLATION:
case TRACE_TRANSLATION_POINT:
case TRACE_ROTATION:
case TRACE_ROTATION_POINT:
MergeTraceResults(finalResult, mergeResults, numMergeResults,
resultSize);
break;
case TRACE_CLIP:
MergeClipResults(finalResult, mergeResults, numMergeResults,
resultSize);
break;
default:
break;
}
switch (dependencyType) {
case DEPENDENCY_MOTION_ROTATION:
MergeMotionResults(
reinterpret_cast<trace_t*>(finalResult->data),
reinterpret_cast<const trace_t*>(finalResult->data),
reinterpret_cast<const trace_t*>(dependency1->data));
break;
case DEPENDENCY_MOTION_CONTACTS: {
const trace_t& motion =
*reinterpret_cast<const trace_t*>(dependency1->data);
contactsResult_t& contacts =
*reinterpret_cast<contactsResult_t*>(finalResult->data);
if (motion.fraction < 1.0f && contacts.numContacts == 0) {
contacts.contacts[0] = motion.c;
contacts.numContacts = 1;
}
break;
}
case DEPENDENCY_STEPMOVE_STEP_DOWN:
MergeStepMoveResults(
reinterpret_cast<trace_t*>(finalResult->data),
reinterpret_cast<const trace_t*>(finalResult->data),
reinterpret_cast<const trace_t*>(dependency1->data),
reinterpret_cast<const trace_t*>(dependency2->data), false);
break;
case DEPENDENCY_SLIDEMOVE_STEP_UP_2:
case DEPENDENCY_SLIDEMOVE_STEP_UP_3:
case DEPENDENCY_SLIDEMOVE_STEP_UP_4:
case DEPENDENCY_SLIDEMOVE_2ND_MOVE_2:
case DEPENDENCY_SLIDEMOVE_2ND_MOVE_3:
case DEPENDENCY_SLIDEMOVE_2ND_MOVE_4:
case DEPENDENCY_SLIDEMOVE_SLIDE:
if (slideMoveState->fractionRemaining <= 0.0f) {
reinterpret_cast<trace_t*>(finalResult->data)->c.flags |=
CONTACT_FLAG_SLIDEMOVE_FINISHED;
}
break;
case DEPENDENCY_SLIDEMOVE_STEP_DOWN_1:
case DEPENDENCY_SLIDEMOVE_STEP_DOWN_2:
case DEPENDENCY_SLIDEMOVE_STEP_DOWN_3:
case DEPENDENCY_SLIDEMOVE_STEP_DOWN_4: {
trace_t* const trace =
reinterpret_cast<trace_t*>(finalResult->data);
MergeStepMoveResults(trace, trace,
reinterpret_cast<const trace_t*>(dependency1->data),
reinterpret_cast<const trace_t*>(dependency2->data), true);
if (slideMoveState->fractionRemaining > 0.0f) {
UpdateSlideMoveState(slideMoveState, trace);
} else {
trace->c.flags |= CONTACT_FLAG_SLIDEMOVE_FINISHED;
}
if (dependencyType == DEPENDENCY_SLIDEMOVE_STEP_DOWN_4) {
FinishSlideMoveState(slideMoveState, trace);
}
break;
}
default:
break;
}
finalResult->query.type = type;
finalResult->query.done = 1;
finalResult->query.merged = 1;
finalResult->query.status = mergeResults->query.status;
}