/** * @brief Tries to find a path from the given actor(-position) to a given target position * * Unlike Grid_CalcPathing, this function does not neccessarily calculate the TU values for * all positions reachable from 'from'. Instead it tries to find the shortest/fastest path to * the target position. There is no limit to maxTUs. * * @param[in] routing Reference to client or server side routing table (clMap, svMap) * @param[in] actorSize The size of thing to calc the move for (e.g. size=2 means 2x2). * The plan is to have the 'origin' in 2x2 units in the bottom-left (towards the lower coordinates) corner of the 2x2 square. * @param[in,out] path Pointer to client or server side pathing table (clMap, svMap) * @param[in] from The position to start the calculation from. * @param[in] targetPos The position where we want to end up. * @param[in] maxTUs The maximum TUs away from 'from' to calculate move-information for * @param[in] crouchingState Whether the actor is currently crouching, 1 is yes, 0 is no. * @param[in] fb_list Forbidden list (entities are standing at those points) * @param[in] fb_length Length of forbidden list * @sa G_MoveCalc * @sa CL_ConditionalMoveCalc */ bool Grid_FindPath (const Routing &routing, const actorSizeEnum_t actorSize, pathing_t *path, const pos3_t from, const pos3_t targetPos, byte crouchingState, int maxTUs, byte ** fb_list, int fb_length) { bool found = false; int count; priorityQueue_t pqueue; pos4_t epos; /**< Extended position; includes crouching state */ pos3_t pos; /* this is the position of the current actor- so the actor can stand in the cell it is in when pathfinding */ pos3_t excludeFromForbiddenList; /* Confirm bounds */ assert((from[2]) < PATHFINDING_HEIGHT); assert(crouchingState == 0 || crouchingState == 1); /* s.a. ACTOR_MAX_STATES */ /* reset move data */ OBJSET(path->area, ROUTING_NOT_REACHABLE); OBJSET(path->areaFrom, ROUTING_NOT_REACHABLE); path->fblist = fb_list; path->fblength = fb_length; /* Prepare exclusion of starting-location (i.e. this should be ent-pos or le-pos) in Grid_CheckForbidden */ VectorCopy(from, excludeFromForbiddenList); /* set starting position to 0 TUs.*/ RT_AREA_POS(path, from, crouchingState) = 0; PQueueInitialise(&pqueue, 1024); Vector4Set(epos, from[0], from[1], from[2], crouchingState); PQueuePush(&pqueue, epos, 0); count = 0; while (!PQueueIsEmpty(&pqueue)) { PQueuePop(&pqueue, epos); VectorCopy(epos, pos); count++; /* if reaching that square already took too many TUs, * don't bother to reach new squares *from* there. */ const byte usedTUs = RT_AREA_POS(path, pos, crouchingState); if (usedTUs >= maxTUs) continue; for (int dir = 0; dir < PATHFINDING_DIRECTIONS; dir++) { Step step(routing, pos, actorSize, crouchingState, dir); /* Directions 12, 14, and 15 are currently undefined. */ if (dir == 12 || dir == 14 || dir == 15) continue; /* If this is a crouching or crouching move, forget it. */ if (dir == DIRECTION_STAND_UP || dir == DIRECTION_CROUCH) continue; if (!step.init()) continue; /* either dir is irrelevant or something worse happened */ if (!step.isPossible(path)) continue; /* Is this a better move into this cell? */ RT_AREA_TEST_POS(path, step.toPos, step.crouchingState); if (RT_AREA_POS(path, step.toPos, step.crouchingState) <= step.TUsAfter) { continue; /* This move is not optimum. */ } /* Test for forbidden (by other entities) areas. */ /* Do NOT check the forbiddenList. We might find a multi-turn path. */ #if 0 if (Grid_CheckForbidden(excludeFromForbiddenList, step.actorSize, path, step.toPos[0], step.toPos[1], step.toPos[2])) { continue; /* That spot is occupied. */ } #endif /* Store move in pathing table. */ Grid_SetMoveData(path, step.toPos, step.crouchingState, step.TUsAfter, step.dir, step.fromPos[2]); pos4_t dummy; const int dist = step.TUsAfter + (int) (2 * VectorDist(step.toPos, targetPos)); Vector4Set(dummy, step.toPos[0], step.toPos[1], step.toPos[2], step.crouchingState); PQueuePush(&pqueue, dummy, dist); if (VectorEqual(step.toPos, targetPos)) { found = true; break; } } if (found) break; } /* Com_Printf("Loop: %i", count); */ PQueueFree(&pqueue); return found; }
/** * @brief Calculates normals and tangents for all frames and does vertex merging based on smoothness * @param mesh The mesh to calculate normals for * @param nFrames How many frames the mesh has * @param smoothness How aggressively should normals be smoothed; value is compared with dotproduct of vectors to decide if they should be merged * @sa R_ModCalcNormalsAndTangents */ void R_ModCalcUniqueNormalsAndTangents (mAliasMesh_t *mesh, int nFrames, float smoothness) { int i, j; vec3_t triangleNormals[MAX_ALIAS_TRIS]; vec3_t triangleTangents[MAX_ALIAS_TRIS]; vec3_t triangleBitangents[MAX_ALIAS_TRIS]; const mAliasVertex_t *vertexes = mesh->vertexes; mAliasCoord_t *stcoords = mesh->stcoords; mAliasVertex_t *newVertexes; mAliasComplexVertex_t tmpVertexes[MAX_ALIAS_VERTS]; vec3_t tmpBitangents[MAX_ALIAS_VERTS]; mAliasCoord_t *newStcoords; const int numIndexes = mesh->num_tris * 3; const int32_t *indexArray = mesh->indexes; int32_t *newIndexArray; int indRemap[MAX_ALIAS_VERTS]; int sharedTris[MAX_ALIAS_VERTS]; int numVerts = 0; newIndexArray = (int32_t *)Mem_PoolAlloc(sizeof(int32_t) * numIndexes, vid_modelPool, 0); /* calculate per-triangle surface normals */ for (i = 0, j = 0; i < numIndexes; i += 3, j++) { vec3_t dir1, dir2; vec2_t dir1uv, dir2uv; /* calculate two mostly perpendicular edge directions */ VectorSubtract(vertexes[indexArray[i + 0]].point, vertexes[indexArray[i + 1]].point, dir1); VectorSubtract(vertexes[indexArray[i + 2]].point, vertexes[indexArray[i + 1]].point, dir2); Vector2Subtract(stcoords[indexArray[i + 0]], stcoords[indexArray[i + 1]], dir1uv); Vector2Subtract(stcoords[indexArray[i + 2]], stcoords[indexArray[i + 1]], dir2uv); /* we have two edge directions, we can calculate a third vector from * them, which is the direction of the surface normal */ CrossProduct(dir1, dir2, triangleNormals[j]); /* then we use the texture coordinates to calculate a tangent space */ if ((dir1uv[1] * dir2uv[0] - dir1uv[0] * dir2uv[1]) != 0.0) { const float frac = 1.0 / (dir1uv[1] * dir2uv[0] - dir1uv[0] * dir2uv[1]); vec3_t tmp1, tmp2; /* calculate tangent */ VectorMul(-1.0 * dir2uv[1] * frac, dir1, tmp1); VectorMul(dir1uv[1] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleTangents[j]); /* calculate bitangent */ VectorMul(-1.0 * dir2uv[0] * frac, dir1, tmp1); VectorMul(dir1uv[0] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleBitangents[j]); } else { const float frac = 1.0 / (0.00001); vec3_t tmp1, tmp2; /* calculate tangent */ VectorMul(-1.0 * dir2uv[1] * frac, dir1, tmp1); VectorMul(dir1uv[1] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleTangents[j]); /* calculate bitangent */ VectorMul(-1.0 * dir2uv[0] * frac, dir1, tmp1); VectorMul(dir1uv[0] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleBitangents[j]); } /* normalize */ VectorNormalizeFast(triangleNormals[j]); VectorNormalizeFast(triangleTangents[j]); VectorNormalizeFast(triangleBitangents[j]); Orthogonalize(triangleTangents[j], triangleBitangents[j]); } /* do smoothing */ for (i = 0; i < numIndexes; i++) { const int idx = (i - i % 3) / 3; VectorCopy(triangleNormals[idx], tmpVertexes[i].normal); VectorCopy(triangleTangents[idx], tmpVertexes[i].tangent); VectorCopy(triangleBitangents[idx], tmpBitangents[i]); for (j = 0; j < numIndexes; j++) { const int idx2 = (j - j % 3) / 3; /* don't add a vertex with itself */ if (j == i) continue; /* only average normals if vertices have the same position * and the normals aren't too far apart to start with */ if (VectorEqual(vertexes[indexArray[i]].point, vertexes[indexArray[j]].point) && DotProduct(triangleNormals[idx], triangleNormals[idx2]) > smoothness) { /* average the normals */ VectorAdd(tmpVertexes[i].normal, triangleNormals[idx2], tmpVertexes[i].normal); /* if the tangents match as well, average them too. * Note that having matching normals without matching tangents happens * when the order of vertices in two triangles sharing the vertex * in question is different. This happens quite frequently if the * modeler does not go out of their way to avoid it. */ if (Vector2Equal(stcoords[indexArray[i]], stcoords[indexArray[j]]) && DotProduct(triangleTangents[idx], triangleTangents[idx2]) > smoothness && DotProduct(triangleBitangents[idx], triangleBitangents[idx2]) > smoothness) { /* average the tangents */ VectorAdd(tmpVertexes[i].tangent, triangleTangents[idx2], tmpVertexes[i].tangent); VectorAdd(tmpBitangents[i], triangleBitangents[idx2], tmpBitangents[i]); } } } VectorNormalizeFast(tmpVertexes[i].normal); VectorNormalizeFast(tmpVertexes[i].tangent); VectorNormalizeFast(tmpBitangents[i]); } /* assume all vertices are unique until proven otherwise */ for (i = 0; i < numIndexes; i++) indRemap[i] = -1; /* merge vertices that have become identical */ for (i = 0; i < numIndexes; i++) { vec3_t n, b, t, v; if (indRemap[i] != -1) continue; for (j = i + 1; j < numIndexes; j++) { if (Vector2Equal(stcoords[indexArray[i]], stcoords[indexArray[j]]) && VectorEqual(vertexes[indexArray[i]].point, vertexes[indexArray[j]].point) && (DotProduct(tmpVertexes[i].normal, tmpVertexes[j].normal) > smoothness) && (DotProduct(tmpVertexes[i].tangent, tmpVertexes[j].tangent) > smoothness)) { indRemap[j] = i; newIndexArray[j] = numVerts; } } VectorCopy(tmpVertexes[i].normal, n); VectorCopy(tmpVertexes[i].tangent, t); VectorCopy(tmpBitangents[i], b); /* normalization here does shared-vertex smoothing */ VectorNormalizeFast(n); VectorNormalizeFast(t); VectorNormalizeFast(b); /* Grahm-Schmidt orthogonalization */ VectorMul(DotProduct(t, n), n, v); VectorSubtract(t, v, t); VectorNormalizeFast(t); /* calculate handedness */ CrossProduct(n, t, v); tmpVertexes[i].tangent[3] = (DotProduct(v, b) < 0.0) ? -1.0 : 1.0; VectorCopy(n, tmpVertexes[i].normal); VectorCopy(t, tmpVertexes[i].tangent); newIndexArray[i] = numVerts++; indRemap[i] = i; } for (i = 0; i < numVerts; i++) sharedTris[i] = 0; for (i = 0; i < numIndexes; i++) sharedTris[newIndexArray[i]]++; /* set up reverse-index that maps Vertex objects to a list of triangle verts */ mesh->revIndexes = (mIndexList_t *)Mem_PoolAlloc(sizeof(mIndexList_t) * numVerts, vid_modelPool, 0); for (i = 0; i < numVerts; i++) { mesh->revIndexes[i].length = 0; mesh->revIndexes[i].list = (int32_t *)Mem_PoolAlloc(sizeof(int32_t) * sharedTris[i], vid_modelPool, 0); } /* merge identical vertexes, storing only unique ones */ newVertexes = (mAliasVertex_t *)Mem_PoolAlloc(sizeof(mAliasVertex_t) * numVerts * nFrames, vid_modelPool, 0); newStcoords = (mAliasCoord_t *)Mem_PoolAlloc(sizeof(mAliasCoord_t) * numVerts, vid_modelPool, 0); for (i = 0; i < numIndexes; i++) { const int idx = indexArray[indRemap[i]]; const int idx2 = newIndexArray[i]; /* add vertex to new vertex array */ VectorCopy(vertexes[idx].point, newVertexes[idx2].point); Vector2Copy(stcoords[idx], newStcoords[idx2]); mesh->revIndexes[idx2].list[mesh->revIndexes[idx2].length++] = i; } /* copy over the points from successive frames */ for (i = 1; i < nFrames; i++) { for (j = 0; j < numIndexes; j++) { const int idx = indexArray[indRemap[j]] + (mesh->num_verts * i); const int idx2 = newIndexArray[j] + (numVerts * i); VectorCopy(vertexes[idx].point, newVertexes[idx2].point); } } /* copy new arrays back into original mesh */ Mem_Free(mesh->stcoords); Mem_Free(mesh->indexes); Mem_Free(mesh->vertexes); mesh->num_verts = numVerts; mesh->vertexes = newVertexes; mesh->stcoords = newStcoords; mesh->indexes = newIndexArray; }