#include "cm/jobs/collisionmerge.h" #include #include #include namespace { const queryResults_t* ResultAt(const queryResults_t* results, const int index, const int resultSize) { return reinterpret_cast( reinterpret_cast(results) + static_cast(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(finalResult->data); *finalTrace = *reinterpret_cast(mergeResults->data); for (int index = 1; index < numMergeResults; ++index) { const trace_t& candidate = *reinterpret_cast( 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(finalResult->data); *finalTrace = *reinterpret_cast(mergeResults->data); for (int index = 1; index < numMergeResults; ++index) { const trace_t& candidate = *reinterpret_cast( 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( 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(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(finalResult->data); *finalClip = *reinterpret_cast(mergeResults->data); for (int resultIndex = 1; resultIndex < numMergeResults; ++resultIndex) { const clipResult_t& source = *reinterpret_cast( 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(existing); break; } } if (vertexRemap[vertex] == -1 && finalClip->numVerts < 32) { vertexRemap[vertex] = static_cast(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(finalResult->data), reinterpret_cast(finalResult->data), reinterpret_cast(dependency1->data)); break; case DEPENDENCY_MOTION_CONTACTS: { const trace_t& motion = *reinterpret_cast(dependency1->data); contactsResult_t& contacts = *reinterpret_cast(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(finalResult->data), reinterpret_cast(finalResult->data), reinterpret_cast(dependency1->data), reinterpret_cast(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(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(finalResult->data); MergeStepMoveResults(trace, trace, reinterpret_cast(dependency1->data), reinterpret_cast(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; }