forked from mirror/DotRecast
1843 lines
64 KiB
C#
1843 lines
64 KiB
C#
|
/*
|
||
|
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||
|
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
|
||
|
|
||
|
This software is provided 'as-is', without any express or implied
|
||
|
warranty. In no event will the authors be held liable for any damages
|
||
|
arising from the use of this software.
|
||
|
Permission is granted to anyone to use this software for any purpose,
|
||
|
including commercial applications, and to alter it and redistribute it
|
||
|
freely, subject to the following restrictions:
|
||
|
1. The origin of this software must not be misrepresented; you must not
|
||
|
claim that you wrote the original software. If you use this software
|
||
|
in a product, an acknowledgment in the product documentation would be
|
||
|
appreciated but is not required.
|
||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||
|
misrepresented as being the original software.
|
||
|
3. This notice may not be removed or altered from any source distribution.
|
||
|
*/
|
||
|
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using DotRecast.Core;
|
||
|
using DotRecast.Detour.TileCache.Io;
|
||
|
using DotRecast.Detour.TileCache.Io.Compress;
|
||
|
using static DotRecast.Detour.DetourCommon;
|
||
|
|
||
|
namespace DotRecast.Detour.TileCache;
|
||
|
|
||
|
public class TileCacheBuilder {
|
||
|
|
||
|
const int DT_TILECACHE_NULL_AREA = 0;
|
||
|
const int DT_TILECACHE_WALKABLE_AREA = 63;
|
||
|
const int DT_TILECACHE_NULL_IDX = 0xffff;
|
||
|
|
||
|
public class LayerSweepSpan {
|
||
|
public int ns; // number samples
|
||
|
public int id; // region id
|
||
|
public int nei; // neighbour id
|
||
|
};
|
||
|
|
||
|
public class LayerMonotoneRegion {
|
||
|
public int area;
|
||
|
public List<int> neis = new(16);
|
||
|
public int regId;
|
||
|
public int areaId;
|
||
|
};
|
||
|
|
||
|
public class TempContour {
|
||
|
public List<int> verts;
|
||
|
public int nverts;
|
||
|
public List<int> poly;
|
||
|
|
||
|
public TempContour() {
|
||
|
verts = new();
|
||
|
nverts = 0;
|
||
|
poly = new();
|
||
|
}
|
||
|
|
||
|
public int npoly() {
|
||
|
return poly.Count;
|
||
|
}
|
||
|
|
||
|
public void clear() {
|
||
|
nverts = 0;
|
||
|
verts.Clear();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
public class Edge {
|
||
|
public int[] vert = new int[2];
|
||
|
public int[] polyEdge = new int[2];
|
||
|
public int[] poly = new int[2];
|
||
|
};
|
||
|
|
||
|
private readonly TileCacheLayerHeaderReader reader = new TileCacheLayerHeaderReader();
|
||
|
|
||
|
public void buildTileCacheRegions(TileCacheLayer layer, int walkableClimb) {
|
||
|
|
||
|
int w = layer.header.width;
|
||
|
int h = layer.header.height;
|
||
|
|
||
|
Array.Fill(layer.regs, (short) 0x00FF);
|
||
|
int nsweeps = w;
|
||
|
LayerSweepSpan[] sweeps = new LayerSweepSpan[nsweeps];
|
||
|
for (int i = 0; i < sweeps.Length; i++) {
|
||
|
sweeps[i] = new LayerSweepSpan();
|
||
|
}
|
||
|
// Partition walkable area into monotone regions.
|
||
|
int[] prevCount = new int[256];
|
||
|
int regId = 0;
|
||
|
|
||
|
for (int y = 0; y < h; ++y) {
|
||
|
if (regId > 0) {
|
||
|
Array.Fill(prevCount, 0, 0, regId);
|
||
|
}
|
||
|
// memset(prevCount,0,sizeof(char)*regId);
|
||
|
int sweepId = 0;
|
||
|
|
||
|
for (int x = 0; x < w; ++x) {
|
||
|
int idx = x + y * w;
|
||
|
if (layer.areas[idx] == DT_TILECACHE_NULL_AREA)
|
||
|
continue;
|
||
|
|
||
|
int sid = 0xff;
|
||
|
|
||
|
// -x
|
||
|
int xidx = (x - 1) + y * w;
|
||
|
if (x > 0 && isConnected(layer, idx, xidx, walkableClimb)) {
|
||
|
if (layer.regs[xidx] != 0xff)
|
||
|
sid = layer.regs[xidx];
|
||
|
}
|
||
|
|
||
|
if (sid == 0xff) {
|
||
|
sid = sweepId++;
|
||
|
sweeps[sid].nei = 0xff;
|
||
|
sweeps[sid].ns = 0;
|
||
|
}
|
||
|
|
||
|
// -y
|
||
|
int yidx = x + (y - 1) * w;
|
||
|
if (y > 0 && isConnected(layer, idx, yidx, walkableClimb)) {
|
||
|
int nr = layer.regs[yidx];
|
||
|
if (nr != 0xff) {
|
||
|
// Set neighbour when first valid neighbour is
|
||
|
// encoutered.
|
||
|
if (sweeps[sid].ns == 0)
|
||
|
sweeps[sid].nei = nr;
|
||
|
|
||
|
if (sweeps[sid].nei == nr) {
|
||
|
// Update existing neighbour
|
||
|
sweeps[sid].ns++;
|
||
|
prevCount[nr]++;
|
||
|
} else {
|
||
|
// This is hit if there is nore than one neighbour.
|
||
|
// Invalidate the neighbour.
|
||
|
sweeps[sid].nei = 0xff;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
layer.regs[idx] = (byte) sid;
|
||
|
}
|
||
|
|
||
|
// Create unique ID.
|
||
|
for (int i = 0; i < sweepId; ++i) {
|
||
|
// If the neighbour is set and there is only one continuous
|
||
|
// connection to it,
|
||
|
// the sweep will be merged with the previous one, else new
|
||
|
// region is created.
|
||
|
if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns) {
|
||
|
sweeps[i].id = sweeps[i].nei;
|
||
|
} else {
|
||
|
if (regId == 255) {
|
||
|
// Region ID's overflow.
|
||
|
throw new Exception("Buffer too small");
|
||
|
}
|
||
|
sweeps[i].id = regId++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Remap local sweep ids to region ids.
|
||
|
for (int x = 0; x < w; ++x) {
|
||
|
int idx = x + y * w;
|
||
|
if (layer.regs[idx] != 0xff)
|
||
|
layer.regs[idx] = (short) sweeps[layer.regs[idx]].id;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Allocate and init layer regions.
|
||
|
int nregs = regId;
|
||
|
LayerMonotoneRegion[] regs = new LayerMonotoneRegion[nregs];
|
||
|
|
||
|
for (int i = 0; i < nregs; ++i) {
|
||
|
regs[i] = new LayerMonotoneRegion();
|
||
|
regs[i].regId = 0xff;
|
||
|
}
|
||
|
|
||
|
// Find region neighbours.
|
||
|
for (int y = 0; y < h; ++y) {
|
||
|
for (int x = 0; x < w; ++x) {
|
||
|
int idx = x + y * w;
|
||
|
int ri = layer.regs[idx];
|
||
|
if (ri == 0xff)
|
||
|
continue;
|
||
|
|
||
|
// Update area.
|
||
|
regs[ri].area++;
|
||
|
regs[ri].areaId = layer.areas[idx];
|
||
|
|
||
|
// Update neighbours
|
||
|
int ymi = x + (y - 1) * w;
|
||
|
if (y > 0 && isConnected(layer, idx, ymi, walkableClimb)) {
|
||
|
int rai = layer.regs[ymi];
|
||
|
if (rai != 0xff && rai != ri) {
|
||
|
addUniqueLast(regs[ri].neis, rai);
|
||
|
addUniqueLast(regs[rai].neis, ri);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < nregs; ++i)
|
||
|
regs[i].regId = i;
|
||
|
|
||
|
for (int i = 0; i < nregs; ++i) {
|
||
|
LayerMonotoneRegion reg = regs[i];
|
||
|
|
||
|
int merge = -1;
|
||
|
int mergea = 0;
|
||
|
foreach (int nei in reg.neis) {
|
||
|
LayerMonotoneRegion regn = regs[nei];
|
||
|
if (reg.regId == regn.regId)
|
||
|
continue;
|
||
|
if (reg.areaId != regn.areaId)
|
||
|
continue;
|
||
|
if (regn.area > mergea) {
|
||
|
if (canMerge(reg.regId, regn.regId, regs, nregs)) {
|
||
|
mergea = regn.area;
|
||
|
merge = nei;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (merge != -1) {
|
||
|
int oldId = reg.regId;
|
||
|
int newId = regs[merge].regId;
|
||
|
for (int j = 0; j < nregs; ++j)
|
||
|
if (regs[j].regId == oldId)
|
||
|
regs[j].regId = newId;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Compact ids.
|
||
|
int[] remap = new int[256];
|
||
|
// Find number of unique regions.
|
||
|
regId = 0;
|
||
|
for (int i = 0; i < nregs; ++i)
|
||
|
remap[regs[i].regId] = 1;
|
||
|
for (int i = 0; i < 256; ++i)
|
||
|
if (remap[i] != 0)
|
||
|
remap[i] = regId++;
|
||
|
// Remap ids.
|
||
|
for (int i = 0; i < nregs; ++i)
|
||
|
regs[i].regId = remap[regs[i].regId];
|
||
|
|
||
|
layer.regCount = regId;
|
||
|
|
||
|
for (int i = 0; i < w * h; ++i) {
|
||
|
if (layer.regs[i] != 0xff)
|
||
|
layer.regs[i] = (short) regs[layer.regs[i]].regId;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
void addUniqueLast(List<int> a, int v) {
|
||
|
int n = a.Count;
|
||
|
if (n > 0 && a[n - 1] == v)
|
||
|
return;
|
||
|
a.Add(v);
|
||
|
}
|
||
|
|
||
|
bool isConnected(TileCacheLayer layer, int ia, int ib, int walkableClimb) {
|
||
|
if (layer.areas[ia] != layer.areas[ib])
|
||
|
return false;
|
||
|
if (Math.Abs(layer.heights[ia] - layer.heights[ib]) > walkableClimb)
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool canMerge(int oldRegId, int newRegId, LayerMonotoneRegion[] regs, int nregs) {
|
||
|
int count = 0;
|
||
|
for (int i = 0; i < nregs; ++i) {
|
||
|
LayerMonotoneRegion reg = regs[i];
|
||
|
if (reg.regId != oldRegId)
|
||
|
continue;
|
||
|
foreach (int nei in reg.neis) {
|
||
|
if (regs[nei].regId == newRegId)
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
return count == 1;
|
||
|
}
|
||
|
|
||
|
private void appendVertex(TempContour cont, int x, int y, int z, int r) {
|
||
|
// Try to merge with existing segments.
|
||
|
if (cont.nverts > 1) {
|
||
|
int pa = (cont.nverts - 2) * 4;
|
||
|
int pb = (cont.nverts - 1) * 4;
|
||
|
if (cont.verts[pb + 3] == r) {
|
||
|
if (cont.verts[pa] == cont.verts[pb] && cont.verts[pb] == x) {
|
||
|
// The verts are aligned aling x-axis, update z.
|
||
|
cont.verts[pb + 1] = y;
|
||
|
cont.verts[pb + 2] = z;
|
||
|
return;
|
||
|
} else if (cont.verts[pa + 2] == cont.verts[pb + 2]
|
||
|
&& cont.verts[pb + 2] == z) {
|
||
|
// The verts are aligned aling z-axis, update x.
|
||
|
cont.verts[pb] = x;
|
||
|
cont.verts[pb + 1] = y;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
cont.verts.Add(x);
|
||
|
cont.verts.Add(y);
|
||
|
cont.verts.Add(z);
|
||
|
cont.verts.Add(r);
|
||
|
cont.nverts++;
|
||
|
}
|
||
|
|
||
|
private int getNeighbourReg(TileCacheLayer layer, int ax, int ay, int dir) {
|
||
|
int w = layer.header.width;
|
||
|
int ia = ax + ay * w;
|
||
|
|
||
|
int con = layer.cons[ia] & 0xf;
|
||
|
int portal = layer.cons[ia] >> 4;
|
||
|
int mask = 1 << dir;
|
||
|
|
||
|
if ((con & mask) == 0) {
|
||
|
// No connection, return portal or hard edge.
|
||
|
if ((portal & mask) != 0)
|
||
|
return 0xf8 + dir;
|
||
|
return 0xff;
|
||
|
}
|
||
|
|
||
|
int bx = ax + getDirOffsetX(dir);
|
||
|
int by = ay + getDirOffsetY(dir);
|
||
|
int ib = bx + by * w;
|
||
|
return layer.regs[ib];
|
||
|
}
|
||
|
|
||
|
private int getDirOffsetX(int dir) {
|
||
|
int[] offset = new int[] { -1, 0, 1, 0, };
|
||
|
return offset[dir & 0x03];
|
||
|
}
|
||
|
|
||
|
private int getDirOffsetY(int dir) {
|
||
|
int[] offset = new int[] { 0, 1, 0, -1 };
|
||
|
return offset[dir & 0x03];
|
||
|
}
|
||
|
|
||
|
private void walkContour(TileCacheLayer layer, int x, int y, TempContour cont) {
|
||
|
int w = layer.header.width;
|
||
|
int h = layer.header.height;
|
||
|
|
||
|
cont.clear();
|
||
|
|
||
|
int startX = x;
|
||
|
int startY = y;
|
||
|
int startDir = -1;
|
||
|
|
||
|
for (int i = 0; i < 4; ++i) {
|
||
|
int ndir = (i + 3) & 3;
|
||
|
int rn = getNeighbourReg(layer, x, y, ndir);
|
||
|
if (rn != layer.regs[x + y * w]) {
|
||
|
startDir = ndir;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (startDir == -1)
|
||
|
return;
|
||
|
|
||
|
int dir = startDir;
|
||
|
int maxIter = w * h;
|
||
|
int iter = 0;
|
||
|
while (iter < maxIter) {
|
||
|
int rn = getNeighbourReg(layer, x, y, dir);
|
||
|
|
||
|
int nx = x;
|
||
|
int ny = y;
|
||
|
int ndir = dir;
|
||
|
|
||
|
if (rn != layer.regs[x + y * w]) {
|
||
|
// Solid edge.
|
||
|
int px = x;
|
||
|
int pz = y;
|
||
|
switch (dir) {
|
||
|
case 0:
|
||
|
pz++;
|
||
|
break;
|
||
|
case 1:
|
||
|
px++;
|
||
|
pz++;
|
||
|
break;
|
||
|
case 2:
|
||
|
px++;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Try to merge with previous vertex.
|
||
|
appendVertex(cont, px, layer.heights[x + y * w], pz, rn);
|
||
|
ndir = (dir + 1) & 0x3; // Rotate CW
|
||
|
} else {
|
||
|
// Move to next.
|
||
|
nx = x + getDirOffsetX(dir);
|
||
|
ny = y + getDirOffsetY(dir);
|
||
|
ndir = (dir + 3) & 0x3; // Rotate CCW
|
||
|
}
|
||
|
|
||
|
if (iter > 0 && x == startX && y == startY && dir == startDir)
|
||
|
break;
|
||
|
|
||
|
x = nx;
|
||
|
y = ny;
|
||
|
dir = ndir;
|
||
|
|
||
|
iter++;
|
||
|
}
|
||
|
|
||
|
// Remove last vertex if it is duplicate of the first one.
|
||
|
int pa = (cont.nverts - 1) * 4;
|
||
|
int pb = 0;
|
||
|
if (cont.verts[pa] == cont.verts[pb]
|
||
|
&& cont.verts[pa + 2] == cont.verts[pb + 2])
|
||
|
cont.nverts--;
|
||
|
|
||
|
}
|
||
|
|
||
|
private float distancePtSeg(int x, int z, int px, int pz, int qx, int qz) {
|
||
|
float pqx = qx - px;
|
||
|
float pqz = qz - pz;
|
||
|
float dx = x - px;
|
||
|
float dz = z - pz;
|
||
|
float d = pqx * pqx + pqz * pqz;
|
||
|
float t = pqx * dx + pqz * dz;
|
||
|
if (d > 0)
|
||
|
t /= d;
|
||
|
if (t < 0)
|
||
|
t = 0;
|
||
|
else if (t > 1)
|
||
|
t = 1;
|
||
|
|
||
|
dx = px + t * pqx - x;
|
||
|
dz = pz + t * pqz - z;
|
||
|
|
||
|
return dx * dx + dz * dz;
|
||
|
}
|
||
|
|
||
|
private void simplifyContour(TempContour cont, float maxError) {
|
||
|
cont.poly.Clear();
|
||
|
|
||
|
for (int i = 0; i < cont.nverts; ++i) {
|
||
|
int j = (i + 1) % cont.nverts;
|
||
|
// Check for start of a wall segment.
|
||
|
int ra = j * 4 + 3;
|
||
|
int rb = i * 4 + 3;
|
||
|
if (cont.verts[ra] != cont.verts[rb])
|
||
|
cont.poly.Add(i);
|
||
|
}
|
||
|
if (cont.npoly() < 2) {
|
||
|
// If there is no transitions at all,
|
||
|
// create some initial points for the simplification process.
|
||
|
// Find lower-left and upper-right vertices of the contour.
|
||
|
int llx = cont.verts[0];
|
||
|
int llz = cont.verts[2];
|
||
|
int lli = 0;
|
||
|
int urx = cont.verts[0];
|
||
|
int urz = cont.verts[2];
|
||
|
int uri = 0;
|
||
|
for (int i = 1; i < cont.nverts; ++i) {
|
||
|
int x = cont.verts[i * 4 + 0];
|
||
|
int z = cont.verts[i * 4 + 2];
|
||
|
if (x < llx || (x == llx && z < llz)) {
|
||
|
llx = x;
|
||
|
llz = z;
|
||
|
lli = i;
|
||
|
}
|
||
|
if (x > urx || (x == urx && z > urz)) {
|
||
|
urx = x;
|
||
|
urz = z;
|
||
|
uri = i;
|
||
|
}
|
||
|
}
|
||
|
cont.poly.Clear();
|
||
|
cont.poly.Add(lli);
|
||
|
cont.poly.Add(uri);
|
||
|
}
|
||
|
|
||
|
// Add points until all raw points are within
|
||
|
// error tolerance to the simplified shape.
|
||
|
for (int i = 0; i < cont.npoly();) {
|
||
|
int ii = (i + 1) % cont.npoly();
|
||
|
|
||
|
int ai = cont.poly[i];
|
||
|
int ax = cont.verts[ai * 4];
|
||
|
int az = cont.verts[ai * 4 + 2];
|
||
|
|
||
|
int bi = cont.poly[ii];
|
||
|
int bx = cont.verts[bi * 4];
|
||
|
int bz = cont.verts[bi * 4 + 2];
|
||
|
|
||
|
// Find maximum deviation from the segment.
|
||
|
float maxd = 0;
|
||
|
int maxi = -1;
|
||
|
int ci, cinc, endi;
|
||
|
|
||
|
// Traverse the segment in lexilogical order so that the
|
||
|
// max deviation is calculated similarly when traversing
|
||
|
// opposite segments.
|
||
|
if (bx > ax || (bx == ax && bz > az)) {
|
||
|
cinc = 1;
|
||
|
ci = (ai + cinc) % cont.nverts;
|
||
|
endi = bi;
|
||
|
} else {
|
||
|
cinc = cont.nverts - 1;
|
||
|
ci = (bi + cinc) % cont.nverts;
|
||
|
endi = ai;
|
||
|
}
|
||
|
|
||
|
// Tessellate only outer edges or edges between areas.
|
||
|
while (ci != endi) {
|
||
|
float d = distancePtSeg(cont.verts[ci * 4], cont.verts[ci * 4 + 2], ax, az, bx, bz);
|
||
|
if (d > maxd) {
|
||
|
maxd = d;
|
||
|
maxi = ci;
|
||
|
}
|
||
|
ci = (ci + cinc) % cont.nverts;
|
||
|
}
|
||
|
|
||
|
// If the max deviation is larger than accepted error,
|
||
|
// add new point, else continue to next segment.
|
||
|
if (maxi != -1 && maxd > (maxError * maxError)) {
|
||
|
cont.poly.Insert(i + 1, maxi);
|
||
|
} else {
|
||
|
++i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Remap vertices
|
||
|
int start = 0;
|
||
|
for (int i = 1; i < cont.npoly(); ++i)
|
||
|
if (cont.poly[i] < cont.poly[start])
|
||
|
start = i;
|
||
|
|
||
|
cont.nverts = 0;
|
||
|
for (int i = 0; i < cont.npoly(); ++i) {
|
||
|
int j = (start + i) % cont.npoly();
|
||
|
int src = cont.poly[j] * 4;
|
||
|
int dst = cont.nverts * 4;
|
||
|
cont.verts[dst] = cont.verts[src];
|
||
|
cont.verts[dst + 1] = cont.verts[src + 1];
|
||
|
cont.verts[dst + 2] = cont.verts[src + 2];
|
||
|
cont.verts[dst + 3] = cont.verts[src + 3];
|
||
|
cont.nverts++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static Tuple<int, bool> getCornerHeight(TileCacheLayer layer, int x, int y, int z, int walkableClimb) {
|
||
|
int w = layer.header.width;
|
||
|
int h = layer.header.height;
|
||
|
|
||
|
int n = 0;
|
||
|
|
||
|
int portal = 0xf;
|
||
|
int height = 0;
|
||
|
int preg = 0xff;
|
||
|
bool allSameReg = true;
|
||
|
|
||
|
for (int dz = -1; dz <= 0; ++dz) {
|
||
|
for (int dx = -1; dx <= 0; ++dx) {
|
||
|
int px = x + dx;
|
||
|
int pz = z + dz;
|
||
|
if (px >= 0 && pz >= 0 && px < w && pz < h) {
|
||
|
int idx = px + pz * w;
|
||
|
int lh = layer.heights[idx];
|
||
|
if (Math.Abs(lh - y) <= walkableClimb && layer.areas[idx] != DT_TILECACHE_NULL_AREA) {
|
||
|
height = Math.Max(height, (char) lh);
|
||
|
portal &= (layer.cons[idx] >> 4);
|
||
|
if (preg != 0xff && preg != layer.regs[idx])
|
||
|
allSameReg = false;
|
||
|
preg = layer.regs[idx];
|
||
|
n++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int portalCount = 0;
|
||
|
for (int dir = 0; dir < 4; ++dir)
|
||
|
if ((portal & (1 << dir)) != 0)
|
||
|
portalCount++;
|
||
|
|
||
|
bool shouldRemove = false;
|
||
|
if (n > 1 && portalCount == 1 && allSameReg) {
|
||
|
shouldRemove = true;
|
||
|
}
|
||
|
|
||
|
return Tuple.Create(height, shouldRemove);
|
||
|
}
|
||
|
|
||
|
// TODO: move this somewhere else, once the layer meshing is done.
|
||
|
public TileCacheContourSet buildTileCacheContours(TileCacheLayer layer, int walkableClimb, float maxError) {
|
||
|
int w = layer.header.width;
|
||
|
int h = layer.header.height;
|
||
|
|
||
|
TileCacheContourSet lcset = new TileCacheContourSet();
|
||
|
lcset.nconts = layer.regCount;
|
||
|
lcset.conts = new TileCacheContour[lcset.nconts];
|
||
|
for (int i = 0; i < lcset.nconts; i++) {
|
||
|
lcset.conts[i] = new TileCacheContour();
|
||
|
}
|
||
|
|
||
|
// Allocate temp buffer for contour tracing.
|
||
|
TempContour temp = new TempContour();
|
||
|
|
||
|
// Find contours.
|
||
|
for (int y = 0; y < h; ++y) {
|
||
|
for (int x = 0; x < w; ++x) {
|
||
|
int idx = x + y * w;
|
||
|
int ri = layer.regs[idx];
|
||
|
if (ri == 0xff)
|
||
|
continue;
|
||
|
|
||
|
TileCacheContour cont = lcset.conts[ri];
|
||
|
|
||
|
if (cont.nverts > 0)
|
||
|
continue;
|
||
|
|
||
|
cont.reg = ri;
|
||
|
cont.area = layer.areas[idx];
|
||
|
|
||
|
walkContour(layer, x, y, temp);
|
||
|
|
||
|
simplifyContour(temp, maxError);
|
||
|
|
||
|
// Store contour.
|
||
|
cont.nverts = temp.nverts;
|
||
|
if (cont.nverts > 0) {
|
||
|
cont.verts = new int[4 * temp.nverts];
|
||
|
|
||
|
for (int i = 0, j = temp.nverts - 1; i < temp.nverts; j = i++) {
|
||
|
int dst = j * 4;
|
||
|
int v = j * 4;
|
||
|
int vn = i * 4;
|
||
|
int nei = temp.verts[vn + 3]; // The neighbour reg
|
||
|
// is
|
||
|
// stored at segment
|
||
|
// vertex of a
|
||
|
// segment.
|
||
|
Tuple<int, bool> res = getCornerHeight(layer, temp.verts[v], temp.verts[v + 1],
|
||
|
temp.verts[v + 2], walkableClimb);
|
||
|
int lh = res.Item1;
|
||
|
bool shouldRemove = res.Item2;
|
||
|
cont.verts[dst + 0] = temp.verts[v];
|
||
|
cont.verts[dst + 1] = lh;
|
||
|
cont.verts[dst + 2] = temp.verts[v + 2];
|
||
|
|
||
|
// Store portal direction and remove status to the
|
||
|
// fourth component.
|
||
|
cont.verts[dst + 3] = 0x0f;
|
||
|
if (nei != 0xff && nei >= 0xf8)
|
||
|
cont.verts[dst + 3] = nei - 0xf8;
|
||
|
if (shouldRemove)
|
||
|
cont.verts[dst + 3] |= 0x80;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return lcset;
|
||
|
|
||
|
}
|
||
|
|
||
|
const uint VERTEX_BUCKET_COUNT2 = (1 << 8);
|
||
|
|
||
|
private int computeVertexHash2(int x, int y, int z) {
|
||
|
uint h1 = 0x8da6b343; // Large multiplicative constants;
|
||
|
uint h2 = 0xd8163841; // here arbitrarily chosen primes
|
||
|
uint h3 = 0xcb1ab31f;
|
||
|
uint n = h1 * (uint)x + h2 * (uint)y + h3 * (uint)z;
|
||
|
return (int)(n & (VERTEX_BUCKET_COUNT2 - 1));
|
||
|
}
|
||
|
|
||
|
private int addVertex(int x, int y, int z, int[] verts, int[] firstVert, int[] nextVert, int nv) {
|
||
|
int bucket = computeVertexHash2(x, 0, z);
|
||
|
int i = firstVert[bucket];
|
||
|
while (i != DT_TILECACHE_NULL_IDX) {
|
||
|
int tv = i * 3;
|
||
|
if (verts[tv] == x && verts[tv + 2] == z && (Math.Abs(verts[tv + 1] - y) <= 2))
|
||
|
return i;
|
||
|
i = nextVert[i]; // next
|
||
|
}
|
||
|
|
||
|
// Could not find, create new.
|
||
|
i = nv;
|
||
|
int v = i * 3;
|
||
|
verts[v] = x;
|
||
|
verts[v + 1] = y;
|
||
|
verts[v + 2] = z;
|
||
|
nextVert[i] = firstVert[bucket];
|
||
|
firstVert[bucket] = i;
|
||
|
return i;
|
||
|
}
|
||
|
|
||
|
private void buildMeshAdjacency(int[] polys, int npolys, int[] verts, int nverts, TileCacheContourSet lcset,
|
||
|
int maxVertsPerPoly) {
|
||
|
// Based on code by Eric Lengyel from:
|
||
|
// http://www.terathon.com/code/edges.php
|
||
|
|
||
|
int maxEdgeCount = npolys * maxVertsPerPoly;
|
||
|
|
||
|
int[] firstEdge = new int[nverts + maxEdgeCount];
|
||
|
int nextEdge = nverts;
|
||
|
int edgeCount = 0;
|
||
|
|
||
|
Edge[] edges = new Edge[maxEdgeCount];
|
||
|
for (int i = 0; i < maxEdgeCount; i++) {
|
||
|
edges[i] = new Edge();
|
||
|
}
|
||
|
for (int i = 0; i < nverts; i++)
|
||
|
firstEdge[i] = DT_TILECACHE_NULL_IDX;
|
||
|
|
||
|
for (int i = 0; i < npolys; ++i) {
|
||
|
int t = i * maxVertsPerPoly * 2;
|
||
|
for (int j = 0; j < maxVertsPerPoly; ++j) {
|
||
|
if (polys[t + j] == DT_TILECACHE_NULL_IDX)
|
||
|
break;
|
||
|
int v0 = polys[t + j];
|
||
|
int v1 = (j + 1 >= maxVertsPerPoly || polys[t + j + 1] == DT_TILECACHE_NULL_IDX) ? polys[t]
|
||
|
: polys[t + j + 1];
|
||
|
if (v0 < v1) {
|
||
|
Edge edge = edges[edgeCount];
|
||
|
edge.vert[0] = v0;
|
||
|
edge.vert[1] = v1;
|
||
|
edge.poly[0] = i;
|
||
|
edge.polyEdge[0] = j;
|
||
|
edge.poly[1] = i;
|
||
|
edge.polyEdge[1] = 0xff;
|
||
|
// Insert edge
|
||
|
firstEdge[nextEdge + edgeCount] = firstEdge[v0];
|
||
|
firstEdge[v0] = (short) edgeCount;
|
||
|
edgeCount++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < npolys; ++i) {
|
||
|
int t = i * maxVertsPerPoly * 2;
|
||
|
for (int j = 0; j < maxVertsPerPoly; ++j) {
|
||
|
if (polys[t + j] == DT_TILECACHE_NULL_IDX)
|
||
|
break;
|
||
|
int v0 = polys[t + j];
|
||
|
int v1 = (j + 1 >= maxVertsPerPoly || polys[t + j + 1] == DT_TILECACHE_NULL_IDX) ? polys[t]
|
||
|
: polys[t + j + 1];
|
||
|
if (v0 > v1) {
|
||
|
bool found = false;
|
||
|
for (int e = firstEdge[v1]; e != DT_TILECACHE_NULL_IDX; e = firstEdge[nextEdge + e]) {
|
||
|
Edge edge = edges[e];
|
||
|
if (edge.vert[1] == v0 && edge.poly[0] == edge.poly[1]) {
|
||
|
edge.poly[1] = i;
|
||
|
edge.polyEdge[1] = j;
|
||
|
found = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!found) {
|
||
|
// Matching edge not found, it is an open edge, add it.
|
||
|
Edge edge = edges[edgeCount];
|
||
|
edge.vert[0] = v1;
|
||
|
edge.vert[1] = v0;
|
||
|
edge.poly[0] = (short) i;
|
||
|
edge.polyEdge[0] = (short) j;
|
||
|
edge.poly[1] = (short) i;
|
||
|
edge.polyEdge[1] = 0xff;
|
||
|
// Insert edge
|
||
|
firstEdge[nextEdge + edgeCount] = firstEdge[v1];
|
||
|
firstEdge[v1] = (short) edgeCount;
|
||
|
edgeCount++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Mark portal edges.
|
||
|
for (int i = 0; i < lcset.nconts; ++i) {
|
||
|
TileCacheContour cont = lcset.conts[i];
|
||
|
if (cont.nverts < 3)
|
||
|
continue;
|
||
|
|
||
|
for (int j = 0, k = cont.nverts - 1; j < cont.nverts; k = j++) {
|
||
|
int va = k * 4;
|
||
|
int vb = j * 4;
|
||
|
int dir = cont.verts[va + 3] & 0xf;
|
||
|
if (dir == 0xf)
|
||
|
continue;
|
||
|
|
||
|
if (dir == 0 || dir == 2) {
|
||
|
// Find matching vertical edge
|
||
|
int x = cont.verts[va];
|
||
|
int zmin = cont.verts[va + 2];
|
||
|
int zmax = cont.verts[vb + 2];
|
||
|
if (zmin > zmax) {
|
||
|
int tmp = zmin;
|
||
|
zmin = zmax;
|
||
|
zmax = tmp;
|
||
|
}
|
||
|
|
||
|
for (int m = 0; m < edgeCount; ++m) {
|
||
|
Edge e = edges[m];
|
||
|
// Skip connected edges.
|
||
|
if (e.poly[0] != e.poly[1])
|
||
|
continue;
|
||
|
int eva = e.vert[0] * 3;
|
||
|
int evb = e.vert[1] * 3;
|
||
|
if (verts[eva] == x && verts[evb] == x) {
|
||
|
int ezmin = verts[eva + 2];
|
||
|
int ezmax = verts[evb + 2];
|
||
|
if (ezmin > ezmax) {
|
||
|
int tmp = ezmin;
|
||
|
ezmin = ezmax;
|
||
|
ezmax = tmp;
|
||
|
}
|
||
|
if (overlapRangeExl(zmin, zmax, ezmin, ezmax)) {
|
||
|
// Reuse the other polyedge to store dir.
|
||
|
e.polyEdge[1] = dir;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
// Find matching vertical edge
|
||
|
int z = cont.verts[va + 2];
|
||
|
int xmin = cont.verts[va];
|
||
|
int xmax = cont.verts[vb];
|
||
|
if (xmin > xmax) {
|
||
|
int tmp = xmin;
|
||
|
xmin = xmax;
|
||
|
xmax = tmp;
|
||
|
}
|
||
|
for (int m = 0; m < edgeCount; ++m) {
|
||
|
Edge e = edges[m];
|
||
|
// Skip connected edges.
|
||
|
if (e.poly[0] != e.poly[1])
|
||
|
continue;
|
||
|
int eva = e.vert[0] * 3;
|
||
|
int evb = e.vert[1] * 3;
|
||
|
if (verts[eva + 2] == z && verts[evb + 2] == z) {
|
||
|
int exmin = verts[eva];
|
||
|
int exmax = verts[evb];
|
||
|
if (exmin > exmax) {
|
||
|
int tmp = exmin;
|
||
|
exmin = exmax;
|
||
|
exmax = tmp;
|
||
|
}
|
||
|
if (overlapRangeExl(xmin, xmax, exmin, exmax)) {
|
||
|
// Reuse the other polyedge to store dir.
|
||
|
e.polyEdge[1] = dir;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Store adjacency
|
||
|
for (int i = 0; i < edgeCount; ++i) {
|
||
|
Edge e = edges[i];
|
||
|
if (e.poly[0] != e.poly[1]) {
|
||
|
int p0 = e.poly[0] * maxVertsPerPoly * 2;
|
||
|
int p1 = e.poly[1] * maxVertsPerPoly * 2;
|
||
|
polys[p0 + maxVertsPerPoly + e.polyEdge[0]] = e.poly[1];
|
||
|
polys[p1 + maxVertsPerPoly + e.polyEdge[1]] = e.poly[0];
|
||
|
} else if (e.polyEdge[1] != 0xff) {
|
||
|
int p0 = e.poly[0] * maxVertsPerPoly * 2;
|
||
|
polys[p0 + maxVertsPerPoly + e.polyEdge[0]] = 0x8000 | (short) e.polyEdge[1];
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private bool overlapRangeExl(int amin, int amax, int bmin, int bmax) {
|
||
|
return (amin >= bmax || amax <= bmin) ? false : true;
|
||
|
}
|
||
|
|
||
|
private int prev(int i, int n) {
|
||
|
return i - 1 >= 0 ? i - 1 : n - 1;
|
||
|
}
|
||
|
|
||
|
private int next(int i, int n) {
|
||
|
return i + 1 < n ? i + 1 : 0;
|
||
|
}
|
||
|
|
||
|
private int area2(int[] verts, int a, int b, int c) {
|
||
|
return (verts[b] - verts[a]) * (verts[c + 2] - verts[a + 2])
|
||
|
- (verts[c] - verts[a]) * (verts[b + 2] - verts[a + 2]);
|
||
|
}
|
||
|
|
||
|
// Returns true iff c is strictly to the left of the directed
|
||
|
// line through a to b.
|
||
|
private bool left(int[] verts, int a, int b, int c) {
|
||
|
return area2(verts, a, b, c) < 0;
|
||
|
}
|
||
|
|
||
|
private bool leftOn(int[] verts, int a, int b, int c) {
|
||
|
return area2(verts, a, b, c) <= 0;
|
||
|
}
|
||
|
|
||
|
private bool collinear(int[] verts, int a, int b, int c) {
|
||
|
return area2(verts, a, b, c) == 0;
|
||
|
}
|
||
|
|
||
|
// Returns true iff ab properly intersects cd: they share
|
||
|
// a point interior to both segments. The properness of the
|
||
|
// intersection is ensured by using strict leftness.
|
||
|
private bool intersectProp(int[] verts, int a, int b, int c, int d) {
|
||
|
// Eliminate improper cases.
|
||
|
if (collinear(verts, a, b, c) || collinear(verts, a, b, d) || collinear(verts, c, d, a)
|
||
|
|| collinear(verts, c, d, b))
|
||
|
return false;
|
||
|
|
||
|
return (left(verts, a, b, c) ^ left(verts, a, b, d)) && (left(verts, c, d, a) ^ left(verts, c, d, b));
|
||
|
}
|
||
|
|
||
|
// Returns T iff (a,b,c) are collinear and point c lies
|
||
|
// on the closed segement ab.
|
||
|
private bool between(int[] verts, int a, int b, int c) {
|
||
|
if (!collinear(verts, a, b, c))
|
||
|
return false;
|
||
|
// If ab not vertical, check betweenness on x; else on y.
|
||
|
if (verts[a] != verts[b])
|
||
|
return ((verts[a] <= verts[c]) && (verts[c] <= verts[b]))
|
||
|
|| ((verts[a] >= verts[c]) && (verts[c] >= verts[b]));
|
||
|
else
|
||
|
return ((verts[a + 2] <= verts[c + 2]) && (verts[c + 2] <= verts[b + 2]))
|
||
|
|| ((verts[a + 2] >= verts[c + 2]) && (verts[c + 2] >= verts[b + 2]));
|
||
|
}
|
||
|
|
||
|
// Returns true iff segments ab and cd intersect, properly or improperly.
|
||
|
private bool intersect(int[] verts, int a, int b, int c, int d) {
|
||
|
if (intersectProp(verts, a, b, c, d))
|
||
|
return true;
|
||
|
else if (between(verts, a, b, c) || between(verts, a, b, d) || between(verts, c, d, a)
|
||
|
|| between(verts, c, d, b))
|
||
|
return true;
|
||
|
else
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private bool vequal(int[] verts, int a, int b) {
|
||
|
return verts[a] == verts[b] && verts[a + 2] == verts[b + 2];
|
||
|
}
|
||
|
|
||
|
// Returns T iff (v_i, v_j) is a proper internal *or* external
|
||
|
// diagonal of P, *ignoring edges incident to v_i and v_j*.
|
||
|
private bool diagonalie(int i, int j, int n, int[] verts, int[] indices) {
|
||
|
int d0 = (indices[i] & 0x7fff) * 4;
|
||
|
int d1 = (indices[j] & 0x7fff) * 4;
|
||
|
|
||
|
// For each edge (k,k+1) of P
|
||
|
for (int k = 0; k < n; k++) {
|
||
|
int k1 = next(k, n);
|
||
|
// Skip edges incident to i or j
|
||
|
if (!((k == i) || (k1 == i) || (k == j) || (k1 == j))) {
|
||
|
int p0 = (indices[k] & 0x7fff) * 4;
|
||
|
int p1 = (indices[k1] & 0x7fff) * 4;
|
||
|
|
||
|
if (vequal(verts, d0, p0) || vequal(verts, d1, p0) || vequal(verts, d0, p1) || vequal(verts, d1, p1))
|
||
|
continue;
|
||
|
|
||
|
if (intersect(verts, d0, d1, p0, p1))
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Returns true iff the diagonal (i,j) is strictly internal to the
|
||
|
// polygon P in the neighborhood of the i endpoint.
|
||
|
private bool inCone(int i, int j, int n, int[] verts, int[] indices) {
|
||
|
int pi = (indices[i] & 0x7fff) * 4;
|
||
|
int pj = (indices[j] & 0x7fff) * 4;
|
||
|
int pi1 = (indices[next(i, n)] & 0x7fff) * 4;
|
||
|
int pin1 = (indices[prev(i, n)] & 0x7fff) * 4;
|
||
|
|
||
|
// If P[i] is a convex vertex [ i+1 left or on (i-1,i) ].
|
||
|
if (leftOn(verts, pin1, pi, pi1))
|
||
|
return left(verts, pi, pj, pin1) && left(verts, pj, pi, pi1);
|
||
|
// Assume (i-1,i,i+1) not collinear.
|
||
|
// else P[i] is reflex.
|
||
|
return !(leftOn(verts, pi, pj, pi1) && leftOn(verts, pj, pi, pin1));
|
||
|
}
|
||
|
|
||
|
// Returns T iff (v_i, v_j) is a proper internal
|
||
|
// diagonal of P.
|
||
|
private bool diagonal(int i, int j, int n, int[] verts, int[] indices) {
|
||
|
return inCone(i, j, n, verts, indices) && diagonalie(i, j, n, verts, indices);
|
||
|
}
|
||
|
|
||
|
private int triangulate(int n, int[] verts, int[] indices, int[] tris) {
|
||
|
int ntris = 0;
|
||
|
int dst = 0;// tris;
|
||
|
// The last bit of the index is used to indicate if the vertex can be
|
||
|
// removed.
|
||
|
for (int i = 0; i < n; i++) {
|
||
|
int i1 = next(i, n);
|
||
|
int i2 = next(i1, n);
|
||
|
if (diagonal(i, i2, n, verts, indices))
|
||
|
indices[i1] |= 0x8000;
|
||
|
}
|
||
|
|
||
|
while (n > 3) {
|
||
|
int minLen = -1;
|
||
|
int mini = -1;
|
||
|
for (int mi = 0; mi < n; mi++) {
|
||
|
int mi1 = next(mi, n);
|
||
|
if ((indices[mi1] & 0x8000) != 0) {
|
||
|
int p0 = (indices[mi] & 0x7fff) * 4;
|
||
|
int p2 = (indices[next(mi1, n)] & 0x7fff) * 4;
|
||
|
|
||
|
int dx = verts[p2] - verts[p0];
|
||
|
int dz = verts[p2 + 2] - verts[p0 + 2];
|
||
|
int len = dx * dx + dz * dz;
|
||
|
if (minLen < 0 || len < minLen) {
|
||
|
minLen = len;
|
||
|
mini = mi;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (mini == -1) {
|
||
|
// Should not happen.
|
||
|
/*
|
||
|
* printf("mini == -1 ntris=%d n=%d\n", ntris, n); for (int i = 0; i < n; i++) { printf("%d ",
|
||
|
* indices[i] & 0x0fffffff); } printf("\n");
|
||
|
*/
|
||
|
return -ntris;
|
||
|
}
|
||
|
|
||
|
int i = mini;
|
||
|
int i1 = next(i, n);
|
||
|
int i2 = next(i1, n);
|
||
|
|
||
|
tris[dst++] = indices[i] & 0x7fff;
|
||
|
tris[dst++] = indices[i1] & 0x7fff;
|
||
|
tris[dst++] = indices[i2] & 0x7fff;
|
||
|
ntris++;
|
||
|
|
||
|
// Removes P[i1] by copying P[i+1]...P[n-1] left one index.
|
||
|
n--;
|
||
|
for (int k = i1; k < n; k++)
|
||
|
indices[k] = indices[k + 1];
|
||
|
|
||
|
if (i1 >= n)
|
||
|
i1 = 0;
|
||
|
i = prev(i1, n);
|
||
|
// Update diagonal flags.
|
||
|
if (diagonal(prev(i, n), i1, n, verts, indices))
|
||
|
indices[i] |= 0x8000;
|
||
|
else
|
||
|
indices[i] &= 0x7fff;
|
||
|
|
||
|
if (diagonal(i, next(i1, n), n, verts, indices))
|
||
|
indices[i1] |= 0x8000;
|
||
|
else
|
||
|
indices[i1] &= 0x7fff;
|
||
|
}
|
||
|
|
||
|
// Append the remaining triangle.
|
||
|
tris[dst++] = indices[0] & 0x7fff;
|
||
|
tris[dst++] = indices[1] & 0x7fff;
|
||
|
tris[dst++] = indices[2] & 0x7fff;
|
||
|
ntris++;
|
||
|
|
||
|
return ntris;
|
||
|
}
|
||
|
|
||
|
private int countPolyVerts(int[] polys, int p, int maxVertsPerPoly) {
|
||
|
for (int i = 0; i < maxVertsPerPoly; ++i)
|
||
|
if (polys[p + i] == DT_TILECACHE_NULL_IDX)
|
||
|
return i;
|
||
|
return maxVertsPerPoly;
|
||
|
}
|
||
|
|
||
|
private bool uleft(int[] verts, int a, int b, int c) {
|
||
|
return (verts[b] - verts[a]) * (verts[c + 2] - verts[a + 2])
|
||
|
- (verts[c] - verts[a]) * (verts[b + 2] - verts[a + 2]) < 0;
|
||
|
}
|
||
|
|
||
|
private int[] getPolyMergeValue(int[] polys, int pa, int pb, int[] verts, int maxVertsPerPoly) {
|
||
|
int na = countPolyVerts(polys, pa, maxVertsPerPoly);
|
||
|
int nb = countPolyVerts(polys, pb, maxVertsPerPoly);
|
||
|
|
||
|
// If the merged polygon would be too big, do not merge.
|
||
|
if (na + nb - 2 > maxVertsPerPoly)
|
||
|
return new int[] { -1, 0, 0 };
|
||
|
|
||
|
// Check if the polygons share an edge.
|
||
|
int ea = -1;
|
||
|
int eb = -1;
|
||
|
|
||
|
for (int i = 0; i < na; ++i) {
|
||
|
int va0 = polys[pa + i];
|
||
|
int va1 = polys[pa + (i + 1) % na];
|
||
|
if (va0 > va1) {
|
||
|
int tmp = va0;
|
||
|
va0 = va1;
|
||
|
va1 = tmp;
|
||
|
}
|
||
|
for (int j = 0; j < nb; ++j) {
|
||
|
int vb0 = polys[pb + j];
|
||
|
int vb1 = polys[pb + (j + 1) % nb];
|
||
|
if (vb0 > vb1) {
|
||
|
int tmp = vb0;
|
||
|
vb0 = vb1;
|
||
|
vb1 = tmp;
|
||
|
}
|
||
|
if (va0 == vb0 && va1 == vb1) {
|
||
|
ea = i;
|
||
|
eb = j;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// No common edge, cannot merge.
|
||
|
if (ea == -1 || eb == -1)
|
||
|
return new int[] { -1, ea, eb };
|
||
|
|
||
|
// Check to see if the merged polygon would be convex.
|
||
|
int va, vb, vc;
|
||
|
|
||
|
va = polys[pa + (ea + na - 1) % na];
|
||
|
vb = polys[pa + ea];
|
||
|
vc = polys[pb + (eb + 2) % nb];
|
||
|
if (!uleft(verts, va * 3, vb * 3, vc * 3))
|
||
|
return new int[] { -1, ea, eb };
|
||
|
|
||
|
va = polys[pb + (eb + nb - 1) % nb];
|
||
|
vb = polys[pb + eb];
|
||
|
vc = polys[pa + (ea + 2) % na];
|
||
|
if (!uleft(verts, va * 3, vb * 3, vc * 3))
|
||
|
return new int[] { -1, ea, eb };
|
||
|
|
||
|
va = polys[pa + ea];
|
||
|
vb = polys[pa + (ea + 1) % na];
|
||
|
|
||
|
int dx = verts[va * 3 + 0] - verts[vb * 3 + 0];
|
||
|
int dy = verts[va * 3 + 2] - verts[vb * 3 + 2];
|
||
|
|
||
|
return new int[] { dx * dx + dy * dy, ea, eb };
|
||
|
}
|
||
|
|
||
|
private void mergePolys(int[] polys, int pa, int pb, int ea, int eb, int maxVertsPerPoly) {
|
||
|
int[] tmp = new int[maxVertsPerPoly * 2];
|
||
|
|
||
|
int na = countPolyVerts(polys, pa, maxVertsPerPoly);
|
||
|
int nb = countPolyVerts(polys, pb, maxVertsPerPoly);
|
||
|
|
||
|
// Merge polygons.
|
||
|
Array.Fill(tmp, DT_TILECACHE_NULL_IDX);
|
||
|
int n = 0;
|
||
|
// Add pa
|
||
|
for (int i = 0; i < na - 1; ++i)
|
||
|
tmp[n++] = polys[pa + (ea + 1 + i) % na];
|
||
|
// Add pb
|
||
|
for (int i = 0; i < nb - 1; ++i)
|
||
|
tmp[n++] = polys[pb + (eb + 1 + i) % nb];
|
||
|
Array.Copy(tmp, 0, polys, pa, maxVertsPerPoly);
|
||
|
}
|
||
|
|
||
|
private int pushFront(int v, List<int> arr) {
|
||
|
arr.Insert(0, v);
|
||
|
return arr.Count;
|
||
|
}
|
||
|
|
||
|
private int pushBack(int v, List<int> arr) {
|
||
|
arr.Add(v);
|
||
|
return arr.Count;
|
||
|
}
|
||
|
|
||
|
private bool canRemoveVertex(TileCachePolyMesh mesh, int rem) {
|
||
|
// Count number of polygons to remove.
|
||
|
int maxVertsPerPoly = mesh.nvp;
|
||
|
int numRemainingEdges = 0;
|
||
|
for (int i = 0; i < mesh.npolys; ++i) {
|
||
|
int p = i * mesh.nvp * 2;
|
||
|
int nv = countPolyVerts(mesh.polys, p, maxVertsPerPoly);
|
||
|
int numRemoved = 0;
|
||
|
int numVerts = 0;
|
||
|
for (int j = 0; j < nv; ++j) {
|
||
|
if (mesh.polys[p + j] == rem) {
|
||
|
numRemoved++;
|
||
|
}
|
||
|
numVerts++;
|
||
|
}
|
||
|
if (numRemoved != 0) {
|
||
|
numRemainingEdges += numVerts - (numRemoved + 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// There would be too few edges remaining to create a polygon.
|
||
|
// This can happen for example when a tip of a triangle is marked
|
||
|
// as deletion, but there are no other polys that share the vertex.
|
||
|
// In this case, the vertex should not be removed.
|
||
|
if (numRemainingEdges <= 2)
|
||
|
return false;
|
||
|
|
||
|
// Find edges which share the removed vertex.
|
||
|
List<int> edges = new();
|
||
|
int nedges = 0;
|
||
|
|
||
|
for (int i = 0; i < mesh.npolys; ++i) {
|
||
|
int p = i * mesh.nvp * 2;
|
||
|
int nv = countPolyVerts(mesh.polys, p, maxVertsPerPoly);
|
||
|
|
||
|
// Collect edges which touches the removed vertex.
|
||
|
for (int j = 0, k = nv - 1; j < nv; k = j++) {
|
||
|
if (mesh.polys[p + j] == rem || mesh.polys[p + k] == rem) {
|
||
|
// Arrange edge so that a=rem.
|
||
|
int a = mesh.polys[p + j], b = mesh.polys[p + k];
|
||
|
if (b == rem) {
|
||
|
int tmp = a;
|
||
|
a = b;
|
||
|
b = tmp;
|
||
|
}
|
||
|
|
||
|
// Check if the edge exists
|
||
|
bool exists = false;
|
||
|
for (int m = 0; m < nedges; ++m) {
|
||
|
int e = m * 3;
|
||
|
if (edges[e + 1] == b) {
|
||
|
// Exists, increment vertex share count.
|
||
|
edges[e + 2] = edges[e + 2] + 1;
|
||
|
exists = true;
|
||
|
}
|
||
|
}
|
||
|
// Add new edge.
|
||
|
if (!exists) {
|
||
|
edges.Add(a);
|
||
|
edges.Add(b);
|
||
|
edges.Add(1);
|
||
|
nedges++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// There should be no more than 2 open edges.
|
||
|
// This catches the case that two non-adjacent polygons
|
||
|
// share the removed vertex. In that case, do not remove the vertex.
|
||
|
int numOpenEdges = 0;
|
||
|
for (int i = 0; i < nedges; ++i) {
|
||
|
if (edges[i * 3 + 2] < 2)
|
||
|
numOpenEdges++;
|
||
|
}
|
||
|
if (numOpenEdges > 2)
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private void removeVertex(TileCachePolyMesh mesh, int rem, int maxTris) {
|
||
|
// Count number of polygons to remove.
|
||
|
int maxVertsPerPoly = mesh.nvp;
|
||
|
int numRemovedVerts = 0;
|
||
|
for (int i = 0; i < mesh.npolys; ++i) {
|
||
|
int p = i * maxVertsPerPoly * 2;
|
||
|
int nv = countPolyVerts(mesh.polys, p, maxVertsPerPoly);
|
||
|
for (int j = 0; j < nv; ++j) {
|
||
|
if (mesh.polys[p + j] == rem)
|
||
|
numRemovedVerts++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int nedges = 0;
|
||
|
List<int> edges = new();
|
||
|
int nhole = 0;
|
||
|
List<int> hole = new();
|
||
|
List<int> harea = new();
|
||
|
|
||
|
for (int i = 0; i < mesh.npolys; ++i) {
|
||
|
int p = i * maxVertsPerPoly * 2;
|
||
|
int nv = countPolyVerts(mesh.polys, p, maxVertsPerPoly);
|
||
|
bool hasRem = false;
|
||
|
for (int j = 0; j < nv; ++j)
|
||
|
if (mesh.polys[p + j] == rem)
|
||
|
hasRem = true;
|
||
|
if (hasRem) {
|
||
|
// Collect edges which does not touch the removed vertex.
|
||
|
for (int j = 0, k = nv - 1; j < nv; k = j++) {
|
||
|
if (mesh.polys[p + j] != rem && mesh.polys[p + k] != rem) {
|
||
|
edges.Add(mesh.polys[p + k]);
|
||
|
edges.Add(mesh.polys[p + j]);
|
||
|
edges.Add(mesh.areas[i]);
|
||
|
nedges++;
|
||
|
}
|
||
|
}
|
||
|
// Remove the polygon.
|
||
|
int p2 = (mesh.npolys - 1) * maxVertsPerPoly * 2;
|
||
|
Array.Copy(mesh.polys, p2, mesh.polys, p, maxVertsPerPoly);
|
||
|
Array.Fill(mesh.polys, DT_TILECACHE_NULL_IDX, p + maxVertsPerPoly, maxVertsPerPoly);
|
||
|
mesh.areas[i] = mesh.areas[mesh.npolys - 1];
|
||
|
mesh.npolys--;
|
||
|
--i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Remove vertex.
|
||
|
for (int i = rem; i < mesh.nverts; ++i) {
|
||
|
mesh.verts[i * 3 + 0] = mesh.verts[(i + 1) * 3 + 0];
|
||
|
mesh.verts[i * 3 + 1] = mesh.verts[(i + 1) * 3 + 1];
|
||
|
mesh.verts[i * 3 + 2] = mesh.verts[(i + 1) * 3 + 2];
|
||
|
}
|
||
|
mesh.nverts--;
|
||
|
|
||
|
// Adjust indices to match the removed vertex layout.
|
||
|
for (int i = 0; i < mesh.npolys; ++i) {
|
||
|
int p = i * maxVertsPerPoly * 2;
|
||
|
int nv = countPolyVerts(mesh.polys, p, maxVertsPerPoly);
|
||
|
for (int j = 0; j < nv; ++j)
|
||
|
if (mesh.polys[p + j] > rem)
|
||
|
mesh.polys[p + j]--;
|
||
|
}
|
||
|
for (int i = 0; i < nedges; ++i) {
|
||
|
if (edges[i * 3] > rem)
|
||
|
edges[i * 3] = edges[i * 3] - 1;
|
||
|
if (edges[i * 3 + 1] > rem)
|
||
|
edges[i * 3 + 1] = edges[i * 3 + 1] - 1;
|
||
|
}
|
||
|
|
||
|
if (nedges == 0)
|
||
|
return;
|
||
|
|
||
|
// Start with one vertex, keep appending connected
|
||
|
// segments to the start and end of the hole.
|
||
|
nhole = pushBack(edges[0], hole);
|
||
|
pushBack(edges[2], harea);
|
||
|
|
||
|
while (nedges != 0) {
|
||
|
bool match = false;
|
||
|
|
||
|
for (int i = 0; i < nedges; ++i) {
|
||
|
int ea = edges[i * 3];
|
||
|
int eb = edges[i * 3 + 1];
|
||
|
int a = edges[i * 3 + 2];
|
||
|
bool add = false;
|
||
|
if (hole[0] == eb) {
|
||
|
// The segment matches the beginning of the hole boundary.
|
||
|
nhole = pushFront(ea, hole);
|
||
|
pushFront(a, harea);
|
||
|
add = true;
|
||
|
} else if (hole[nhole - 1] == ea) {
|
||
|
// The segment matches the end of the hole boundary.
|
||
|
nhole = pushBack(eb, hole);
|
||
|
pushBack(a, harea);
|
||
|
add = true;
|
||
|
}
|
||
|
if (add) {
|
||
|
// The edge segment was added, remove it.
|
||
|
edges[i * 3] = edges[(nedges - 1) * 3];
|
||
|
edges[i * 3 + 1] = edges[(nedges - 1) * 3] + 1;
|
||
|
edges[i * 3 + 2] = edges[(nedges - 1) * 3] + 2;
|
||
|
--nedges;
|
||
|
match = true;
|
||
|
--i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!match)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
int[] tris = new int[nhole * 3];
|
||
|
int[] tverts = new int[nhole * 4];
|
||
|
int[] tpoly = new int[nhole];
|
||
|
|
||
|
// Generate temp vertex array for triangulation.
|
||
|
for (int i = 0; i < nhole; ++i) {
|
||
|
int pi = hole[i];
|
||
|
tverts[i * 4 + 0] = mesh.verts[pi * 3 + 0];
|
||
|
tverts[i * 4 + 1] = mesh.verts[pi * 3 + 1];
|
||
|
tverts[i * 4 + 2] = mesh.verts[pi * 3 + 2];
|
||
|
tverts[i * 4 + 3] = 0;
|
||
|
tpoly[i] = i;
|
||
|
}
|
||
|
|
||
|
// Triangulate the hole.
|
||
|
int ntris = triangulate(nhole, tverts, tpoly, tris);
|
||
|
if (ntris < 0) {
|
||
|
// TODO: issue warning!
|
||
|
ntris = -ntris;
|
||
|
}
|
||
|
|
||
|
int[] polys = new int[ntris * maxVertsPerPoly];
|
||
|
int[] pareas = new int[ntris];
|
||
|
|
||
|
// Build initial polygons.
|
||
|
int npolys = 0;
|
||
|
Array.Fill(polys, DT_TILECACHE_NULL_IDX, 0, ntris * maxVertsPerPoly);
|
||
|
for (int j = 0; j < ntris; ++j) {
|
||
|
int t = j * 3;
|
||
|
if (tris[t] != tris[t + 1] && tris[t] != tris[t + 2] && tris[t + 1] != tris[t + 2]) {
|
||
|
polys[npolys * maxVertsPerPoly + 0] = hole[tris[t]];
|
||
|
polys[npolys * maxVertsPerPoly + 1] = hole[tris[t + 1]];
|
||
|
polys[npolys * maxVertsPerPoly + 2] = hole[tris[t + 2]];
|
||
|
pareas[npolys] = harea[tris[t]];
|
||
|
npolys++;
|
||
|
}
|
||
|
}
|
||
|
if (npolys == 0)
|
||
|
return;
|
||
|
|
||
|
// Merge polygons.
|
||
|
if (maxVertsPerPoly > 3) {
|
||
|
for (;;) {
|
||
|
// Find best polygons to merge.
|
||
|
int bestMergeVal = 0;
|
||
|
int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0;
|
||
|
|
||
|
for (int j = 0; j < npolys - 1; ++j) {
|
||
|
int pj = j * maxVertsPerPoly;
|
||
|
for (int k = j + 1; k < npolys; ++k) {
|
||
|
int pk = k * maxVertsPerPoly;
|
||
|
int[] pm = getPolyMergeValue(polys, pj, pk, mesh.verts, maxVertsPerPoly);
|
||
|
int v = pm[0];
|
||
|
int ea = pm[1];
|
||
|
int eb = pm[2];
|
||
|
if (v > bestMergeVal) {
|
||
|
bestMergeVal = v;
|
||
|
bestPa = j;
|
||
|
bestPb = k;
|
||
|
bestEa = ea;
|
||
|
bestEb = eb;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bestMergeVal > 0) {
|
||
|
// Found best, merge.
|
||
|
int pa = bestPa * maxVertsPerPoly;
|
||
|
int pb = bestPb * maxVertsPerPoly;
|
||
|
mergePolys(polys, pa, pb, bestEa, bestEb, maxVertsPerPoly);
|
||
|
Array.Copy(polys, (npolys - 1) * maxVertsPerPoly, polys, pb, maxVertsPerPoly);
|
||
|
pareas[bestPb] = pareas[npolys - 1];
|
||
|
npolys--;
|
||
|
} else {
|
||
|
// Could not merge any polygons, stop.
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Store polygons.
|
||
|
for (int i = 0; i < npolys; ++i) {
|
||
|
if (mesh.npolys >= maxTris)
|
||
|
break;
|
||
|
int p = mesh.npolys * maxVertsPerPoly * 2;
|
||
|
Array.Fill(mesh.polys, DT_TILECACHE_NULL_IDX, p, maxVertsPerPoly * 2);
|
||
|
for (int j = 0; j < maxVertsPerPoly; ++j)
|
||
|
mesh.polys[p + j] = polys[i * maxVertsPerPoly + j];
|
||
|
mesh.areas[mesh.npolys] = pareas[i];
|
||
|
mesh.npolys++;
|
||
|
if (mesh.npolys > maxTris) {
|
||
|
throw new Exception("Buffer too small");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
public TileCachePolyMesh buildTileCachePolyMesh(TileCacheContourSet lcset, int maxVertsPerPoly) {
|
||
|
|
||
|
int maxVertices = 0;
|
||
|
int maxTris = 0;
|
||
|
int maxVertsPerCont = 0;
|
||
|
for (int i = 0; i < lcset.nconts; ++i) {
|
||
|
// Skip null contours.
|
||
|
if (lcset.conts[i].nverts < 3)
|
||
|
continue;
|
||
|
maxVertices += lcset.conts[i].nverts;
|
||
|
maxTris += lcset.conts[i].nverts - 2;
|
||
|
maxVertsPerCont = Math.Max(maxVertsPerCont, lcset.conts[i].nverts);
|
||
|
}
|
||
|
|
||
|
// TODO: warn about too many vertices?
|
||
|
|
||
|
TileCachePolyMesh mesh = new TileCachePolyMesh(maxVertsPerPoly);
|
||
|
|
||
|
int[] vflags = new int[maxVertices];
|
||
|
|
||
|
mesh.verts = new int[maxVertices * 3];
|
||
|
mesh.polys = new int[maxTris * maxVertsPerPoly * 2];
|
||
|
mesh.areas = new int[maxTris];
|
||
|
// Just allocate and clean the mesh flags array. The user is resposible
|
||
|
// for filling it.
|
||
|
mesh.flags = new int[maxTris];
|
||
|
|
||
|
mesh.nverts = 0;
|
||
|
mesh.npolys = 0;
|
||
|
|
||
|
Array.Fill(mesh.polys, DT_TILECACHE_NULL_IDX);
|
||
|
|
||
|
int[] firstVert = new int[VERTEX_BUCKET_COUNT2];
|
||
|
for (int i = 0; i < VERTEX_BUCKET_COUNT2; ++i)
|
||
|
firstVert[i] = DT_TILECACHE_NULL_IDX;
|
||
|
|
||
|
int[] nextVert = new int[maxVertices];
|
||
|
int[] indices = new int[maxVertsPerCont];
|
||
|
int[] tris = new int[maxVertsPerCont * 3];
|
||
|
int[] polys = new int[maxVertsPerCont * maxVertsPerPoly];
|
||
|
|
||
|
for (int i = 0; i < lcset.nconts; ++i) {
|
||
|
TileCacheContour cont = lcset.conts[i];
|
||
|
|
||
|
// Skip null contours.
|
||
|
if (cont.nverts < 3)
|
||
|
continue;
|
||
|
|
||
|
// Triangulate contour
|
||
|
for (int j = 0; j < cont.nverts; ++j)
|
||
|
indices[j] = j;
|
||
|
|
||
|
int ntris = triangulate(cont.nverts, cont.verts, indices, tris);
|
||
|
if (ntris <= 0) {
|
||
|
// TODO: issue warning!
|
||
|
ntris = -ntris;
|
||
|
}
|
||
|
|
||
|
// Add and merge vertices.
|
||
|
for (int j = 0; j < cont.nverts; ++j) {
|
||
|
int v = j * 4;
|
||
|
indices[j] = addVertex(cont.verts[v], cont.verts[v + 1], cont.verts[v + 2], mesh.verts, firstVert,
|
||
|
nextVert, mesh.nverts);
|
||
|
mesh.nverts = Math.Max(mesh.nverts, indices[j] + 1);
|
||
|
if ((cont.verts[v + 3] & 0x80) != 0) {
|
||
|
// This vertex should be removed.
|
||
|
vflags[indices[j]] = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Build initial polygons.
|
||
|
int npolys = 0;
|
||
|
Array.Fill(polys, DT_TILECACHE_NULL_IDX);
|
||
|
for (int j = 0; j < ntris; ++j) {
|
||
|
int t = j * 3;
|
||
|
if (tris[t] != tris[t + 1] && tris[t] != tris[t + 2] && tris[t + 1] != tris[t + 2]) {
|
||
|
polys[npolys * maxVertsPerPoly + 0] = indices[tris[t]];
|
||
|
polys[npolys * maxVertsPerPoly + 1] = indices[tris[t + 1]];
|
||
|
polys[npolys * maxVertsPerPoly + 2] = indices[tris[t + 2]];
|
||
|
npolys++;
|
||
|
}
|
||
|
}
|
||
|
if (npolys == 0)
|
||
|
continue;
|
||
|
|
||
|
// Merge polygons.
|
||
|
if (maxVertsPerPoly > 3) {
|
||
|
for (;;) {
|
||
|
// Find best polygons to merge.
|
||
|
int bestMergeVal = 0;
|
||
|
int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0;
|
||
|
|
||
|
for (int j = 0; j < npolys - 1; ++j) {
|
||
|
int pj = j * maxVertsPerPoly;
|
||
|
for (int k = j + 1; k < npolys; ++k) {
|
||
|
int pk = k * maxVertsPerPoly;
|
||
|
int[] pm = getPolyMergeValue(polys, pj, pk, mesh.verts, maxVertsPerPoly);
|
||
|
int v = pm[0];
|
||
|
int ea = pm[1];
|
||
|
int eb = pm[2];
|
||
|
if (v > bestMergeVal) {
|
||
|
bestMergeVal = v;
|
||
|
bestPa = j;
|
||
|
bestPb = k;
|
||
|
bestEa = ea;
|
||
|
bestEb = eb;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bestMergeVal > 0) {
|
||
|
// Found best, merge.
|
||
|
int pa = bestPa * maxVertsPerPoly;
|
||
|
int pb = bestPb * maxVertsPerPoly;
|
||
|
mergePolys(polys, pa, pb, bestEa, bestEb, maxVertsPerPoly);
|
||
|
Array.Copy(polys, (npolys - 1) * maxVertsPerPoly, polys, pb, maxVertsPerPoly);
|
||
|
npolys--;
|
||
|
} else {
|
||
|
// Could not merge any polygons, stop.
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Store polygons.
|
||
|
for (int j = 0; j < npolys; ++j) {
|
||
|
int p = mesh.npolys * maxVertsPerPoly * 2;
|
||
|
int q = j * maxVertsPerPoly;
|
||
|
for (int k = 0; k < maxVertsPerPoly; ++k)
|
||
|
mesh.polys[p + k] = polys[q + k];
|
||
|
mesh.areas[mesh.npolys] = cont.area;
|
||
|
mesh.npolys++;
|
||
|
if (mesh.npolys > maxTris)
|
||
|
throw new Exception("Buffer too small");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Remove edge vertices.
|
||
|
for (int i = 0; i < mesh.nverts; ++i) {
|
||
|
if (vflags[i] != 0) {
|
||
|
if (!canRemoveVertex(mesh, i))
|
||
|
continue;
|
||
|
removeVertex(mesh, i, maxTris);
|
||
|
// Remove vertex
|
||
|
// Note: mesh.nverts is already decremented inside
|
||
|
// removeVertex()!
|
||
|
for (int j = i; j < mesh.nverts; ++j)
|
||
|
vflags[j] = vflags[j + 1];
|
||
|
--i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Calculate adjacency.
|
||
|
buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.verts, mesh.nverts, lcset, maxVertsPerPoly);
|
||
|
|
||
|
return mesh;
|
||
|
}
|
||
|
|
||
|
public void markCylinderArea(TileCacheLayer layer, float[] orig, float cs, float ch, float[] pos, float radius,
|
||
|
float height, int areaId) {
|
||
|
float[] bmin = new float[3];
|
||
|
float[] bmax = new float[3];
|
||
|
bmin[0] = pos[0] - radius;
|
||
|
bmin[1] = pos[1];
|
||
|
bmin[2] = pos[2] - radius;
|
||
|
bmax[0] = pos[0] + radius;
|
||
|
bmax[1] = pos[1] + height;
|
||
|
bmax[2] = pos[2] + radius;
|
||
|
float r2 = sqr(radius / cs + 0.5f);
|
||
|
|
||
|
int w = layer.header.width;
|
||
|
int h = layer.header.height;
|
||
|
float ics = 1.0f / cs;
|
||
|
float ich = 1.0f / ch;
|
||
|
|
||
|
float px = (pos[0] - orig[0]) * ics;
|
||
|
float pz = (pos[2] - orig[2]) * ics;
|
||
|
|
||
|
int minx = (int) Math.Floor((bmin[0] - orig[0]) * ics);
|
||
|
int miny = (int) Math.Floor((bmin[1] - orig[1]) * ich);
|
||
|
int minz = (int) Math.Floor((bmin[2] - orig[2]) * ics);
|
||
|
int maxx = (int) Math.Floor((bmax[0] - orig[0]) * ics);
|
||
|
int maxy = (int) Math.Floor((bmax[1] - orig[1]) * ich);
|
||
|
int maxz = (int) Math.Floor((bmax[2] - orig[2]) * ics);
|
||
|
|
||
|
if (maxx < 0)
|
||
|
return;
|
||
|
if (minx >= w)
|
||
|
return;
|
||
|
if (maxz < 0)
|
||
|
return;
|
||
|
if (minz >= h)
|
||
|
return;
|
||
|
|
||
|
if (minx < 0)
|
||
|
minx = 0;
|
||
|
if (maxx >= w)
|
||
|
maxx = w - 1;
|
||
|
if (minz < 0)
|
||
|
minz = 0;
|
||
|
if (maxz >= h)
|
||
|
maxz = h - 1;
|
||
|
|
||
|
for (int z = minz; z <= maxz; ++z) {
|
||
|
for (int x = minx; x <= maxx; ++x) {
|
||
|
float dx = x + 0.5f - px;
|
||
|
float dz = z + 0.5f - pz;
|
||
|
if (dx * dx + dz * dz > r2)
|
||
|
continue;
|
||
|
int y = layer.heights[x + z * w];
|
||
|
if (y < miny || y > maxy)
|
||
|
continue;
|
||
|
layer.areas[x + z * w] = (short) areaId;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void markBoxArea(TileCacheLayer layer, float[] orig, float cs, float ch, float[] bmin, float[] bmax,
|
||
|
int areaId) {
|
||
|
int w = layer.header.width;
|
||
|
int h = layer.header.height;
|
||
|
float ics = 1.0f / cs;
|
||
|
float ich = 1.0f / ch;
|
||
|
|
||
|
int minx = (int) Math.Floor((bmin[0] - orig[0]) * ics);
|
||
|
int miny = (int) Math.Floor((bmin[1] - orig[1]) * ich);
|
||
|
int minz = (int) Math.Floor((bmin[2] - orig[2]) * ics);
|
||
|
int maxx = (int) Math.Floor((bmax[0] - orig[0]) * ics);
|
||
|
int maxy = (int) Math.Floor((bmax[1] - orig[1]) * ich);
|
||
|
int maxz = (int) Math.Floor((bmax[2] - orig[2]) * ics);
|
||
|
|
||
|
if (maxx < 0)
|
||
|
return;
|
||
|
if (minx >= w)
|
||
|
return;
|
||
|
if (maxz < 0)
|
||
|
return;
|
||
|
if (minz >= h)
|
||
|
return;
|
||
|
|
||
|
if (minx < 0)
|
||
|
minx = 0;
|
||
|
if (maxx >= w)
|
||
|
maxx = w - 1;
|
||
|
if (minz < 0)
|
||
|
minz = 0;
|
||
|
if (maxz >= h)
|
||
|
maxz = h - 1;
|
||
|
|
||
|
for (int z = minz; z <= maxz; ++z) {
|
||
|
for (int x = minx; x <= maxx; ++x) {
|
||
|
int y = layer.heights[x + z * w];
|
||
|
if (y < miny || y > maxy)
|
||
|
continue;
|
||
|
layer.areas[x + z * w] = (short) areaId;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
public byte[] compressTileCacheLayer(TileCacheLayer layer, ByteOrder order, bool cCompatibility) {
|
||
|
using var ms = new MemoryStream();
|
||
|
using var baos = new BinaryWriter(ms);
|
||
|
TileCacheLayerHeaderWriter hw = new TileCacheLayerHeaderWriter();
|
||
|
try {
|
||
|
hw.write(baos, layer.header, order, cCompatibility);
|
||
|
int gridSize = layer.header.width * layer.header.height;
|
||
|
byte[] buffer = new byte[gridSize * 3];
|
||
|
for (int i = 0; i < gridSize; i++) {
|
||
|
buffer[i] = (byte) layer.heights[i];
|
||
|
buffer[gridSize + i] = (byte) layer.areas[i];
|
||
|
buffer[gridSize * 2 + i] = (byte) layer.cons[i];
|
||
|
}
|
||
|
baos.Write(TileCacheCompressorFactory.get(cCompatibility).compress(buffer));
|
||
|
return ms.ToArray();
|
||
|
} catch (IOException e) {
|
||
|
throw new Exception(e.Message, e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public byte[] compressTileCacheLayer(TileCacheLayerHeader header, int[] heights, int[] areas, int[] cons,
|
||
|
ByteOrder order, bool cCompatibility) {
|
||
|
using var ms = new MemoryStream();
|
||
|
using var baos = new BinaryWriter(ms);
|
||
|
TileCacheLayerHeaderWriter hw = new TileCacheLayerHeaderWriter();
|
||
|
try {
|
||
|
hw.write(baos, header, order, cCompatibility);
|
||
|
int gridSize = header.width * header.height;
|
||
|
byte[] buffer = new byte[gridSize * 3];
|
||
|
for (int i = 0; i < gridSize; i++) {
|
||
|
buffer[i] = (byte) heights[i];
|
||
|
buffer[gridSize + i] = (byte) areas[i];
|
||
|
buffer[gridSize * 2 + i] = (byte) cons[i];
|
||
|
}
|
||
|
baos.Write(TileCacheCompressorFactory.get(cCompatibility).compress(buffer));
|
||
|
return ms.ToArray();
|
||
|
} catch (IOException e) {
|
||
|
throw new Exception(e.Message, e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public TileCacheLayer decompressTileCacheLayer(TileCacheCompressor comp, byte[] compressed, ByteOrder order,
|
||
|
bool cCompatibility) {
|
||
|
ByteBuffer buf = new ByteBuffer(compressed);
|
||
|
buf.order(order);
|
||
|
TileCacheLayer layer = new TileCacheLayer();
|
||
|
try {
|
||
|
layer.header = reader.read(buf, cCompatibility);
|
||
|
} catch (IOException e) {
|
||
|
throw new Exception(e.Message, e);
|
||
|
}
|
||
|
|
||
|
int gridSize = layer.header.width * layer.header.height;
|
||
|
byte[] grids = comp.decompress(compressed, buf.position(), compressed.Length - buf.position(), gridSize * 3);
|
||
|
layer.heights = new short[gridSize];
|
||
|
layer.areas = new short[gridSize];
|
||
|
layer.cons = new short[gridSize];
|
||
|
layer.regs = new short[gridSize];
|
||
|
for (int i = 0; i < gridSize; i++) {
|
||
|
layer.heights[i] = (short) (grids[i] & 0xFF);
|
||
|
layer.areas[i] = (short) (grids[i + gridSize] & 0xFF);
|
||
|
layer.cons[i] = (short) (grids[i + gridSize * 2] & 0xFF);
|
||
|
}
|
||
|
return layer;
|
||
|
|
||
|
}
|
||
|
|
||
|
public void markBoxArea(TileCacheLayer layer, float[] orig, float cs, float ch, float[] center, float[] extents,
|
||
|
float[] rotAux, int areaId) {
|
||
|
int w = layer.header.width;
|
||
|
int h = layer.header.height;
|
||
|
float ics = 1.0f / cs;
|
||
|
float ich = 1.0f / ch;
|
||
|
|
||
|
float cx = (center[0] - orig[0]) * ics;
|
||
|
float cz = (center[2] - orig[2]) * ics;
|
||
|
|
||
|
float maxr = 1.41f * Math.Max(extents[0], extents[2]);
|
||
|
int minx = (int) Math.Floor(cx - maxr * ics);
|
||
|
int maxx = (int) Math.Floor(cx + maxr * ics);
|
||
|
int minz = (int) Math.Floor(cz - maxr * ics);
|
||
|
int maxz = (int) Math.Floor(cz + maxr * ics);
|
||
|
int miny = (int) Math.Floor((center[1] - extents[1] - orig[1]) * ich);
|
||
|
int maxy = (int) Math.Floor((center[1] + extents[1] - orig[1]) * ich);
|
||
|
|
||
|
if (maxx < 0)
|
||
|
return;
|
||
|
if (minx >= w)
|
||
|
return;
|
||
|
if (maxz < 0)
|
||
|
return;
|
||
|
if (minz >= h)
|
||
|
return;
|
||
|
|
||
|
if (minx < 0)
|
||
|
minx = 0;
|
||
|
if (maxx >= w)
|
||
|
maxx = w - 1;
|
||
|
if (minz < 0)
|
||
|
minz = 0;
|
||
|
if (maxz >= h)
|
||
|
maxz = h - 1;
|
||
|
|
||
|
float xhalf = extents[0] * ics + 0.5f;
|
||
|
float zhalf = extents[2] * ics + 0.5f;
|
||
|
for (int z = minz; z <= maxz; ++z) {
|
||
|
for (int x = minx; x <= maxx; ++x) {
|
||
|
float x2 = 2.0f * (x - cx);
|
||
|
float z2 = 2.0f * (z - cz);
|
||
|
float xrot = rotAux[1] * x2 + rotAux[0] * z2;
|
||
|
if (xrot > xhalf || xrot < -xhalf)
|
||
|
continue;
|
||
|
float zrot = rotAux[1] * z2 - rotAux[0] * x2;
|
||
|
if (zrot > zhalf || zrot < -zhalf)
|
||
|
continue;
|
||
|
int y = layer.heights[x + z * w];
|
||
|
if (y < miny || y > maxy)
|
||
|
continue;
|
||
|
layer.areas[x + z * w] = (short) areaId;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|