📄 transformmat.cpp
字号:
/////////////////////////////////////////////////////////
////-------------------------------------------------////
//// Fast Line-of-sight implementation ////
//// (c) Copyright 2001, Tom Vykruta ////
//// All Rights Reserved. ////
//// *not to be used without author's permission* ////
////-------------------------------------------------////
/////////////////////////////////////////////////////////
//////////////////////////////////////////////////////
// TransformMat.cpp : Generic code for Line-Of-Sight. Works in 3D.
//
/////////////////////////////////////////////////////////
#include "stdafx.h"
#include "LOSGridDemo.h"
#include "TransformMat.h"
#include "ChildView.h"
extern Layer g_Layer;
extern CPaintDC* g_dc;
// This function would in reality do a more expensive ray-polygon test and return the impact point.
// For purposes of this demo it only draws a ray representing the element division
bool ElemDivisionCheck(const VECTOR3D& vFrom,const VECTOR3D& vTo, const int iElemX, const int iElemZ, VECTOR3D *pvImpact)
{
VECTOR2D vTemp;
vTemp = VECTOR2D((float)iElemX * ELEMWID, (float)iElemZ * ELEMWID);
DrawLine2(vTemp, VECTOR2D(ELEMWID,ELEMWID), 2);
return(FALSE);
}
// This is the meat of the package, where it all happens
// note: It is assumed that vFrom and vTo are already clipped to the layer's ABBox!
// squiggly lines ~^~^ represent visualization code
bool Layer::DoesElemCollideRay(const VECTOR3D &vFrom, const VECTOR3D &vTo, VECTOR3D* pvImpact) const
{
VECTOR2D _DEBUGvOrig(0,0);
VECTOR2D vPathLu;
VECTOR2D vFromLu, vToLu;
int iNumSteps;
float rXCurr, rZCurr;
vFromLu.Set(vFrom.x/rELEMWID,vFrom.z/rELEMWID); // convert to lu to make things easier
vToLu.Set(vTo.x/rELEMWID,vTo.z/rELEMWID);
{
VECTOR2D vLayerOrig;
vLayerOrig.Set( (float)GetXOrigin(), (float)GetZOrigin() );
vFromLu -= vLayerOrig;
vToLu -= vLayerOrig;
}
vPathLu.Set(vToLu.x - vFromLu.x, vToLu.y - vFromLu.y);
// ~^~^~^~^~^~^~~^DEBUG CODE~^~^~^~^~^~^~~^~^~^~^~^~^~^~~^
DrawLine2(VECTOR2D(vFromLu.x*ELEMWID, vFromLu.y*ELEMWID), VECTOR2D(vPathLu.x*ELEMWID, vPathLu.y*ELEMWID), 1);
// ~^~^~^~^~^~^~~^~^~^~^~^~^~^~~^~^~^~^~^~^~^~~^
// We will invert/mirror/rotate the grid in such a way that our line is always going +X, +Y, where slope is < 0.5.
TransformMatrix trans2dMat(vPathLu, vPathLu);
trans2dMat.TransformToGridSpace(vFromLu.x, vFromLu.y);
trans2dMat.TransformToGridSpace(vToLu.x, vToLu.y);
trans2dMat.TransformToGridSpace(vPathLu.x, vPathLu.y);
// ~^~^~^~^~^~^~~^DEBUG CODE~^~^~^~^~^~^~~^~^~^~^~^~^~^~~^
VECTOR2D vTemp, vTempTo;
DrawLine(VECTOR2D(vFromLu.x*ELEMWID, vFromLu.y*ELEMWID), VECTOR2D(vPathLu.x*ELEMWID, vPathLu.y*ELEMWID), 2);
// ~^~^~^~^~^~^~~^~^~^~^~^~^~^~~^~^~^~^~^~^~^~~^
// It' possible we will extend beyond layer boundaries.. make sure we dont try to check these
int iZAbsoluteStart = (int)floor(vFromLu.y);
int iZAbsoluteEnd = (int)ceil(vToLu.y);
const int iXMod = 0; // which way do we step to get a normal division line?
const int iZMod = 1;
const int iXModAlt = -1; // which way do we step to get alternate divison line?
const int iZModAlt = 0;
int xShiftDivision, zShiftDivision;
int xShiftDivisionAlt, zShiftDivisionAlt;
xShiftDivision = 1; // offset vector from the origin of the element
zShiftDivision = 0;
xShiftDivisionAlt = 1;
zShiftDivisionAlt = 1;
trans2dMat.TransformOffsetToRealSpace(xShiftDivision,zShiftDivision);
trans2dMat.TransformOffsetToRealSpace(xShiftDivisionAlt,zShiftDivisionAlt);
// ~^~^~^~^~^~^~~^visualization code~^~^~^~^~~^~^~^~^~^~^~^~~^
char message[128];
g_dc->Rectangle(40,270,240,320);
wsprintf(message, "NegX NegY FlipXY");
g_dc->TextOut(50, 280, message, strlen(message) );
wsprintf(message, "[ %i ][ %i ][ %i ]", (int)trans2dMat.matrix[TransformMatrix::tNegateX],
(int)trans2dMat.matrix[TransformMatrix::tNegateY],
(int)trans2dMat.matrix[TransformMatrix::tInvertXY]);
g_dc->TextOut(50, 300, message, strlen(message) );
g_dc->Rectangle(40,330,240,395);
wsprintf(message, "Division Offsets (X,Y)");
g_dc->TextOut(50, 340, message, strlen(message) );
wsprintf(message, "Normal: (%i, %i)", xShiftDivision, zShiftDivision );
g_dc->TextOut(50, 360, message, strlen(message) );
wsprintf(message, "Alternate: (%i, %i)", xShiftDivisionAlt, zShiftDivisionAlt );
g_dc->TextOut(50, 375, message, strlen(message) );
// ~^~^~^~^~^~^~~^~^~^~^~^~^~^~~^~^~^~^~^~^~^~~^
// We have an entry point and exit point based on a layer's bounding box.
// figure out how much we need to extend the vFrom->vTo ray by to get to the first element cross. We do this by snapping the vert along the XZ
// plane to the appropriate division line, then moving the other 2 components by same amount.
float rStep, rStepY;
float rYCurrWu;
// Depending on which way we are stepping, pick the proper land division line to start on
// and fraction of total ray we have to move by to get to the preious and last element crossing
{
float rShiftFract, rShiftFractEnd;
float rXEnd, rZEnd;
rXCurr = floor((double)vFromLu.x);
rXEnd = ceil((double)vToLu.x);
rShiftFract = (vFromLu.x - rXCurr);
rShiftFract *= -1.0f / vPathLu.x;
rShiftFractEnd = (vToLu.x - rXEnd);
rShiftFractEnd *= -1.0f / vPathLu.x;
// Extend the ray - it's possible we will extend them off the layer, catch that later
rXCurr = vFromLu.x + vPathLu.x * rShiftFract;
rZCurr = vFromLu.y + vPathLu.y * rShiftFract;
rXEnd = vToLu.x + vPathLu.x * rShiftFractEnd;
rZEnd = vToLu.y + vPathLu.y * rShiftFractEnd;
rYCurrWu = vFrom.y + (vTo.y - vFrom.y) * rShiftFract;
vPathLu.Set( rXEnd - rXCurr, rZEnd - rZCurr );
// figure out what our DX and DY is
float r1_Sections = 1.0f / vPathLu.dx;
rStep = vPathLu.dy * r1_Sections;
// shift the Y component once again by the same fraction that x and z where moved by
rStepY = (vTo.y - vFrom.y) * (1.0f + -rShiftFract + rShiftFractEnd) * r1_Sections;
// how many elements we iterate to reach the exit point
iNumSteps = (int)(vPathLu.dx + 0.5f) + 1; // 0.5f is for rounding (float) inaccuracies!
}
// precompute 1/rstep for similar triangles when calculating height offset
const float r1_StepHeight = rStepY * (1.f / rStep);
int iZTest, iXTest;
// to get around expensiv efloat->int conversions, using a float counter.. when it reaches 1, increment int
iZTest = (int)floor(rZCurr); // this cannot be FTOI because negative numbers woudln't be handled correctly
rZCurr = rZCurr - (float)iZTest;
iXTest = (int)(rXCurr + SIGN(rXCurr) * 0.5f); // 0.5f is for rounding (float) inaccuracies!
///////////////////////////
enum eTestPoint {etpBelow = 0, etpAbove, etpBetween, etpUninitialized};
eTestPoint ePosInitial = etpUninitialized; // our first test point lies abov eor below the layer..
eTestPoint ePosNow; // current testpoint
BOOL fAltDivision = FALSE;
int iXModReal, iZModReal, iXModAltReal, iZModAltReal;
// Figure out which way to step (in real grid space) to get the division line
iXModReal = iXMod; // in local grid space, we step +Z to get division
iZModReal = iZMod;
trans2dMat.TransformToRealSpace(iXModReal, iZModReal);
iXModAltReal = iXModAlt;// in local grid space, we step -X to get division
iZModAltReal = iZModAlt;
trans2dMat.TransformToRealSpace(iXModAltReal, iZModAltReal);
int iXTestReal, iZTestReal;
float rHeight;
float rAltDivisionYCorrection = 0.0f;
for (int i = 0; i < iNumSteps; )
{
// Get XZ coordinates transformed into real grid space
iXTestReal = iXTest;
iZTestReal = iZTest;
trans2dMat.TransformToRealSpace(iXTestReal, iZTestReal);
if (iZTest < iZAbsoluteStart || ((iZTest > iZAbsoluteEnd-1) && !fAltDivision) )
goto skip;
rHeight = rYCurrWu;
float rHeight1, rHeight2;
rHeight1 = VertHeight(iXTestReal, iZTestReal);
if (fAltDivision)
rHeight2 = VertHeight(iXTestReal + iXModAltReal, iZTestReal + iZModAltReal);
else
rHeight2 = VertHeight(iXTestReal + iXModReal, iZTestReal + iZModReal);
ePosNow = (eTestPoint)(rHeight > rHeight1);
if ( (eTestPoint)(rHeight > rHeight2) != ePosNow)
ePosNow = etpBetween;
// ~^~^~^~^~^~^~~^DEBUG CODE~^~^~^~^~^~^~~^~^~^~^~^~^~^~~^
vTemp.Set((float)iXTest*ELEMWID, (float)iZTest*ELEMWID);
vTempTo = VECTOR2D((fAltDivision ? iXModAlt : iXMod)*ELEMWID, (fAltDivision ? iZModAlt : iZMod)*ELEMWID);
DrawLine(vTemp, vTempTo);
vTemp.Set(iXTestReal*ELEMWID, iZTestReal*ELEMWID);
vTempTo = VECTOR2D((fAltDivision ? iXModAltReal : iXModReal)*ELEMWID, (fAltDivision ? iZModAltReal : iZModReal)*ELEMWID);
DrawLine2(vTemp, vTempTo);
// ~^~^~^~^~^~^~~^~^~^~^~^~^~^~~^~^~^~^~^~^~^~~^
if (ePosInitial == etpUninitialized)
ePosInitial = ePosNow; // first time, initialize height, don't check verts
else
{
// when i is 0, just initialize the etpPosNow
if (ePosNow == etpBetween || ePosInitial != ePosNow)
{
ePosInitial = ePosNow;
const int& xShift = (fAltDivision == FALSE) ? xShiftDivision : xShiftDivisionAlt;
const int& zShift = (fAltDivision == FALSE) ? zShiftDivision : zShiftDivisionAlt;
// In transformed space, to get the corner of elem, step back by 1X. Transform this to get the real step.
VECTOR3D vFromLocal = vFrom;
vFromLocal -= this->GetOrigin();
VECTOR3D vToLocal = vTo;
vToLocal -= this->GetOrigin();
if ( ElemDivisionCheck(vFromLocal,vToLocal,iXTestReal - xShift, iZTestReal - zShift, pvImpact) )
{
if (pvImpact)
*pvImpact += this->GetOrigin();
return(TRUE);
}
}
}
skip:
if (fAltDivision)// Go through the same loop, but with this flag set as false
{
fAltDivision = FALSE;
rYCurrWu += rAltDivisionYCorrection;
}
else
{
// move along our ray, to the next element division.
rYCurrWu += rStepY;
iXTest += 1;
rZCurr += rStep;
if ( rZCurr > 1.0f)
{
fAltDivision = TRUE; // we crossed a division that doesnt lie along the common axis..
rZCurr -= 1.0f;
iZTest += 1;
// Since we are checking the alt division, but our height check is a measure at the regular division, we must figure out the
// old height using similar triangles. equation: (1.0 * rZCurr) / rStep; gives us the horizontal fraction to step back by.
rAltDivisionYCorrection = r1_StepHeight * rZCurr;
rYCurrWu -= rAltDivisionYCorrection;
}
i++;
}
}
return(FALSE);
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -