float CGround::LineGroundCol(float3 from, float3 to) { float savedLength = 0.0f; if (from.z > float3::maxzpos && to.z < float3::maxzpos) { // a special case since the camera in overhead mode can often do this float3 dir = to - from; float maxLength = dir.Length(); dir /= maxLength; savedLength = -(from.z - float3::maxzpos) / dir.z; from += dir * savedLength; } from.CheckInBounds(); float3 dir = to - from; float maxLength = dir.Length(); dir /= maxLength; if (from.x + dir.x * maxLength < 1.0f) maxLength = (1.0f - from.x) / dir.x; else if (from.x + dir.x * maxLength > float3::maxxpos) maxLength = (float3::maxxpos - from.x) / dir.x; if (from.z + dir.z * maxLength < 1.0f) maxLength = (1.0f - from.z) / dir.z; else if (from.z + dir.z * maxLength > float3::maxzpos) maxLength = (float3::maxzpos - from.z) / dir.z; to = from + dir * maxLength; const float dx=to.x-from.x; const float dz=to.z-from.z; float xp=from.x; float zp=from.z; float ret; float xn,zn; bool keepgoing=true; if((floor(from.x/SQUARE_SIZE)==floor(to.x/SQUARE_SIZE)) && (floor(from.z/SQUARE_SIZE)==floor(to.z/SQUARE_SIZE))) { ret = LineGroundSquareCol(from,to,(int)floor(from.x/SQUARE_SIZE),(int)floor(from.z/SQUARE_SIZE)); if(ret>=0) { return ret; } } else if(floor(from.x/SQUARE_SIZE)==floor(to.x/SQUARE_SIZE)) { while(keepgoing) { ret = LineGroundSquareCol(from,to,(int)floor(xp/SQUARE_SIZE),(int)floor(zp/SQUARE_SIZE)); if(ret>=0) { return ret+savedLength; } keepgoing=fabs(zp-from.z)<fabs(dz); if(dz>0) zp+=SQUARE_SIZE; else zp-=SQUARE_SIZE; } // if you hit this the collision detection hit an infinite loop assert(!keepgoing); } else if(floor(from.z/SQUARE_SIZE)==floor(to.z/SQUARE_SIZE)) { while(keepgoing) { ret = LineGroundSquareCol(from,to,(int)floor(xp/SQUARE_SIZE),(int)floor(zp/SQUARE_SIZE)); if(ret>=0) { return ret+savedLength; } keepgoing=fabs(xp-from.x)<fabs(dx); if(dx>0) xp+=SQUARE_SIZE; else xp-=SQUARE_SIZE; } // if you hit this the collision detection hit an infinite loop assert(!keepgoing); } else { while(keepgoing) { float xs, zs; // Push value just over the edge of the square // This is the best accuracy we can get with floats: // add one digit and (xp*constant) reduces to xp itself // This accuracy means that at (16384,16384) (lower right of 32x32 map) // 1 in every 1/(16384*1e-7f/8)=4883 clicks on the map will be ignored. if (dx>0) xs = floor(xp*1.0000001f/SQUARE_SIZE); else xs = floor(xp*0.9999999f/SQUARE_SIZE); if (dz>0) zs = floor(zp*1.0000001f/SQUARE_SIZE); else zs = floor(zp*0.9999999f/SQUARE_SIZE); ret = LineGroundSquareCol(from, to, (int)xs, (int)zs); if(ret>=0) { return ret+savedLength; } keepgoing=fabs(xp-from.x)<fabs(dx) && fabs(zp-from.z)<fabs(dz); if(dx>0) { // distance xp to right edge of square (xs,zs) divided by dx, xp += xn*dx puts xp on the right edge xn=(xs*SQUARE_SIZE+SQUARE_SIZE-xp)/dx; } else { // distance xp to left edge of square (xs,zs) divided by dx, xp += xn*dx puts xp on the left edge xn=(xs*SQUARE_SIZE-xp)/dx; } if(dz>0) { // distance zp to bottom edge of square (xs,zs) divided by dz, zp += zn*dz puts zp on the bottom edge zn=(zs*SQUARE_SIZE+SQUARE_SIZE-zp)/dz; } else { // distance zp to top edge of square (xs,zs) divided by dz, zp += zn*dz puts zp on the top edge zn=(zs*SQUARE_SIZE-zp)/dz; } // xn and zn are always positive, minus signs are divided out above // this puts (xp,zp) exactly on the first edge you see if you look from (xp,zp) in the (dx,dz) direction if(xn<zn) { xp+=xn*dx; zp+=xn*dz; } else { xp+=zn*dx; zp+=zn*dz; } } } return -1; }
float CGround::LineGroundCol(float3 from, float3 to, bool synced) { const float* hm = readMap->GetSharedCornerHeightMap(synced); const float3* nm = readMap->GetSharedFaceNormals(synced); const float3 pfrom = from; // only for performance -> skip part that can impossibly collide // with the terrain, cause it is above map's current max height ClampInMapHeight(from, to); // handle special cases where the ray origin is out of bounds: // need to move <from> to the closest map-edge along the ray // (if both <from> and <to> are out of bounds, the ray might // still hit) // clamping <from> naively would change the direction of the // ray, hence we save the distance along it that got skipped ClampLineInMap(from, to); if (from == to) { // ClampLineInMap & ClampInMapHeight set `from == to == vec(-1,-1,-1)` // in case the line is outside of the map return -1.0f; } const float skippedDist = pfrom.distance(from); if (synced) { //TODO do this in unsynced too once the map border rendering is finished? // check if our start position is underground (assume ground is unpassable for cannons etc.) const int sx = from.x / SQUARE_SIZE; const int sz = from.z / SQUARE_SIZE; if (from.y <= hm[sz * mapDims.mapxp1 + sx]) { return 0.0f + skippedDist; } } const float dx = to.x - from.x; const float dz = to.z - from.z; const int dirx = (dx > 0.0f) ? 1 : -1; const int dirz = (dz > 0.0f) ? 1 : -1; // Clamping is done cause LineGroundSquareCol() operates on the 2 triangles faces each heightmap // square is formed of. const float ffsx = Clamp(from.x / SQUARE_SIZE, 0.0f, (float)mapDims.mapx); const float ffsz = Clamp(from.z / SQUARE_SIZE, 0.0f, (float)mapDims.mapy); const float ttsx = Clamp(to.x / SQUARE_SIZE, 0.0f, (float)mapDims.mapx); const float ttsz = Clamp(to.z / SQUARE_SIZE, 0.0f, (float)mapDims.mapy); const int fsx = ffsx; const int fsz = ffsz; const int tsx = ttsx; const int tsz = ttsz; bool keepgoing = true; if ((fsx == tsx) && (fsz == tsz)) { // <from> and <to> are the same const float ret = LineGroundSquareCol(hm, nm, from, to, fsx, fsz); if (ret >= 0.0f) { return (ret + skippedDist); } } else if (fsx == tsx) { // ray is parallel to z-axis int zp = fsz; while (keepgoing) { const float ret = LineGroundSquareCol(hm, nm, from, to, fsx, zp); if (ret >= 0.0f) { return (ret + skippedDist); } keepgoing = (zp != tsz); zp += dirz; } } else if (fsz == tsz) { // ray is parallel to x-axis int xp = fsx; while (keepgoing) { const float ret = LineGroundSquareCol(hm, nm, from, to, xp, fsz); if (ret >= 0.0f) { return (ret + skippedDist); } keepgoing = (xp != tsx); xp += dirx; } } else { // general case const float rdsx = SQUARE_SIZE / dx; // := 1 / (dx / SQUARE_SIZE) const float rdsz = SQUARE_SIZE / dz; // we need to shift the `test`-point in case of negative directions // case: dir<0 // ___________ // | | | | // |___|___|___| // ^cur // ^cur + dir // > < range of int(cur + dir) // ^wanted test point := cur - epsilon // you can set epsilon=0 and then handle the `beyond end`-case (xn >= 1.0f && zn >= 1.0f) separate // (we already need to do so cause of floating point precision limits, so skipping epsilon doesn't add // any additional performance cost nor precision issue) // // case : dir>0 // in case of `dir>0` the wanted test point is idential with `cur + dir` const float testposx = (dx > 0.0f) ? 0.0f : 1.0f; const float testposz = (dz > 0.0f) ? 0.0f : 1.0f; int curx = fsx; int curz = fsz; while (keepgoing) { // do the collision test with the squares triangles const float ret = LineGroundSquareCol(hm, nm, from, to, curx, curz); if (ret >= 0.0f) { return (ret + skippedDist); } // check if we reached the end already and need to stop the loop const bool endReached = (curx == tsx && curz == tsz); const bool beyondEnd = ((curx - tsx) * dirx > 0) || ((curz - tsz) * dirz > 0); assert(!beyondEnd); keepgoing = !endReached && !beyondEnd; if (!keepgoing) break; // calculate the `normalized position` of the next edge in x & z direction // `normalized position`:=n : x = from.x + n * (to.x - from.x) (with 0<= n <=1) int nextx = curx + dirx; int nextz = curz + dirz; float xn = (nextx + testposx - ffsx) * rdsx; float zn = (nextz + testposz - ffsz) * rdsz; // handles the following 2 case: // case1: (floor(to.x) == to.x) && (to.x < from.x) // In this case we calculate xn at to.x but set curx = to.x - 1, // and so we would be beyond the end of the ray. // case2: floating point precision issues if ((nextx - tsx) * dirx > 0) { xn=1337.0f; nextx=tsx; } if ((nextz - tsz) * dirz > 0) { zn=1337.0f; nextz=tsz; } // advance to the next nearest edge in either x or z dir, or in the case we reached the end make sure // we set it to the exact square positions (floating point precision sometimes hinders us to hit it) if (xn >= 1.0f && zn >= 1.0f) { assert(curx != nextx || curz != nextz); curx = nextx; curz = nextz; } else if (xn < zn) { assert(curx != nextx); curx = nextx; } else { assert(curz != nextz); curz = nextz; } } } return -1.0f; }