/* =========================================================================== Copyright (C) 2008 Leszek Godlewski This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // cm_terain.c : LOD Terrain support // so many headaches #include "cm_local.h" #include "cm_terrain.h" typedef struct varnodeIndex_s { short unsigned int iTreeAndMask; short unsigned int iNode; } varnodeIndex_t; typedef struct worknode_s { int i0; int j0; int i1; int j1; int i2; int j2; } worknode_t; typedef struct pointtrace_s { traceWork_t *tw; terrainCollide_t *tc; vec3_t vStart; vec3_t vEnd; int i; int j; float fSurfaceClipEpsilon; } pointtrace_t; varnodeIndex_t g_vni[2][8][8][2]; static pointtrace_t g_trace; static int modeTable[] = {2, 2, 5, 6, 4, 3, 0, 0}; /* ================= CM_SignbitsForNormal ================= Copied over from cm_patch.c */ static int CM_SignbitsForNormal(vec3_t normal) { int bits, j; bits = 0; for (j = 0; j < 3; j++) { if (normal[j] < 0) { bits |= 1 << j; } } return bits; } /* ==================== CM_CalculateTerrainIndices ==================== */ void CM_CalculateTerrainIndices(worknode_t *worknode, int iDiagonal, int iTree) { int i; int i2; int j2; varnodeIndex_t *vni; for (i = 0; i <= 30; i++) { i2 = worknode[i + 1].i0 + worknode[i + 1].i1; j2 = worknode[i + 1].j0 + worknode[i + 1].j1; worknode[i * 2 + 2].i0 = worknode[i + 1].i1; worknode[i * 2 + 2].j0 = worknode[i + 1].j1; worknode[i * 2 + 2].i1 = worknode[i + 1].i2; worknode[i * 2 + 2].j1 = worknode[i + 1].j2; worknode[i * 2 + 2].i2 = i2 >> 1; worknode[i * 2 + 2].j2 = j2 >> 1; worknode[i * 2 + 2 + 1].i0 = worknode[i + 1].i2; worknode[i * 2 + 2 + 1].j0 = worknode[i + 1].j2; worknode[i * 2 + 2 + 1].i1 = worknode[i + 1].i0; worknode[i * 2 + 2 + 1].j1 = worknode[i + 1].j0; worknode[i * 2 + 2 + 1].i2 = i2 >> 1; worknode[i * 2 + 2 + 1].j2 = j2 >> 1; } for (i = 32; i < 64; i++) { i2 = (worknode[i].i0 + worknode[i].i1) >> 1; j2 = (worknode[i].j0 + worknode[i].j1) >> 1; if (worknode[i].i0 == worknode[i].i1) { if (worknode[i].j0 <= worknode[i].j1) { vni = &g_vni[iDiagonal][i2][j2][1]; vni->iNode = i - 1; vni->iTreeAndMask = iTree | 0x2000; vni = &g_vni[iDiagonal][i2][j2 - 1][0]; vni->iNode = i - 1; vni->iTreeAndMask = iTree | 0x1000; } else { vni = &g_vni[iDiagonal][i2 - 1][j2][1]; vni->iNode = i - 1; vni->iTreeAndMask = iTree | 0x1000; vni = &g_vni[iDiagonal][i2 - 1][j2 - 1][0]; vni->iNode = i - 1; vni->iTreeAndMask = iTree | 0x2000; } } else { if (worknode[i].i0 <= worknode[i].i1) { vni = &g_vni[iDiagonal][i2][j2 - 1][0]; vni->iNode = i - 1; vni->iTreeAndMask = iTree | 0x2000; vni = &g_vni[iDiagonal][i2 - 1][j2 - 1][0]; vni->iNode = i - 1; vni->iTreeAndMask = iTree | 0x1000; } else { vni = &g_vni[iDiagonal][i2][j2][1]; vni->iNode = i - 1; vni->iTreeAndMask = iTree | 0x1000; vni = &g_vni[iDiagonal][i2 - 1][j2][1]; vni->iNode = i - 1; vni->iTreeAndMask = iTree | 0x2000; } } } } /* ==================== CM_PrepareGenerateTerrainCollide ==================== */ void CM_PrepareGenerateTerrainCollide(void) { worknode_t worknode[64]; memset(&g_vni, 0, sizeof(g_vni)); worknode[1].i0 = 8; worknode[1].j0 = 8; worknode[1].i1 = 0; worknode[1].j1 = 0; worknode[1].i2 = 0; worknode[1].j2 = 8; CM_CalculateTerrainIndices(worknode, 0, 0); worknode[1].i0 = 0; worknode[1].j0 = 0; worknode[1].i1 = 8; worknode[1].j1 = 8; worknode[1].i2 = 8; worknode[1].j2 = 0; CM_CalculateTerrainIndices(worknode, 0, 1); worknode[1].i0 = 8; worknode[1].j0 = 0; worknode[1].i1 = 0; worknode[1].j1 = 8; worknode[1].i2 = 8; worknode[1].j2 = 8; CM_CalculateTerrainIndices(worknode, 1, 0); worknode[1].i0 = 0; worknode[1].j0 = 8; worknode[1].i1 = 8; worknode[1].j1 = 0; worknode[1].i2 = 0; worknode[1].j2 = 0; CM_CalculateTerrainIndices(worknode, 1, 1); } void CM_PickTerrainSquareMode(terrainCollideSquare_t *square, vec3_t vTest, int i, int j, cTerraPatch_t *patch) { int flags0, flags1; varnodeIndex_t *vni; if (patch->flags & TERPATCH_NEIGHBOR) { vni = g_vni[1][i][j]; } else { vni = g_vni[0][i][j]; } if ((vni[0].iTreeAndMask & patch->varTree[vni[0].iTreeAndMask & 1][vni[0].iNode].flags & 0xFFFE) != 0) { flags0 = 2; } else { flags0 = 0; } if ((vni[1].iTreeAndMask & patch->varTree[vni[1].iTreeAndMask & 1][vni[1].iNode].flags & 0xFFFFFFFE) != 0) { flags1 = 4; } else { flags1 = 0; } square->eMode = modeTable[(j + i) & 1 | flags0 | flags1]; if (square->eMode == 2) { if (DotProduct(vTest, square->plane[0]) < square->plane[0][3]) { square->eMode = 1; } } else if (square->eMode == 5 || square->eMode == 6) { VectorCopy(square->plane[1], square->plane[0]); square->plane[0][3] = square->plane[1][3]; } } /* ==================== CM_GenerateTerPatchCollide ==================== */ void CM_GenerateTerrainCollide(cTerraPatch_t *patch, terrainCollide_t *tc) { int i; int j; int x0, y0, z0; float fMaxHeight; float heightmap[9][9]; terrainCollideSquare_t *square; vec3_t v1; vec3_t v2; vec3_t v3; vec3_t v4; x0 = (patch->x << 6); y0 = (patch->y << 6); z0 = (patch->iBaseHeight); fMaxHeight = z0; for (j = 0; j < 9; j++) { for (i = 0; i < 9; i++) { heightmap[i][j] = (float)(z0 + 2 * patch->heightmap[j * 9 + i]); } } for (j = 0; j < 8; j++) { for (i = 0; i < 8; i++) { v1[0] = ((i << 6) + x0); v1[1] = ((j << 6) + y0); v1[2] = heightmap[i][j]; v2[0] = ((i << 6) + x0) + 64; v2[1] = ((j << 6) + y0); v2[2] = heightmap[i + 1][j]; v3[0] = ((i << 6) + x0) + 64; v3[1] = ((j << 6) + y0) + 64; v3[2] = heightmap[i + 1][j + 1]; v4[0] = ((i << 6) + x0); v4[1] = ((j << 6) + y0) + 64; v4[2] = heightmap[i][j + 1]; if (fMaxHeight < v1[2]) { fMaxHeight = v1[2]; } if (fMaxHeight < v2[2]) { fMaxHeight = v2[2]; } if (fMaxHeight < v3[2]) { fMaxHeight = v3[2]; } if (fMaxHeight < v4[2]) { fMaxHeight = v4[2]; } square = &tc->squares[i][j]; if ((i + j) & 1) { if (patch->flags & TERPATCH_FLIP) { CM_PlaneFromPoints(square->plane[0], v4, v2, v3); CM_PlaneFromPoints(square->plane[1], v2, v4, v1); } else { CM_PlaneFromPoints(square->plane[0], v2, v4, v3); CM_PlaneFromPoints(square->plane[1], v4, v2, v1); } CM_PickTerrainSquareMode(square, v1, i, j, patch); } else { if (patch->flags & TERPATCH_FLIP) { CM_PlaneFromPoints(square->plane[0], v1, v3, v4); CM_PlaneFromPoints(square->plane[1], v3, v1, v2); } else { CM_PlaneFromPoints(square->plane[0], v3, v1, v4); CM_PlaneFromPoints(square->plane[1], v1, v3, v2); } CM_PickTerrainSquareMode(square, v2, i, j, patch); } } } tc->vBounds[0][0] = x0; tc->vBounds[0][1] = y0; tc->vBounds[0][2] = z0; tc->vBounds[1][0] = (x0 + 512); tc->vBounds[1][1] = (y0 + 512); tc->vBounds[1][2] = fMaxHeight; } /* ==================== CM_CheckTerrainPlane ==================== */ float CM_CheckTerrainPlane(vec4_t plane) { float d1, d2; float f; d1 = DotProduct(g_trace.vStart, plane) - plane[3]; d2 = DotProduct(g_trace.vEnd, plane) - plane[3]; // if completely in front of face, no intersection with the entire brush if (d1 > 0 && (d2 >= SURFACE_CLIP_EPSILON || d2 >= d1)) { return 1; } if (d1 <= 0 && d2 <= 0) { if (d1 >= -32) { return 0; } if (d2 >= -32) { return 0; } return 1; } if (d1 <= d2) { return 1; } f = (d1 - SURFACE_CLIP_EPSILON) / (d1 - d2); if (f < 0) { f = 0; } return f; } /* ==================== CM_CheckTerrainTriSpherePoint ==================== */ float CM_CheckTerrainTriSpherePoint(vec3_t v) { vec3_t vDelta, vDir; float fLenSq; float fRadSq; float fA, fB; float fDiscr; float fFrac; float fSq; float f; VectorSubtract(g_trace.vStart, v, vDir); fRadSq = sphere.radius * sphere.radius; fLenSq = VectorLengthSquared(vDir); if (fLenSq <= fRadSq) { g_trace.tw->trace.startsolid = qtrue; g_trace.tw->trace.allsolid = qtrue; return 0; } VectorSubtract(g_trace.vEnd, g_trace.vStart, vDelta); fA = VectorLengthSquared(vDelta); fB = DotProduct(vDelta, vDir); fDiscr = fB * fB - (fLenSq - fRadSq) * fA; if (fDiscr <= 0.0f) { return g_trace.tw->trace.fraction; } fSq = sqrt(fDiscr); if (fA > 0) { fFrac = (-fB - fSq) / fA - g_trace.fSurfaceClipEpsilon; if (fFrac >= 0.0f && fFrac <= g_trace.tw->trace.fraction) { return fFrac; } fFrac = -fB + fSq; } else { fFrac = (-fB + fSq) / fA - g_trace.fSurfaceClipEpsilon; if (fFrac >= 0.0f && fFrac <= g_trace.tw->trace.fraction) { return fFrac; } fFrac = -fB - fSq; } f = fFrac / fA - g_trace.fSurfaceClipEpsilon; if (f < 0 || f > g_trace.tw->trace.fraction) { f = g_trace.tw->trace.fraction; } return f; } /* ==================== CM_CheckTerrainTriSphereCorner ==================== */ float CM_CheckTerrainTriSphereCorner(vec4_t plane, float x0, float y0, int i, int j) { vec3_t v; v[0] = ((i << 6) + x0); v[1] = ((j << 6) + y0); v[2] = (plane[3] - (v[1] * plane[1] + v[0] * plane[0])) / plane[2]; return CM_CheckTerrainTriSpherePoint(v); } /* ==================== CM_CheckTerrainTriSphereEdge ==================== */ float CM_CheckTerrainTriSphereEdge(float *plane, float x0, float y0, int i0, int j0, int i1, int j1) { vec3_t v0, v1; float fScale; float fRadSq; float S, T; vec3_t vDeltaStart; vec3_t vDirEdge; vec3_t vDirTrace; float fSFromT_Const; float fSFromT_Scale; vec3_t vRFromT_Const; vec3_t vRFromT_Scale; // junk variable(s) as usual float fLengthSq, fDot; float fFrac, fFracClip; fScale = 1.0 / plane[2]; v0[0] = (i0 << 6) + x0; v0[1] = (j0 << 6) + y0; v0[2] = (plane[3] - (v0[0] * plane[0] + v0[1] * plane[1])) * fScale; v1[0] = (i1 << 6) + x0; v1[1] = (j1 << 6) + y0; v1[2] = (plane[3] - (v1[0] * plane[0] + v1[1] * plane[1])) * fScale; VectorSubtract(g_trace.vStart, v0, vDirTrace); VectorSubtract(v1, v0, vDirEdge); VectorSubtract(g_trace.vEnd, g_trace.vStart, vDeltaStart); fScale = 1.0 / VectorLengthSquared(vDirEdge); S = DotProduct(vDirTrace, vDirEdge) * fScale; T = DotProduct(vDeltaStart, vDirEdge) * fScale; VectorMA(vDirTrace, -S, vDirEdge, vRFromT_Const); VectorMA(vDeltaStart, -T, vDirEdge, vRFromT_Scale); fRadSq = sphere.radius * sphere.radius; fLengthSq = VectorLengthSquared(vRFromT_Const); if (fLengthSq <= fRadSq) { if (S < 0 || S > 1) { return CM_CheckTerrainTriSpherePoint(v0); } g_trace.tw->trace.startsolid = qtrue; g_trace.tw->trace.allsolid = qtrue; return 1; } fDot = DotProduct(vRFromT_Scale, vRFromT_Const); fSFromT_Scale = VectorLengthSquared(vRFromT_Scale); fSFromT_Const = fDot * fDot - (fLengthSq - fRadSq) * fSFromT_Scale; if (fSFromT_Const <= 0) { return g_trace.tw->trace.fraction; } if (fSFromT_Scale > 0) { fFrac = (-fDot - sqrt(fSFromT_Const)) / fSFromT_Scale; } else { fFrac = (-fDot + sqrt(fSFromT_Const)) / fSFromT_Scale; } fFracClip = fFrac - g_trace.fSurfaceClipEpsilon; if (fFrac <= 0 || fFracClip >= g_trace.tw->trace.fraction) { return g_trace.tw->trace.fraction; } fFrac = fFrac * T + S; if (fFrac < 0) { return CM_CheckTerrainTriSpherePoint(v0); } if (fFrac > 1) { return CM_CheckTerrainTriSpherePoint(v1); } if (fFracClip < 0) { fFracClip = 0; } return fFracClip; } /* ==================== CM_CheckTerrainTriSphere ==================== */ float CM_CheckTerrainTriSphere(float x0, float y0, int iPlane) { float *plane; float fMaxFraction; float d1, d2; float fSpherePlane; int eMode; qboolean bFitsX, bFitsY; qboolean bFitsDiag; int iX[3]; int iY[3]; plane = g_trace.tc->squares[g_trace.i][g_trace.j].plane[iPlane]; d1 = DotProduct(g_trace.vStart, plane) - plane[3]; d2 = DotProduct(g_trace.vEnd, plane) - plane[3]; if (d1 > sphere.radius) { if (d2 >= sphere.radius + SURFACE_CLIP_EPSILON) { return g_trace.tw->trace.fraction; } if (d2 >= d1) { return g_trace.tw->trace.fraction; } } if (d1 <= -sphere.radius && d2 <= -sphere.radius) { return g_trace.tw->trace.fraction; } if (d1 <= d2) { return g_trace.tw->trace.fraction; } fMaxFraction = SURFACE_CLIP_EPSILON / (d1 - d2); g_trace.fSurfaceClipEpsilon = fMaxFraction; fSpherePlane = (d1 - sphere.radius) / (d1 - d2) - fMaxFraction; if (fSpherePlane < 0) { fSpherePlane = 0; } if (fSpherePlane >= g_trace.tw->trace.fraction) { return g_trace.tw->trace.fraction; } d1 = (g_trace.vEnd[0] - g_trace.vStart[0]) * fSpherePlane + g_trace.vEnd[0] - sphere.radius * plane[0] - x0; d2 = (g_trace.vEnd[1] - g_trace.vStart[1]) * fSpherePlane + g_trace.vEnd[1] - sphere.radius * plane[1] - y0; eMode = g_trace.tc->squares[g_trace.i][g_trace.j].eMode; if (eMode == 1 || eMode == 2) { if ((g_trace.i + g_trace.j) & 1) { eMode = iPlane ? 6 : 3; } else { eMode = iPlane ? 5 : 4; } } switch (eMode) { case 3: bFitsX = d1 <= 64; bFitsY = d2 <= 64; bFitsDiag = d1 >= 64 - d2; iX[0] = 1; iX[1] = 0; iX[2] = 1; iY[0] = 1; iY[1] = 1; iY[2] = 0; break; case 4: bFitsX = d1 >= 0; bFitsY = d2 <= 64; bFitsDiag = d1 <= d2; iX[0] = 0; iX[1] = 1; iX[2] = 0; iY[0] = 1; iY[1] = 1; iY[2] = 0; break; case 5: bFitsX = d1 <= 64; bFitsY = d2 >= 0; bFitsDiag = d1 >= d2; iX[0] = 1; iX[1] = 0; iX[2] = 1; iY[0] = 0; iY[1] = 0; iY[2] = 1; break; case 6: bFitsX = d1 >= 0; bFitsY = d2 >= 0; bFitsDiag = d1 <= 64 - d2; iX[0] = 0; iX[1] = 1; iX[2] = 0; iY[0] = 0; iY[1] = 0; iY[2] = 1; break; default: return 0; } if (bFitsX && bFitsY) { if (bFitsDiag) { return fSpherePlane; } return CM_CheckTerrainTriSphereEdge(plane, x0, y0, iX[1], iY[1], iX[2], iY[2]); } if (bFitsX && !bFitsY) { if (bFitsDiag) { return CM_CheckTerrainTriSphereEdge(plane, x0, y0, iX[0], iY[0], iX[1], iY[1]); } return CM_CheckTerrainTriSphereCorner(plane, x0, y0, iX[1], iY[1]); } if (!bFitsX && bFitsY) { if (bFitsDiag) { return CM_CheckTerrainTriSphereEdge(plane, x0, y0, iX[0], iY[0], iX[2], iY[2]); } return CM_CheckTerrainTriSphereCorner(plane, x0, y0, iX[2], iY[2]); } if (!bFitsX && !bFitsY) { if (bFitsDiag) { return CM_CheckTerrainTriSphereCorner(plane, x0, y0, iX[0], iY[0]); } } return g_trace.tw->trace.fraction; } /* ==================== CM_ValidateTerrainCollidePointSquare ==================== */ qboolean CM_ValidateTerrainCollidePointSquare(float frac) { float f; f = g_trace.vStart[0] + frac * (g_trace.vEnd[0] - g_trace.vStart[0]) - ((g_trace.i << 6) + g_trace.tc->vBounds[0][0]); if (f >= 0 && f <= 64) { f = g_trace.vStart[1] + frac * (g_trace.vEnd[1] - g_trace.vStart[1]) - ((g_trace.j << 6) + g_trace.tc->vBounds[0][1]); if (f >= 0 && f <= 64) { return qtrue; } } return qfalse; } /* ==================== CM_ValidateTerrainCollidePointTri ==================== */ qboolean CM_ValidateTerrainCollidePointTri(int eMode, float frac) { float x0, y0; float x, y; float dx, dy; x0 = (g_trace.i << 6) + g_trace.tc->vBounds[0][0]; dx = g_trace.vStart[0] + (g_trace.vEnd[0] - g_trace.vStart[0]) * frac; x = x0 + 64; if (x0 > dx) { return qfalse; } if (x < dx) { return qfalse; } y0 = (g_trace.j << 6) + g_trace.tc->vBounds[0][1]; dy = g_trace.vStart[1] + (g_trace.vEnd[1] - g_trace.vStart[1]) * frac; y = y0 + 64; if (y0 > dy) { return qfalse; } if (y < dy) { return qfalse; } switch (eMode) { case 3: return (dx - x0) >= (64 - (dy - y0)); case 4: return (dx - x0) <= (dy - y0); case 5: return (dx - x0) >= (dy - y0); case 6: return (dx - x0) <= (64 - (dy - y0)); default: return qtrue; } } /* ==================== CM_TestTerrainCollideSquare ==================== */ qboolean CM_TestTerrainCollideSquare(void) { float *plane; float frac0; float enterFrac; int eMode; eMode = g_trace.tc->squares[g_trace.i][g_trace.j].eMode; if (!eMode) { return qfalse; } if (eMode >= 0 && eMode <= 2) { enterFrac = CM_CheckTerrainPlane(g_trace.tc->squares[g_trace.i][g_trace.j].plane[0]); plane = g_trace.tc->squares[g_trace.i][g_trace.j].plane[1]; frac0 = CM_CheckTerrainPlane(plane); if (eMode == 2) { if (enterFrac > frac0) { enterFrac = frac0; } } else { if (enterFrac < frac0) { enterFrac = frac0; } } if (enterFrac < g_trace.tw->trace.fraction && CM_ValidateTerrainCollidePointSquare(enterFrac)) { g_trace.tw->trace.fraction = enterFrac; VectorCopy(plane, g_trace.tw->trace.plane.normal); g_trace.tw->trace.plane.dist = plane[3]; return qtrue; } } else { plane = g_trace.tc->squares[g_trace.i][g_trace.j].plane[0]; enterFrac = CM_CheckTerrainPlane(plane); if (enterFrac < g_trace.tw->trace.fraction && CM_ValidateTerrainCollidePointTri(g_trace.tc->squares[g_trace.i][g_trace.j].eMode, enterFrac)) { g_trace.tw->trace.fraction = enterFrac; VectorCopy(plane, g_trace.tw->trace.plane.normal); g_trace.tw->trace.plane.dist = plane[3]; return qtrue; } } return qfalse; } /* ==================== CM_CheckStartInsideTerrain ==================== */ static qboolean CM_CheckStartInsideTerrain(int i, int j, float fx, float fy) { float *plane; float fDot; if (i < 0 || i > 7) { return qfalse; } if (j < 0 || j > 7) { return qfalse; } if (!g_trace.tc->squares[i][j].eMode) { return qfalse; } if ((i + j) & 1) { if (fx + fy >= 1) { if (g_trace.tc->squares[i][j].eMode == 6) { return qfalse; } plane = g_trace.tc->squares[i][j].plane[0]; } else { if (g_trace.tc->squares[i][j].eMode == 3) { return qfalse; } plane = g_trace.tc->squares[i][j].plane[1]; } } else { if (fy >= fx) { if (g_trace.tc->squares[i][j].eMode == 5) { return qfalse; } plane = g_trace.tc->squares[i][j].plane[0]; } else { if (g_trace.tc->squares[i][j].eMode == 4) { return qfalse; } plane = g_trace.tc->squares[i][j].plane[1]; } } fDot = DotProduct(g_trace.vStart, plane); if (fDot <= plane[3] && fDot + 32.0f >= plane[3]) { return qtrue; } return qfalse; } /* ==================== CM_PositionTestPointInTerrainCollide ==================== */ qboolean CM_PositionTestPointInTerrainCollide(void) { int i0, j0; float fx, fy; fx = (g_trace.vStart[0] - g_trace.tc->vBounds[0][0]) * (SURFACE_CLIP_EPSILON / 8); fy = (g_trace.vStart[1] - g_trace.tc->vBounds[0][1]) * (SURFACE_CLIP_EPSILON / 8); i0 = (int)floor(fx); j0 = (int)floor(fy); return CM_CheckStartInsideTerrain(i0, j0, fx - i0, fy - j0); } /* ==================== CM_TracePointThroughTerrainCollide ==================== */ void CM_TracePointThroughTerrainCollide(void) { int i0, j0, i1, j1; int di, dj; int d1, d2; //int nTotal; float fx, fy; float dx, dy, dx2, dy2; fx = (g_trace.vStart[0] - g_trace.tc->vBounds[0][0]) * (SURFACE_CLIP_EPSILON / 8); fy = (g_trace.vStart[1] - g_trace.tc->vBounds[0][1]) * (SURFACE_CLIP_EPSILON / 8); i0 = (int64_t)floor(fx); j0 = (int64_t)floor(fy); i1 = (int64_t)floor((g_trace.vEnd[0] - g_trace.tc->vBounds[0][0]) * (SURFACE_CLIP_EPSILON / 8)); j1 = (int64_t)floor((g_trace.vEnd[1] - g_trace.tc->vBounds[0][1]) * (SURFACE_CLIP_EPSILON / 8)); const float dfx = fx - i0; const float dfy = fy - j0; if (CM_CheckStartInsideTerrain(i0, j0, dfx, dfy)) { g_trace.tw->trace.startsolid = qtrue; g_trace.tw->trace.allsolid = qtrue; g_trace.tw->trace.fraction = 0; return; } if (i0 == i1) { if (i0 < 0 || i0 > 7) { return; } if (j0 == j1) { if (j0 < 0 || j0 > 7) { return; } g_trace.i = i0; g_trace.j = j0; CM_TestTerrainCollideSquare(); } else if (j0 >= j1) { if (j0 > 7) { j0 = 7; } if (j1 < 0) { j1 = 0; } g_trace.i = i0; for (g_trace.j = j0; g_trace.j >= j1; g_trace.j--) { if (CM_TestTerrainCollideSquare()) { return; } } } else { if (j0 < 0) { j0 = 0; } if (j1 > 7) { j1 = 7; } g_trace.i = i0; for (g_trace.j = j0; g_trace.j <= j1; g_trace.j++) { if (CM_TestTerrainCollideSquare()) { return; } } } } else if (j0 == j1) { if (j0 < 0 || j0 > 7) { return; } if (i0 >= i1) { if (i0 > 7) { i0 = 7; } if (i1 < 0) { i1 = 0; } g_trace.j = j0; for (g_trace.i = i0; g_trace.i >= i1; g_trace.i--) { if (CM_TestTerrainCollideSquare()) { break; } } } else { if (i0 < 0) { i0 = 0; } if (i1 > 7) { i1 = 7; } g_trace.j = j0; for (g_trace.i = i0; g_trace.i <= i1; g_trace.i++) { if (CM_TestTerrainCollideSquare()) { break; } } } } else { dx = g_trace.vEnd[0] - g_trace.vStart[0]; dy = g_trace.vEnd[1] - g_trace.vStart[1]; // Fix //== // The original compares if delta float is zero // not only it's slower, but it can be problematic if those floats are NaN //== if (i1 > i0) { // dx positive d1 = 1; di = i1 - i0; dx2 = (i0 + 1 - fx) * dy; } else { d1 = -1; di = i0 - i1; dx = -dx; dx2 = dfx * dy; } if (j1 > j0) { // dy positive d2 = 1; dj = di + j1 - j0 + 1; dy2 = (j0 + 1 - fy) * dx; } else { d2 = -1; dy = -dy; dj = di + j0 - j1 + 1; dy2 = dfy * dx; dx2 = -dx2; } g_trace.i = i0; g_trace.j = j0; while (1) { if (g_trace.i >= 0 && g_trace.i <= 7 && g_trace.j >= 0 && g_trace.j <= 7) { if (CM_TestTerrainCollideSquare()) { return; } } dj--; if (!dj) { break; } if (dx2 < dy2) { dy2 -= dx2; dx2 = dy; g_trace.i += d1; } else { dx2 -= dy2; dy2 = dx; g_trace.j += d2; } } } } /* ==================== CM_TraceCylinderThroughTerrainCollide ==================== */ void CM_TraceCylinderThroughTerrainCollide(traceWork_t *tw, const terrainCollide_t *tc) { int i0, j0, i1, j1; float x0, y0; float enterFrac; i0 = (int)(floor(tw->bounds[0][0] - tc->vBounds[0][0]) * (SURFACE_CLIP_EPSILON / 8)); i1 = (int)(floor(tw->bounds[1][0] - tc->vBounds[0][0]) * (SURFACE_CLIP_EPSILON / 8)); j0 = (int)(floor(tw->bounds[0][1] - tc->vBounds[0][1]) * (SURFACE_CLIP_EPSILON / 8)); j1 = (int)(floor(tw->bounds[1][1] - tc->vBounds[0][1]) * (SURFACE_CLIP_EPSILON / 8)); if (i0 < 0) { i0 = 0; } if (j0 < 0) { j0 = 0; } if (i1 > 7) { i1 = 7; } if (j1 > 7) { j1 = 7; } y0 = (j0 << 6) + tc->vBounds[0][1]; for (g_trace.j = j0; g_trace.j <= j1; g_trace.j++) { x0 = (i0 << 6) + tc->vBounds[0][0]; for (g_trace.i = i0; g_trace.i <= i1; g_trace.i++) { switch (tc->squares[g_trace.i][g_trace.j].eMode) { case 1: case 2: enterFrac = CM_CheckTerrainTriSphere(x0, y0, 0); if (enterFrac < 0) { enterFrac = 0; } if (enterFrac < g_trace.tw->trace.fraction) { g_trace.tw->trace.fraction = enterFrac; VectorCopy(g_trace.tc->squares[g_trace.i][g_trace.j].plane[0], g_trace.tw->trace.plane.normal); g_trace.tw->trace.plane.dist = g_trace.tc->squares[g_trace.i][g_trace.j].plane[0][3]; } enterFrac = CM_CheckTerrainTriSphere(x0, y0, 1); if (enterFrac < 0) { enterFrac = 0; } if (enterFrac < g_trace.tw->trace.fraction) { g_trace.tw->trace.fraction = enterFrac; VectorCopy(g_trace.tc->squares[g_trace.i][g_trace.j].plane[1], g_trace.tw->trace.plane.normal); g_trace.tw->trace.plane.dist = g_trace.tc->squares[g_trace.i][g_trace.j].plane[1][3]; } break; case 3: case 4: enterFrac = CM_CheckTerrainTriSphere(x0, y0, 0); if (enterFrac < 0) { enterFrac = 0; } if (enterFrac < g_trace.tw->trace.fraction) { g_trace.tw->trace.fraction = enterFrac; VectorCopy(g_trace.tc->squares[g_trace.i][g_trace.j].plane[0], g_trace.tw->trace.plane.normal); g_trace.tw->trace.plane.dist = g_trace.tc->squares[g_trace.i][g_trace.j].plane[0][3]; } break; case 5: case 6: enterFrac = CM_CheckTerrainTriSphere(x0, y0, 1); if (enterFrac < 0) { enterFrac = 0; } if (enterFrac < g_trace.tw->trace.fraction) { g_trace.tw->trace.fraction = enterFrac; VectorCopy(g_trace.tc->squares[g_trace.i][g_trace.j].plane[1], g_trace.tw->trace.plane.normal); g_trace.tw->trace.plane.dist = g_trace.tc->squares[g_trace.i][g_trace.j].plane[1][3]; } break; default: break; } x0 += 64; } y0 += 64; } } /* ==================== CM_TraceThroughTerrainCollide ==================== */ void CM_TraceThroughTerrainCollide(traceWork_t *tw, terrainCollide_t *tc) { int i; if (tw->bounds[0][0] >= tc->vBounds[1][0] || tw->bounds[1][0] <= tc->vBounds[0][0]) { return; } if (tw->bounds[0][1] >= tc->vBounds[1][1] || tw->bounds[1][1] <= tc->vBounds[0][1]) { return; } if (tw->bounds[0][2] >= tc->vBounds[1][2] || tw->bounds[1][2] <= tc->vBounds[0][2]) { return; } g_trace.tw = tw; g_trace.tc = tc; VectorCopy(tw->start, g_trace.vStart); VectorCopy(tw->end, g_trace.vEnd); if (sphere.use && cm_ter_usesphere->integer) { VectorSubtract(tw->start, sphere.offset, g_trace.vStart); VectorSubtract(tw->end, sphere.offset, g_trace.vEnd); CM_TraceCylinderThroughTerrainCollide(tw, tc); } else if (tw->isPoint) { VectorCopy(tw->start, g_trace.vStart); VectorCopy(tw->end, g_trace.vEnd); CM_TracePointThroughTerrainCollide(); } else { if (tc->squares[0][0].plane[0][2] >= 0) { for (i = 0; i < 4; i++) { VectorAdd(tw->start, tw->offsets[i], g_trace.vStart); VectorAdd(tw->end, tw->offsets[i], g_trace.vEnd); CM_TracePointThroughTerrainCollide(); if (tw->trace.allsolid) { return; } } } else { for (i = 4; i < 8; i++) { VectorAdd(tw->start, tw->offsets[i], g_trace.vStart); VectorAdd(tw->end, tw->offsets[i], g_trace.vEnd); CM_TracePointThroughTerrainCollide(); if (tw->trace.allsolid) { return; } } } } } /* ==================== CM_PositionTestInTerrainCollide ==================== */ qboolean CM_PositionTestInTerrainCollide(traceWork_t *tw, terrainCollide_t *tc) { int i; if (tw->bounds[0][0] >= tc->vBounds[1][0] || tw->bounds[1][0] <= tc->vBounds[0][0]) { return qfalse; } if (tw->bounds[0][1] >= tc->vBounds[1][1] || tw->bounds[1][1] <= tc->vBounds[0][1]) { return qfalse; } if (tw->bounds[0][2] >= tc->vBounds[1][2] || tw->bounds[1][2] <= tc->vBounds[0][2]) { return qfalse; } g_trace.tw = tw; g_trace.tc = tc; VectorCopy(tw->start, g_trace.vStart); VectorCopy(tw->end, g_trace.vEnd); if (sphere.use && cm_ter_usesphere->integer) { VectorSubtract(tw->start, sphere.offset, g_trace.vStart); VectorSubtract(tw->end, sphere.offset, g_trace.vEnd); CM_TraceCylinderThroughTerrainCollide(tw, tc); return tw->trace.startsolid; } else if (tw->isPoint) { VectorCopy(tw->start, g_trace.vStart); VectorCopy(tw->end, g_trace.vEnd); return CM_PositionTestPointInTerrainCollide(); } else { if (tc->squares[0][0].plane[0][2] >= 0) { for (i = 0; i < 4; i++) { VectorAdd(tw->start, tw->offsets[i], g_trace.vStart); VectorAdd(tw->end, tw->offsets[i], g_trace.vEnd); if (CM_PositionTestPointInTerrainCollide()) { return qtrue; } } } else { for (i = 4; i < 8; i++) { VectorAdd(tw->start, tw->offsets[i], g_trace.vStart); VectorAdd(tw->end, tw->offsets[i], g_trace.vEnd); if (CM_PositionTestPointInTerrainCollide()) { return qtrue; } } } } return qfalse; } /* ==================== CM_SightTracePointThroughTerrainCollide ==================== */ qboolean CM_SightTracePointThroughTerrainCollide(void) { int i0, j0; int i1, j1; int di, dj; float fx, fy; float dx, dy, dx2, dy2; float d1, d2; fx = (g_trace.vStart[0] - g_trace.tc->vBounds[0][0]) * (SURFACE_CLIP_EPSILON / 8); fy = (g_trace.vStart[1] - g_trace.tc->vBounds[0][1]) * (SURFACE_CLIP_EPSILON / 8); i0 = (int)floor(fx); j0 = (int)floor(fy); i1 = (int)floor((g_trace.vEnd[0] - g_trace.tc->vBounds[0][0]) * (SURFACE_CLIP_EPSILON / 8)); j1 = (int)floor((g_trace.vEnd[1] - g_trace.tc->vBounds[0][1]) * (SURFACE_CLIP_EPSILON / 8)); if (CM_CheckStartInsideTerrain(i0, j0, fx - i0, fy - j0)) { return qfalse; } if (i0 == i1) { if (i0 < 0 || i0 > 7) { return qtrue; } if (j0 == j1) { if (j0 < 0 || j0 > 7) { return qtrue; } g_trace.i = i0; g_trace.j = j0; return !CM_TestTerrainCollideSquare(); } else if (j0 >= j1) { if (j0 > 7) { j0 = 7; } if (j1 < 0) { j1 = 0; } g_trace.i = i0; for (g_trace.j = j0; g_trace.j >= j1; g_trace.j--) { if (CM_TestTerrainCollideSquare()) { return qfalse; } } } else { if (j0 < 0) { j0 = 0; } if (j1 > 7) { j1 = 7; } g_trace.i = i0; for (g_trace.j = j0; g_trace.j <= j1; g_trace.j++) { if (CM_TestTerrainCollideSquare()) { return qfalse; } } } } else if (j0 == j1) { if (j0 < 0 || j0 > 7) { return qtrue; } if (i0 >= i1) { if (i0 > 7) { i0 = 7; } if (i1 < 0) { i1 = 0; } g_trace.j = j0; for (g_trace.i = i0; g_trace.i >= i1; g_trace.i--) { if (CM_TestTerrainCollideSquare()) { return qfalse; } } } else { if (i0 < 0) { i0 = 0; } if (i1 > 7) { i1 = 7; } g_trace.j = j0; for (g_trace.i = i0; g_trace.i <= i1; g_trace.i++) { if (CM_TestTerrainCollideSquare()) { return qfalse; } } } } else { dx = g_trace.vEnd[0] - g_trace.vStart[0]; dy = g_trace.vEnd[1] - g_trace.vStart[1]; if (dx > 0) { d1 = 1; di = i1 - i0; dx2 = (i0 + 1 - fx) * dy; } else { d1 = -1; di = i0 - i1; dx = -dx; dx2 = (fx - i0) * dy; } if (dy > 0) { d2 = 1; dj = di + j1 - j0 + 1; dy2 = (j0 + 1 - fy) * dx; } else { d2 = -1; dy = -dy; dj = di + j0 - j1 + 1; dy2 = (fy - j0) * dx; dx2 = -dx2; } g_trace.i = i0; g_trace.j = j0; while (1) { if (g_trace.i >= 0 && g_trace.i <= 7 && g_trace.j >= 0 && g_trace.j <= 7) { if (CM_TestTerrainCollideSquare()) { return qfalse; } } dj--; if (!dj) { break; } if (dx2 < dy2) { dy2 -= dx2; dx2 = dy; g_trace.i += d1; } else { dx2 -= dy2; dy2 = dx; g_trace.j += d2; } } } return qtrue; } /* ==================== CM_SightTraceThroughTerrainCollide ==================== */ qboolean CM_SightTraceThroughTerrainCollide(traceWork_t *tw, terrainCollide_t *tc) { int i; if (tw->bounds[0][0] >= tc->vBounds[1][0] || tw->bounds[1][0] <= tc->vBounds[0][0]) { return qfalse; } if (tw->bounds[0][1] >= tc->vBounds[1][1] || tw->bounds[1][1] <= tc->vBounds[0][1]) { return qfalse; } if (tw->bounds[0][2] >= tc->vBounds[1][2] || tw->bounds[1][2] <= tc->vBounds[0][2]) { return qfalse; } g_trace.tw = tw; g_trace.tc = tc; VectorCopy(tw->start, g_trace.vStart); VectorCopy(tw->end, g_trace.vEnd); if (tw->isPoint) { VectorCopy(tw->start, g_trace.vStart); VectorCopy(tw->end, g_trace.vEnd); return CM_SightTracePointThroughTerrainCollide(); } else { if (tc->squares[0][0].plane[0][2] >= 0) { for (i = 0; i < 4; i++) { VectorAdd(tw->start, tw->offsets[i], g_trace.vStart); VectorAdd(tw->end, tw->offsets[i], g_trace.vEnd); if (!CM_SightTracePointThroughTerrainCollide()) { return qfalse; } } } else { for (i = 4; i < 8; i++) { VectorAdd(tw->start, tw->offsets[i], g_trace.vStart); VectorAdd(tw->end, tw->offsets[i], g_trace.vEnd); if (!CM_SightTracePointThroughTerrainCollide()) { return qfalse; } } } } return qtrue; } /* ==================== CM_TerrainSquareType ==================== */ int CM_TerrainSquareType(int iTerrainPatch, int i, int j) { return cm.terrain[iTerrainPatch].tc.squares[i][j].eMode; } /* ==================== CM_SwapTerraPatch Swaps the patch on big-endian ==================== */ void CM_SwapTerraPatch(cTerraPatch_t* pPatch) { #ifdef Q3_BIG_ENDIAN int i; pPatch->texCoord[0][0][0] = LittleFloat(pPatch->texCoord[0][0][0]); pPatch->texCoord[0][0][1] = LittleFloat(pPatch->texCoord[0][0][1]); pPatch->texCoord[0][1][0] = LittleFloat(pPatch->texCoord[0][1][0]); pPatch->texCoord[0][1][1] = LittleFloat(pPatch->texCoord[0][1][1]); pPatch->texCoord[1][0][0] = LittleFloat(pPatch->texCoord[1][0][0]); pPatch->texCoord[1][0][1] = LittleFloat(pPatch->texCoord[1][0][1]); pPatch->texCoord[1][1][0] = LittleFloat(pPatch->texCoord[1][1][0]); pPatch->texCoord[1][1][1] = LittleFloat(pPatch->texCoord[1][1][1]); pPatch->iBaseHeight = LittleShort(pPatch->iBaseHeight); pPatch->iShader = LittleUnsignedShort(pPatch->iShader); pPatch->iLightMap = LittleUnsignedShort(pPatch->iLightMap); pPatch->iNorth = LittleShort(pPatch->iNorth); pPatch->iEast = LittleShort(pPatch->iEast); pPatch->iSouth = LittleShort(pPatch->iSouth); pPatch->iWest = LittleShort(pPatch->iWest); for (i = 0; i < MAX_TERRAIN_VARNODES; i++) { pPatch->varTree[0][i].flags = LittleUnsignedShort(pPatch->varTree[0][i].flags); pPatch->varTree[1][i].flags = LittleUnsignedShort(pPatch->varTree[1][i].flags); } #endif }