mirror of
https://github.com/EQEmu/Server.git
synced 2026-06-15 00:28:21 +00:00
Recast navigation
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#include "ChunkyTriMesh.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
struct BoundsItem
|
||||
{
|
||||
float bmin[2];
|
||||
float bmax[2];
|
||||
int i;
|
||||
};
|
||||
|
||||
static int compareItemX(const void* va, const void* vb)
|
||||
{
|
||||
const BoundsItem* a = (const BoundsItem*)va;
|
||||
const BoundsItem* b = (const BoundsItem*)vb;
|
||||
if (a->bmin[0] < b->bmin[0])
|
||||
return -1;
|
||||
if (a->bmin[0] > b->bmin[0])
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int compareItemY(const void* va, const void* vb)
|
||||
{
|
||||
const BoundsItem* a = (const BoundsItem*)va;
|
||||
const BoundsItem* b = (const BoundsItem*)vb;
|
||||
if (a->bmin[1] < b->bmin[1])
|
||||
return -1;
|
||||
if (a->bmin[1] > b->bmin[1])
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void calcExtends(const BoundsItem* items, const int /*nitems*/,
|
||||
const int imin, const int imax,
|
||||
float* bmin, float* bmax)
|
||||
{
|
||||
bmin[0] = items[imin].bmin[0];
|
||||
bmin[1] = items[imin].bmin[1];
|
||||
|
||||
bmax[0] = items[imin].bmax[0];
|
||||
bmax[1] = items[imin].bmax[1];
|
||||
|
||||
for (int i = imin+1; i < imax; ++i)
|
||||
{
|
||||
const BoundsItem& it = items[i];
|
||||
if (it.bmin[0] < bmin[0]) bmin[0] = it.bmin[0];
|
||||
if (it.bmin[1] < bmin[1]) bmin[1] = it.bmin[1];
|
||||
|
||||
if (it.bmax[0] > bmax[0]) bmax[0] = it.bmax[0];
|
||||
if (it.bmax[1] > bmax[1]) bmax[1] = it.bmax[1];
|
||||
}
|
||||
}
|
||||
|
||||
inline int longestAxis(float x, float y)
|
||||
{
|
||||
return y > x ? 1 : 0;
|
||||
}
|
||||
|
||||
static void subdivide(BoundsItem* items, int nitems, int imin, int imax, int trisPerChunk,
|
||||
int& curNode, rcChunkyTriMeshNode* nodes, const int maxNodes,
|
||||
int& curTri, int* outTris, const int* inTris)
|
||||
{
|
||||
int inum = imax - imin;
|
||||
int icur = curNode;
|
||||
|
||||
if (curNode > maxNodes)
|
||||
return;
|
||||
|
||||
rcChunkyTriMeshNode& node = nodes[curNode++];
|
||||
|
||||
if (inum <= trisPerChunk)
|
||||
{
|
||||
// Leaf
|
||||
calcExtends(items, nitems, imin, imax, node.bmin, node.bmax);
|
||||
|
||||
// Copy triangles.
|
||||
node.i = curTri;
|
||||
node.n = inum;
|
||||
|
||||
for (int i = imin; i < imax; ++i)
|
||||
{
|
||||
const int* src = &inTris[items[i].i*3];
|
||||
int* dst = &outTris[curTri*3];
|
||||
curTri++;
|
||||
dst[0] = src[0];
|
||||
dst[1] = src[1];
|
||||
dst[2] = src[2];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split
|
||||
calcExtends(items, nitems, imin, imax, node.bmin, node.bmax);
|
||||
|
||||
int axis = longestAxis(node.bmax[0] - node.bmin[0],
|
||||
node.bmax[1] - node.bmin[1]);
|
||||
|
||||
if (axis == 0)
|
||||
{
|
||||
// Sort along x-axis
|
||||
qsort(items+imin, static_cast<size_t>(inum), sizeof(BoundsItem), compareItemX);
|
||||
}
|
||||
else if (axis == 1)
|
||||
{
|
||||
// Sort along y-axis
|
||||
qsort(items+imin, static_cast<size_t>(inum), sizeof(BoundsItem), compareItemY);
|
||||
}
|
||||
|
||||
int isplit = imin+inum/2;
|
||||
|
||||
// Left
|
||||
subdivide(items, nitems, imin, isplit, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris);
|
||||
// Right
|
||||
subdivide(items, nitems, isplit, imax, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris);
|
||||
|
||||
int iescape = curNode - icur;
|
||||
// Negative index means escape.
|
||||
node.i = -iescape;
|
||||
}
|
||||
}
|
||||
|
||||
bool rcCreateChunkyTriMesh(const float* verts, const int* tris, int ntris,
|
||||
int trisPerChunk, rcChunkyTriMesh* cm)
|
||||
{
|
||||
int nchunks = (ntris + trisPerChunk-1) / trisPerChunk;
|
||||
|
||||
cm->nodes = new rcChunkyTriMeshNode[nchunks*4];
|
||||
if (!cm->nodes)
|
||||
return false;
|
||||
|
||||
cm->tris = new int[ntris*3];
|
||||
if (!cm->tris)
|
||||
return false;
|
||||
|
||||
cm->ntris = ntris;
|
||||
|
||||
// Build tree
|
||||
BoundsItem* items = new BoundsItem[ntris];
|
||||
if (!items)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < ntris; i++)
|
||||
{
|
||||
const int* t = &tris[i*3];
|
||||
BoundsItem& it = items[i];
|
||||
it.i = i;
|
||||
// Calc triangle XZ bounds.
|
||||
it.bmin[0] = it.bmax[0] = verts[t[0]*3+0];
|
||||
it.bmin[1] = it.bmax[1] = verts[t[0]*3+2];
|
||||
for (int j = 1; j < 3; ++j)
|
||||
{
|
||||
const float* v = &verts[t[j]*3];
|
||||
if (v[0] < it.bmin[0]) it.bmin[0] = v[0];
|
||||
if (v[2] < it.bmin[1]) it.bmin[1] = v[2];
|
||||
|
||||
if (v[0] > it.bmax[0]) it.bmax[0] = v[0];
|
||||
if (v[2] > it.bmax[1]) it.bmax[1] = v[2];
|
||||
}
|
||||
}
|
||||
|
||||
int curTri = 0;
|
||||
int curNode = 0;
|
||||
subdivide(items, ntris, 0, ntris, trisPerChunk, curNode, cm->nodes, nchunks*4, curTri, cm->tris, tris);
|
||||
|
||||
delete [] items;
|
||||
|
||||
cm->nnodes = curNode;
|
||||
|
||||
// Calc max tris per node.
|
||||
cm->maxTrisPerChunk = 0;
|
||||
for (int i = 0; i < cm->nnodes; ++i)
|
||||
{
|
||||
rcChunkyTriMeshNode& node = cm->nodes[i];
|
||||
const bool isLeaf = node.i >= 0;
|
||||
if (!isLeaf) continue;
|
||||
if (node.n > cm->maxTrisPerChunk)
|
||||
cm->maxTrisPerChunk = node.n;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
inline bool checkOverlapRect(const float amin[2], const float amax[2],
|
||||
const float bmin[2], const float bmax[2])
|
||||
{
|
||||
bool overlap = true;
|
||||
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
|
||||
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
|
||||
return overlap;
|
||||
}
|
||||
|
||||
int rcGetChunksOverlappingRect(const rcChunkyTriMesh* cm,
|
||||
float bmin[2], float bmax[2],
|
||||
int* ids, const int maxIds)
|
||||
{
|
||||
// Traverse tree
|
||||
int i = 0;
|
||||
int n = 0;
|
||||
while (i < cm->nnodes)
|
||||
{
|
||||
const rcChunkyTriMeshNode* node = &cm->nodes[i];
|
||||
const bool overlap = checkOverlapRect(bmin, bmax, node->bmin, node->bmax);
|
||||
const bool isLeafNode = node->i >= 0;
|
||||
|
||||
if (isLeafNode && overlap)
|
||||
{
|
||||
if (n < maxIds)
|
||||
{
|
||||
ids[n] = i;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
if (overlap || isLeafNode)
|
||||
i++;
|
||||
else
|
||||
{
|
||||
const int escapeIndex = -node->i;
|
||||
i += escapeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static bool checkOverlapSegment(const float p[2], const float q[2],
|
||||
const float bmin[2], const float bmax[2])
|
||||
{
|
||||
static const float EPSILON = 1e-6f;
|
||||
|
||||
float tmin = 0;
|
||||
float tmax = 1;
|
||||
float d[2];
|
||||
d[0] = q[0] - p[0];
|
||||
d[1] = q[1] - p[1];
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (fabsf(d[i]) < EPSILON)
|
||||
{
|
||||
// Ray is parallel to slab. No hit if origin not within slab
|
||||
if (p[i] < bmin[i] || p[i] > bmax[i])
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compute intersection t value of ray with near and far plane of slab
|
||||
float ood = 1.0f / d[i];
|
||||
float t1 = (bmin[i] - p[i]) * ood;
|
||||
float t2 = (bmax[i] - p[i]) * ood;
|
||||
if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; }
|
||||
if (t1 > tmin) tmin = t1;
|
||||
if (t2 < tmax) tmax = t2;
|
||||
if (tmin > tmax) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int rcGetChunksOverlappingSegment(const rcChunkyTriMesh* cm,
|
||||
float p[2], float q[2],
|
||||
int* ids, const int maxIds)
|
||||
{
|
||||
// Traverse tree
|
||||
int i = 0;
|
||||
int n = 0;
|
||||
while (i < cm->nnodes)
|
||||
{
|
||||
const rcChunkyTriMeshNode* node = &cm->nodes[i];
|
||||
const bool overlap = checkOverlapSegment(p, q, node->bmin, node->bmax);
|
||||
const bool isLeafNode = node->i >= 0;
|
||||
|
||||
if (isLeafNode && overlap)
|
||||
{
|
||||
if (n < maxIds)
|
||||
{
|
||||
ids[n] = i;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
if (overlap || isLeafNode)
|
||||
i++;
|
||||
else
|
||||
{
|
||||
const int escapeIndex = -node->i;
|
||||
i += escapeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <float.h>
|
||||
#include "SDL.h"
|
||||
#include "SDL_opengl.h"
|
||||
#include "imgui.h"
|
||||
#include "ConvexVolumeTool.h"
|
||||
#include "InputGeom.h"
|
||||
#include "Sample.h"
|
||||
#include "Recast.h"
|
||||
#include "RecastDebugDraw.h"
|
||||
#include "DetourDebugDraw.h"
|
||||
|
||||
#ifdef WIN32
|
||||
# define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
// Quick and dirty convex hull.
|
||||
|
||||
// Returns true if 'c' is left of line 'a'-'b'.
|
||||
inline bool left(const float* a, const float* b, const float* c)
|
||||
{
|
||||
const float u1 = b[0] - a[0];
|
||||
const float v1 = b[2] - a[2];
|
||||
const float u2 = c[0] - a[0];
|
||||
const float v2 = c[2] - a[2];
|
||||
return u1 * v2 - v1 * u2 < 0;
|
||||
}
|
||||
|
||||
// Returns true if 'a' is more lower-left than 'b'.
|
||||
inline bool cmppt(const float* a, const float* b)
|
||||
{
|
||||
if (a[0] < b[0]) return true;
|
||||
if (a[0] > b[0]) return false;
|
||||
if (a[2] < b[2]) return true;
|
||||
if (a[2] > b[2]) return false;
|
||||
return false;
|
||||
}
|
||||
// Calculates convex hull on xz-plane of points on 'pts',
|
||||
// stores the indices of the resulting hull in 'out' and
|
||||
// returns number of points on hull.
|
||||
static int convexhull(const float* pts, int npts, int* out)
|
||||
{
|
||||
// Find lower-leftmost point.
|
||||
int hull = 0;
|
||||
for (int i = 1; i < npts; ++i)
|
||||
if (cmppt(&pts[i*3], &pts[hull*3]))
|
||||
hull = i;
|
||||
// Gift wrap hull.
|
||||
int endpt = 0;
|
||||
int i = 0;
|
||||
do
|
||||
{
|
||||
out[i++] = hull;
|
||||
endpt = 0;
|
||||
for (int j = 1; j < npts; ++j)
|
||||
if (hull == endpt || left(&pts[hull*3], &pts[endpt*3], &pts[j*3]))
|
||||
endpt = j;
|
||||
hull = endpt;
|
||||
}
|
||||
while (endpt != out[0]);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static int pointInPoly(int nvert, const float* verts, const float* p)
|
||||
{
|
||||
int i, j, c = 0;
|
||||
for (i = 0, j = nvert-1; i < nvert; j = i++)
|
||||
{
|
||||
const float* vi = &verts[i*3];
|
||||
const float* vj = &verts[j*3];
|
||||
if (((vi[2] > p[2]) != (vj[2] > p[2])) &&
|
||||
(p[0] < (vj[0]-vi[0]) * (p[2]-vi[2]) / (vj[2]-vi[2]) + vi[0]) )
|
||||
c = !c;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
ConvexVolumeTool::ConvexVolumeTool() :
|
||||
m_sample(0),
|
||||
m_areaType(SAMPLE_POLYAREA_GRASS),
|
||||
m_polyOffset(0.0f),
|
||||
m_boxHeight(6.0f),
|
||||
m_boxDescent(1.0f),
|
||||
m_npts(0),
|
||||
m_nhull(0)
|
||||
{
|
||||
}
|
||||
|
||||
void ConvexVolumeTool::init(Sample* sample)
|
||||
{
|
||||
m_sample = sample;
|
||||
}
|
||||
|
||||
void ConvexVolumeTool::reset()
|
||||
{
|
||||
m_npts = 0;
|
||||
m_nhull = 0;
|
||||
}
|
||||
|
||||
void ConvexVolumeTool::handleMenu()
|
||||
{
|
||||
imguiSlider("Shape Height", &m_boxHeight, 0.1f, 20.0f, 0.1f);
|
||||
imguiSlider("Shape Descent", &m_boxDescent, 0.1f, 20.0f, 0.1f);
|
||||
imguiSlider("Poly Offset", &m_polyOffset, 0.0f, 10.0f, 0.1f);
|
||||
|
||||
imguiSeparator();
|
||||
|
||||
imguiLabel("Area Type");
|
||||
imguiIndent();
|
||||
if (imguiCheck("Ground", m_areaType == SAMPLE_POLYAREA_GROUND))
|
||||
m_areaType = SAMPLE_POLYAREA_GROUND;
|
||||
if (imguiCheck("Water", m_areaType == SAMPLE_POLYAREA_WATER))
|
||||
m_areaType = SAMPLE_POLYAREA_WATER;
|
||||
if (imguiCheck("Road", m_areaType == SAMPLE_POLYAREA_ROAD))
|
||||
m_areaType = SAMPLE_POLYAREA_ROAD;
|
||||
if (imguiCheck("Door", m_areaType == SAMPLE_POLYAREA_DOOR))
|
||||
m_areaType = SAMPLE_POLYAREA_DOOR;
|
||||
if (imguiCheck("Grass", m_areaType == SAMPLE_POLYAREA_GRASS))
|
||||
m_areaType = SAMPLE_POLYAREA_GRASS;
|
||||
if (imguiCheck("Jump", m_areaType == SAMPLE_POLYAREA_JUMP))
|
||||
m_areaType = SAMPLE_POLYAREA_JUMP;
|
||||
imguiUnindent();
|
||||
|
||||
imguiSeparator();
|
||||
|
||||
if (imguiButton("Clear Shape"))
|
||||
{
|
||||
m_npts = 0;
|
||||
m_nhull = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ConvexVolumeTool::handleClick(const float* /*s*/, const float* p, bool shift)
|
||||
{
|
||||
if (!m_sample) return;
|
||||
InputGeom* geom = m_sample->getInputGeom();
|
||||
if (!geom) return;
|
||||
|
||||
if (shift)
|
||||
{
|
||||
// Delete
|
||||
int nearestIndex = -1;
|
||||
const ConvexVolume* vols = geom->getConvexVolumes();
|
||||
for (int i = 0; i < geom->getConvexVolumeCount(); ++i)
|
||||
{
|
||||
if (pointInPoly(vols[i].nverts, vols[i].verts, p) &&
|
||||
p[1] >= vols[i].hmin && p[1] <= vols[i].hmax)
|
||||
{
|
||||
nearestIndex = i;
|
||||
}
|
||||
}
|
||||
// If end point close enough, delete it.
|
||||
if (nearestIndex != -1)
|
||||
{
|
||||
geom->deleteConvexVolume(nearestIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create
|
||||
|
||||
// If clicked on that last pt, create the shape.
|
||||
if (m_npts && rcVdistSqr(p, &m_pts[(m_npts-1)*3]) < rcSqr(0.2f))
|
||||
{
|
||||
if (m_nhull > 2)
|
||||
{
|
||||
// Create shape.
|
||||
float verts[MAX_PTS*3];
|
||||
for (int i = 0; i < m_nhull; ++i)
|
||||
rcVcopy(&verts[i*3], &m_pts[m_hull[i]*3]);
|
||||
|
||||
float minh = FLT_MAX, maxh = 0;
|
||||
for (int i = 0; i < m_nhull; ++i)
|
||||
minh = rcMin(minh, verts[i*3+1]);
|
||||
minh -= m_boxDescent;
|
||||
maxh = minh + m_boxHeight;
|
||||
|
||||
if (m_polyOffset > 0.01f)
|
||||
{
|
||||
float offset[MAX_PTS*2*3];
|
||||
int noffset = rcOffsetPoly(verts, m_nhull, m_polyOffset, offset, MAX_PTS*2);
|
||||
if (noffset > 0)
|
||||
geom->addConvexVolume(offset, noffset, minh, maxh, (unsigned char)m_areaType);
|
||||
}
|
||||
else
|
||||
{
|
||||
geom->addConvexVolume(verts, m_nhull, minh, maxh, (unsigned char)m_areaType);
|
||||
}
|
||||
}
|
||||
|
||||
m_npts = 0;
|
||||
m_nhull = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new point
|
||||
if (m_npts < MAX_PTS)
|
||||
{
|
||||
rcVcopy(&m_pts[m_npts*3], p);
|
||||
m_npts++;
|
||||
// Update hull.
|
||||
if (m_npts > 1)
|
||||
m_nhull = convexhull(m_pts, m_npts, m_hull);
|
||||
else
|
||||
m_nhull = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ConvexVolumeTool::handleToggle()
|
||||
{
|
||||
}
|
||||
|
||||
void ConvexVolumeTool::handleStep()
|
||||
{
|
||||
}
|
||||
|
||||
void ConvexVolumeTool::handleUpdate(const float /*dt*/)
|
||||
{
|
||||
}
|
||||
|
||||
void ConvexVolumeTool::handleRender()
|
||||
{
|
||||
duDebugDraw& dd = m_sample->getDebugDraw();
|
||||
|
||||
// Find height extent of the shape.
|
||||
float minh = FLT_MAX, maxh = 0;
|
||||
for (int i = 0; i < m_npts; ++i)
|
||||
minh = rcMin(minh, m_pts[i*3+1]);
|
||||
minh -= m_boxDescent;
|
||||
maxh = minh + m_boxHeight;
|
||||
|
||||
dd.begin(DU_DRAW_POINTS, 4.0f);
|
||||
for (int i = 0; i < m_npts; ++i)
|
||||
{
|
||||
unsigned int col = duRGBA(255,255,255,255);
|
||||
if (i == m_npts-1)
|
||||
col = duRGBA(240,32,16,255);
|
||||
dd.vertex(m_pts[i*3+0],m_pts[i*3+1]+0.1f,m_pts[i*3+2], col);
|
||||
}
|
||||
dd.end();
|
||||
|
||||
dd.begin(DU_DRAW_LINES, 2.0f);
|
||||
for (int i = 0, j = m_nhull-1; i < m_nhull; j = i++)
|
||||
{
|
||||
const float* vi = &m_pts[m_hull[j]*3];
|
||||
const float* vj = &m_pts[m_hull[i]*3];
|
||||
dd.vertex(vj[0],minh,vj[2], duRGBA(255,255,255,64));
|
||||
dd.vertex(vi[0],minh,vi[2], duRGBA(255,255,255,64));
|
||||
dd.vertex(vj[0],maxh,vj[2], duRGBA(255,255,255,64));
|
||||
dd.vertex(vi[0],maxh,vi[2], duRGBA(255,255,255,64));
|
||||
dd.vertex(vj[0],minh,vj[2], duRGBA(255,255,255,64));
|
||||
dd.vertex(vj[0],maxh,vj[2], duRGBA(255,255,255,64));
|
||||
}
|
||||
dd.end();
|
||||
}
|
||||
|
||||
void ConvexVolumeTool::handleRenderOverlay(double* /*proj*/, double* /*model*/, int* view)
|
||||
{
|
||||
// Tool help
|
||||
const int h = view[3];
|
||||
if (!m_npts)
|
||||
{
|
||||
imguiDrawText(280, h-40, IMGUI_ALIGN_LEFT, "LMB: Create new shape. SHIFT+LMB: Delete existing shape (click inside a shape).", imguiRGBA(255,255,255,192));
|
||||
}
|
||||
else
|
||||
{
|
||||
imguiDrawText(280, h-40, IMGUI_ALIGN_LEFT, "Click LMB to add new points. Click on the red point to finish the shape.", imguiRGBA(255,255,255,192));
|
||||
imguiDrawText(280, h-60, IMGUI_ALIGN_LEFT, "The shape will be convex hull of all added points.", imguiRGBA(255,255,255,192));
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#include "Filelist.h"
|
||||
|
||||
#include <algorithm>
|
||||
#ifdef WIN32
|
||||
# include <io.h>
|
||||
#else
|
||||
# include <dirent.h>
|
||||
# include <cstring>
|
||||
#endif
|
||||
|
||||
using std::vector;
|
||||
using std::string;
|
||||
|
||||
void scanDirectoryAppend(const string& path, const string& ext, vector<string>& filelist)
|
||||
{
|
||||
#ifdef WIN32
|
||||
string pathWithExt = path + "/*" + ext;
|
||||
|
||||
_finddata_t dir;
|
||||
intptr_t fh = _findfirst(pathWithExt.c_str(), &dir);
|
||||
if (fh == -1L)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
filelist.push_back(dir.name);
|
||||
}
|
||||
while (_findnext(fh, &dir) == 0);
|
||||
_findclose(fh);
|
||||
#else
|
||||
dirent* current = 0;
|
||||
DIR* dp = opendir(path.c_str());
|
||||
if (!dp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int extLen = strlen(ext.c_str());
|
||||
while ((current = readdir(dp)) != 0)
|
||||
{
|
||||
int len = strlen(current->d_name);
|
||||
if (len > extLen && strncmp(current->d_name + len - extLen, ext.c_str(), extLen) == 0)
|
||||
{
|
||||
filelist.push_back(current->d_name);
|
||||
}
|
||||
}
|
||||
closedir(dp);
|
||||
#endif
|
||||
|
||||
// Sort the list of files alphabetically.
|
||||
std::sort(filelist.begin(), filelist.end());
|
||||
}
|
||||
|
||||
void scanDirectory(const string& path, const string& ext, vector<string>& filelist)
|
||||
{
|
||||
filelist.clear();
|
||||
scanDirectoryAppend(path, ext, filelist);
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
#include "Recast.h"
|
||||
#include "InputGeom.h"
|
||||
#include "ChunkyTriMesh.h"
|
||||
#include "MeshLoaderObj.h"
|
||||
#include "DebugDraw.h"
|
||||
#include "RecastDebugDraw.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include "Sample.h"
|
||||
|
||||
static bool intersectSegmentTriangle(const float* sp, const float* sq,
|
||||
const float* a, const float* b, const float* c,
|
||||
float &t)
|
||||
{
|
||||
float v, w;
|
||||
float ab[3], ac[3], qp[3], ap[3], norm[3], e[3];
|
||||
rcVsub(ab, b, a);
|
||||
rcVsub(ac, c, a);
|
||||
rcVsub(qp, sp, sq);
|
||||
|
||||
// Compute triangle normal. Can be precalculated or cached if
|
||||
// intersecting multiple segments against the same triangle
|
||||
rcVcross(norm, ab, ac);
|
||||
|
||||
// Compute denominator d. If d <= 0, segment is parallel to or points
|
||||
// away from triangle, so exit early
|
||||
float d = rcVdot(qp, norm);
|
||||
if (d <= 0.0f) return false;
|
||||
|
||||
// Compute intersection t value of pq with plane of triangle. A ray
|
||||
// intersects iff 0 <= t. Segment intersects iff 0 <= t <= 1. Delay
|
||||
// dividing by d until intersection has been found to pierce triangle
|
||||
rcVsub(ap, sp, a);
|
||||
t = rcVdot(ap, norm);
|
||||
if (t < 0.0f) return false;
|
||||
if (t > d) return false; // For segment; exclude this code line for a ray test
|
||||
|
||||
// Compute barycentric coordinate components and test if within bounds
|
||||
rcVcross(e, qp, ap);
|
||||
v = rcVdot(ac, e);
|
||||
if (v < 0.0f || v > d) return false;
|
||||
w = -rcVdot(ab, e);
|
||||
if (w < 0.0f || v + w > d) return false;
|
||||
|
||||
// Segment/ray intersects triangle. Perform delayed division
|
||||
t /= d;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static char* parseRow(char* buf, char* bufEnd, char* row, int len)
|
||||
{
|
||||
bool start = true;
|
||||
bool done = false;
|
||||
int n = 0;
|
||||
while (!done && buf < bufEnd)
|
||||
{
|
||||
char c = *buf;
|
||||
buf++;
|
||||
// multirow
|
||||
switch (c)
|
||||
{
|
||||
case '\n':
|
||||
if (start) break;
|
||||
done = true;
|
||||
break;
|
||||
case '\r':
|
||||
break;
|
||||
case '\t':
|
||||
case ' ':
|
||||
if (start) break;
|
||||
// else falls through
|
||||
default:
|
||||
start = false;
|
||||
row[n++] = c;
|
||||
if (n >= len-1)
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
row[n] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
|
||||
InputGeom::InputGeom() :
|
||||
m_chunkyMesh(0),
|
||||
m_mesh(0),
|
||||
m_hasBuildSettings(false),
|
||||
m_offMeshConCount(0),
|
||||
m_volumeCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
InputGeom::~InputGeom()
|
||||
{
|
||||
delete m_chunkyMesh;
|
||||
delete m_mesh;
|
||||
}
|
||||
|
||||
bool InputGeom::loadMesh(rcContext* ctx, const std::string& filepath)
|
||||
{
|
||||
if (m_mesh)
|
||||
{
|
||||
delete m_chunkyMesh;
|
||||
m_chunkyMesh = 0;
|
||||
delete m_mesh;
|
||||
m_mesh = 0;
|
||||
}
|
||||
m_offMeshConCount = 0;
|
||||
m_volumeCount = 0;
|
||||
|
||||
m_mesh = new rcMeshLoaderObj;
|
||||
if (!m_mesh)
|
||||
{
|
||||
ctx->log(RC_LOG_ERROR, "loadMesh: Out of memory 'm_mesh'.");
|
||||
return false;
|
||||
}
|
||||
if (!m_mesh->load(filepath))
|
||||
{
|
||||
ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
rcCalcBounds(m_mesh->getVerts(), m_mesh->getVertCount(), m_meshBMin, m_meshBMax);
|
||||
|
||||
m_chunkyMesh = new rcChunkyTriMesh;
|
||||
if (!m_chunkyMesh)
|
||||
{
|
||||
ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Out of memory 'm_chunkyMesh'.");
|
||||
return false;
|
||||
}
|
||||
if (!rcCreateChunkyTriMesh(m_mesh->getVerts(), m_mesh->getTris(), m_mesh->getTriCount(), 256, m_chunkyMesh))
|
||||
{
|
||||
ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Failed to build chunky mesh.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputGeom::loadGeomSet(rcContext* ctx, const std::string& filepath)
|
||||
{
|
||||
char* buf = 0;
|
||||
FILE* fp = fopen(filepath.c_str(), "rb");
|
||||
if (!fp)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (fseek(fp, 0, SEEK_END) != 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
long bufSize = ftell(fp);
|
||||
if (bufSize < 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
if (fseek(fp, 0, SEEK_SET) != 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
buf = new char[bufSize];
|
||||
if (!buf)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
size_t readLen = fread(buf, bufSize, 1, fp);
|
||||
fclose(fp);
|
||||
if (readLen != 1)
|
||||
{
|
||||
delete[] buf;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_offMeshConCount = 0;
|
||||
m_volumeCount = 0;
|
||||
delete m_mesh;
|
||||
m_mesh = 0;
|
||||
|
||||
char* src = buf;
|
||||
char* srcEnd = buf + bufSize;
|
||||
char row[512];
|
||||
while (src < srcEnd)
|
||||
{
|
||||
// Parse one row
|
||||
row[0] = '\0';
|
||||
src = parseRow(src, srcEnd, row, sizeof(row)/sizeof(char));
|
||||
if (row[0] == 'f')
|
||||
{
|
||||
// File name.
|
||||
const char* name = row+1;
|
||||
// Skip white spaces
|
||||
while (*name && isspace(*name))
|
||||
name++;
|
||||
if (*name)
|
||||
{
|
||||
if (!loadMesh(ctx, name))
|
||||
{
|
||||
delete [] buf;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (row[0] == 'c')
|
||||
{
|
||||
// Off-mesh connection
|
||||
if (m_offMeshConCount < MAX_OFFMESH_CONNECTIONS)
|
||||
{
|
||||
float* v = &m_offMeshConVerts[m_offMeshConCount*3*2];
|
||||
int bidir, area = 0, flags = 0;
|
||||
float rad;
|
||||
sscanf(row+1, "%f %f %f %f %f %f %f %d %d %d",
|
||||
&v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &rad, &bidir, &area, &flags);
|
||||
m_offMeshConRads[m_offMeshConCount] = rad;
|
||||
m_offMeshConDirs[m_offMeshConCount] = (unsigned char)bidir;
|
||||
m_offMeshConAreas[m_offMeshConCount] = (unsigned char)area;
|
||||
m_offMeshConFlags[m_offMeshConCount] = (unsigned short)flags;
|
||||
m_offMeshConCount++;
|
||||
}
|
||||
}
|
||||
else if (row[0] == 'v')
|
||||
{
|
||||
// Convex volumes
|
||||
if (m_volumeCount < MAX_VOLUMES)
|
||||
{
|
||||
ConvexVolume* vol = &m_volumes[m_volumeCount++];
|
||||
sscanf(row+1, "%d %d %f %f", &vol->nverts, &vol->area, &vol->hmin, &vol->hmax);
|
||||
for (int i = 0; i < vol->nverts; ++i)
|
||||
{
|
||||
row[0] = '\0';
|
||||
src = parseRow(src, srcEnd, row, sizeof(row)/sizeof(char));
|
||||
sscanf(row, "%f %f %f", &vol->verts[i*3+0], &vol->verts[i*3+1], &vol->verts[i*3+2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (row[0] == 's')
|
||||
{
|
||||
// Settings
|
||||
m_hasBuildSettings = true;
|
||||
sscanf(row + 1, "%f %f %f %f %f %f %f %f %f %f %f %f %f %d %f %f %f %f %f %f %f",
|
||||
&m_buildSettings.cellSize,
|
||||
&m_buildSettings.cellHeight,
|
||||
&m_buildSettings.agentHeight,
|
||||
&m_buildSettings.agentRadius,
|
||||
&m_buildSettings.agentMaxClimb,
|
||||
&m_buildSettings.agentMaxSlope,
|
||||
&m_buildSettings.regionMinSize,
|
||||
&m_buildSettings.regionMergeSize,
|
||||
&m_buildSettings.edgeMaxLen,
|
||||
&m_buildSettings.edgeMaxError,
|
||||
&m_buildSettings.vertsPerPoly,
|
||||
&m_buildSettings.detailSampleDist,
|
||||
&m_buildSettings.detailSampleMaxError,
|
||||
&m_buildSettings.partitionType,
|
||||
&m_buildSettings.navMeshBMin[0],
|
||||
&m_buildSettings.navMeshBMin[1],
|
||||
&m_buildSettings.navMeshBMin[2],
|
||||
&m_buildSettings.navMeshBMax[0],
|
||||
&m_buildSettings.navMeshBMax[1],
|
||||
&m_buildSettings.navMeshBMax[2],
|
||||
&m_buildSettings.tileSize);
|
||||
}
|
||||
}
|
||||
|
||||
delete [] buf;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputGeom::load(rcContext* ctx, const std::string& filepath)
|
||||
{
|
||||
size_t extensionPos = filepath.find_last_of('.');
|
||||
if (extensionPos == std::string::npos)
|
||||
return false;
|
||||
|
||||
std::string extension = filepath.substr(extensionPos);
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), tolower);
|
||||
|
||||
if (extension == ".gset")
|
||||
return loadGeomSet(ctx, filepath);
|
||||
if (extension == ".obj")
|
||||
return loadMesh(ctx, filepath);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputGeom::saveGeomSet(const BuildSettings* settings)
|
||||
{
|
||||
if (!m_mesh) return false;
|
||||
|
||||
// Change extension
|
||||
std::string filepath = m_mesh->getFileName();
|
||||
size_t extPos = filepath.find_last_of('.');
|
||||
if (extPos != std::string::npos)
|
||||
filepath = filepath.substr(0, extPos);
|
||||
|
||||
filepath += ".gset";
|
||||
|
||||
FILE* fp = fopen(filepath.c_str(), "w");
|
||||
if (!fp) return false;
|
||||
|
||||
// Store mesh filename.
|
||||
fprintf(fp, "f %s\n", m_mesh->getFileName().c_str());
|
||||
|
||||
// Store settings if any
|
||||
if (settings)
|
||||
{
|
||||
fprintf(fp,
|
||||
"s %f %f %f %f %f %f %f %f %f %f %f %f %f %d %f %f %f %f %f %f %f\n",
|
||||
settings->cellSize,
|
||||
settings->cellHeight,
|
||||
settings->agentHeight,
|
||||
settings->agentRadius,
|
||||
settings->agentMaxClimb,
|
||||
settings->agentMaxSlope,
|
||||
settings->regionMinSize,
|
||||
settings->regionMergeSize,
|
||||
settings->edgeMaxLen,
|
||||
settings->edgeMaxError,
|
||||
settings->vertsPerPoly,
|
||||
settings->detailSampleDist,
|
||||
settings->detailSampleMaxError,
|
||||
settings->partitionType,
|
||||
settings->navMeshBMin[0],
|
||||
settings->navMeshBMin[1],
|
||||
settings->navMeshBMin[2],
|
||||
settings->navMeshBMax[0],
|
||||
settings->navMeshBMax[1],
|
||||
settings->navMeshBMax[2],
|
||||
settings->tileSize);
|
||||
}
|
||||
|
||||
// Store off-mesh links.
|
||||
for (int i = 0; i < m_offMeshConCount; ++i)
|
||||
{
|
||||
const float* v = &m_offMeshConVerts[i*3*2];
|
||||
const float rad = m_offMeshConRads[i];
|
||||
const int bidir = m_offMeshConDirs[i];
|
||||
const int area = m_offMeshConAreas[i];
|
||||
const int flags = m_offMeshConFlags[i];
|
||||
fprintf(fp, "c %f %f %f %f %f %f %f %d %d %d\n",
|
||||
v[0], v[1], v[2], v[3], v[4], v[5], rad, bidir, area, flags);
|
||||
}
|
||||
|
||||
// Convex volumes
|
||||
for (int i = 0; i < m_volumeCount; ++i)
|
||||
{
|
||||
ConvexVolume* vol = &m_volumes[i];
|
||||
fprintf(fp, "v %d %d %f %f\n", vol->nverts, vol->area, vol->hmin, vol->hmax);
|
||||
for (int j = 0; j < vol->nverts; ++j)
|
||||
fprintf(fp, "%f %f %f\n", vol->verts[j*3+0], vol->verts[j*3+1], vol->verts[j*3+2]);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isectSegAABB(const float* sp, const float* sq,
|
||||
const float* amin, const float* amax,
|
||||
float& tmin, float& tmax)
|
||||
{
|
||||
static const float EPS = 1e-6f;
|
||||
|
||||
float d[3];
|
||||
d[0] = sq[0] - sp[0];
|
||||
d[1] = sq[1] - sp[1];
|
||||
d[2] = sq[2] - sp[2];
|
||||
tmin = 0.0;
|
||||
tmax = 1.0f;
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (fabsf(d[i]) < EPS)
|
||||
{
|
||||
if (sp[i] < amin[i] || sp[i] > amax[i])
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float ood = 1.0f / d[i];
|
||||
float t1 = (amin[i] - sp[i]) * ood;
|
||||
float t2 = (amax[i] - sp[i]) * ood;
|
||||
if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; }
|
||||
if (t1 > tmin) tmin = t1;
|
||||
if (t2 < tmax) tmax = t2;
|
||||
if (tmin > tmax) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool InputGeom::raycastMesh(float* src, float* dst, float& tmin)
|
||||
{
|
||||
float dir[3];
|
||||
rcVsub(dir, dst, src);
|
||||
|
||||
// Prune hit ray.
|
||||
float btmin, btmax;
|
||||
if (!isectSegAABB(src, dst, m_meshBMin, m_meshBMax, btmin, btmax))
|
||||
return false;
|
||||
float p[2], q[2];
|
||||
p[0] = src[0] + (dst[0]-src[0])*btmin;
|
||||
p[1] = src[2] + (dst[2]-src[2])*btmin;
|
||||
q[0] = src[0] + (dst[0]-src[0])*btmax;
|
||||
q[1] = src[2] + (dst[2]-src[2])*btmax;
|
||||
|
||||
int cid[512];
|
||||
const int ncid = rcGetChunksOverlappingSegment(m_chunkyMesh, p, q, cid, 512);
|
||||
if (!ncid)
|
||||
return false;
|
||||
|
||||
tmin = 1.0f;
|
||||
bool hit = false;
|
||||
const float* verts = m_mesh->getVerts();
|
||||
|
||||
for (int i = 0; i < ncid; ++i)
|
||||
{
|
||||
const rcChunkyTriMeshNode& node = m_chunkyMesh->nodes[cid[i]];
|
||||
const int* tris = &m_chunkyMesh->tris[node.i*3];
|
||||
const int ntris = node.n;
|
||||
|
||||
for (int j = 0; j < ntris*3; j += 3)
|
||||
{
|
||||
float t = 1;
|
||||
if (intersectSegmentTriangle(src, dst,
|
||||
&verts[tris[j]*3],
|
||||
&verts[tris[j+1]*3],
|
||||
&verts[tris[j+2]*3], t))
|
||||
{
|
||||
if (t < tmin)
|
||||
tmin = t;
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
void InputGeom::addOffMeshConnection(const float* spos, const float* epos, const float rad,
|
||||
unsigned char bidir, unsigned char area, unsigned short flags)
|
||||
{
|
||||
if (m_offMeshConCount >= MAX_OFFMESH_CONNECTIONS) return;
|
||||
float* v = &m_offMeshConVerts[m_offMeshConCount*3*2];
|
||||
m_offMeshConRads[m_offMeshConCount] = rad;
|
||||
m_offMeshConDirs[m_offMeshConCount] = bidir;
|
||||
m_offMeshConAreas[m_offMeshConCount] = area;
|
||||
m_offMeshConFlags[m_offMeshConCount] = flags;
|
||||
m_offMeshConId[m_offMeshConCount] = 1000 + m_offMeshConCount;
|
||||
rcVcopy(&v[0], spos);
|
||||
rcVcopy(&v[3], epos);
|
||||
m_offMeshConCount++;
|
||||
}
|
||||
|
||||
void InputGeom::deleteOffMeshConnection(int i)
|
||||
{
|
||||
m_offMeshConCount--;
|
||||
float* src = &m_offMeshConVerts[m_offMeshConCount*3*2];
|
||||
float* dst = &m_offMeshConVerts[i*3*2];
|
||||
rcVcopy(&dst[0], &src[0]);
|
||||
rcVcopy(&dst[3], &src[3]);
|
||||
m_offMeshConRads[i] = m_offMeshConRads[m_offMeshConCount];
|
||||
m_offMeshConDirs[i] = m_offMeshConDirs[m_offMeshConCount];
|
||||
m_offMeshConAreas[i] = m_offMeshConAreas[m_offMeshConCount];
|
||||
m_offMeshConFlags[i] = m_offMeshConFlags[m_offMeshConCount];
|
||||
}
|
||||
|
||||
void InputGeom::drawOffMeshConnections(duDebugDraw* dd, bool hilight)
|
||||
{
|
||||
unsigned int conColor = duRGBA(192,0,128,192);
|
||||
unsigned int baseColor = duRGBA(0,0,0,64);
|
||||
dd->depthMask(false);
|
||||
|
||||
dd->begin(DU_DRAW_LINES, 2.0f);
|
||||
for (int i = 0; i < m_offMeshConCount; ++i)
|
||||
{
|
||||
float* v = &m_offMeshConVerts[i*3*2];
|
||||
|
||||
dd->vertex(v[0],v[1],v[2], baseColor);
|
||||
dd->vertex(v[0],v[1]+0.2f,v[2], baseColor);
|
||||
|
||||
dd->vertex(v[3],v[4],v[5], baseColor);
|
||||
dd->vertex(v[3],v[4]+0.2f,v[5], baseColor);
|
||||
|
||||
duAppendCircle(dd, v[0],v[1]+0.1f,v[2], m_offMeshConRads[i], baseColor);
|
||||
duAppendCircle(dd, v[3],v[4]+0.1f,v[5], m_offMeshConRads[i], baseColor);
|
||||
|
||||
if (hilight)
|
||||
{
|
||||
duAppendArc(dd, v[0],v[1],v[2], v[3],v[4],v[5], 0.25f,
|
||||
(m_offMeshConDirs[i]&1) ? 0.6f : 0.0f, 0.6f, conColor);
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
|
||||
dd->depthMask(true);
|
||||
}
|
||||
|
||||
void InputGeom::addConvexVolume(const float* verts, const int nverts,
|
||||
const float minh, const float maxh, unsigned char area)
|
||||
{
|
||||
if (m_volumeCount >= MAX_VOLUMES) return;
|
||||
ConvexVolume* vol = &m_volumes[m_volumeCount++];
|
||||
memset(vol, 0, sizeof(ConvexVolume));
|
||||
memcpy(vol->verts, verts, sizeof(float)*3*nverts);
|
||||
vol->hmin = minh;
|
||||
vol->hmax = maxh;
|
||||
vol->nverts = nverts;
|
||||
vol->area = area;
|
||||
}
|
||||
|
||||
void InputGeom::deleteConvexVolume(int i)
|
||||
{
|
||||
m_volumeCount--;
|
||||
m_volumes[i] = m_volumes[m_volumeCount];
|
||||
}
|
||||
|
||||
void InputGeom::drawConvexVolumes(struct duDebugDraw* dd, bool /*hilight*/)
|
||||
{
|
||||
dd->depthMask(false);
|
||||
|
||||
dd->begin(DU_DRAW_TRIS);
|
||||
|
||||
for (int i = 0; i < m_volumeCount; ++i)
|
||||
{
|
||||
const ConvexVolume* vol = &m_volumes[i];
|
||||
unsigned int col = duTransCol(dd->areaToCol(vol->area), 32);
|
||||
for (int j = 0, k = vol->nverts-1; j < vol->nverts; k = j++)
|
||||
{
|
||||
const float* va = &vol->verts[k*3];
|
||||
const float* vb = &vol->verts[j*3];
|
||||
|
||||
dd->vertex(vol->verts[0],vol->hmax,vol->verts[2], col);
|
||||
dd->vertex(vb[0],vol->hmax,vb[2], col);
|
||||
dd->vertex(va[0],vol->hmax,va[2], col);
|
||||
|
||||
dd->vertex(va[0],vol->hmin,va[2], duDarkenCol(col));
|
||||
dd->vertex(va[0],vol->hmax,va[2], col);
|
||||
dd->vertex(vb[0],vol->hmax,vb[2], col);
|
||||
|
||||
dd->vertex(va[0],vol->hmin,va[2], duDarkenCol(col));
|
||||
dd->vertex(vb[0],vol->hmax,vb[2], col);
|
||||
dd->vertex(vb[0],vol->hmin,vb[2], duDarkenCol(col));
|
||||
}
|
||||
}
|
||||
|
||||
dd->end();
|
||||
|
||||
dd->begin(DU_DRAW_LINES, 2.0f);
|
||||
for (int i = 0; i < m_volumeCount; ++i)
|
||||
{
|
||||
const ConvexVolume* vol = &m_volumes[i];
|
||||
unsigned int col = duTransCol(dd->areaToCol(vol->area), 220);
|
||||
for (int j = 0, k = vol->nverts-1; j < vol->nverts; k = j++)
|
||||
{
|
||||
const float* va = &vol->verts[k*3];
|
||||
const float* vb = &vol->verts[j*3];
|
||||
dd->vertex(va[0],vol->hmin,va[2], duDarkenCol(col));
|
||||
dd->vertex(vb[0],vol->hmin,vb[2], duDarkenCol(col));
|
||||
dd->vertex(va[0],vol->hmax,va[2], col);
|
||||
dd->vertex(vb[0],vol->hmax,vb[2], col);
|
||||
dd->vertex(va[0],vol->hmin,va[2], duDarkenCol(col));
|
||||
dd->vertex(va[0],vol->hmax,va[2], col);
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
|
||||
dd->begin(DU_DRAW_POINTS, 3.0f);
|
||||
for (int i = 0; i < m_volumeCount; ++i)
|
||||
{
|
||||
const ConvexVolume* vol = &m_volumes[i];
|
||||
unsigned int col = duDarkenCol(duTransCol(dd->areaToCol(vol->area), 220));
|
||||
for (int j = 0; j < vol->nverts; ++j)
|
||||
{
|
||||
dd->vertex(vol->verts[j*3+0],vol->verts[j*3+1]+0.1f,vol->verts[j*3+2], col);
|
||||
dd->vertex(vol->verts[j*3+0],vol->hmin,vol->verts[j*3+2], col);
|
||||
dd->vertex(vol->verts[j*3+0],vol->hmax,vol->verts[j*3+2], col);
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
|
||||
|
||||
dd->depthMask(true);
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#include "MeshLoaderObj.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <cstring>
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
rcMeshLoaderObj::rcMeshLoaderObj() :
|
||||
m_scale(1.0f),
|
||||
m_verts(0),
|
||||
m_tris(0),
|
||||
m_normals(0),
|
||||
m_vertCount(0),
|
||||
m_triCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
rcMeshLoaderObj::~rcMeshLoaderObj()
|
||||
{
|
||||
delete [] m_verts;
|
||||
delete [] m_normals;
|
||||
delete [] m_tris;
|
||||
}
|
||||
|
||||
void rcMeshLoaderObj::addVertex(float x, float y, float z, int& cap)
|
||||
{
|
||||
if (m_vertCount+1 > cap)
|
||||
{
|
||||
cap = !cap ? 8 : cap*2;
|
||||
float* nv = new float[cap*3];
|
||||
if (m_vertCount)
|
||||
memcpy(nv, m_verts, m_vertCount*3*sizeof(float));
|
||||
delete [] m_verts;
|
||||
m_verts = nv;
|
||||
}
|
||||
float* dst = &m_verts[m_vertCount*3];
|
||||
*dst++ = x*m_scale;
|
||||
*dst++ = y*m_scale;
|
||||
*dst++ = z*m_scale;
|
||||
m_vertCount++;
|
||||
}
|
||||
|
||||
void rcMeshLoaderObj::addTriangle(int a, int b, int c, int& cap)
|
||||
{
|
||||
if (m_triCount+1 > cap)
|
||||
{
|
||||
cap = !cap ? 8 : cap*2;
|
||||
int* nv = new int[cap*3];
|
||||
if (m_triCount)
|
||||
memcpy(nv, m_tris, m_triCount*3*sizeof(int));
|
||||
delete [] m_tris;
|
||||
m_tris = nv;
|
||||
}
|
||||
int* dst = &m_tris[m_triCount*3];
|
||||
*dst++ = a;
|
||||
*dst++ = b;
|
||||
*dst++ = c;
|
||||
m_triCount++;
|
||||
}
|
||||
|
||||
static char* parseRow(char* buf, char* bufEnd, char* row, int len)
|
||||
{
|
||||
bool start = true;
|
||||
bool done = false;
|
||||
int n = 0;
|
||||
while (!done && buf < bufEnd)
|
||||
{
|
||||
char c = *buf;
|
||||
buf++;
|
||||
// multirow
|
||||
switch (c)
|
||||
{
|
||||
case '\\':
|
||||
break;
|
||||
case '\n':
|
||||
if (start) break;
|
||||
done = true;
|
||||
break;
|
||||
case '\r':
|
||||
break;
|
||||
case '\t':
|
||||
case ' ':
|
||||
if (start) break;
|
||||
// else falls through
|
||||
default:
|
||||
start = false;
|
||||
row[n++] = c;
|
||||
if (n >= len-1)
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
row[n] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int parseFace(char* row, int* data, int n, int vcnt)
|
||||
{
|
||||
int j = 0;
|
||||
while (*row != '\0')
|
||||
{
|
||||
// Skip initial white space
|
||||
while (*row != '\0' && (*row == ' ' || *row == '\t'))
|
||||
row++;
|
||||
char* s = row;
|
||||
// Find vertex delimiter and terminated the string there for conversion.
|
||||
while (*row != '\0' && *row != ' ' && *row != '\t')
|
||||
{
|
||||
if (*row == '/') *row = '\0';
|
||||
row++;
|
||||
}
|
||||
if (*s == '\0')
|
||||
continue;
|
||||
int vi = atoi(s);
|
||||
data[j++] = vi < 0 ? vi+vcnt : vi-1;
|
||||
if (j >= n) return j;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
bool rcMeshLoaderObj::load(const std::string& filename)
|
||||
{
|
||||
char* buf = 0;
|
||||
FILE* fp = fopen(filename.c_str(), "rb");
|
||||
if (!fp)
|
||||
return false;
|
||||
if (fseek(fp, 0, SEEK_END) != 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
long bufSize = ftell(fp);
|
||||
if (bufSize < 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
if (fseek(fp, 0, SEEK_SET) != 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
buf = new char[bufSize];
|
||||
if (!buf)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
size_t readLen = fread(buf, bufSize, 1, fp);
|
||||
fclose(fp);
|
||||
|
||||
if (readLen != 1)
|
||||
{
|
||||
delete[] buf;
|
||||
return false;
|
||||
}
|
||||
|
||||
char* src = buf;
|
||||
char* srcEnd = buf + bufSize;
|
||||
char row[512];
|
||||
int face[32];
|
||||
float x,y,z;
|
||||
int nv;
|
||||
int vcap = 0;
|
||||
int tcap = 0;
|
||||
|
||||
while (src < srcEnd)
|
||||
{
|
||||
// Parse one row
|
||||
row[0] = '\0';
|
||||
src = parseRow(src, srcEnd, row, sizeof(row)/sizeof(char));
|
||||
// Skip comments
|
||||
if (row[0] == '#') continue;
|
||||
if (row[0] == 'v' && row[1] != 'n' && row[1] != 't')
|
||||
{
|
||||
// Vertex pos
|
||||
sscanf(row+1, "%f %f %f", &x, &y, &z);
|
||||
addVertex(x, y, z, vcap);
|
||||
}
|
||||
if (row[0] == 'f')
|
||||
{
|
||||
// Faces
|
||||
nv = parseFace(row+1, face, 32, m_vertCount);
|
||||
for (int i = 2; i < nv; ++i)
|
||||
{
|
||||
const int a = face[0];
|
||||
const int b = face[i-1];
|
||||
const int c = face[i];
|
||||
if (a < 0 || a >= m_vertCount || b < 0 || b >= m_vertCount || c < 0 || c >= m_vertCount)
|
||||
continue;
|
||||
addTriangle(a, b, c, tcap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete [] buf;
|
||||
|
||||
// Calculate normals.
|
||||
m_normals = new float[m_triCount*3];
|
||||
for (int i = 0; i < m_triCount*3; i += 3)
|
||||
{
|
||||
const float* v0 = &m_verts[m_tris[i]*3];
|
||||
const float* v1 = &m_verts[m_tris[i+1]*3];
|
||||
const float* v2 = &m_verts[m_tris[i+2]*3];
|
||||
float e0[3], e1[3];
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
e0[j] = v1[j] - v0[j];
|
||||
e1[j] = v2[j] - v0[j];
|
||||
}
|
||||
float* n = &m_normals[i];
|
||||
n[0] = e0[1]*e1[2] - e0[2]*e1[1];
|
||||
n[1] = e0[2]*e1[0] - e0[0]*e1[2];
|
||||
n[2] = e0[0]*e1[1] - e0[1]*e1[0];
|
||||
float d = sqrtf(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]);
|
||||
if (d > 0)
|
||||
{
|
||||
d = 1.0f/d;
|
||||
n[0] *= d;
|
||||
n[1] *= d;
|
||||
n[2] *= d;
|
||||
}
|
||||
}
|
||||
|
||||
m_filename = filename;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <float.h>
|
||||
#include <vector>
|
||||
#include "SDL.h"
|
||||
#include "SDL_opengl.h"
|
||||
#include "imgui.h"
|
||||
#include "NavMeshPruneTool.h"
|
||||
#include "InputGeom.h"
|
||||
#include "Sample.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "DetourAssert.h"
|
||||
#include "DetourDebugDraw.h"
|
||||
|
||||
#ifdef WIN32
|
||||
# define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
class NavmeshFlags
|
||||
{
|
||||
struct TileFlags
|
||||
{
|
||||
inline void purge() { dtFree(flags); }
|
||||
unsigned char* flags;
|
||||
int nflags;
|
||||
dtPolyRef base;
|
||||
};
|
||||
|
||||
const dtNavMesh* m_nav;
|
||||
TileFlags* m_tiles;
|
||||
int m_ntiles;
|
||||
|
||||
public:
|
||||
NavmeshFlags() :
|
||||
m_nav(0), m_tiles(0), m_ntiles(0)
|
||||
{
|
||||
}
|
||||
|
||||
~NavmeshFlags()
|
||||
{
|
||||
for (int i = 0; i < m_ntiles; ++i)
|
||||
m_tiles[i].purge();
|
||||
dtFree(m_tiles);
|
||||
}
|
||||
|
||||
bool init(const dtNavMesh* nav)
|
||||
{
|
||||
m_ntiles = nav->getMaxTiles();
|
||||
if (!m_ntiles)
|
||||
return true;
|
||||
m_tiles = (TileFlags*)dtAlloc(sizeof(TileFlags)*m_ntiles, DT_ALLOC_TEMP);
|
||||
if (!m_tiles)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
memset(m_tiles, 0, sizeof(TileFlags)*m_ntiles);
|
||||
|
||||
// Alloc flags for each tile.
|
||||
for (int i = 0; i < nav->getMaxTiles(); ++i)
|
||||
{
|
||||
const dtMeshTile* tile = nav->getTile(i);
|
||||
if (!tile->header) continue;
|
||||
TileFlags* tf = &m_tiles[i];
|
||||
tf->nflags = tile->header->polyCount;
|
||||
tf->base = nav->getPolyRefBase(tile);
|
||||
if (tf->nflags)
|
||||
{
|
||||
tf->flags = (unsigned char*)dtAlloc(tf->nflags, DT_ALLOC_TEMP);
|
||||
if (!tf->flags)
|
||||
return false;
|
||||
memset(tf->flags, 0, tf->nflags);
|
||||
}
|
||||
}
|
||||
|
||||
m_nav = nav;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void clearAllFlags()
|
||||
{
|
||||
for (int i = 0; i < m_ntiles; ++i)
|
||||
{
|
||||
TileFlags* tf = &m_tiles[i];
|
||||
if (tf->nflags)
|
||||
memset(tf->flags, 0, tf->nflags);
|
||||
}
|
||||
}
|
||||
|
||||
inline unsigned char getFlags(dtPolyRef ref)
|
||||
{
|
||||
dtAssert(m_nav);
|
||||
dtAssert(m_ntiles);
|
||||
// Assume the ref is valid, no bounds checks.
|
||||
unsigned int salt, it, ip;
|
||||
m_nav->decodePolyId(ref, salt, it, ip);
|
||||
return m_tiles[it].flags[ip];
|
||||
}
|
||||
|
||||
inline void setFlags(dtPolyRef ref, unsigned char flags)
|
||||
{
|
||||
dtAssert(m_nav);
|
||||
dtAssert(m_ntiles);
|
||||
// Assume the ref is valid, no bounds checks.
|
||||
unsigned int salt, it, ip;
|
||||
m_nav->decodePolyId(ref, salt, it, ip);
|
||||
m_tiles[it].flags[ip] = flags;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
static void floodNavmesh(dtNavMesh* nav, NavmeshFlags* flags, dtPolyRef start, unsigned char flag)
|
||||
{
|
||||
// If already visited, skip.
|
||||
if (flags->getFlags(start))
|
||||
return;
|
||||
|
||||
flags->setFlags(start, flag);
|
||||
|
||||
std::vector<dtPolyRef> openList;
|
||||
openList.push_back(start);
|
||||
|
||||
while (openList.size())
|
||||
{
|
||||
const dtPolyRef ref = openList.back();
|
||||
openList.pop_back();
|
||||
|
||||
// Get current poly and tile.
|
||||
// The API input has been cheked already, skip checking internal data.
|
||||
const dtMeshTile* tile = 0;
|
||||
const dtPoly* poly = 0;
|
||||
nav->getTileAndPolyByRefUnsafe(ref, &tile, &poly);
|
||||
|
||||
// Visit linked polygons.
|
||||
for (unsigned int i = poly->firstLink; i != DT_NULL_LINK; i = tile->links[i].next)
|
||||
{
|
||||
const dtPolyRef neiRef = tile->links[i].ref;
|
||||
// Skip invalid and already visited.
|
||||
if (!neiRef || flags->getFlags(neiRef))
|
||||
continue;
|
||||
// Mark as visited
|
||||
flags->setFlags(neiRef, flag);
|
||||
// Visit neighbours
|
||||
openList.push_back(neiRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void disableUnvisitedPolys(dtNavMesh* nav, NavmeshFlags* flags)
|
||||
{
|
||||
for (int i = 0; i < nav->getMaxTiles(); ++i)
|
||||
{
|
||||
const dtMeshTile* tile = ((const dtNavMesh*)nav)->getTile(i);
|
||||
if (!tile->header) continue;
|
||||
const dtPolyRef base = nav->getPolyRefBase(tile);
|
||||
for (int j = 0; j < tile->header->polyCount; ++j)
|
||||
{
|
||||
const dtPolyRef ref = base | (unsigned int)j;
|
||||
if (!flags->getFlags(ref))
|
||||
{
|
||||
unsigned short f = 0;
|
||||
nav->getPolyFlags(ref, &f);
|
||||
nav->setPolyFlags(ref, f | SAMPLE_POLYFLAGS_DISABLED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavMeshPruneTool::NavMeshPruneTool() :
|
||||
m_sample(0),
|
||||
m_flags(0),
|
||||
m_hitPosSet(false)
|
||||
{
|
||||
}
|
||||
|
||||
NavMeshPruneTool::~NavMeshPruneTool()
|
||||
{
|
||||
delete m_flags;
|
||||
}
|
||||
|
||||
void NavMeshPruneTool::init(Sample* sample)
|
||||
{
|
||||
m_sample = sample;
|
||||
}
|
||||
|
||||
void NavMeshPruneTool::reset()
|
||||
{
|
||||
m_hitPosSet = false;
|
||||
delete m_flags;
|
||||
m_flags = 0;
|
||||
}
|
||||
|
||||
void NavMeshPruneTool::handleMenu()
|
||||
{
|
||||
dtNavMesh* nav = m_sample->getNavMesh();
|
||||
if (!nav) return;
|
||||
if (!m_flags) return;
|
||||
|
||||
if (imguiButton("Clear Selection"))
|
||||
{
|
||||
m_flags->clearAllFlags();
|
||||
}
|
||||
|
||||
if (imguiButton("Prune Unselected"))
|
||||
{
|
||||
disableUnvisitedPolys(nav, m_flags);
|
||||
delete m_flags;
|
||||
m_flags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void NavMeshPruneTool::handleClick(const float* s, const float* p, bool shift)
|
||||
{
|
||||
rcIgnoreUnused(s);
|
||||
rcIgnoreUnused(shift);
|
||||
|
||||
if (!m_sample) return;
|
||||
InputGeom* geom = m_sample->getInputGeom();
|
||||
if (!geom) return;
|
||||
dtNavMesh* nav = m_sample->getNavMesh();
|
||||
if (!nav) return;
|
||||
dtNavMeshQuery* query = m_sample->getNavMeshQuery();
|
||||
if (!query) return;
|
||||
|
||||
dtVcopy(m_hitPos, p);
|
||||
m_hitPosSet = true;
|
||||
|
||||
if (!m_flags)
|
||||
{
|
||||
m_flags = new NavmeshFlags;
|
||||
m_flags->init(nav);
|
||||
}
|
||||
|
||||
const float halfExtents[3] = { 2, 4, 2 };
|
||||
dtQueryFilter filter;
|
||||
dtPolyRef ref = 0;
|
||||
query->findNearestPoly(p, halfExtents, &filter, &ref, 0);
|
||||
|
||||
floodNavmesh(nav, m_flags, ref, 1);
|
||||
}
|
||||
|
||||
void NavMeshPruneTool::handleToggle()
|
||||
{
|
||||
}
|
||||
|
||||
void NavMeshPruneTool::handleStep()
|
||||
{
|
||||
}
|
||||
|
||||
void NavMeshPruneTool::handleUpdate(const float /*dt*/)
|
||||
{
|
||||
}
|
||||
|
||||
void NavMeshPruneTool::handleRender()
|
||||
{
|
||||
duDebugDraw& dd = m_sample->getDebugDraw();
|
||||
|
||||
if (m_hitPosSet)
|
||||
{
|
||||
const float s = m_sample->getAgentRadius();
|
||||
const unsigned int col = duRGBA(255,255,255,255);
|
||||
dd.begin(DU_DRAW_LINES);
|
||||
dd.vertex(m_hitPos[0]-s,m_hitPos[1],m_hitPos[2], col);
|
||||
dd.vertex(m_hitPos[0]+s,m_hitPos[1],m_hitPos[2], col);
|
||||
dd.vertex(m_hitPos[0],m_hitPos[1]-s,m_hitPos[2], col);
|
||||
dd.vertex(m_hitPos[0],m_hitPos[1]+s,m_hitPos[2], col);
|
||||
dd.vertex(m_hitPos[0],m_hitPos[1],m_hitPos[2]-s, col);
|
||||
dd.vertex(m_hitPos[0],m_hitPos[1],m_hitPos[2]+s, col);
|
||||
dd.end();
|
||||
}
|
||||
|
||||
const dtNavMesh* nav = m_sample->getNavMesh();
|
||||
if (m_flags && nav)
|
||||
{
|
||||
for (int i = 0; i < nav->getMaxTiles(); ++i)
|
||||
{
|
||||
const dtMeshTile* tile = nav->getTile(i);
|
||||
if (!tile->header) continue;
|
||||
const dtPolyRef base = nav->getPolyRefBase(tile);
|
||||
for (int j = 0; j < tile->header->polyCount; ++j)
|
||||
{
|
||||
const dtPolyRef ref = base | (unsigned int)j;
|
||||
if (m_flags->getFlags(ref))
|
||||
{
|
||||
duDebugDrawNavMeshPoly(&dd, *nav, ref, duRGBA(255,255,255,128));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void NavMeshPruneTool::handleRenderOverlay(double* proj, double* model, int* view)
|
||||
{
|
||||
rcIgnoreUnused(model);
|
||||
rcIgnoreUnused(proj);
|
||||
|
||||
// Tool help
|
||||
const int h = view[3];
|
||||
|
||||
imguiDrawText(280, h-40, IMGUI_ALIGN_LEFT, "LMB: Click fill area.", imguiRGBA(255,255,255,192));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <float.h>
|
||||
#include "SDL.h"
|
||||
#include "SDL_opengl.h"
|
||||
#ifdef __APPLE__
|
||||
# include <OpenGL/glu.h>
|
||||
#else
|
||||
# include <GL/glu.h>
|
||||
#endif
|
||||
#include "imgui.h"
|
||||
#include "OffMeshConnectionTool.h"
|
||||
#include "InputGeom.h"
|
||||
#include "Sample.h"
|
||||
#include "Recast.h"
|
||||
#include "RecastDebugDraw.h"
|
||||
#include "DetourDebugDraw.h"
|
||||
|
||||
#ifdef WIN32
|
||||
# define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
OffMeshConnectionTool::OffMeshConnectionTool() :
|
||||
m_sample(0),
|
||||
m_hitPosSet(0),
|
||||
m_bidir(true),
|
||||
m_oldFlags(0)
|
||||
{
|
||||
}
|
||||
|
||||
OffMeshConnectionTool::~OffMeshConnectionTool()
|
||||
{
|
||||
if (m_sample)
|
||||
{
|
||||
m_sample->setNavMeshDrawFlags(m_oldFlags);
|
||||
}
|
||||
}
|
||||
|
||||
void OffMeshConnectionTool::init(Sample* sample)
|
||||
{
|
||||
if (m_sample != sample)
|
||||
{
|
||||
m_sample = sample;
|
||||
m_oldFlags = m_sample->getNavMeshDrawFlags();
|
||||
m_sample->setNavMeshDrawFlags(m_oldFlags & ~DU_DRAWNAVMESH_OFFMESHCONS);
|
||||
}
|
||||
}
|
||||
|
||||
void OffMeshConnectionTool::reset()
|
||||
{
|
||||
m_hitPosSet = false;
|
||||
}
|
||||
|
||||
void OffMeshConnectionTool::handleMenu()
|
||||
{
|
||||
if (imguiCheck("One Way", !m_bidir))
|
||||
m_bidir = false;
|
||||
if (imguiCheck("Bidirectional", m_bidir))
|
||||
m_bidir = true;
|
||||
}
|
||||
|
||||
void OffMeshConnectionTool::handleClick(const float* /*s*/, const float* p, bool shift)
|
||||
{
|
||||
if (!m_sample) return;
|
||||
InputGeom* geom = m_sample->getInputGeom();
|
||||
if (!geom) return;
|
||||
|
||||
if (shift)
|
||||
{
|
||||
// Delete
|
||||
// Find nearest link end-point
|
||||
float nearestDist = FLT_MAX;
|
||||
int nearestIndex = -1;
|
||||
const float* verts = geom->getOffMeshConnectionVerts();
|
||||
for (int i = 0; i < geom->getOffMeshConnectionCount()*2; ++i)
|
||||
{
|
||||
const float* v = &verts[i*3];
|
||||
float d = rcVdistSqr(p, v);
|
||||
if (d < nearestDist)
|
||||
{
|
||||
nearestDist = d;
|
||||
nearestIndex = i/2; // Each link has two vertices.
|
||||
}
|
||||
}
|
||||
// If end point close enough, delete it.
|
||||
if (nearestIndex != -1 &&
|
||||
sqrtf(nearestDist) < m_sample->getAgentRadius())
|
||||
{
|
||||
geom->deleteOffMeshConnection(nearestIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create
|
||||
if (!m_hitPosSet)
|
||||
{
|
||||
rcVcopy(m_hitPos, p);
|
||||
m_hitPosSet = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const unsigned char area = SAMPLE_POLYAREA_JUMP;
|
||||
const unsigned short flags = SAMPLE_POLYFLAGS_JUMP;
|
||||
geom->addOffMeshConnection(m_hitPos, p, m_sample->getAgentRadius(), m_bidir ? 1 : 0, area, flags);
|
||||
m_hitPosSet = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void OffMeshConnectionTool::handleToggle()
|
||||
{
|
||||
}
|
||||
|
||||
void OffMeshConnectionTool::handleStep()
|
||||
{
|
||||
}
|
||||
|
||||
void OffMeshConnectionTool::handleUpdate(const float /*dt*/)
|
||||
{
|
||||
}
|
||||
|
||||
void OffMeshConnectionTool::handleRender()
|
||||
{
|
||||
duDebugDraw& dd = m_sample->getDebugDraw();
|
||||
const float s = m_sample->getAgentRadius();
|
||||
|
||||
if (m_hitPosSet)
|
||||
duDebugDrawCross(&dd, m_hitPos[0],m_hitPos[1]+0.1f,m_hitPos[2], s, duRGBA(0,0,0,128), 2.0f);
|
||||
|
||||
InputGeom* geom = m_sample->getInputGeom();
|
||||
if (geom)
|
||||
geom->drawOffMeshConnections(&dd, true);
|
||||
}
|
||||
|
||||
void OffMeshConnectionTool::handleRenderOverlay(double* proj, double* model, int* view)
|
||||
{
|
||||
GLdouble x, y, z;
|
||||
|
||||
// Draw start and end point labels
|
||||
if (m_hitPosSet && gluProject((GLdouble)m_hitPos[0], (GLdouble)m_hitPos[1], (GLdouble)m_hitPos[2],
|
||||
model, proj, view, &x, &y, &z))
|
||||
{
|
||||
imguiDrawText((int)x, (int)(y-25), IMGUI_ALIGN_CENTER, "Start", imguiRGBA(0,0,0,220));
|
||||
}
|
||||
|
||||
// Tool help
|
||||
const int h = view[3];
|
||||
if (!m_hitPosSet)
|
||||
{
|
||||
imguiDrawText(280, h-40, IMGUI_ALIGN_LEFT, "LMB: Create new connection. SHIFT+LMB: Delete existing connection, click close to start or end point.", imguiRGBA(255,255,255,192));
|
||||
}
|
||||
else
|
||||
{
|
||||
imguiDrawText(280, h-40, IMGUI_ALIGN_LEFT, "LMB: Set connection end point and finish.", imguiRGBA(255,255,255,192));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#include "PerfTimer.h"
|
||||
|
||||
#if defined(WIN32)
|
||||
|
||||
// Win32
|
||||
#include <windows.h>
|
||||
|
||||
TimeVal getPerfTime()
|
||||
{
|
||||
__int64 count;
|
||||
QueryPerformanceCounter((LARGE_INTEGER*)&count);
|
||||
return count;
|
||||
}
|
||||
|
||||
int getPerfTimeUsec(const TimeVal duration)
|
||||
{
|
||||
static __int64 freq = 0;
|
||||
if (freq == 0)
|
||||
QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
|
||||
return (int)(duration*1000000 / freq);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// Linux, BSD, OSX
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
TimeVal getPerfTime()
|
||||
{
|
||||
timeval now;
|
||||
gettimeofday(&now, 0);
|
||||
return (TimeVal)now.tv_sec*1000000L + (TimeVal)now.tv_usec;
|
||||
}
|
||||
|
||||
int getPerfTimeUsec(const TimeVal duration)
|
||||
{
|
||||
return (int)duration;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,449 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include "Sample.h"
|
||||
#include "InputGeom.h"
|
||||
#include "Recast.h"
|
||||
#include "RecastDebugDraw.h"
|
||||
#include "DetourDebugDraw.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "DetourCrowd.h"
|
||||
#include "imgui.h"
|
||||
#include "SDL.h"
|
||||
#include "SDL_opengl.h"
|
||||
|
||||
#ifdef WIN32
|
||||
# define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
unsigned int SampleDebugDraw::areaToCol(unsigned int area)
|
||||
{
|
||||
switch(area)
|
||||
{
|
||||
// Ground (0) : light blue
|
||||
case SAMPLE_POLYAREA_GROUND: return duRGBA(0, 192, 255, 255);
|
||||
// Water : blue
|
||||
case SAMPLE_POLYAREA_WATER: return duRGBA(0, 0, 255, 255);
|
||||
// Road : brown
|
||||
case SAMPLE_POLYAREA_ROAD: return duRGBA(50, 20, 12, 255);
|
||||
// Door : cyan
|
||||
case SAMPLE_POLYAREA_DOOR: return duRGBA(0, 255, 255, 255);
|
||||
// Grass : green
|
||||
case SAMPLE_POLYAREA_GRASS: return duRGBA(0, 255, 0, 255);
|
||||
// Jump : yellow
|
||||
case SAMPLE_POLYAREA_JUMP: return duRGBA(255, 255, 0, 255);
|
||||
// Unexpected : red
|
||||
default: return duRGBA(255, 0, 0, 255);
|
||||
}
|
||||
}
|
||||
|
||||
Sample::Sample() :
|
||||
m_geom(0),
|
||||
m_navMesh(0),
|
||||
m_navQuery(0),
|
||||
m_crowd(0),
|
||||
m_navMeshDrawFlags(DU_DRAWNAVMESH_OFFMESHCONS|DU_DRAWNAVMESH_CLOSEDLIST),
|
||||
m_filterLowHangingObstacles(true),
|
||||
m_filterLedgeSpans(true),
|
||||
m_filterWalkableLowHeightSpans(true),
|
||||
m_tool(0),
|
||||
m_ctx(0)
|
||||
{
|
||||
resetCommonSettings();
|
||||
m_navQuery = dtAllocNavMeshQuery();
|
||||
m_crowd = dtAllocCrowd();
|
||||
|
||||
for (int i = 0; i < MAX_TOOLS; i++)
|
||||
m_toolStates[i] = 0;
|
||||
}
|
||||
|
||||
Sample::~Sample()
|
||||
{
|
||||
dtFreeNavMeshQuery(m_navQuery);
|
||||
dtFreeNavMesh(m_navMesh);
|
||||
dtFreeCrowd(m_crowd);
|
||||
delete m_tool;
|
||||
for (int i = 0; i < MAX_TOOLS; i++)
|
||||
delete m_toolStates[i];
|
||||
}
|
||||
|
||||
void Sample::setTool(SampleTool* tool)
|
||||
{
|
||||
delete m_tool;
|
||||
m_tool = tool;
|
||||
if (tool)
|
||||
m_tool->init(this);
|
||||
}
|
||||
|
||||
void Sample::handleSettings()
|
||||
{
|
||||
}
|
||||
|
||||
void Sample::handleTools()
|
||||
{
|
||||
}
|
||||
|
||||
void Sample::handleDebugMode()
|
||||
{
|
||||
}
|
||||
|
||||
void Sample::handleRender()
|
||||
{
|
||||
if (!m_geom)
|
||||
return;
|
||||
|
||||
// Draw mesh
|
||||
duDebugDrawTriMesh(&m_dd, m_geom->getMesh()->getVerts(), m_geom->getMesh()->getVertCount(),
|
||||
m_geom->getMesh()->getTris(), m_geom->getMesh()->getNormals(), m_geom->getMesh()->getTriCount(), 0, 1.0f);
|
||||
// Draw bounds
|
||||
const float* bmin = m_geom->getMeshBoundsMin();
|
||||
const float* bmax = m_geom->getMeshBoundsMax();
|
||||
duDebugDrawBoxWire(&m_dd, bmin[0],bmin[1],bmin[2], bmax[0],bmax[1],bmax[2], duRGBA(255,255,255,128), 1.0f);
|
||||
}
|
||||
|
||||
void Sample::handleRenderOverlay(double* /*proj*/, double* /*model*/, int* /*view*/)
|
||||
{
|
||||
}
|
||||
|
||||
void Sample::handleMeshChanged(InputGeom* geom)
|
||||
{
|
||||
m_geom = geom;
|
||||
|
||||
const BuildSettings* buildSettings = geom->getBuildSettings();
|
||||
if (buildSettings)
|
||||
{
|
||||
m_cellSize = buildSettings->cellSize;
|
||||
m_cellHeight = buildSettings->cellHeight;
|
||||
m_agentHeight = buildSettings->agentHeight;
|
||||
m_agentRadius = buildSettings->agentRadius;
|
||||
m_agentMaxClimb = buildSettings->agentMaxClimb;
|
||||
m_agentMaxSlope = buildSettings->agentMaxSlope;
|
||||
m_regionMinSize = buildSettings->regionMinSize;
|
||||
m_regionMergeSize = buildSettings->regionMergeSize;
|
||||
m_edgeMaxLen = buildSettings->edgeMaxLen;
|
||||
m_edgeMaxError = buildSettings->edgeMaxError;
|
||||
m_vertsPerPoly = buildSettings->vertsPerPoly;
|
||||
m_detailSampleDist = buildSettings->detailSampleDist;
|
||||
m_detailSampleMaxError = buildSettings->detailSampleMaxError;
|
||||
m_partitionType = buildSettings->partitionType;
|
||||
}
|
||||
}
|
||||
|
||||
void Sample::collectSettings(BuildSettings& settings)
|
||||
{
|
||||
settings.cellSize = m_cellSize;
|
||||
settings.cellHeight = m_cellHeight;
|
||||
settings.agentHeight = m_agentHeight;
|
||||
settings.agentRadius = m_agentRadius;
|
||||
settings.agentMaxClimb = m_agentMaxClimb;
|
||||
settings.agentMaxSlope = m_agentMaxSlope;
|
||||
settings.regionMinSize = m_regionMinSize;
|
||||
settings.regionMergeSize = m_regionMergeSize;
|
||||
settings.edgeMaxLen = m_edgeMaxLen;
|
||||
settings.edgeMaxError = m_edgeMaxError;
|
||||
settings.vertsPerPoly = m_vertsPerPoly;
|
||||
settings.detailSampleDist = m_detailSampleDist;
|
||||
settings.detailSampleMaxError = m_detailSampleMaxError;
|
||||
settings.partitionType = m_partitionType;
|
||||
}
|
||||
|
||||
|
||||
void Sample::resetCommonSettings()
|
||||
{
|
||||
m_cellSize = 0.3f;
|
||||
m_cellHeight = 0.2f;
|
||||
m_agentHeight = 2.0f;
|
||||
m_agentRadius = 0.6f;
|
||||
m_agentMaxClimb = 0.9f;
|
||||
m_agentMaxSlope = 45.0f;
|
||||
m_regionMinSize = 8;
|
||||
m_regionMergeSize = 20;
|
||||
m_edgeMaxLen = 12.0f;
|
||||
m_edgeMaxError = 1.3f;
|
||||
m_vertsPerPoly = 6.0f;
|
||||
m_detailSampleDist = 6.0f;
|
||||
m_detailSampleMaxError = 1.0f;
|
||||
m_partitionType = SAMPLE_PARTITION_WATERSHED;
|
||||
}
|
||||
|
||||
void Sample::handleCommonSettings()
|
||||
{
|
||||
imguiLabel("Rasterization");
|
||||
imguiSlider("Cell Size", &m_cellSize, 0.1f, 1.0f, 0.01f);
|
||||
imguiSlider("Cell Height", &m_cellHeight, 0.1f, 1.0f, 0.01f);
|
||||
|
||||
if (m_geom)
|
||||
{
|
||||
const float* bmin = m_geom->getNavMeshBoundsMin();
|
||||
const float* bmax = m_geom->getNavMeshBoundsMax();
|
||||
int gw = 0, gh = 0;
|
||||
rcCalcGridSize(bmin, bmax, m_cellSize, &gw, &gh);
|
||||
char text[64];
|
||||
snprintf(text, 64, "Voxels %d x %d", gw, gh);
|
||||
imguiValue(text);
|
||||
}
|
||||
|
||||
imguiSeparator();
|
||||
imguiLabel("Agent");
|
||||
imguiSlider("Height", &m_agentHeight, 0.1f, 5.0f, 0.1f);
|
||||
imguiSlider("Radius", &m_agentRadius, 0.0f, 5.0f, 0.1f);
|
||||
imguiSlider("Max Climb", &m_agentMaxClimb, 0.1f, 5.0f, 0.1f);
|
||||
imguiSlider("Max Slope", &m_agentMaxSlope, 0.0f, 90.0f, 1.0f);
|
||||
|
||||
imguiSeparator();
|
||||
imguiLabel("Region");
|
||||
imguiSlider("Min Region Size", &m_regionMinSize, 0.0f, 150.0f, 1.0f);
|
||||
imguiSlider("Merged Region Size", &m_regionMergeSize, 0.0f, 150.0f, 1.0f);
|
||||
|
||||
imguiSeparator();
|
||||
imguiLabel("Partitioning");
|
||||
if (imguiCheck("Watershed", m_partitionType == SAMPLE_PARTITION_WATERSHED))
|
||||
m_partitionType = SAMPLE_PARTITION_WATERSHED;
|
||||
if (imguiCheck("Monotone", m_partitionType == SAMPLE_PARTITION_MONOTONE))
|
||||
m_partitionType = SAMPLE_PARTITION_MONOTONE;
|
||||
if (imguiCheck("Layers", m_partitionType == SAMPLE_PARTITION_LAYERS))
|
||||
m_partitionType = SAMPLE_PARTITION_LAYERS;
|
||||
|
||||
imguiSeparator();
|
||||
imguiLabel("Filtering");
|
||||
if (imguiCheck("Low Hanging Obstacles", m_filterLowHangingObstacles))
|
||||
m_filterLowHangingObstacles = !m_filterLowHangingObstacles;
|
||||
if (imguiCheck("Ledge Spans", m_filterLedgeSpans))
|
||||
m_filterLedgeSpans= !m_filterLedgeSpans;
|
||||
if (imguiCheck("Walkable Low Height Spans", m_filterWalkableLowHeightSpans))
|
||||
m_filterWalkableLowHeightSpans = !m_filterWalkableLowHeightSpans;
|
||||
|
||||
imguiSeparator();
|
||||
imguiLabel("Polygonization");
|
||||
imguiSlider("Max Edge Length", &m_edgeMaxLen, 0.0f, 50.0f, 1.0f);
|
||||
imguiSlider("Max Edge Error", &m_edgeMaxError, 0.1f, 3.0f, 0.1f);
|
||||
imguiSlider("Verts Per Poly", &m_vertsPerPoly, 3.0f, 12.0f, 1.0f);
|
||||
|
||||
imguiSeparator();
|
||||
imguiLabel("Detail Mesh");
|
||||
imguiSlider("Sample Distance", &m_detailSampleDist, 0.0f, 16.0f, 1.0f);
|
||||
imguiSlider("Max Sample Error", &m_detailSampleMaxError, 0.0f, 16.0f, 1.0f);
|
||||
|
||||
imguiSeparator();
|
||||
}
|
||||
|
||||
void Sample::handleClick(const float* s, const float* p, bool shift)
|
||||
{
|
||||
if (m_tool)
|
||||
m_tool->handleClick(s, p, shift);
|
||||
}
|
||||
|
||||
void Sample::handleToggle()
|
||||
{
|
||||
if (m_tool)
|
||||
m_tool->handleToggle();
|
||||
}
|
||||
|
||||
void Sample::handleStep()
|
||||
{
|
||||
if (m_tool)
|
||||
m_tool->handleStep();
|
||||
}
|
||||
|
||||
bool Sample::handleBuild()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sample::handleUpdate(const float dt)
|
||||
{
|
||||
if (m_tool)
|
||||
m_tool->handleUpdate(dt);
|
||||
updateToolStates(dt);
|
||||
}
|
||||
|
||||
|
||||
void Sample::updateToolStates(const float dt)
|
||||
{
|
||||
for (int i = 0; i < MAX_TOOLS; i++)
|
||||
{
|
||||
if (m_toolStates[i])
|
||||
m_toolStates[i]->handleUpdate(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void Sample::initToolStates(Sample* sample)
|
||||
{
|
||||
for (int i = 0; i < MAX_TOOLS; i++)
|
||||
{
|
||||
if (m_toolStates[i])
|
||||
m_toolStates[i]->init(sample);
|
||||
}
|
||||
}
|
||||
|
||||
void Sample::resetToolStates()
|
||||
{
|
||||
for (int i = 0; i < MAX_TOOLS; i++)
|
||||
{
|
||||
if (m_toolStates[i])
|
||||
m_toolStates[i]->reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Sample::renderToolStates()
|
||||
{
|
||||
for (int i = 0; i < MAX_TOOLS; i++)
|
||||
{
|
||||
if (m_toolStates[i])
|
||||
m_toolStates[i]->handleRender();
|
||||
}
|
||||
}
|
||||
|
||||
void Sample::renderOverlayToolStates(double* proj, double* model, int* view)
|
||||
{
|
||||
for (int i = 0; i < MAX_TOOLS; i++)
|
||||
{
|
||||
if (m_toolStates[i])
|
||||
m_toolStates[i]->handleRenderOverlay(proj, model, view);
|
||||
}
|
||||
}
|
||||
|
||||
static const int NAVMESHSET_MAGIC = 'M'<<24 | 'S'<<16 | 'E'<<8 | 'T'; //'MSET';
|
||||
static const int NAVMESHSET_VERSION = 1;
|
||||
|
||||
struct NavMeshSetHeader
|
||||
{
|
||||
int magic;
|
||||
int version;
|
||||
int numTiles;
|
||||
dtNavMeshParams params;
|
||||
};
|
||||
|
||||
struct NavMeshTileHeader
|
||||
{
|
||||
dtTileRef tileRef;
|
||||
int dataSize;
|
||||
};
|
||||
|
||||
dtNavMesh* Sample::loadAll(const char* path)
|
||||
{
|
||||
FILE* fp = fopen(path, "rb");
|
||||
if (!fp) return 0;
|
||||
|
||||
// Read header.
|
||||
NavMeshSetHeader header;
|
||||
size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, fp);
|
||||
if (readLen != 1)
|
||||
{
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
if (header.magic != NAVMESHSET_MAGIC)
|
||||
{
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
if (header.version != NAVMESHSET_VERSION)
|
||||
{
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dtNavMesh* mesh = dtAllocNavMesh();
|
||||
if (!mesh)
|
||||
{
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
dtStatus status = mesh->init(&header.params);
|
||||
if (dtStatusFailed(status))
|
||||
{
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read tiles.
|
||||
for (int i = 0; i < header.numTiles; ++i)
|
||||
{
|
||||
NavMeshTileHeader tileHeader;
|
||||
readLen = fread(&tileHeader, sizeof(tileHeader), 1, fp);
|
||||
if (readLen != 1)
|
||||
{
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!tileHeader.tileRef || !tileHeader.dataSize)
|
||||
break;
|
||||
|
||||
unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
|
||||
if (!data) break;
|
||||
memset(data, 0, tileHeader.dataSize);
|
||||
readLen = fread(data, tileHeader.dataSize, 1, fp);
|
||||
if (readLen != 1)
|
||||
{
|
||||
dtFree(data);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void Sample::saveAll(const char* path, const dtNavMesh* mesh)
|
||||
{
|
||||
if (!mesh) return;
|
||||
|
||||
FILE* fp = fopen(path, "wb");
|
||||
if (!fp)
|
||||
return;
|
||||
|
||||
// Store header.
|
||||
NavMeshSetHeader header;
|
||||
header.magic = NAVMESHSET_MAGIC;
|
||||
header.version = NAVMESHSET_VERSION;
|
||||
header.numTiles = 0;
|
||||
for (int i = 0; i < mesh->getMaxTiles(); ++i)
|
||||
{
|
||||
const dtMeshTile* tile = mesh->getTile(i);
|
||||
if (!tile || !tile->header || !tile->dataSize) continue;
|
||||
header.numTiles++;
|
||||
}
|
||||
memcpy(&header.params, mesh->getParams(), sizeof(dtNavMeshParams));
|
||||
fwrite(&header, sizeof(NavMeshSetHeader), 1, fp);
|
||||
|
||||
// Store tiles.
|
||||
for (int i = 0; i < mesh->getMaxTiles(); ++i)
|
||||
{
|
||||
const dtMeshTile* tile = mesh->getTile(i);
|
||||
if (!tile || !tile->header || !tile->dataSize) continue;
|
||||
|
||||
NavMeshTileHeader tileHeader;
|
||||
tileHeader.tileRef = mesh->getTileRef(tile);
|
||||
tileHeader.dataSize = tile->dataSize;
|
||||
fwrite(&tileHeader, sizeof(tileHeader), 1, fp);
|
||||
|
||||
fwrite(tile->data, tile->dataSize, 1, fp);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include "SampleInterfaces.h"
|
||||
#include "Recast.h"
|
||||
#include "RecastDebugDraw.h"
|
||||
#include "DetourDebugDraw.h"
|
||||
#include "PerfTimer.h"
|
||||
#include "SDL.h"
|
||||
#include "SDL_opengl.h"
|
||||
|
||||
#ifdef WIN32
|
||||
# define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
BuildContext::BuildContext() :
|
||||
m_messageCount(0),
|
||||
m_textPoolSize(0)
|
||||
{
|
||||
memset(m_messages, 0, sizeof(char*) * MAX_MESSAGES);
|
||||
|
||||
resetTimers();
|
||||
}
|
||||
|
||||
// Virtual functions for custom implementations.
|
||||
void BuildContext::doResetLog()
|
||||
{
|
||||
m_messageCount = 0;
|
||||
m_textPoolSize = 0;
|
||||
}
|
||||
|
||||
void BuildContext::doLog(const rcLogCategory category, const char* msg, const int len)
|
||||
{
|
||||
if (!len) return;
|
||||
if (m_messageCount >= MAX_MESSAGES)
|
||||
return;
|
||||
char* dst = &m_textPool[m_textPoolSize];
|
||||
int n = TEXT_POOL_SIZE - m_textPoolSize;
|
||||
if (n < 2)
|
||||
return;
|
||||
char* cat = dst;
|
||||
char* text = dst+1;
|
||||
const int maxtext = n-1;
|
||||
// Store category
|
||||
*cat = (char)category;
|
||||
// Store message
|
||||
const int count = rcMin(len+1, maxtext);
|
||||
memcpy(text, msg, count);
|
||||
text[count-1] = '\0';
|
||||
m_textPoolSize += 1 + count;
|
||||
m_messages[m_messageCount++] = dst;
|
||||
}
|
||||
|
||||
void BuildContext::doResetTimers()
|
||||
{
|
||||
for (int i = 0; i < RC_MAX_TIMERS; ++i)
|
||||
m_accTime[i] = -1;
|
||||
}
|
||||
|
||||
void BuildContext::doStartTimer(const rcTimerLabel label)
|
||||
{
|
||||
m_startTime[label] = getPerfTime();
|
||||
}
|
||||
|
||||
void BuildContext::doStopTimer(const rcTimerLabel label)
|
||||
{
|
||||
const TimeVal endTime = getPerfTime();
|
||||
const TimeVal deltaTime = endTime - m_startTime[label];
|
||||
if (m_accTime[label] == -1)
|
||||
m_accTime[label] = deltaTime;
|
||||
else
|
||||
m_accTime[label] += deltaTime;
|
||||
}
|
||||
|
||||
int BuildContext::doGetAccumulatedTime(const rcTimerLabel label) const
|
||||
{
|
||||
return getPerfTimeUsec(m_accTime[label]);
|
||||
}
|
||||
|
||||
void BuildContext::dumpLog(const char* format, ...)
|
||||
{
|
||||
// Print header.
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
vprintf(format, ap);
|
||||
va_end(ap);
|
||||
printf("\n");
|
||||
|
||||
// Print messages
|
||||
const int TAB_STOPS[4] = { 28, 36, 44, 52 };
|
||||
for (int i = 0; i < m_messageCount; ++i)
|
||||
{
|
||||
const char* msg = m_messages[i]+1;
|
||||
int n = 0;
|
||||
while (*msg)
|
||||
{
|
||||
if (*msg == '\t')
|
||||
{
|
||||
int count = 1;
|
||||
for (int j = 0; j < 4; ++j)
|
||||
{
|
||||
if (n < TAB_STOPS[j])
|
||||
{
|
||||
count = TAB_STOPS[j] - n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (--count)
|
||||
{
|
||||
putchar(' ');
|
||||
n++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
putchar(*msg);
|
||||
n++;
|
||||
}
|
||||
msg++;
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
int BuildContext::getLogCount() const
|
||||
{
|
||||
return m_messageCount;
|
||||
}
|
||||
|
||||
const char* BuildContext::getLogText(const int i) const
|
||||
{
|
||||
return m_messages[i]+1;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class GLCheckerTexture
|
||||
{
|
||||
unsigned int m_texId;
|
||||
public:
|
||||
GLCheckerTexture() : m_texId(0)
|
||||
{
|
||||
}
|
||||
|
||||
~GLCheckerTexture()
|
||||
{
|
||||
if (m_texId != 0)
|
||||
glDeleteTextures(1, &m_texId);
|
||||
}
|
||||
void bind()
|
||||
{
|
||||
if (m_texId == 0)
|
||||
{
|
||||
// Create checker pattern.
|
||||
const unsigned int col0 = duRGBA(215,215,215,255);
|
||||
const unsigned int col1 = duRGBA(255,255,255,255);
|
||||
static const int TSIZE = 64;
|
||||
unsigned int data[TSIZE*TSIZE];
|
||||
|
||||
glGenTextures(1, &m_texId);
|
||||
glBindTexture(GL_TEXTURE_2D, m_texId);
|
||||
|
||||
int level = 0;
|
||||
int size = TSIZE;
|
||||
while (size > 0)
|
||||
{
|
||||
for (int y = 0; y < size; ++y)
|
||||
for (int x = 0; x < size; ++x)
|
||||
data[x+y*size] = (x==0 || y==0) ? col0 : col1;
|
||||
glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, size,size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||
size /= 2;
|
||||
level++;
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
}
|
||||
else
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, m_texId);
|
||||
}
|
||||
}
|
||||
};
|
||||
GLCheckerTexture g_tex;
|
||||
|
||||
|
||||
void DebugDrawGL::depthMask(bool state)
|
||||
{
|
||||
glDepthMask(state ? GL_TRUE : GL_FALSE);
|
||||
}
|
||||
|
||||
void DebugDrawGL::texture(bool state)
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
g_tex.bind();
|
||||
}
|
||||
else
|
||||
{
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugDrawGL::begin(duDebugDrawPrimitives prim, float size)
|
||||
{
|
||||
switch (prim)
|
||||
{
|
||||
case DU_DRAW_POINTS:
|
||||
glPointSize(size);
|
||||
glBegin(GL_POINTS);
|
||||
break;
|
||||
case DU_DRAW_LINES:
|
||||
glLineWidth(size);
|
||||
glBegin(GL_LINES);
|
||||
break;
|
||||
case DU_DRAW_TRIS:
|
||||
glBegin(GL_TRIANGLES);
|
||||
break;
|
||||
case DU_DRAW_QUADS:
|
||||
glBegin(GL_QUADS);
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void DebugDrawGL::vertex(const float* pos, unsigned int color)
|
||||
{
|
||||
glColor4ubv((GLubyte*)&color);
|
||||
glVertex3fv(pos);
|
||||
}
|
||||
|
||||
void DebugDrawGL::vertex(const float x, const float y, const float z, unsigned int color)
|
||||
{
|
||||
glColor4ubv((GLubyte*)&color);
|
||||
glVertex3f(x,y,z);
|
||||
}
|
||||
|
||||
void DebugDrawGL::vertex(const float* pos, unsigned int color, const float* uv)
|
||||
{
|
||||
glColor4ubv((GLubyte*)&color);
|
||||
glTexCoord2fv(uv);
|
||||
glVertex3fv(pos);
|
||||
}
|
||||
|
||||
void DebugDrawGL::vertex(const float x, const float y, const float z, unsigned int color, const float u, const float v)
|
||||
{
|
||||
glColor4ubv((GLubyte*)&color);
|
||||
glTexCoord2f(u,v);
|
||||
glVertex3f(x,y,z);
|
||||
}
|
||||
|
||||
void DebugDrawGL::end()
|
||||
{
|
||||
glEnd();
|
||||
glLineWidth(1.0f);
|
||||
glPointSize(1.0f);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FileIO::FileIO() :
|
||||
m_fp(0),
|
||||
m_mode(-1)
|
||||
{
|
||||
}
|
||||
|
||||
FileIO::~FileIO()
|
||||
{
|
||||
if (m_fp) fclose(m_fp);
|
||||
}
|
||||
|
||||
bool FileIO::openForWrite(const char* path)
|
||||
{
|
||||
if (m_fp) return false;
|
||||
m_fp = fopen(path, "wb");
|
||||
if (!m_fp) return false;
|
||||
m_mode = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileIO::openForRead(const char* path)
|
||||
{
|
||||
if (m_fp) return false;
|
||||
m_fp = fopen(path, "rb");
|
||||
if (!m_fp) return false;
|
||||
m_mode = 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileIO::isWriting() const
|
||||
{
|
||||
return m_mode == 1;
|
||||
}
|
||||
|
||||
bool FileIO::isReading() const
|
||||
{
|
||||
return m_mode == 2;
|
||||
}
|
||||
|
||||
bool FileIO::write(const void* ptr, const size_t size)
|
||||
{
|
||||
if (!m_fp || m_mode != 1) return false;
|
||||
fwrite(ptr, size, 1, m_fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileIO::read(void* ptr, const size_t size)
|
||||
{
|
||||
if (!m_fp || m_mode != 2) return false;
|
||||
size_t readLen = fread(ptr, size, 1, m_fp);
|
||||
return readLen == 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,387 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include "Sample_Debug.h"
|
||||
#include "InputGeom.h"
|
||||
#include "Recast.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include "RecastDebugDraw.h"
|
||||
#include "DetourDebugDraw.h"
|
||||
#include "RecastDump.h"
|
||||
#include "imgui.h"
|
||||
#include "SDL.h"
|
||||
#include "SDL_opengl.h"
|
||||
|
||||
#ifdef WIN32
|
||||
# define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
/*
|
||||
static int loadBin(const char* path, unsigned char** data)
|
||||
{
|
||||
FILE* fp = fopen(path, "rb");
|
||||
if (!fp) return 0;
|
||||
fseek(fp, 0, SEEK_END);
|
||||
int size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
*data = new unsigned char[size];
|
||||
fread(*data, size, 1, fp);
|
||||
fclose(fp);
|
||||
return size;
|
||||
}
|
||||
*/
|
||||
|
||||
Sample_Debug::Sample_Debug() :
|
||||
m_chf(0),
|
||||
m_cset(0),
|
||||
m_pmesh(0)
|
||||
{
|
||||
resetCommonSettings();
|
||||
|
||||
// Test
|
||||
/* m_chf = rcAllocCompactHeightfield();
|
||||
FileIO io;
|
||||
if (!io.openForRead("test.chf"))
|
||||
{
|
||||
delete m_chf;
|
||||
m_chf = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!duReadCompactHeightfield(*m_chf, &io))
|
||||
{
|
||||
delete m_chf;
|
||||
m_chf = 0;
|
||||
}
|
||||
}*/
|
||||
|
||||
/* if (m_chf)
|
||||
{
|
||||
unsigned short ymin = 0xffff;
|
||||
unsigned short ymax = 0;
|
||||
for (int i = 0; i < m_chf->spanCount; ++i)
|
||||
{
|
||||
const rcCompactSpan& s = m_chf->spans[i];
|
||||
if (s.y < ymin) ymin = s.y;
|
||||
if (s.y > ymax) ymax = s.y;
|
||||
}
|
||||
printf("ymin=%d ymax=%d\n", (int)ymin, (int)ymax);
|
||||
|
||||
int maxSpans = 0;
|
||||
for (int i = 0; i < m_chf->width*m_chf->height; ++i)
|
||||
{
|
||||
maxSpans = rcMax(maxSpans, (int)m_chf->cells[i].count);
|
||||
}
|
||||
printf("maxSpans = %d\n", maxSpans);
|
||||
}*/
|
||||
|
||||
|
||||
/* const float orig[3] = {0,0,0};
|
||||
m_navMesh = new dtNavMesh;
|
||||
m_navMesh->init(orig, 133.333f,133.333f, 2048, 4096, 4096);
|
||||
|
||||
unsigned char* data = 0;
|
||||
int dataSize = 0;
|
||||
|
||||
// Tile_-13_-14.bin is basically just the bytes that was output by Detour. It should be loaded at X: -13 and Y: -14.
|
||||
|
||||
dataSize = loadBin("Tile_-13_-13.bin", &data);
|
||||
if (dataSize > 0)
|
||||
{
|
||||
m_navMesh->addTileAt(-13,-13, data, dataSize, true);
|
||||
dtMeshHeader* header = (dtMeshHeader*)data;
|
||||
vcopy(m_bmin, header->bmin);
|
||||
vcopy(m_bmax, header->bmax);
|
||||
}
|
||||
|
||||
dataSize = loadBin("Tile_-13_-14.bin", &data);
|
||||
if (dataSize > 0)
|
||||
{
|
||||
m_navMesh->addTileAt(-13,-14, data, dataSize, true);
|
||||
}
|
||||
|
||||
dataSize = loadBin("Tile_-14_-14.bin", &data);
|
||||
if (dataSize > 0)
|
||||
{
|
||||
m_navMesh->addTileAt(-14,-14, data, dataSize, true);
|
||||
}
|
||||
|
||||
const float halfExtents[3] = {40,100,40};
|
||||
const float center[3] = { -1667.9491f, 135.52649f, -1680.6149f };
|
||||
dtQueryFilter filter;
|
||||
m_ref = m_navMesh->findNearestPoly(center, halfExtents, &filter, 0);
|
||||
|
||||
vcopy(m_halfExtents, halfExtents);
|
||||
vcopy(m_center, center);*/
|
||||
|
||||
|
||||
{
|
||||
m_cset = rcAllocContourSet();
|
||||
if (m_cset)
|
||||
{
|
||||
FileIO io;
|
||||
if (io.openForRead("PathSet_TMP_NA_PathingTestAReg1_1_2_CS.rc"))
|
||||
{
|
||||
duReadContourSet(*m_cset, &io);
|
||||
|
||||
printf("bmin=(%f,%f,%f) bmax=(%f,%f,%f)\n",
|
||||
m_cset->bmin[0], m_cset->bmin[1], m_cset->bmin[2],
|
||||
m_cset->bmax[0], m_cset->bmax[1], m_cset->bmax[2]);
|
||||
printf("cs=%f ch=%f\n", m_cset->cs, m_cset->ch);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("could not open test.cset\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Could not alloc cset\n");
|
||||
}
|
||||
|
||||
|
||||
/* if (m_cset)
|
||||
{
|
||||
m_pmesh = rcAllocPolyMesh();
|
||||
if (m_pmesh)
|
||||
{
|
||||
rcBuildPolyMesh(m_ctx, *m_cset, 6, *m_pmesh);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Sample_Debug::~Sample_Debug()
|
||||
{
|
||||
rcFreeCompactHeightfield(m_chf);
|
||||
rcFreeContourSet(m_cset);
|
||||
rcFreePolyMesh(m_pmesh);
|
||||
}
|
||||
|
||||
void Sample_Debug::handleSettings()
|
||||
{
|
||||
}
|
||||
|
||||
void Sample_Debug::handleTools()
|
||||
{
|
||||
}
|
||||
|
||||
void Sample_Debug::handleDebugMode()
|
||||
{
|
||||
}
|
||||
|
||||
void Sample_Debug::handleRender()
|
||||
{
|
||||
if (m_chf)
|
||||
{
|
||||
duDebugDrawCompactHeightfieldRegions(&m_dd, *m_chf);
|
||||
// duDebugDrawCompactHeightfieldSolid(&dd, *m_chf);
|
||||
}
|
||||
|
||||
if (m_navMesh)
|
||||
duDebugDrawNavMesh(&m_dd, *m_navMesh, DU_DRAWNAVMESH_OFFMESHCONS);
|
||||
|
||||
if (m_ref && m_navMesh)
|
||||
duDebugDrawNavMeshPoly(&m_dd, *m_navMesh, m_ref, duRGBA(255,0,0,128));
|
||||
|
||||
/* float bmin[3], bmax[3];
|
||||
rcVsub(bmin, m_center, m_halfExtents);
|
||||
rcVadd(bmax, m_center, m_halfExtents);
|
||||
duDebugDrawBoxWire(&dd, bmin[0],bmin[1],bmin[2], bmax[0],bmax[1],bmax[2], duRGBA(255,255,255,128), 1.0f);
|
||||
duDebugDrawCross(&dd, m_center[0], m_center[1], m_center[2], 1.0f, duRGBA(255,255,255,128), 2.0f);*/
|
||||
|
||||
if (m_cset)
|
||||
{
|
||||
duDebugDrawRawContours(&m_dd, *m_cset, 0.25f);
|
||||
duDebugDrawContours(&m_dd, *m_cset);
|
||||
}
|
||||
|
||||
if (m_pmesh)
|
||||
{
|
||||
duDebugDrawPolyMesh(&m_dd, *m_pmesh);
|
||||
}
|
||||
|
||||
/*
|
||||
dd.depthMask(false);
|
||||
{
|
||||
const float bmin[3] = {-32.000004f,-11.488281f,-115.343544f};
|
||||
const float cs = 0.300000f;
|
||||
const float ch = 0.200000f;
|
||||
const int verts[] = {
|
||||
158,46,336,0,
|
||||
157,47,331,0,
|
||||
161,53,330,0,
|
||||
162,52,335,0,
|
||||
158,46,336,0,
|
||||
154,46,339,5,
|
||||
161,46,365,5,
|
||||
171,46,385,5,
|
||||
174,46,400,5,
|
||||
177,46,404,5,
|
||||
177,46,410,5,
|
||||
183,46,416,5,
|
||||
188,49,416,5,
|
||||
193,52,411,6,
|
||||
194,53,382,6,
|
||||
188,52,376,6,
|
||||
188,57,363,6,
|
||||
174,57,349,6,
|
||||
174,60,342,6,
|
||||
168,58,336,6,
|
||||
167,59,328,6,
|
||||
162,55,324,6,
|
||||
159,53,324,5,
|
||||
152,46,328,5,
|
||||
151,46,336,5,
|
||||
154,46,339,5,
|
||||
158,46,336,0,
|
||||
160,46,340,0,
|
||||
164,52,339,0,
|
||||
168,55,343,0,
|
||||
168,50,351,0,
|
||||
182,54,364,0,
|
||||
182,47,378,0,
|
||||
188,50,383,0,
|
||||
188,49,409,0,
|
||||
183,46,409,0,
|
||||
183,46,403,0,
|
||||
180,46,399,0,
|
||||
177,46,384,0,
|
||||
165,46,359,0,
|
||||
160,46,340,0,
|
||||
};
|
||||
const int nverts = sizeof(verts)/(sizeof(int)*4);
|
||||
|
||||
const unsigned int colln = duRGBA(255,255,255,128);
|
||||
dd.begin(DU_DRAW_LINES, 1.0f);
|
||||
for (int i = 0, j = nverts-1; i < nverts; j=i++)
|
||||
{
|
||||
const int* va = &verts[j*4];
|
||||
const int* vb = &verts[i*4];
|
||||
dd.vertex(bmin[0]+va[0]*cs, bmin[1]+va[1]*ch+j*0.01f, bmin[2]+va[2]*cs, colln);
|
||||
dd.vertex(bmin[0]+vb[0]*cs, bmin[1]+vb[1]*ch+i*0.01f, bmin[2]+vb[2]*cs, colln);
|
||||
}
|
||||
dd.end();
|
||||
|
||||
const unsigned int colpt = duRGBA(255,255,255,255);
|
||||
dd.begin(DU_DRAW_POINTS, 3.0f);
|
||||
for (int i = 0, j = nverts-1; i < nverts; j=i++)
|
||||
{
|
||||
const int* va = &verts[j*4];
|
||||
dd.vertex(bmin[0]+va[0]*cs, bmin[1]+va[1]*ch+j*0.01f, bmin[2]+va[2]*cs, colpt);
|
||||
}
|
||||
dd.end();
|
||||
|
||||
extern int triangulate(int n, const int* verts, int* indices, int* tris);
|
||||
|
||||
static int indices[nverts];
|
||||
static int tris[nverts*3];
|
||||
for (int j = 0; j < nverts; ++j)
|
||||
indices[j] = j;
|
||||
|
||||
static int ntris = 0;
|
||||
if (!ntris)
|
||||
{
|
||||
ntris = triangulate(nverts, verts, &indices[0], &tris[0]);
|
||||
if (ntris < 0) ntris = -ntris;
|
||||
}
|
||||
|
||||
const unsigned int coltri = duRGBA(255,255,255,64);
|
||||
dd.begin(DU_DRAW_TRIS);
|
||||
for (int i = 0; i < ntris*3; ++i)
|
||||
{
|
||||
const int* va = &verts[indices[tris[i]]*4];
|
||||
dd.vertex(bmin[0]+va[0]*cs, bmin[1]+va[1]*ch, bmin[2]+va[2]*cs, coltri);
|
||||
}
|
||||
dd.end();
|
||||
|
||||
}
|
||||
dd.depthMask(true);*/
|
||||
}
|
||||
|
||||
void Sample_Debug::handleRenderOverlay(double* /*proj*/, double* /*model*/, int* /*view*/)
|
||||
{
|
||||
}
|
||||
|
||||
void Sample_Debug::handleMeshChanged(InputGeom* geom)
|
||||
{
|
||||
m_geom = geom;
|
||||
}
|
||||
|
||||
const float* Sample_Debug::getBoundsMin()
|
||||
{
|
||||
if (m_cset)
|
||||
return m_cset->bmin;
|
||||
if (m_chf)
|
||||
return m_chf->bmin;
|
||||
if (m_navMesh)
|
||||
return m_bmin;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const float* Sample_Debug::getBoundsMax()
|
||||
{
|
||||
if (m_cset)
|
||||
return m_cset->bmax;
|
||||
if (m_chf)
|
||||
return m_chf->bmax;
|
||||
if (m_navMesh)
|
||||
return m_bmax;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Sample_Debug::handleClick(const float* s, const float* p, bool shift)
|
||||
{
|
||||
if (m_tool)
|
||||
m_tool->handleClick(s, p, shift);
|
||||
}
|
||||
|
||||
void Sample_Debug::handleToggle()
|
||||
{
|
||||
if (m_tool)
|
||||
m_tool->handleToggle();
|
||||
}
|
||||
|
||||
bool Sample_Debug::handleBuild()
|
||||
{
|
||||
|
||||
if (m_chf)
|
||||
{
|
||||
rcFreeContourSet(m_cset);
|
||||
m_cset = 0;
|
||||
|
||||
// Create contours.
|
||||
m_cset = rcAllocContourSet();
|
||||
if (!m_cset)
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'.");
|
||||
return false;
|
||||
}
|
||||
if (!rcBuildContours(m_ctx, *m_chf, /*m_cfg.maxSimplificationError*/1.3f, /*m_cfg.maxEdgeLen*/12, *m_cset))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create contours.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,755 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "SDL.h"
|
||||
#include "SDL_opengl.h"
|
||||
#include "imgui.h"
|
||||
#include "InputGeom.h"
|
||||
#include "Sample.h"
|
||||
#include "Sample_SoloMesh.h"
|
||||
#include "Recast.h"
|
||||
#include "RecastDebugDraw.h"
|
||||
#include "RecastDump.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourNavMeshBuilder.h"
|
||||
#include "DetourDebugDraw.h"
|
||||
#include "NavMeshTesterTool.h"
|
||||
#include "NavMeshPruneTool.h"
|
||||
#include "OffMeshConnectionTool.h"
|
||||
#include "ConvexVolumeTool.h"
|
||||
#include "CrowdTool.h"
|
||||
|
||||
#ifdef WIN32
|
||||
# define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
|
||||
Sample_SoloMesh::Sample_SoloMesh() :
|
||||
m_keepInterResults(true),
|
||||
m_totalBuildTimeMs(0),
|
||||
m_triareas(0),
|
||||
m_solid(0),
|
||||
m_chf(0),
|
||||
m_cset(0),
|
||||
m_pmesh(0),
|
||||
m_dmesh(0),
|
||||
m_drawMode(DRAWMODE_NAVMESH)
|
||||
{
|
||||
setTool(new NavMeshTesterTool);
|
||||
}
|
||||
|
||||
Sample_SoloMesh::~Sample_SoloMesh()
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void Sample_SoloMesh::cleanup()
|
||||
{
|
||||
delete [] m_triareas;
|
||||
m_triareas = 0;
|
||||
rcFreeHeightField(m_solid);
|
||||
m_solid = 0;
|
||||
rcFreeCompactHeightfield(m_chf);
|
||||
m_chf = 0;
|
||||
rcFreeContourSet(m_cset);
|
||||
m_cset = 0;
|
||||
rcFreePolyMesh(m_pmesh);
|
||||
m_pmesh = 0;
|
||||
rcFreePolyMeshDetail(m_dmesh);
|
||||
m_dmesh = 0;
|
||||
dtFreeNavMesh(m_navMesh);
|
||||
m_navMesh = 0;
|
||||
}
|
||||
|
||||
void Sample_SoloMesh::handleSettings()
|
||||
{
|
||||
Sample::handleCommonSettings();
|
||||
|
||||
if (imguiCheck("Keep Itermediate Results", m_keepInterResults))
|
||||
m_keepInterResults = !m_keepInterResults;
|
||||
|
||||
imguiSeparator();
|
||||
|
||||
imguiIndent();
|
||||
imguiIndent();
|
||||
|
||||
if (imguiButton("Save"))
|
||||
{
|
||||
Sample::saveAll("solo_navmesh.bin", m_navMesh);
|
||||
}
|
||||
|
||||
if (imguiButton("Load"))
|
||||
{
|
||||
dtFreeNavMesh(m_navMesh);
|
||||
m_navMesh = Sample::loadAll("solo_navmesh.bin");
|
||||
m_navQuery->init(m_navMesh, 2048);
|
||||
}
|
||||
|
||||
imguiUnindent();
|
||||
imguiUnindent();
|
||||
|
||||
char msg[64];
|
||||
snprintf(msg, 64, "Build Time: %.1fms", m_totalBuildTimeMs);
|
||||
imguiLabel(msg);
|
||||
|
||||
imguiSeparator();
|
||||
}
|
||||
|
||||
void Sample_SoloMesh::handleTools()
|
||||
{
|
||||
int type = !m_tool ? TOOL_NONE : m_tool->type();
|
||||
|
||||
if (imguiCheck("Test Navmesh", type == TOOL_NAVMESH_TESTER))
|
||||
{
|
||||
setTool(new NavMeshTesterTool);
|
||||
}
|
||||
if (imguiCheck("Prune Navmesh", type == TOOL_NAVMESH_PRUNE))
|
||||
{
|
||||
setTool(new NavMeshPruneTool);
|
||||
}
|
||||
if (imguiCheck("Create Off-Mesh Connections", type == TOOL_OFFMESH_CONNECTION))
|
||||
{
|
||||
setTool(new OffMeshConnectionTool);
|
||||
}
|
||||
if (imguiCheck("Create Convex Volumes", type == TOOL_CONVEX_VOLUME))
|
||||
{
|
||||
setTool(new ConvexVolumeTool);
|
||||
}
|
||||
if (imguiCheck("Create Crowds", type == TOOL_CROWD))
|
||||
{
|
||||
setTool(new CrowdTool);
|
||||
}
|
||||
|
||||
imguiSeparatorLine();
|
||||
|
||||
imguiIndent();
|
||||
|
||||
if (m_tool)
|
||||
m_tool->handleMenu();
|
||||
|
||||
imguiUnindent();
|
||||
|
||||
}
|
||||
|
||||
void Sample_SoloMesh::handleDebugMode()
|
||||
{
|
||||
// Check which modes are valid.
|
||||
bool valid[MAX_DRAWMODE];
|
||||
for (int i = 0; i < MAX_DRAWMODE; ++i)
|
||||
valid[i] = false;
|
||||
|
||||
if (m_geom)
|
||||
{
|
||||
valid[DRAWMODE_NAVMESH] = m_navMesh != 0;
|
||||
valid[DRAWMODE_NAVMESH_TRANS] = m_navMesh != 0;
|
||||
valid[DRAWMODE_NAVMESH_BVTREE] = m_navMesh != 0;
|
||||
valid[DRAWMODE_NAVMESH_NODES] = m_navQuery != 0;
|
||||
valid[DRAWMODE_NAVMESH_INVIS] = m_navMesh != 0;
|
||||
valid[DRAWMODE_MESH] = true;
|
||||
valid[DRAWMODE_VOXELS] = m_solid != 0;
|
||||
valid[DRAWMODE_VOXELS_WALKABLE] = m_solid != 0;
|
||||
valid[DRAWMODE_COMPACT] = m_chf != 0;
|
||||
valid[DRAWMODE_COMPACT_DISTANCE] = m_chf != 0;
|
||||
valid[DRAWMODE_COMPACT_REGIONS] = m_chf != 0;
|
||||
valid[DRAWMODE_REGION_CONNECTIONS] = m_cset != 0;
|
||||
valid[DRAWMODE_RAW_CONTOURS] = m_cset != 0;
|
||||
valid[DRAWMODE_BOTH_CONTOURS] = m_cset != 0;
|
||||
valid[DRAWMODE_CONTOURS] = m_cset != 0;
|
||||
valid[DRAWMODE_POLYMESH] = m_pmesh != 0;
|
||||
valid[DRAWMODE_POLYMESH_DETAIL] = m_dmesh != 0;
|
||||
}
|
||||
|
||||
int unavail = 0;
|
||||
for (int i = 0; i < MAX_DRAWMODE; ++i)
|
||||
if (!valid[i]) unavail++;
|
||||
|
||||
if (unavail == MAX_DRAWMODE)
|
||||
return;
|
||||
|
||||
imguiLabel("Draw");
|
||||
if (imguiCheck("Input Mesh", m_drawMode == DRAWMODE_MESH, valid[DRAWMODE_MESH]))
|
||||
m_drawMode = DRAWMODE_MESH;
|
||||
if (imguiCheck("Navmesh", m_drawMode == DRAWMODE_NAVMESH, valid[DRAWMODE_NAVMESH]))
|
||||
m_drawMode = DRAWMODE_NAVMESH;
|
||||
if (imguiCheck("Navmesh Invis", m_drawMode == DRAWMODE_NAVMESH_INVIS, valid[DRAWMODE_NAVMESH_INVIS]))
|
||||
m_drawMode = DRAWMODE_NAVMESH_INVIS;
|
||||
if (imguiCheck("Navmesh Trans", m_drawMode == DRAWMODE_NAVMESH_TRANS, valid[DRAWMODE_NAVMESH_TRANS]))
|
||||
m_drawMode = DRAWMODE_NAVMESH_TRANS;
|
||||
if (imguiCheck("Navmesh BVTree", m_drawMode == DRAWMODE_NAVMESH_BVTREE, valid[DRAWMODE_NAVMESH_BVTREE]))
|
||||
m_drawMode = DRAWMODE_NAVMESH_BVTREE;
|
||||
if (imguiCheck("Navmesh Nodes", m_drawMode == DRAWMODE_NAVMESH_NODES, valid[DRAWMODE_NAVMESH_NODES]))
|
||||
m_drawMode = DRAWMODE_NAVMESH_NODES;
|
||||
if (imguiCheck("Voxels", m_drawMode == DRAWMODE_VOXELS, valid[DRAWMODE_VOXELS]))
|
||||
m_drawMode = DRAWMODE_VOXELS;
|
||||
if (imguiCheck("Walkable Voxels", m_drawMode == DRAWMODE_VOXELS_WALKABLE, valid[DRAWMODE_VOXELS_WALKABLE]))
|
||||
m_drawMode = DRAWMODE_VOXELS_WALKABLE;
|
||||
if (imguiCheck("Compact", m_drawMode == DRAWMODE_COMPACT, valid[DRAWMODE_COMPACT]))
|
||||
m_drawMode = DRAWMODE_COMPACT;
|
||||
if (imguiCheck("Compact Distance", m_drawMode == DRAWMODE_COMPACT_DISTANCE, valid[DRAWMODE_COMPACT_DISTANCE]))
|
||||
m_drawMode = DRAWMODE_COMPACT_DISTANCE;
|
||||
if (imguiCheck("Compact Regions", m_drawMode == DRAWMODE_COMPACT_REGIONS, valid[DRAWMODE_COMPACT_REGIONS]))
|
||||
m_drawMode = DRAWMODE_COMPACT_REGIONS;
|
||||
if (imguiCheck("Region Connections", m_drawMode == DRAWMODE_REGION_CONNECTIONS, valid[DRAWMODE_REGION_CONNECTIONS]))
|
||||
m_drawMode = DRAWMODE_REGION_CONNECTIONS;
|
||||
if (imguiCheck("Raw Contours", m_drawMode == DRAWMODE_RAW_CONTOURS, valid[DRAWMODE_RAW_CONTOURS]))
|
||||
m_drawMode = DRAWMODE_RAW_CONTOURS;
|
||||
if (imguiCheck("Both Contours", m_drawMode == DRAWMODE_BOTH_CONTOURS, valid[DRAWMODE_BOTH_CONTOURS]))
|
||||
m_drawMode = DRAWMODE_BOTH_CONTOURS;
|
||||
if (imguiCheck("Contours", m_drawMode == DRAWMODE_CONTOURS, valid[DRAWMODE_CONTOURS]))
|
||||
m_drawMode = DRAWMODE_CONTOURS;
|
||||
if (imguiCheck("Poly Mesh", m_drawMode == DRAWMODE_POLYMESH, valid[DRAWMODE_POLYMESH]))
|
||||
m_drawMode = DRAWMODE_POLYMESH;
|
||||
if (imguiCheck("Poly Mesh Detail", m_drawMode == DRAWMODE_POLYMESH_DETAIL, valid[DRAWMODE_POLYMESH_DETAIL]))
|
||||
m_drawMode = DRAWMODE_POLYMESH_DETAIL;
|
||||
|
||||
if (unavail)
|
||||
{
|
||||
imguiValue("Tick 'Keep Itermediate Results'");
|
||||
imguiValue("to see more debug mode options.");
|
||||
}
|
||||
}
|
||||
|
||||
void Sample_SoloMesh::handleRender()
|
||||
{
|
||||
if (!m_geom || !m_geom->getMesh())
|
||||
return;
|
||||
|
||||
glEnable(GL_FOG);
|
||||
glDepthMask(GL_TRUE);
|
||||
|
||||
const float texScale = 1.0f / (m_cellSize * 10.0f);
|
||||
|
||||
if (m_drawMode != DRAWMODE_NAVMESH_TRANS)
|
||||
{
|
||||
// Draw mesh
|
||||
duDebugDrawTriMeshSlope(&m_dd, m_geom->getMesh()->getVerts(), m_geom->getMesh()->getVertCount(),
|
||||
m_geom->getMesh()->getTris(), m_geom->getMesh()->getNormals(), m_geom->getMesh()->getTriCount(),
|
||||
m_agentMaxSlope, texScale);
|
||||
m_geom->drawOffMeshConnections(&m_dd);
|
||||
}
|
||||
|
||||
glDisable(GL_FOG);
|
||||
glDepthMask(GL_FALSE);
|
||||
|
||||
// Draw bounds
|
||||
const float* bmin = m_geom->getNavMeshBoundsMin();
|
||||
const float* bmax = m_geom->getNavMeshBoundsMax();
|
||||
duDebugDrawBoxWire(&m_dd, bmin[0],bmin[1],bmin[2], bmax[0],bmax[1],bmax[2], duRGBA(255,255,255,128), 1.0f);
|
||||
m_dd.begin(DU_DRAW_POINTS, 5.0f);
|
||||
m_dd.vertex(bmin[0],bmin[1],bmin[2],duRGBA(255,255,255,128));
|
||||
m_dd.end();
|
||||
|
||||
if (m_navMesh && m_navQuery &&
|
||||
(m_drawMode == DRAWMODE_NAVMESH ||
|
||||
m_drawMode == DRAWMODE_NAVMESH_TRANS ||
|
||||
m_drawMode == DRAWMODE_NAVMESH_BVTREE ||
|
||||
m_drawMode == DRAWMODE_NAVMESH_NODES ||
|
||||
m_drawMode == DRAWMODE_NAVMESH_INVIS))
|
||||
{
|
||||
if (m_drawMode != DRAWMODE_NAVMESH_INVIS)
|
||||
duDebugDrawNavMeshWithClosedList(&m_dd, *m_navMesh, *m_navQuery, m_navMeshDrawFlags);
|
||||
if (m_drawMode == DRAWMODE_NAVMESH_BVTREE)
|
||||
duDebugDrawNavMeshBVTree(&m_dd, *m_navMesh);
|
||||
if (m_drawMode == DRAWMODE_NAVMESH_NODES)
|
||||
duDebugDrawNavMeshNodes(&m_dd, *m_navQuery);
|
||||
duDebugDrawNavMeshPolysWithFlags(&m_dd, *m_navMesh, SAMPLE_POLYFLAGS_DISABLED, duRGBA(0,0,0,128));
|
||||
}
|
||||
|
||||
glDepthMask(GL_TRUE);
|
||||
|
||||
if (m_chf && m_drawMode == DRAWMODE_COMPACT)
|
||||
duDebugDrawCompactHeightfieldSolid(&m_dd, *m_chf);
|
||||
|
||||
if (m_chf && m_drawMode == DRAWMODE_COMPACT_DISTANCE)
|
||||
duDebugDrawCompactHeightfieldDistance(&m_dd, *m_chf);
|
||||
if (m_chf && m_drawMode == DRAWMODE_COMPACT_REGIONS)
|
||||
duDebugDrawCompactHeightfieldRegions(&m_dd, *m_chf);
|
||||
if (m_solid && m_drawMode == DRAWMODE_VOXELS)
|
||||
{
|
||||
glEnable(GL_FOG);
|
||||
duDebugDrawHeightfieldSolid(&m_dd, *m_solid);
|
||||
glDisable(GL_FOG);
|
||||
}
|
||||
if (m_solid && m_drawMode == DRAWMODE_VOXELS_WALKABLE)
|
||||
{
|
||||
glEnable(GL_FOG);
|
||||
duDebugDrawHeightfieldWalkable(&m_dd, *m_solid);
|
||||
glDisable(GL_FOG);
|
||||
}
|
||||
if (m_cset && m_drawMode == DRAWMODE_RAW_CONTOURS)
|
||||
{
|
||||
glDepthMask(GL_FALSE);
|
||||
duDebugDrawRawContours(&m_dd, *m_cset);
|
||||
glDepthMask(GL_TRUE);
|
||||
}
|
||||
if (m_cset && m_drawMode == DRAWMODE_BOTH_CONTOURS)
|
||||
{
|
||||
glDepthMask(GL_FALSE);
|
||||
duDebugDrawRawContours(&m_dd, *m_cset, 0.5f);
|
||||
duDebugDrawContours(&m_dd, *m_cset);
|
||||
glDepthMask(GL_TRUE);
|
||||
}
|
||||
if (m_cset && m_drawMode == DRAWMODE_CONTOURS)
|
||||
{
|
||||
glDepthMask(GL_FALSE);
|
||||
duDebugDrawContours(&m_dd, *m_cset);
|
||||
glDepthMask(GL_TRUE);
|
||||
}
|
||||
if (m_chf && m_cset && m_drawMode == DRAWMODE_REGION_CONNECTIONS)
|
||||
{
|
||||
duDebugDrawCompactHeightfieldRegions(&m_dd, *m_chf);
|
||||
|
||||
glDepthMask(GL_FALSE);
|
||||
duDebugDrawRegionConnections(&m_dd, *m_cset);
|
||||
glDepthMask(GL_TRUE);
|
||||
}
|
||||
if (m_pmesh && m_drawMode == DRAWMODE_POLYMESH)
|
||||
{
|
||||
glDepthMask(GL_FALSE);
|
||||
duDebugDrawPolyMesh(&m_dd, *m_pmesh);
|
||||
glDepthMask(GL_TRUE);
|
||||
}
|
||||
if (m_dmesh && m_drawMode == DRAWMODE_POLYMESH_DETAIL)
|
||||
{
|
||||
glDepthMask(GL_FALSE);
|
||||
duDebugDrawPolyMeshDetail(&m_dd, *m_dmesh);
|
||||
glDepthMask(GL_TRUE);
|
||||
}
|
||||
|
||||
m_geom->drawConvexVolumes(&m_dd);
|
||||
|
||||
if (m_tool)
|
||||
m_tool->handleRender();
|
||||
renderToolStates();
|
||||
|
||||
glDepthMask(GL_TRUE);
|
||||
}
|
||||
|
||||
void Sample_SoloMesh::handleRenderOverlay(double* proj, double* model, int* view)
|
||||
{
|
||||
if (m_tool)
|
||||
m_tool->handleRenderOverlay(proj, model, view);
|
||||
renderOverlayToolStates(proj, model, view);
|
||||
}
|
||||
|
||||
void Sample_SoloMesh::handleMeshChanged(class InputGeom* geom)
|
||||
{
|
||||
Sample::handleMeshChanged(geom);
|
||||
|
||||
dtFreeNavMesh(m_navMesh);
|
||||
m_navMesh = 0;
|
||||
|
||||
if (m_tool)
|
||||
{
|
||||
m_tool->reset();
|
||||
m_tool->init(this);
|
||||
}
|
||||
resetToolStates();
|
||||
initToolStates(this);
|
||||
}
|
||||
|
||||
|
||||
bool Sample_SoloMesh::handleBuild()
|
||||
{
|
||||
if (!m_geom || !m_geom->getMesh())
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Input mesh is not specified.");
|
||||
return false;
|
||||
}
|
||||
|
||||
cleanup();
|
||||
|
||||
const float* bmin = m_geom->getNavMeshBoundsMin();
|
||||
const float* bmax = m_geom->getNavMeshBoundsMax();
|
||||
const float* verts = m_geom->getMesh()->getVerts();
|
||||
const int nverts = m_geom->getMesh()->getVertCount();
|
||||
const int* tris = m_geom->getMesh()->getTris();
|
||||
const int ntris = m_geom->getMesh()->getTriCount();
|
||||
|
||||
//
|
||||
// Step 1. Initialize build config.
|
||||
//
|
||||
|
||||
// Init build configuration from GUI
|
||||
memset(&m_cfg, 0, sizeof(m_cfg));
|
||||
m_cfg.cs = m_cellSize;
|
||||
m_cfg.ch = m_cellHeight;
|
||||
m_cfg.walkableSlopeAngle = m_agentMaxSlope;
|
||||
m_cfg.walkableHeight = (int)ceilf(m_agentHeight / m_cfg.ch);
|
||||
m_cfg.walkableClimb = (int)floorf(m_agentMaxClimb / m_cfg.ch);
|
||||
m_cfg.walkableRadius = (int)ceilf(m_agentRadius / m_cfg.cs);
|
||||
m_cfg.maxEdgeLen = (int)(m_edgeMaxLen / m_cellSize);
|
||||
m_cfg.maxSimplificationError = m_edgeMaxError;
|
||||
m_cfg.minRegionArea = (int)rcSqr(m_regionMinSize); // Note: area = size*size
|
||||
m_cfg.mergeRegionArea = (int)rcSqr(m_regionMergeSize); // Note: area = size*size
|
||||
m_cfg.maxVertsPerPoly = (int)m_vertsPerPoly;
|
||||
m_cfg.detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist;
|
||||
m_cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError;
|
||||
|
||||
// Set the area where the navigation will be build.
|
||||
// Here the bounds of the input mesh are used, but the
|
||||
// area could be specified by an user defined box, etc.
|
||||
rcVcopy(m_cfg.bmin, bmin);
|
||||
rcVcopy(m_cfg.bmax, bmax);
|
||||
rcCalcGridSize(m_cfg.bmin, m_cfg.bmax, m_cfg.cs, &m_cfg.width, &m_cfg.height);
|
||||
|
||||
// Reset build times gathering.
|
||||
m_ctx->resetTimers();
|
||||
|
||||
// Start the build process.
|
||||
m_ctx->startTimer(RC_TIMER_TOTAL);
|
||||
|
||||
m_ctx->log(RC_LOG_PROGRESS, "Building navigation:");
|
||||
m_ctx->log(RC_LOG_PROGRESS, " - %d x %d cells", m_cfg.width, m_cfg.height);
|
||||
m_ctx->log(RC_LOG_PROGRESS, " - %.1fK verts, %.1fK tris", nverts/1000.0f, ntris/1000.0f);
|
||||
|
||||
//
|
||||
// Step 2. Rasterize input polygon soup.
|
||||
//
|
||||
|
||||
// Allocate voxel heightfield where we rasterize our input data to.
|
||||
m_solid = rcAllocHeightfield();
|
||||
if (!m_solid)
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'solid'.");
|
||||
return false;
|
||||
}
|
||||
if (!rcCreateHeightfield(m_ctx, *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create solid heightfield.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate array that can hold triangle area types.
|
||||
// If you have multiple meshes you need to process, allocate
|
||||
// and array which can hold the max number of triangles you need to process.
|
||||
m_triareas = new unsigned char[ntris];
|
||||
if (!m_triareas)
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'm_triareas' (%d).", ntris);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find triangles which are walkable based on their slope and rasterize them.
|
||||
// If your input data is multiple meshes, you can transform them here, calculate
|
||||
// the are type for each of the meshes and rasterize them.
|
||||
memset(m_triareas, 0, ntris*sizeof(unsigned char));
|
||||
rcMarkWalkableTriangles(m_ctx, m_cfg.walkableSlopeAngle, verts, nverts, tris, ntris, m_triareas);
|
||||
if (!rcRasterizeTriangles(m_ctx, verts, nverts, tris, m_triareas, ntris, *m_solid, m_cfg.walkableClimb))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not rasterize triangles.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_keepInterResults)
|
||||
{
|
||||
delete [] m_triareas;
|
||||
m_triareas = 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Step 3. Filter walkables surfaces.
|
||||
//
|
||||
|
||||
// Once all geoemtry is rasterized, we do initial pass of filtering to
|
||||
// remove unwanted overhangs caused by the conservative rasterization
|
||||
// as well as filter spans where the character cannot possibly stand.
|
||||
if (m_filterLowHangingObstacles)
|
||||
rcFilterLowHangingWalkableObstacles(m_ctx, m_cfg.walkableClimb, *m_solid);
|
||||
if (m_filterLedgeSpans)
|
||||
rcFilterLedgeSpans(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid);
|
||||
if (m_filterWalkableLowHeightSpans)
|
||||
rcFilterWalkableLowHeightSpans(m_ctx, m_cfg.walkableHeight, *m_solid);
|
||||
|
||||
|
||||
//
|
||||
// Step 4. Partition walkable surface to simple regions.
|
||||
//
|
||||
|
||||
// Compact the heightfield so that it is faster to handle from now on.
|
||||
// This will result more cache coherent data as well as the neighbours
|
||||
// between walkable cells will be calculated.
|
||||
m_chf = rcAllocCompactHeightfield();
|
||||
if (!m_chf)
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'.");
|
||||
return false;
|
||||
}
|
||||
if (!rcBuildCompactHeightfield(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_keepInterResults)
|
||||
{
|
||||
rcFreeHeightField(m_solid);
|
||||
m_solid = 0;
|
||||
}
|
||||
|
||||
// Erode the walkable area by agent radius.
|
||||
if (!rcErodeWalkableArea(m_ctx, m_cfg.walkableRadius, *m_chf))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// (Optional) Mark areas.
|
||||
const ConvexVolume* vols = m_geom->getConvexVolumes();
|
||||
for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i)
|
||||
rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf);
|
||||
|
||||
|
||||
// Partition the heightfield so that we can use simple algorithm later to triangulate the walkable areas.
|
||||
// There are 3 martitioning methods, each with some pros and cons:
|
||||
// 1) Watershed partitioning
|
||||
// - the classic Recast partitioning
|
||||
// - creates the nicest tessellation
|
||||
// - usually slowest
|
||||
// - partitions the heightfield into nice regions without holes or overlaps
|
||||
// - the are some corner cases where this method creates produces holes and overlaps
|
||||
// - holes may appear when a small obstacles is close to large open area (triangulation can handle this)
|
||||
// - overlaps may occur if you have narrow spiral corridors (i.e stairs), this make triangulation to fail
|
||||
// * generally the best choice if you precompute the nacmesh, use this if you have large open areas
|
||||
// 2) Monotone partioning
|
||||
// - fastest
|
||||
// - partitions the heightfield into regions without holes and overlaps (guaranteed)
|
||||
// - creates long thin polygons, which sometimes causes paths with detours
|
||||
// * use this if you want fast navmesh generation
|
||||
// 3) Layer partitoining
|
||||
// - quite fast
|
||||
// - partitions the heighfield into non-overlapping regions
|
||||
// - relies on the triangulation code to cope with holes (thus slower than monotone partitioning)
|
||||
// - produces better triangles than monotone partitioning
|
||||
// - does not have the corner cases of watershed partitioning
|
||||
// - can be slow and create a bit ugly tessellation (still better than monotone)
|
||||
// if you have large open areas with small obstacles (not a problem if you use tiles)
|
||||
// * good choice to use for tiled navmesh with medium and small sized tiles
|
||||
|
||||
if (m_partitionType == SAMPLE_PARTITION_WATERSHED)
|
||||
{
|
||||
// Prepare for region partitioning, by calculating distance field along the walkable surface.
|
||||
if (!rcBuildDistanceField(m_ctx, *m_chf))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build distance field.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Partition the walkable surface into simple regions without holes.
|
||||
if (!rcBuildRegions(m_ctx, *m_chf, 0, m_cfg.minRegionArea, m_cfg.mergeRegionArea))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build watershed regions.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (m_partitionType == SAMPLE_PARTITION_MONOTONE)
|
||||
{
|
||||
// Partition the walkable surface into simple regions without holes.
|
||||
// Monotone partitioning does not need distancefield.
|
||||
if (!rcBuildRegionsMonotone(m_ctx, *m_chf, 0, m_cfg.minRegionArea, m_cfg.mergeRegionArea))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build monotone regions.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else // SAMPLE_PARTITION_LAYERS
|
||||
{
|
||||
// Partition the walkable surface into simple regions without holes.
|
||||
if (!rcBuildLayerRegions(m_ctx, *m_chf, 0, m_cfg.minRegionArea))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build layer regions.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Step 5. Trace and simplify region contours.
|
||||
//
|
||||
|
||||
// Create contours.
|
||||
m_cset = rcAllocContourSet();
|
||||
if (!m_cset)
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'.");
|
||||
return false;
|
||||
}
|
||||
if (!rcBuildContours(m_ctx, *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create contours.");
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Step 6. Build polygons mesh from contours.
|
||||
//
|
||||
|
||||
// Build polygon navmesh from the contours.
|
||||
m_pmesh = rcAllocPolyMesh();
|
||||
if (!m_pmesh)
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'.");
|
||||
return false;
|
||||
}
|
||||
if (!rcBuildPolyMesh(m_ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours.");
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Step 7. Create detail mesh which allows to access approximate height on each polygon.
|
||||
//
|
||||
|
||||
m_dmesh = rcAllocPolyMeshDetail();
|
||||
if (!m_dmesh)
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmdtl'.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rcBuildPolyMeshDetail(m_ctx, *m_pmesh, *m_chf, m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, *m_dmesh))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build detail mesh.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_keepInterResults)
|
||||
{
|
||||
rcFreeCompactHeightfield(m_chf);
|
||||
m_chf = 0;
|
||||
rcFreeContourSet(m_cset);
|
||||
m_cset = 0;
|
||||
}
|
||||
|
||||
// At this point the navigation mesh data is ready, you can access it from m_pmesh.
|
||||
// See duDebugDrawPolyMesh or dtCreateNavMeshData as examples how to access the data.
|
||||
|
||||
//
|
||||
// (Optional) Step 8. Create Detour data from Recast poly mesh.
|
||||
//
|
||||
|
||||
// The GUI may allow more max points per polygon than Detour can handle.
|
||||
// Only build the detour navmesh if we do not exceed the limit.
|
||||
if (m_cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON)
|
||||
{
|
||||
unsigned char* navData = 0;
|
||||
int navDataSize = 0;
|
||||
|
||||
// Update poly flags from areas.
|
||||
for (int i = 0; i < m_pmesh->npolys; ++i)
|
||||
{
|
||||
if (m_pmesh->areas[i] == RC_WALKABLE_AREA)
|
||||
m_pmesh->areas[i] = SAMPLE_POLYAREA_GROUND;
|
||||
|
||||
if (m_pmesh->areas[i] == SAMPLE_POLYAREA_GROUND ||
|
||||
m_pmesh->areas[i] == SAMPLE_POLYAREA_GRASS ||
|
||||
m_pmesh->areas[i] == SAMPLE_POLYAREA_ROAD)
|
||||
{
|
||||
m_pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK;
|
||||
}
|
||||
else if (m_pmesh->areas[i] == SAMPLE_POLYAREA_WATER)
|
||||
{
|
||||
m_pmesh->flags[i] = SAMPLE_POLYFLAGS_SWIM;
|
||||
}
|
||||
else if (m_pmesh->areas[i] == SAMPLE_POLYAREA_DOOR)
|
||||
{
|
||||
m_pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dtNavMeshCreateParams params;
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
params.verts = m_pmesh->verts;
|
||||
params.vertCount = m_pmesh->nverts;
|
||||
params.polys = m_pmesh->polys;
|
||||
params.polyAreas = m_pmesh->areas;
|
||||
params.polyFlags = m_pmesh->flags;
|
||||
params.polyCount = m_pmesh->npolys;
|
||||
params.nvp = m_pmesh->nvp;
|
||||
params.detailMeshes = m_dmesh->meshes;
|
||||
params.detailVerts = m_dmesh->verts;
|
||||
params.detailVertsCount = m_dmesh->nverts;
|
||||
params.detailTris = m_dmesh->tris;
|
||||
params.detailTriCount = m_dmesh->ntris;
|
||||
params.offMeshConVerts = m_geom->getOffMeshConnectionVerts();
|
||||
params.offMeshConRad = m_geom->getOffMeshConnectionRads();
|
||||
params.offMeshConDir = m_geom->getOffMeshConnectionDirs();
|
||||
params.offMeshConAreas = m_geom->getOffMeshConnectionAreas();
|
||||
params.offMeshConFlags = m_geom->getOffMeshConnectionFlags();
|
||||
params.offMeshConUserID = m_geom->getOffMeshConnectionId();
|
||||
params.offMeshConCount = m_geom->getOffMeshConnectionCount();
|
||||
params.walkableHeight = m_agentHeight;
|
||||
params.walkableRadius = m_agentRadius;
|
||||
params.walkableClimb = m_agentMaxClimb;
|
||||
rcVcopy(params.bmin, m_pmesh->bmin);
|
||||
rcVcopy(params.bmax, m_pmesh->bmax);
|
||||
params.cs = m_cfg.cs;
|
||||
params.ch = m_cfg.ch;
|
||||
params.buildBvTree = true;
|
||||
|
||||
if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "Could not build Detour navmesh.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_navMesh = dtAllocNavMesh();
|
||||
if (!m_navMesh)
|
||||
{
|
||||
dtFree(navData);
|
||||
m_ctx->log(RC_LOG_ERROR, "Could not create Detour navmesh");
|
||||
return false;
|
||||
}
|
||||
|
||||
dtStatus status;
|
||||
|
||||
status = m_navMesh->init(navData, navDataSize, DT_TILE_FREE_DATA);
|
||||
if (dtStatusFailed(status))
|
||||
{
|
||||
dtFree(navData);
|
||||
m_ctx->log(RC_LOG_ERROR, "Could not init Detour navmesh");
|
||||
return false;
|
||||
}
|
||||
|
||||
status = m_navQuery->init(m_navMesh, 2048);
|
||||
if (dtStatusFailed(status))
|
||||
{
|
||||
m_ctx->log(RC_LOG_ERROR, "Could not init Detour navmesh query");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_ctx->stopTimer(RC_TIMER_TOTAL);
|
||||
|
||||
// Show performance stats.
|
||||
duLogBuildTimes(*m_ctx, m_ctx->getAccumulatedTime(RC_TIMER_TOTAL));
|
||||
m_ctx->log(RC_LOG_PROGRESS, ">> Polymesh: %d vertices %d polygons", m_pmesh->nverts, m_pmesh->npolys);
|
||||
|
||||
m_totalBuildTimeMs = m_ctx->getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f;
|
||||
|
||||
if (m_tool)
|
||||
m_tool->init(this);
|
||||
initToolStates(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,464 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "TestCase.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "SDL.h"
|
||||
#include "SDL_opengl.h"
|
||||
#ifdef __APPLE__
|
||||
# include <OpenGL/glu.h>
|
||||
#else
|
||||
# include <GL/glu.h>
|
||||
#endif
|
||||
#include "imgui.h"
|
||||
#include "PerfTimer.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
TestCase::TestCase() :
|
||||
m_tests(0)
|
||||
{
|
||||
}
|
||||
|
||||
TestCase::~TestCase()
|
||||
{
|
||||
Test* iter = m_tests;
|
||||
while (iter)
|
||||
{
|
||||
Test* next = iter->next;
|
||||
delete iter;
|
||||
iter = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static char* parseRow(char* buf, char* bufEnd, char* row, int len)
|
||||
{
|
||||
bool start = true;
|
||||
bool done = false;
|
||||
int n = 0;
|
||||
while (!done && buf < bufEnd)
|
||||
{
|
||||
char c = *buf;
|
||||
buf++;
|
||||
// multirow
|
||||
switch (c)
|
||||
{
|
||||
case '\n':
|
||||
if (start) break;
|
||||
done = true;
|
||||
break;
|
||||
case '\r':
|
||||
break;
|
||||
case '\t':
|
||||
case ' ':
|
||||
if (start) break;
|
||||
// else falls through
|
||||
default:
|
||||
start = false;
|
||||
row[n++] = c;
|
||||
if (n >= len-1)
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
row[n] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void copyName(std::string& dst, const char* src)
|
||||
{
|
||||
// Skip white spaces
|
||||
while (*src && isspace(*src))
|
||||
src++;
|
||||
dst = src;
|
||||
}
|
||||
|
||||
bool TestCase::load(const std::string& filePath)
|
||||
{
|
||||
char* buf = 0;
|
||||
FILE* fp = fopen(filePath.c_str(), "rb");
|
||||
if (!fp)
|
||||
return false;
|
||||
if (fseek(fp, 0, SEEK_END) != 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
long bufSize = ftell(fp);
|
||||
if (bufSize < 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
if (fseek(fp, 0, SEEK_SET) != 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
buf = new char[bufSize];
|
||||
if (!buf)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
size_t readLen = fread(buf, bufSize, 1, fp);
|
||||
fclose(fp);
|
||||
if (readLen != 1)
|
||||
{
|
||||
delete[] buf;
|
||||
return false;
|
||||
}
|
||||
|
||||
char* src = buf;
|
||||
char* srcEnd = buf + bufSize;
|
||||
char row[512];
|
||||
while (src < srcEnd)
|
||||
{
|
||||
// Parse one row
|
||||
row[0] = '\0';
|
||||
src = parseRow(src, srcEnd, row, sizeof(row)/sizeof(char));
|
||||
if (row[0] == 's')
|
||||
{
|
||||
// Sample name.
|
||||
copyName(m_sampleName, row+1);
|
||||
}
|
||||
else if (row[0] == 'f')
|
||||
{
|
||||
// File name.
|
||||
copyName(m_geomFileName, row+1);
|
||||
}
|
||||
else if (row[0] == 'p' && row[1] == 'f')
|
||||
{
|
||||
// Pathfind test.
|
||||
Test* test = new Test;
|
||||
memset(test, 0, sizeof(Test));
|
||||
test->type = TEST_PATHFIND;
|
||||
test->expand = false;
|
||||
test->next = m_tests;
|
||||
m_tests = test;
|
||||
sscanf(row+2, "%f %f %f %f %f %f %hx %hx",
|
||||
&test->spos[0], &test->spos[1], &test->spos[2],
|
||||
&test->epos[0], &test->epos[1], &test->epos[2],
|
||||
&test->includeFlags, &test->excludeFlags);
|
||||
}
|
||||
else if (row[0] == 'r' && row[1] == 'c')
|
||||
{
|
||||
// Pathfind test.
|
||||
Test* test = new Test;
|
||||
memset(test, 0, sizeof(Test));
|
||||
test->type = TEST_RAYCAST;
|
||||
test->expand = false;
|
||||
test->next = m_tests;
|
||||
m_tests = test;
|
||||
sscanf(row+2, "%f %f %f %f %f %f %hx %hx",
|
||||
&test->spos[0], &test->spos[1], &test->spos[2],
|
||||
&test->epos[0], &test->epos[1], &test->epos[2],
|
||||
&test->includeFlags, &test->excludeFlags);
|
||||
}
|
||||
}
|
||||
|
||||
delete [] buf;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TestCase::resetTimes()
|
||||
{
|
||||
for (Test* iter = m_tests; iter; iter = iter->next)
|
||||
{
|
||||
iter->findNearestPolyTime = 0;
|
||||
iter->findPathTime = 0;
|
||||
iter->findStraightPathTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TestCase::doTests(dtNavMesh* navmesh, dtNavMeshQuery* navquery)
|
||||
{
|
||||
if (!navmesh || !navquery)
|
||||
return;
|
||||
|
||||
resetTimes();
|
||||
|
||||
static const int MAX_POLYS = 256;
|
||||
dtPolyRef polys[MAX_POLYS];
|
||||
float straight[MAX_POLYS*3];
|
||||
const float polyPickExt[3] = {2,4,2};
|
||||
|
||||
for (Test* iter = m_tests; iter; iter = iter->next)
|
||||
{
|
||||
delete [] iter->polys;
|
||||
iter->polys = 0;
|
||||
iter->npolys = 0;
|
||||
delete [] iter->straight;
|
||||
iter->straight = 0;
|
||||
iter->nstraight = 0;
|
||||
|
||||
dtQueryFilter filter;
|
||||
filter.setIncludeFlags(iter->includeFlags);
|
||||
filter.setExcludeFlags(iter->excludeFlags);
|
||||
|
||||
// Find start points
|
||||
TimeVal findNearestPolyStart = getPerfTime();
|
||||
|
||||
dtPolyRef startRef, endRef;
|
||||
navquery->findNearestPoly(iter->spos, polyPickExt, &filter, &startRef, iter->nspos);
|
||||
navquery->findNearestPoly(iter->epos, polyPickExt, &filter, &endRef, iter->nepos);
|
||||
|
||||
TimeVal findNearestPolyEnd = getPerfTime();
|
||||
iter->findNearestPolyTime += getPerfTimeUsec(findNearestPolyEnd - findNearestPolyStart);
|
||||
|
||||
if (!startRef || ! endRef)
|
||||
continue;
|
||||
|
||||
if (iter->type == TEST_PATHFIND)
|
||||
{
|
||||
// Find path
|
||||
TimeVal findPathStart = getPerfTime();
|
||||
|
||||
navquery->findPath(startRef, endRef, iter->spos, iter->epos, &filter, polys, &iter->npolys, MAX_POLYS);
|
||||
|
||||
TimeVal findPathEnd = getPerfTime();
|
||||
iter->findPathTime += getPerfTimeUsec(findPathEnd - findPathStart);
|
||||
|
||||
// Find straight path
|
||||
if (iter->npolys)
|
||||
{
|
||||
TimeVal findStraightPathStart = getPerfTime();
|
||||
|
||||
navquery->findStraightPath(iter->spos, iter->epos, polys, iter->npolys,
|
||||
straight, 0, 0, &iter->nstraight, MAX_POLYS);
|
||||
TimeVal findStraightPathEnd = getPerfTime();
|
||||
iter->findStraightPathTime += getPerfTimeUsec(findStraightPathEnd - findStraightPathStart);
|
||||
}
|
||||
|
||||
// Copy results
|
||||
if (iter->npolys)
|
||||
{
|
||||
iter->polys = new dtPolyRef[iter->npolys];
|
||||
memcpy(iter->polys, polys, sizeof(dtPolyRef)*iter->npolys);
|
||||
}
|
||||
if (iter->nstraight)
|
||||
{
|
||||
iter->straight = new float[iter->nstraight*3];
|
||||
memcpy(iter->straight, straight, sizeof(float)*3*iter->nstraight);
|
||||
}
|
||||
}
|
||||
else if (iter->type == TEST_RAYCAST)
|
||||
{
|
||||
float t = 0;
|
||||
float hitNormal[3], hitPos[3];
|
||||
|
||||
iter->straight = new float[2*3];
|
||||
iter->nstraight = 2;
|
||||
|
||||
iter->straight[0] = iter->spos[0];
|
||||
iter->straight[1] = iter->spos[1];
|
||||
iter->straight[2] = iter->spos[2];
|
||||
|
||||
TimeVal findPathStart = getPerfTime();
|
||||
|
||||
navquery->raycast(startRef, iter->spos, iter->epos, &filter, &t, hitNormal, polys, &iter->npolys, MAX_POLYS);
|
||||
|
||||
TimeVal findPathEnd = getPerfTime();
|
||||
iter->findPathTime += getPerfTimeUsec(findPathEnd - findPathStart);
|
||||
|
||||
if (t > 1)
|
||||
{
|
||||
// No hit
|
||||
dtVcopy(hitPos, iter->epos);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hit
|
||||
dtVlerp(hitPos, iter->spos, iter->epos, t);
|
||||
}
|
||||
// Adjust height.
|
||||
if (iter->npolys > 0)
|
||||
{
|
||||
float h = 0;
|
||||
navquery->getPolyHeight(polys[iter->npolys-1], hitPos, &h);
|
||||
hitPos[1] = h;
|
||||
}
|
||||
dtVcopy(&iter->straight[3], hitPos);
|
||||
|
||||
if (iter->npolys)
|
||||
{
|
||||
iter->polys = new dtPolyRef[iter->npolys];
|
||||
memcpy(iter->polys, polys, sizeof(dtPolyRef)*iter->npolys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
printf("Test Results:\n");
|
||||
int n = 0;
|
||||
for (Test* iter = m_tests; iter; iter = iter->next)
|
||||
{
|
||||
const int total = iter->findNearestPolyTime + iter->findPathTime + iter->findStraightPathTime;
|
||||
printf(" - Path %02d: %.4f ms\n", n, (float)total/1000.0f);
|
||||
printf(" - poly: %.4f ms\n", (float)iter->findNearestPolyTime/1000.0f);
|
||||
printf(" - path: %.4f ms\n", (float)iter->findPathTime/1000.0f);
|
||||
printf(" - straight: %.4f ms\n", (float)iter->findStraightPathTime/1000.0f);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
void TestCase::handleRender()
|
||||
{
|
||||
glLineWidth(2.0f);
|
||||
glBegin(GL_LINES);
|
||||
for (Test* iter = m_tests; iter; iter = iter->next)
|
||||
{
|
||||
float dir[3];
|
||||
dtVsub(dir, iter->epos, iter->spos);
|
||||
dtVnormalize(dir);
|
||||
glColor4ub(128,25,0,192);
|
||||
glVertex3f(iter->spos[0],iter->spos[1]-0.3f,iter->spos[2]);
|
||||
glVertex3f(iter->spos[0],iter->spos[1]+0.3f,iter->spos[2]);
|
||||
glVertex3f(iter->spos[0],iter->spos[1]+0.3f,iter->spos[2]);
|
||||
glVertex3f(iter->spos[0]+dir[0]*0.3f,iter->spos[1]+0.3f+dir[1]*0.3f,iter->spos[2]+dir[2]*0.3f);
|
||||
glColor4ub(51,102,0,129);
|
||||
glVertex3f(iter->epos[0],iter->epos[1]-0.3f,iter->epos[2]);
|
||||
glVertex3f(iter->epos[0],iter->epos[1]+0.3f,iter->epos[2]);
|
||||
|
||||
if (iter->expand)
|
||||
{
|
||||
const float s = 0.1f;
|
||||
glColor4ub(255,32,0,128);
|
||||
glVertex3f(iter->spos[0]-s,iter->spos[1],iter->spos[2]);
|
||||
glVertex3f(iter->spos[0]+s,iter->spos[1],iter->spos[2]);
|
||||
glVertex3f(iter->spos[0],iter->spos[1],iter->spos[2]-s);
|
||||
glVertex3f(iter->spos[0],iter->spos[1],iter->spos[2]+s);
|
||||
glColor4ub(255,192,0,255);
|
||||
glVertex3f(iter->nspos[0]-s,iter->nspos[1],iter->nspos[2]);
|
||||
glVertex3f(iter->nspos[0]+s,iter->nspos[1],iter->nspos[2]);
|
||||
glVertex3f(iter->nspos[0],iter->nspos[1],iter->nspos[2]-s);
|
||||
glVertex3f(iter->nspos[0],iter->nspos[1],iter->nspos[2]+s);
|
||||
|
||||
glColor4ub(255,32,0,128);
|
||||
glVertex3f(iter->epos[0]-s,iter->epos[1],iter->epos[2]);
|
||||
glVertex3f(iter->epos[0]+s,iter->epos[1],iter->epos[2]);
|
||||
glVertex3f(iter->epos[0],iter->epos[1],iter->epos[2]-s);
|
||||
glVertex3f(iter->epos[0],iter->epos[1],iter->epos[2]+s);
|
||||
glColor4ub(255,192,0,255);
|
||||
glVertex3f(iter->nepos[0]-s,iter->nepos[1],iter->nepos[2]);
|
||||
glVertex3f(iter->nepos[0]+s,iter->nepos[1],iter->nepos[2]);
|
||||
glVertex3f(iter->nepos[0],iter->nepos[1],iter->nepos[2]-s);
|
||||
glVertex3f(iter->nepos[0],iter->nepos[1],iter->nepos[2]+s);
|
||||
}
|
||||
|
||||
if (iter->expand)
|
||||
glColor4ub(255,192,0,255);
|
||||
else
|
||||
glColor4ub(0,0,0,64);
|
||||
|
||||
for (int i = 0; i < iter->nstraight-1; ++i)
|
||||
{
|
||||
glVertex3f(iter->straight[i*3+0],iter->straight[i*3+1]+0.3f,iter->straight[i*3+2]);
|
||||
glVertex3f(iter->straight[(i+1)*3+0],iter->straight[(i+1)*3+1]+0.3f,iter->straight[(i+1)*3+2]);
|
||||
}
|
||||
}
|
||||
glEnd();
|
||||
glLineWidth(1.0f);
|
||||
}
|
||||
|
||||
bool TestCase::handleRenderOverlay(double* proj, double* model, int* view)
|
||||
{
|
||||
GLdouble x, y, z;
|
||||
char text[64], subtext[64];
|
||||
int n = 0;
|
||||
|
||||
static const float LABEL_DIST = 1.0f;
|
||||
|
||||
for (Test* iter = m_tests; iter; iter = iter->next)
|
||||
{
|
||||
float pt[3], dir[3];
|
||||
if (iter->nstraight)
|
||||
{
|
||||
dtVcopy(pt, &iter->straight[3]);
|
||||
if (dtVdist(pt, iter->spos) > LABEL_DIST)
|
||||
{
|
||||
dtVsub(dir, pt, iter->spos);
|
||||
dtVnormalize(dir);
|
||||
dtVmad(pt, iter->spos, dir, LABEL_DIST);
|
||||
}
|
||||
pt[1]+=0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
dtVsub(dir, iter->epos, iter->spos);
|
||||
dtVnormalize(dir);
|
||||
dtVmad(pt, iter->spos, dir, LABEL_DIST);
|
||||
pt[1]+=0.5f;
|
||||
}
|
||||
|
||||
if (gluProject((GLdouble)pt[0], (GLdouble)pt[1], (GLdouble)pt[2],
|
||||
model, proj, view, &x, &y, &z))
|
||||
{
|
||||
snprintf(text, 64, "Path %d\n", n);
|
||||
unsigned int col = imguiRGBA(0,0,0,128);
|
||||
if (iter->expand)
|
||||
col = imguiRGBA(255,192,0,220);
|
||||
imguiDrawText((int)x, (int)(y-25), IMGUI_ALIGN_CENTER, text, col);
|
||||
}
|
||||
n++;
|
||||
}
|
||||
|
||||
static int resScroll = 0;
|
||||
bool mouseOverMenu = imguiBeginScrollArea("Test Results", 10, view[3] - 10 - 350, 200, 350, &resScroll);
|
||||
// mouseOverMenu = true;
|
||||
|
||||
n = 0;
|
||||
for (Test* iter = m_tests; iter; iter = iter->next)
|
||||
{
|
||||
const int total = iter->findNearestPolyTime + iter->findPathTime + iter->findStraightPathTime;
|
||||
snprintf(subtext, 64, "%.4f ms", (float)total/1000.0f);
|
||||
snprintf(text, 64, "Path %d", n);
|
||||
|
||||
if (imguiCollapse(text, subtext, iter->expand))
|
||||
iter->expand = !iter->expand;
|
||||
if (iter->expand)
|
||||
{
|
||||
snprintf(text, 64, "Poly: %.4f ms", (float)iter->findNearestPolyTime/1000.0f);
|
||||
imguiValue(text);
|
||||
|
||||
snprintf(text, 64, "Path: %.4f ms", (float)iter->findPathTime/1000.0f);
|
||||
imguiValue(text);
|
||||
|
||||
snprintf(text, 64, "Straight: %.4f ms", (float)iter->findStraightPathTime/1000.0f);
|
||||
imguiValue(text);
|
||||
|
||||
imguiSeparator();
|
||||
}
|
||||
|
||||
n++;
|
||||
}
|
||||
|
||||
imguiEndScrollArea();
|
||||
|
||||
return mouseOverMenu;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "ValueHistory.h"
|
||||
#include "imgui.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef WIN32
|
||||
# define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
ValueHistory::ValueHistory() :
|
||||
m_hsamples(0)
|
||||
{
|
||||
for (int i = 0; i < MAX_HISTORY; ++i)
|
||||
m_samples[i] = 0;
|
||||
}
|
||||
|
||||
float ValueHistory::getSampleMin() const
|
||||
{
|
||||
float val = m_samples[0];
|
||||
for (int i = 1; i < MAX_HISTORY; ++i)
|
||||
if (m_samples[i] < val)
|
||||
val = m_samples[i];
|
||||
return val;
|
||||
}
|
||||
|
||||
float ValueHistory::getSampleMax() const
|
||||
{
|
||||
float val = m_samples[0];
|
||||
for (int i = 1; i < MAX_HISTORY; ++i)
|
||||
if (m_samples[i] > val)
|
||||
val = m_samples[i];
|
||||
return val;
|
||||
}
|
||||
|
||||
float ValueHistory::getAverage() const
|
||||
{
|
||||
float val = 0;
|
||||
for (int i = 0; i < MAX_HISTORY; ++i)
|
||||
val += m_samples[i];
|
||||
return val/(float)MAX_HISTORY;
|
||||
}
|
||||
|
||||
void GraphParams::setRect(int ix, int iy, int iw, int ih, int ipad)
|
||||
{
|
||||
x = ix;
|
||||
y = iy;
|
||||
w = iw;
|
||||
h = ih;
|
||||
pad = ipad;
|
||||
}
|
||||
|
||||
void GraphParams::setValueRange(float ivmin, float ivmax, int indiv, const char* iunits)
|
||||
{
|
||||
vmin = ivmin;
|
||||
vmax = ivmax;
|
||||
ndiv = indiv;
|
||||
strcpy(units, iunits);
|
||||
}
|
||||
|
||||
void drawGraphBackground(const GraphParams* p)
|
||||
{
|
||||
// BG
|
||||
imguiDrawRoundedRect((float)p->x, (float)p->y, (float)p->w, (float)p->h, (float)p->pad, imguiRGBA(64,64,64,128));
|
||||
|
||||
const float sy = (p->h-p->pad*2) / (p->vmax-p->vmin);
|
||||
const float oy = p->y+p->pad-p->vmin*sy;
|
||||
|
||||
char text[64];
|
||||
|
||||
// Divider Lines
|
||||
for (int i = 0; i <= p->ndiv; ++i)
|
||||
{
|
||||
const float u = (float)i/(float)p->ndiv;
|
||||
const float v = p->vmin + (p->vmax-p->vmin)*u;
|
||||
snprintf(text, 64, "%.2f %s", v, p->units);
|
||||
const float fy = oy + v*sy;
|
||||
imguiDrawText(p->x + p->w - p->pad, (int)fy-4, IMGUI_ALIGN_RIGHT, text, imguiRGBA(0,0,0,255));
|
||||
imguiDrawLine((float)p->x + (float)p->pad, fy, (float)p->x + (float)p->w - (float)p->pad - 50, fy, 1.0f, imguiRGBA(0,0,0,64));
|
||||
}
|
||||
}
|
||||
|
||||
void drawGraph(const GraphParams* p, const ValueHistory* graph,
|
||||
int idx, const char* label, const unsigned int col)
|
||||
{
|
||||
const float sx = (p->w - p->pad*2) / (float)graph->getSampleCount();
|
||||
const float sy = (p->h - p->pad*2) / (p->vmax - p->vmin);
|
||||
const float ox = (float)p->x + (float)p->pad;
|
||||
const float oy = (float)p->y + (float)p->pad - p->vmin*sy;
|
||||
|
||||
// Values
|
||||
float px=0, py=0;
|
||||
for (int i = 0; i < graph->getSampleCount()-1; ++i)
|
||||
{
|
||||
const float x = ox + i*sx;
|
||||
const float y = oy + graph->getSample(i)*sy;
|
||||
if (i > 0)
|
||||
imguiDrawLine(px,py, x,y, 2.0f, col);
|
||||
px = x;
|
||||
py = y;
|
||||
}
|
||||
|
||||
// Label
|
||||
const int size = 15;
|
||||
const int spacing = 10;
|
||||
int ix = p->x + p->w + 5;
|
||||
int iy = p->y + p->h - (idx+1)*(size+spacing);
|
||||
|
||||
imguiDrawRoundedRect((float)ix, (float)iy, (float)size, (float)size, 2.0f, col);
|
||||
|
||||
char text[64];
|
||||
snprintf(text, 64, "%.2f %s", graph->getAverage(), p->units);
|
||||
imguiDrawText(ix+size+5, iy+3, IMGUI_ALIGN_LEFT, label, imguiRGBA(255,255,255,192));
|
||||
imguiDrawText(ix+size+150, iy+3, IMGUI_ALIGN_RIGHT, text, imguiRGBA(255,255,255,128));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,676 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include "imgui.h"
|
||||
|
||||
#ifdef WIN32
|
||||
# define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static const unsigned TEXT_POOL_SIZE = 50000;
|
||||
static char g_textPool[TEXT_POOL_SIZE];
|
||||
static unsigned g_textPoolSize = 0;
|
||||
static const char* allocText(const char* text)
|
||||
{
|
||||
unsigned len = static_cast<unsigned>(strlen(text)+1);
|
||||
if (g_textPoolSize + len >= TEXT_POOL_SIZE)
|
||||
return 0;
|
||||
char* dst = &g_textPool[g_textPoolSize];
|
||||
memcpy(dst, text, len);
|
||||
g_textPoolSize += len;
|
||||
return dst;
|
||||
}
|
||||
|
||||
static const unsigned GFXCMD_QUEUE_SIZE = 5000;
|
||||
static imguiGfxCmd g_gfxCmdQueue[GFXCMD_QUEUE_SIZE];
|
||||
static unsigned g_gfxCmdQueueSize = 0;
|
||||
|
||||
static void resetGfxCmdQueue()
|
||||
{
|
||||
g_gfxCmdQueueSize = 0;
|
||||
g_textPoolSize = 0;
|
||||
}
|
||||
|
||||
static void addGfxCmdScissor(int x, int y, int w, int h)
|
||||
{
|
||||
if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE)
|
||||
return;
|
||||
imguiGfxCmd& cmd = g_gfxCmdQueue[g_gfxCmdQueueSize++];
|
||||
cmd.type = IMGUI_GFXCMD_SCISSOR;
|
||||
cmd.flags = x < 0 ? 0 : 1; // on/off flag.
|
||||
cmd.col = 0;
|
||||
cmd.rect.x = (short)x;
|
||||
cmd.rect.y = (short)y;
|
||||
cmd.rect.w = (short)w;
|
||||
cmd.rect.h = (short)h;
|
||||
}
|
||||
|
||||
static void addGfxCmdRect(float x, float y, float w, float h, unsigned int color)
|
||||
{
|
||||
if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE)
|
||||
return;
|
||||
imguiGfxCmd& cmd = g_gfxCmdQueue[g_gfxCmdQueueSize++];
|
||||
cmd.type = IMGUI_GFXCMD_RECT;
|
||||
cmd.flags = 0;
|
||||
cmd.col = color;
|
||||
cmd.rect.x = (short)(x*8.0f);
|
||||
cmd.rect.y = (short)(y*8.0f);
|
||||
cmd.rect.w = (short)(w*8.0f);
|
||||
cmd.rect.h = (short)(h*8.0f);
|
||||
cmd.rect.r = 0;
|
||||
}
|
||||
|
||||
static void addGfxCmdLine(float x0, float y0, float x1, float y1, float r, unsigned int color)
|
||||
{
|
||||
if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE)
|
||||
return;
|
||||
imguiGfxCmd& cmd = g_gfxCmdQueue[g_gfxCmdQueueSize++];
|
||||
cmd.type = IMGUI_GFXCMD_LINE;
|
||||
cmd.flags = 0;
|
||||
cmd.col = color;
|
||||
cmd.line.x0 = (short)(x0*8.0f);
|
||||
cmd.line.y0 = (short)(y0*8.0f);
|
||||
cmd.line.x1 = (short)(x1*8.0f);
|
||||
cmd.line.y1 = (short)(y1*8.0f);
|
||||
cmd.line.r = (short)(r*8.0f);
|
||||
}
|
||||
|
||||
static void addGfxCmdRoundedRect(float x, float y, float w, float h, float r, unsigned int color)
|
||||
{
|
||||
if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE)
|
||||
return;
|
||||
imguiGfxCmd& cmd = g_gfxCmdQueue[g_gfxCmdQueueSize++];
|
||||
cmd.type = IMGUI_GFXCMD_RECT;
|
||||
cmd.flags = 0;
|
||||
cmd.col = color;
|
||||
cmd.rect.x = (short)(x*8.0f);
|
||||
cmd.rect.y = (short)(y*8.0f);
|
||||
cmd.rect.w = (short)(w*8.0f);
|
||||
cmd.rect.h = (short)(h*8.0f);
|
||||
cmd.rect.r = (short)(r*8.0f);
|
||||
}
|
||||
|
||||
static void addGfxCmdTriangle(int x, int y, int w, int h, int flags, unsigned int color)
|
||||
{
|
||||
if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE)
|
||||
return;
|
||||
imguiGfxCmd& cmd = g_gfxCmdQueue[g_gfxCmdQueueSize++];
|
||||
cmd.type = IMGUI_GFXCMD_TRIANGLE;
|
||||
cmd.flags = (char)flags;
|
||||
cmd.col = color;
|
||||
cmd.rect.x = (short)(x*8.0f);
|
||||
cmd.rect.y = (short)(y*8.0f);
|
||||
cmd.rect.w = (short)(w*8.0f);
|
||||
cmd.rect.h = (short)(h*8.0f);
|
||||
}
|
||||
|
||||
static void addGfxCmdText(int x, int y, int align, const char* text, unsigned int color)
|
||||
{
|
||||
if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE)
|
||||
return;
|
||||
imguiGfxCmd& cmd = g_gfxCmdQueue[g_gfxCmdQueueSize++];
|
||||
cmd.type = IMGUI_GFXCMD_TEXT;
|
||||
cmd.flags = 0;
|
||||
cmd.col = color;
|
||||
cmd.text.x = (short)x;
|
||||
cmd.text.y = (short)y;
|
||||
cmd.text.align = (short)align;
|
||||
cmd.text.text = allocText(text);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
struct GuiState
|
||||
{
|
||||
GuiState() :
|
||||
left(false), leftPressed(false), leftReleased(false),
|
||||
mx(-1), my(-1), scroll(0),
|
||||
active(0), hot(0), hotToBe(0), isHot(false), isActive(false), wentActive(false),
|
||||
dragX(0), dragY(0), dragOrig(0), widgetX(0), widgetY(0), widgetW(100),
|
||||
insideCurrentScroll(false), areaId(0), widgetId(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool left;
|
||||
bool leftPressed, leftReleased;
|
||||
int mx,my;
|
||||
int scroll;
|
||||
unsigned int active;
|
||||
unsigned int hot;
|
||||
unsigned int hotToBe;
|
||||
bool isHot;
|
||||
bool isActive;
|
||||
bool wentActive;
|
||||
int dragX, dragY;
|
||||
float dragOrig;
|
||||
int widgetX, widgetY, widgetW;
|
||||
bool insideCurrentScroll;
|
||||
|
||||
unsigned int areaId;
|
||||
unsigned int widgetId;
|
||||
};
|
||||
|
||||
static GuiState g_state;
|
||||
|
||||
inline bool anyActive()
|
||||
{
|
||||
return g_state.active != 0;
|
||||
}
|
||||
|
||||
inline bool isActive(unsigned int id)
|
||||
{
|
||||
return g_state.active == id;
|
||||
}
|
||||
|
||||
inline bool isHot(unsigned int id)
|
||||
{
|
||||
return g_state.hot == id;
|
||||
}
|
||||
|
||||
inline bool inRect(int x, int y, int w, int h, bool checkScroll = true)
|
||||
{
|
||||
return (!checkScroll || g_state.insideCurrentScroll) && g_state.mx >= x && g_state.mx <= x+w && g_state.my >= y && g_state.my <= y+h;
|
||||
}
|
||||
|
||||
inline void clearInput()
|
||||
{
|
||||
g_state.leftPressed = false;
|
||||
g_state.leftReleased = false;
|
||||
g_state.scroll = 0;
|
||||
}
|
||||
|
||||
inline void clearActive()
|
||||
{
|
||||
g_state.active = 0;
|
||||
// mark all UI for this frame as processed
|
||||
clearInput();
|
||||
}
|
||||
|
||||
inline void setActive(unsigned int id)
|
||||
{
|
||||
g_state.active = id;
|
||||
g_state.wentActive = true;
|
||||
}
|
||||
|
||||
inline void setHot(unsigned int id)
|
||||
{
|
||||
g_state.hotToBe = id;
|
||||
}
|
||||
|
||||
|
||||
static bool buttonLogic(unsigned int id, bool over)
|
||||
{
|
||||
bool res = false;
|
||||
// process down
|
||||
if (!anyActive())
|
||||
{
|
||||
if (over)
|
||||
setHot(id);
|
||||
if (isHot(id) && g_state.leftPressed)
|
||||
setActive(id);
|
||||
}
|
||||
|
||||
// if button is active, then react on left up
|
||||
if (isActive(id))
|
||||
{
|
||||
g_state.isActive = true;
|
||||
if (over)
|
||||
setHot(id);
|
||||
if (g_state.leftReleased)
|
||||
{
|
||||
if (isHot(id))
|
||||
res = true;
|
||||
clearActive();
|
||||
}
|
||||
}
|
||||
|
||||
if (isHot(id))
|
||||
g_state.isHot = true;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void updateInput(int mx, int my, unsigned char mbut, int scroll)
|
||||
{
|
||||
bool left = (mbut & IMGUI_MBUT_LEFT) != 0;
|
||||
|
||||
g_state.mx = mx;
|
||||
g_state.my = my;
|
||||
g_state.leftPressed = !g_state.left && left;
|
||||
g_state.leftReleased = g_state.left && !left;
|
||||
g_state.left = left;
|
||||
|
||||
g_state.scroll = scroll;
|
||||
}
|
||||
|
||||
void imguiBeginFrame(int mx, int my, unsigned char mbut, int scroll)
|
||||
{
|
||||
updateInput(mx,my,mbut,scroll);
|
||||
|
||||
g_state.hot = g_state.hotToBe;
|
||||
g_state.hotToBe = 0;
|
||||
|
||||
g_state.wentActive = false;
|
||||
g_state.isActive = false;
|
||||
g_state.isHot = false;
|
||||
|
||||
g_state.widgetX = 0;
|
||||
g_state.widgetY = 0;
|
||||
g_state.widgetW = 0;
|
||||
|
||||
g_state.areaId = 1;
|
||||
g_state.widgetId = 1;
|
||||
|
||||
resetGfxCmdQueue();
|
||||
}
|
||||
|
||||
void imguiEndFrame()
|
||||
{
|
||||
clearInput();
|
||||
}
|
||||
|
||||
const imguiGfxCmd* imguiGetRenderQueue()
|
||||
{
|
||||
return g_gfxCmdQueue;
|
||||
}
|
||||
|
||||
int imguiGetRenderQueueSize()
|
||||
{
|
||||
return g_gfxCmdQueueSize;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
static const int BUTTON_HEIGHT = 20;
|
||||
static const int SLIDER_HEIGHT = 20;
|
||||
static const int SLIDER_MARKER_WIDTH = 10;
|
||||
static const int CHECK_SIZE = 8;
|
||||
static const int DEFAULT_SPACING = 4;
|
||||
static const int TEXT_HEIGHT = 8;
|
||||
static const int SCROLL_AREA_PADDING = 6;
|
||||
static const int INDENT_SIZE = 16;
|
||||
static const int AREA_HEADER = 28;
|
||||
|
||||
static int g_scrollTop = 0;
|
||||
static int g_scrollBottom = 0;
|
||||
static int g_scrollRight = 0;
|
||||
static int g_scrollAreaTop = 0;
|
||||
static int* g_scrollVal = 0;
|
||||
static int g_focusTop = 0;
|
||||
static int g_focusBottom = 0;
|
||||
static unsigned int g_scrollId = 0;
|
||||
static bool g_insideScrollArea = false;
|
||||
|
||||
bool imguiBeginScrollArea(const char* name, int x, int y, int w, int h, int* scroll)
|
||||
{
|
||||
g_state.areaId++;
|
||||
g_state.widgetId = 0;
|
||||
g_scrollId = (g_state.areaId<<16) | g_state.widgetId;
|
||||
|
||||
g_state.widgetX = x + SCROLL_AREA_PADDING;
|
||||
g_state.widgetY = y+h-AREA_HEADER + (*scroll);
|
||||
g_state.widgetW = w - SCROLL_AREA_PADDING*4;
|
||||
g_scrollTop = y-AREA_HEADER+h;
|
||||
g_scrollBottom = y+SCROLL_AREA_PADDING;
|
||||
g_scrollRight = x+w - SCROLL_AREA_PADDING*3;
|
||||
g_scrollVal = scroll;
|
||||
|
||||
g_scrollAreaTop = g_state.widgetY;
|
||||
|
||||
g_focusTop = y-AREA_HEADER;
|
||||
g_focusBottom = y-AREA_HEADER+h;
|
||||
|
||||
g_insideScrollArea = inRect(x, y, w, h, false);
|
||||
g_state.insideCurrentScroll = g_insideScrollArea;
|
||||
|
||||
addGfxCmdRoundedRect((float)x, (float)y, (float)w, (float)h, 6, imguiRGBA(0,0,0,192));
|
||||
|
||||
addGfxCmdText(x+AREA_HEADER/2, y+h-AREA_HEADER/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, name, imguiRGBA(255,255,255,128));
|
||||
|
||||
addGfxCmdScissor(x+SCROLL_AREA_PADDING, y+SCROLL_AREA_PADDING, w-SCROLL_AREA_PADDING*4, h-AREA_HEADER-SCROLL_AREA_PADDING);
|
||||
|
||||
return g_insideScrollArea;
|
||||
}
|
||||
|
||||
void imguiEndScrollArea()
|
||||
{
|
||||
// Disable scissoring.
|
||||
addGfxCmdScissor(-1,-1,-1,-1);
|
||||
|
||||
// Draw scroll bar
|
||||
int x = g_scrollRight+SCROLL_AREA_PADDING/2;
|
||||
int y = g_scrollBottom;
|
||||
int w = SCROLL_AREA_PADDING*2;
|
||||
int h = g_scrollTop - g_scrollBottom;
|
||||
|
||||
int stop = g_scrollAreaTop;
|
||||
int sbot = g_state.widgetY;
|
||||
int sh = stop - sbot; // The scrollable area height.
|
||||
|
||||
float barHeight = (float)h/(float)sh;
|
||||
|
||||
if (barHeight < 1)
|
||||
{
|
||||
float barY = (float)(y - sbot)/(float)sh;
|
||||
if (barY < 0) barY = 0;
|
||||
if (barY > 1) barY = 1;
|
||||
|
||||
// Handle scroll bar logic.
|
||||
unsigned int hid = g_scrollId;
|
||||
int hx = x;
|
||||
int hy = y + (int)(barY*h);
|
||||
int hw = w;
|
||||
int hh = (int)(barHeight*h);
|
||||
|
||||
const int range = h - (hh-1);
|
||||
bool over = inRect(hx, hy, hw, hh);
|
||||
buttonLogic(hid, over);
|
||||
if (isActive(hid))
|
||||
{
|
||||
float u = (float)(hy-y) / (float)range;
|
||||
if (g_state.wentActive)
|
||||
{
|
||||
g_state.dragY = g_state.my;
|
||||
g_state.dragOrig = u;
|
||||
}
|
||||
if (g_state.dragY != g_state.my)
|
||||
{
|
||||
u = g_state.dragOrig + (g_state.my - g_state.dragY) / (float)range;
|
||||
if (u < 0) u = 0;
|
||||
if (u > 1) u = 1;
|
||||
*g_scrollVal = (int)((1-u) * (sh - h));
|
||||
}
|
||||
}
|
||||
|
||||
// BG
|
||||
addGfxCmdRoundedRect((float)x, (float)y, (float)w, (float)h, (float)w/2-1, imguiRGBA(0,0,0,196));
|
||||
// Bar
|
||||
if (isActive(hid))
|
||||
addGfxCmdRoundedRect((float)hx, (float)hy, (float)hw, (float)hh, (float)w/2-1, imguiRGBA(255,196,0,196));
|
||||
else
|
||||
addGfxCmdRoundedRect((float)hx, (float)hy, (float)hw, (float)hh, (float)w/2-1, isHot(hid) ? imguiRGBA(255,196,0,96) : imguiRGBA(255,255,255,64));
|
||||
|
||||
// Handle mouse scrolling.
|
||||
if (g_insideScrollArea) // && !anyActive())
|
||||
{
|
||||
if (g_state.scroll)
|
||||
{
|
||||
*g_scrollVal += 20*g_state.scroll;
|
||||
if (*g_scrollVal < 0) *g_scrollVal = 0;
|
||||
if (*g_scrollVal > (sh - h)) *g_scrollVal = (sh - h);
|
||||
}
|
||||
}
|
||||
}
|
||||
g_state.insideCurrentScroll = false;
|
||||
}
|
||||
|
||||
bool imguiButton(const char* text, bool enabled)
|
||||
{
|
||||
g_state.widgetId++;
|
||||
unsigned int id = (g_state.areaId<<16) | g_state.widgetId;
|
||||
|
||||
int x = g_state.widgetX;
|
||||
int y = g_state.widgetY - BUTTON_HEIGHT;
|
||||
int w = g_state.widgetW;
|
||||
int h = BUTTON_HEIGHT;
|
||||
g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING;
|
||||
|
||||
bool over = enabled && inRect(x, y, w, h);
|
||||
bool res = buttonLogic(id, over);
|
||||
|
||||
addGfxCmdRoundedRect((float)x, (float)y, (float)w, (float)h, (float)BUTTON_HEIGHT/2-1, imguiRGBA(128,128,128, isActive(id)?196:96));
|
||||
if (enabled)
|
||||
addGfxCmdText(x+BUTTON_HEIGHT/2, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, isHot(id) ? imguiRGBA(255,196,0,255) : imguiRGBA(255,255,255,200));
|
||||
else
|
||||
addGfxCmdText(x+BUTTON_HEIGHT/2, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(128,128,128,200));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool imguiItem(const char* text, bool enabled)
|
||||
{
|
||||
g_state.widgetId++;
|
||||
unsigned int id = (g_state.areaId<<16) | g_state.widgetId;
|
||||
|
||||
int x = g_state.widgetX;
|
||||
int y = g_state.widgetY - BUTTON_HEIGHT;
|
||||
int w = g_state.widgetW;
|
||||
int h = BUTTON_HEIGHT;
|
||||
g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING;
|
||||
|
||||
bool over = enabled && inRect(x, y, w, h);
|
||||
bool res = buttonLogic(id, over);
|
||||
|
||||
if (isHot(id))
|
||||
addGfxCmdRoundedRect((float)x, (float)y, (float)w, (float)h, 2.0f, imguiRGBA(255,196,0,isActive(id)?196:96));
|
||||
|
||||
if (enabled)
|
||||
addGfxCmdText(x+BUTTON_HEIGHT/2, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(255,255,255,200));
|
||||
else
|
||||
addGfxCmdText(x+BUTTON_HEIGHT/2, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(128,128,128,200));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool imguiCheck(const char* text, bool checked, bool enabled)
|
||||
{
|
||||
g_state.widgetId++;
|
||||
unsigned int id = (g_state.areaId<<16) | g_state.widgetId;
|
||||
|
||||
int x = g_state.widgetX;
|
||||
int y = g_state.widgetY - BUTTON_HEIGHT;
|
||||
int w = g_state.widgetW;
|
||||
int h = BUTTON_HEIGHT;
|
||||
g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING;
|
||||
|
||||
bool over = enabled && inRect(x, y, w, h);
|
||||
bool res = buttonLogic(id, over);
|
||||
|
||||
const int cx = x+BUTTON_HEIGHT/2-CHECK_SIZE/2;
|
||||
const int cy = y+BUTTON_HEIGHT/2-CHECK_SIZE/2;
|
||||
addGfxCmdRoundedRect((float)cx-3, (float)cy-3, (float)CHECK_SIZE+6, (float)CHECK_SIZE+6, 4, imguiRGBA(128,128,128, isActive(id)?196:96));
|
||||
if (checked)
|
||||
{
|
||||
if (enabled)
|
||||
addGfxCmdRoundedRect((float)cx, (float)cy, (float)CHECK_SIZE, (float)CHECK_SIZE, (float)CHECK_SIZE/2-1, imguiRGBA(255,255,255,isActive(id)?255:200));
|
||||
else
|
||||
addGfxCmdRoundedRect((float)cx, (float)cy, (float)CHECK_SIZE, (float)CHECK_SIZE, (float)CHECK_SIZE/2-1, imguiRGBA(128,128,128,200));
|
||||
}
|
||||
|
||||
if (enabled)
|
||||
addGfxCmdText(x+BUTTON_HEIGHT, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, isHot(id) ? imguiRGBA(255,196,0,255) : imguiRGBA(255,255,255,200));
|
||||
else
|
||||
addGfxCmdText(x+BUTTON_HEIGHT, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(128,128,128,200));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool imguiCollapse(const char* text, const char* subtext, bool checked, bool enabled)
|
||||
{
|
||||
g_state.widgetId++;
|
||||
unsigned int id = (g_state.areaId<<16) | g_state.widgetId;
|
||||
|
||||
int x = g_state.widgetX;
|
||||
int y = g_state.widgetY - BUTTON_HEIGHT;
|
||||
int w = g_state.widgetW;
|
||||
int h = BUTTON_HEIGHT;
|
||||
g_state.widgetY -= BUTTON_HEIGHT; // + DEFAULT_SPACING;
|
||||
|
||||
const int cx = x+BUTTON_HEIGHT/2-CHECK_SIZE/2;
|
||||
const int cy = y+BUTTON_HEIGHT/2-CHECK_SIZE/2;
|
||||
|
||||
bool over = enabled && inRect(x, y, w, h);
|
||||
bool res = buttonLogic(id, over);
|
||||
|
||||
if (checked)
|
||||
addGfxCmdTriangle(cx, cy, CHECK_SIZE, CHECK_SIZE, 2, imguiRGBA(255,255,255,isActive(id)?255:200));
|
||||
else
|
||||
addGfxCmdTriangle(cx, cy, CHECK_SIZE, CHECK_SIZE, 1, imguiRGBA(255,255,255,isActive(id)?255:200));
|
||||
|
||||
if (enabled)
|
||||
addGfxCmdText(x+BUTTON_HEIGHT, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, isHot(id) ? imguiRGBA(255,196,0,255) : imguiRGBA(255,255,255,200));
|
||||
else
|
||||
addGfxCmdText(x+BUTTON_HEIGHT, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(128,128,128,200));
|
||||
|
||||
if (subtext)
|
||||
addGfxCmdText(x+w-BUTTON_HEIGHT/2, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_RIGHT, subtext, imguiRGBA(255,255,255,128));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void imguiLabel(const char* text)
|
||||
{
|
||||
int x = g_state.widgetX;
|
||||
int y = g_state.widgetY - BUTTON_HEIGHT;
|
||||
g_state.widgetY -= BUTTON_HEIGHT;
|
||||
addGfxCmdText(x, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(255,255,255,255));
|
||||
}
|
||||
|
||||
void imguiValue(const char* text)
|
||||
{
|
||||
const int x = g_state.widgetX;
|
||||
const int y = g_state.widgetY - BUTTON_HEIGHT;
|
||||
const int w = g_state.widgetW;
|
||||
g_state.widgetY -= BUTTON_HEIGHT;
|
||||
|
||||
addGfxCmdText(x+w-BUTTON_HEIGHT/2, y+BUTTON_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_RIGHT, text, imguiRGBA(255,255,255,200));
|
||||
}
|
||||
|
||||
bool imguiSlider(const char* text, float* val, float vmin, float vmax, float vinc, bool enabled)
|
||||
{
|
||||
g_state.widgetId++;
|
||||
unsigned int id = (g_state.areaId<<16) | g_state.widgetId;
|
||||
|
||||
int x = g_state.widgetX;
|
||||
int y = g_state.widgetY - BUTTON_HEIGHT;
|
||||
int w = g_state.widgetW;
|
||||
int h = SLIDER_HEIGHT;
|
||||
g_state.widgetY -= SLIDER_HEIGHT + DEFAULT_SPACING;
|
||||
|
||||
addGfxCmdRoundedRect((float)x, (float)y, (float)w, (float)h, 4.0f, imguiRGBA(0,0,0,128));
|
||||
|
||||
const int range = w - SLIDER_MARKER_WIDTH;
|
||||
|
||||
float u = (*val - vmin) / (vmax-vmin);
|
||||
if (u < 0) u = 0;
|
||||
if (u > 1) u = 1;
|
||||
int m = (int)(u * range);
|
||||
|
||||
bool over = enabled && inRect(x+m, y, SLIDER_MARKER_WIDTH, SLIDER_HEIGHT);
|
||||
bool res = buttonLogic(id, over);
|
||||
bool valChanged = false;
|
||||
|
||||
if (isActive(id))
|
||||
{
|
||||
if (g_state.wentActive)
|
||||
{
|
||||
g_state.dragX = g_state.mx;
|
||||
g_state.dragOrig = u;
|
||||
}
|
||||
if (g_state.dragX != g_state.mx)
|
||||
{
|
||||
u = g_state.dragOrig + (float)(g_state.mx - g_state.dragX) / (float)range;
|
||||
if (u < 0) u = 0;
|
||||
if (u > 1) u = 1;
|
||||
*val = vmin + u*(vmax-vmin);
|
||||
*val = floorf(*val/vinc+0.5f)*vinc; // Snap to vinc
|
||||
m = (int)(u * range);
|
||||
valChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isActive(id))
|
||||
addGfxCmdRoundedRect((float)(x+m), (float)y, (float)SLIDER_MARKER_WIDTH, (float)SLIDER_HEIGHT, 4.0f, imguiRGBA(255,255,255,255));
|
||||
else
|
||||
addGfxCmdRoundedRect((float)(x+m), (float)y, (float)SLIDER_MARKER_WIDTH, (float)SLIDER_HEIGHT, 4.0f, isHot(id) ? imguiRGBA(255,196,0,128) : imguiRGBA(255,255,255,64));
|
||||
|
||||
// TODO: fix this, take a look at 'nicenum'.
|
||||
int digits = (int)(ceilf(log10f(vinc)));
|
||||
char fmt[16];
|
||||
snprintf(fmt, 16, "%%.%df", digits >= 0 ? 0 : -digits);
|
||||
char msg[128];
|
||||
snprintf(msg, 128, fmt, *val);
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
addGfxCmdText(x+SLIDER_HEIGHT/2, y+SLIDER_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, isHot(id) ? imguiRGBA(255,196,0,255) : imguiRGBA(255,255,255,200));
|
||||
addGfxCmdText(x+w-SLIDER_HEIGHT/2, y+SLIDER_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_RIGHT, msg, isHot(id) ? imguiRGBA(255,196,0,255) : imguiRGBA(255,255,255,200));
|
||||
}
|
||||
else
|
||||
{
|
||||
addGfxCmdText(x+SLIDER_HEIGHT/2, y+SLIDER_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_LEFT, text, imguiRGBA(128,128,128,200));
|
||||
addGfxCmdText(x+w-SLIDER_HEIGHT/2, y+SLIDER_HEIGHT/2-TEXT_HEIGHT/2, IMGUI_ALIGN_RIGHT, msg, imguiRGBA(128,128,128,200));
|
||||
}
|
||||
|
||||
return res || valChanged;
|
||||
}
|
||||
|
||||
|
||||
void imguiIndent()
|
||||
{
|
||||
g_state.widgetX += INDENT_SIZE;
|
||||
g_state.widgetW -= INDENT_SIZE;
|
||||
}
|
||||
|
||||
void imguiUnindent()
|
||||
{
|
||||
g_state.widgetX -= INDENT_SIZE;
|
||||
g_state.widgetW += INDENT_SIZE;
|
||||
}
|
||||
|
||||
void imguiSeparator()
|
||||
{
|
||||
g_state.widgetY -= DEFAULT_SPACING*3;
|
||||
}
|
||||
|
||||
void imguiSeparatorLine()
|
||||
{
|
||||
int x = g_state.widgetX;
|
||||
int y = g_state.widgetY - DEFAULT_SPACING*2;
|
||||
int w = g_state.widgetW;
|
||||
int h = 1;
|
||||
g_state.widgetY -= DEFAULT_SPACING*4;
|
||||
|
||||
addGfxCmdRect((float)x, (float)y, (float)w, (float)h, imguiRGBA(255,255,255,32));
|
||||
}
|
||||
|
||||
void imguiDrawText(int x, int y, int align, const char* text, unsigned int color)
|
||||
{
|
||||
addGfxCmdText(x, y, align, text, color);
|
||||
}
|
||||
|
||||
void imguiDrawLine(float x0, float y0, float x1, float y1, float r, unsigned int color)
|
||||
{
|
||||
addGfxCmdLine(x0, y0, x1, y1, r, color);
|
||||
}
|
||||
|
||||
void imguiDrawRect(float x, float y, float w, float h, unsigned int color)
|
||||
{
|
||||
addGfxCmdRect(x, y, w, h, color);
|
||||
}
|
||||
|
||||
void imguiDrawRoundedRect(float x, float y, float w, float h, float r, unsigned int color)
|
||||
{
|
||||
addGfxCmdRoundedRect(x, y, w, h, r, color);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,500 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include "imgui.h"
|
||||
#include "SDL.h"
|
||||
#include "SDL_opengl.h"
|
||||
|
||||
// Some math headers don't have PI defined.
|
||||
static const float PI = 3.14159265f;
|
||||
|
||||
void imguifree(void* ptr, void* userptr);
|
||||
void* imguimalloc(size_t size, void* userptr);
|
||||
|
||||
#define STBTT_malloc(x,y) imguimalloc(x,y)
|
||||
#define STBTT_free(x,y) imguifree(x,y)
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include "stb_truetype.h"
|
||||
|
||||
void imguifree(void* ptr, void* /*userptr*/)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
void* imguimalloc(size_t size, void* /*userptr*/)
|
||||
{
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
static const unsigned TEMP_COORD_COUNT = 100;
|
||||
static float g_tempCoords[TEMP_COORD_COUNT*2];
|
||||
static float g_tempNormals[TEMP_COORD_COUNT*2];
|
||||
|
||||
static const int CIRCLE_VERTS = 8*4;
|
||||
static float g_circleVerts[CIRCLE_VERTS*2];
|
||||
|
||||
static stbtt_bakedchar g_cdata[96]; // ASCII 32..126 is 95 glyphs
|
||||
static GLuint g_ftex = 0;
|
||||
|
||||
inline unsigned int RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
|
||||
{
|
||||
return (r) | (g << 8) | (b << 16) | (a << 24);
|
||||
}
|
||||
|
||||
static void drawPolygon(const float* coords, unsigned numCoords, float r, unsigned int col)
|
||||
{
|
||||
if (numCoords > TEMP_COORD_COUNT) numCoords = TEMP_COORD_COUNT;
|
||||
|
||||
for (unsigned i = 0, j = numCoords-1; i < numCoords; j=i++)
|
||||
{
|
||||
const float* v0 = &coords[j*2];
|
||||
const float* v1 = &coords[i*2];
|
||||
float dx = v1[0] - v0[0];
|
||||
float dy = v1[1] - v0[1];
|
||||
float d = sqrtf(dx*dx+dy*dy);
|
||||
if (d > 0)
|
||||
{
|
||||
d = 1.0f/d;
|
||||
dx *= d;
|
||||
dy *= d;
|
||||
}
|
||||
g_tempNormals[j*2+0] = dy;
|
||||
g_tempNormals[j*2+1] = -dx;
|
||||
}
|
||||
|
||||
for (unsigned i = 0, j = numCoords-1; i < numCoords; j=i++)
|
||||
{
|
||||
float dlx0 = g_tempNormals[j*2+0];
|
||||
float dly0 = g_tempNormals[j*2+1];
|
||||
float dlx1 = g_tempNormals[i*2+0];
|
||||
float dly1 = g_tempNormals[i*2+1];
|
||||
float dmx = (dlx0 + dlx1) * 0.5f;
|
||||
float dmy = (dly0 + dly1) * 0.5f;
|
||||
float dmr2 = dmx*dmx + dmy*dmy;
|
||||
if (dmr2 > 0.000001f)
|
||||
{
|
||||
float scale = 1.0f / dmr2;
|
||||
if (scale > 10.0f) scale = 10.0f;
|
||||
dmx *= scale;
|
||||
dmy *= scale;
|
||||
}
|
||||
g_tempCoords[i*2+0] = coords[i*2+0]+dmx*r;
|
||||
g_tempCoords[i*2+1] = coords[i*2+1]+dmy*r;
|
||||
}
|
||||
|
||||
unsigned int colTrans = RGBA(col&0xff, (col>>8)&0xff, (col>>16)&0xff, 0);
|
||||
|
||||
glBegin(GL_TRIANGLES);
|
||||
|
||||
glColor4ubv((GLubyte*)&col);
|
||||
|
||||
for (unsigned i = 0, j = numCoords-1; i < numCoords; j=i++)
|
||||
{
|
||||
glVertex2fv(&coords[i*2]);
|
||||
glVertex2fv(&coords[j*2]);
|
||||
glColor4ubv((GLubyte*)&colTrans);
|
||||
glVertex2fv(&g_tempCoords[j*2]);
|
||||
|
||||
glVertex2fv(&g_tempCoords[j*2]);
|
||||
glVertex2fv(&g_tempCoords[i*2]);
|
||||
|
||||
glColor4ubv((GLubyte*)&col);
|
||||
glVertex2fv(&coords[i*2]);
|
||||
}
|
||||
|
||||
glColor4ubv((GLubyte*)&col);
|
||||
for (unsigned i = 2; i < numCoords; ++i)
|
||||
{
|
||||
glVertex2fv(&coords[0]);
|
||||
glVertex2fv(&coords[(i-1)*2]);
|
||||
glVertex2fv(&coords[i*2]);
|
||||
}
|
||||
|
||||
glEnd();
|
||||
}
|
||||
|
||||
static void drawRect(float x, float y, float w, float h, float fth, unsigned int col)
|
||||
{
|
||||
float verts[4*2] =
|
||||
{
|
||||
x+0.5f, y+0.5f,
|
||||
x+w-0.5f, y+0.5f,
|
||||
x+w-0.5f, y+h-0.5f,
|
||||
x+0.5f, y+h-0.5f,
|
||||
};
|
||||
drawPolygon(verts, 4, fth, col);
|
||||
}
|
||||
|
||||
/*
|
||||
static void drawEllipse(float x, float y, float w, float h, float fth, unsigned int col)
|
||||
{
|
||||
float verts[CIRCLE_VERTS*2];
|
||||
const float* cverts = g_circleVerts;
|
||||
float* v = verts;
|
||||
|
||||
for (int i = 0; i < CIRCLE_VERTS; ++i)
|
||||
{
|
||||
*v++ = x + cverts[i*2]*w;
|
||||
*v++ = y + cverts[i*2+1]*h;
|
||||
}
|
||||
|
||||
drawPolygon(verts, CIRCLE_VERTS, fth, col);
|
||||
}
|
||||
*/
|
||||
|
||||
static void drawRoundedRect(float x, float y, float w, float h, float r, float fth, unsigned int col)
|
||||
{
|
||||
const unsigned n = CIRCLE_VERTS/4;
|
||||
float verts[(n+1)*4*2];
|
||||
const float* cverts = g_circleVerts;
|
||||
float* v = verts;
|
||||
|
||||
for (unsigned i = 0; i <= n; ++i)
|
||||
{
|
||||
*v++ = x+w-r + cverts[i*2]*r;
|
||||
*v++ = y+h-r + cverts[i*2+1]*r;
|
||||
}
|
||||
|
||||
for (unsigned i = n; i <= n*2; ++i)
|
||||
{
|
||||
*v++ = x+r + cverts[i*2]*r;
|
||||
*v++ = y+h-r + cverts[i*2+1]*r;
|
||||
}
|
||||
|
||||
for (unsigned i = n*2; i <= n*3; ++i)
|
||||
{
|
||||
*v++ = x+r + cverts[i*2]*r;
|
||||
*v++ = y+r + cverts[i*2+1]*r;
|
||||
}
|
||||
|
||||
for (unsigned i = n*3; i < n*4; ++i)
|
||||
{
|
||||
*v++ = x+w-r + cverts[i*2]*r;
|
||||
*v++ = y+r + cverts[i*2+1]*r;
|
||||
}
|
||||
*v++ = x+w-r + cverts[0]*r;
|
||||
*v++ = y+r + cverts[1]*r;
|
||||
|
||||
drawPolygon(verts, (n+1)*4, fth, col);
|
||||
}
|
||||
|
||||
|
||||
static void drawLine(float x0, float y0, float x1, float y1, float r, float fth, unsigned int col)
|
||||
{
|
||||
float dx = x1-x0;
|
||||
float dy = y1-y0;
|
||||
float d = sqrtf(dx*dx+dy*dy);
|
||||
if (d > 0.0001f)
|
||||
{
|
||||
d = 1.0f/d;
|
||||
dx *= d;
|
||||
dy *= d;
|
||||
}
|
||||
float nx = dy;
|
||||
float ny = -dx;
|
||||
float verts[4*2];
|
||||
r -= fth;
|
||||
r *= 0.5f;
|
||||
if (r < 0.01f) r = 0.01f;
|
||||
dx *= r;
|
||||
dy *= r;
|
||||
nx *= r;
|
||||
ny *= r;
|
||||
|
||||
verts[0] = x0-dx-nx;
|
||||
verts[1] = y0-dy-ny;
|
||||
|
||||
verts[2] = x0-dx+nx;
|
||||
verts[3] = y0-dy+ny;
|
||||
|
||||
verts[4] = x1+dx+nx;
|
||||
verts[5] = y1+dy+ny;
|
||||
|
||||
verts[6] = x1+dx-nx;
|
||||
verts[7] = y1+dy-ny;
|
||||
|
||||
drawPolygon(verts, 4, fth, col);
|
||||
}
|
||||
|
||||
|
||||
bool imguiRenderGLInit(const char* fontpath)
|
||||
{
|
||||
for (int i = 0; i < CIRCLE_VERTS; ++i)
|
||||
{
|
||||
float a = (float)i/(float)CIRCLE_VERTS * PI*2;
|
||||
g_circleVerts[i*2+0] = cosf(a);
|
||||
g_circleVerts[i*2+1] = sinf(a);
|
||||
}
|
||||
|
||||
// Load font.
|
||||
FILE* fp = fopen(fontpath, "rb");
|
||||
if (!fp) return false;
|
||||
if (fseek(fp, 0, SEEK_END) != 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
long size = ftell(fp);
|
||||
if (size < 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
if (fseek(fp, 0, SEEK_SET) != 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char* ttfBuffer = (unsigned char*)malloc(size);
|
||||
if (!ttfBuffer)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t readLen = fread(ttfBuffer, 1, size, fp);
|
||||
fclose(fp);
|
||||
if (readLen != static_cast<size_t>(size))
|
||||
{
|
||||
free(ttfBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
fp = 0;
|
||||
|
||||
unsigned char* bmap = (unsigned char*)malloc(512*512);
|
||||
if (!bmap)
|
||||
{
|
||||
free(ttfBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
stbtt_BakeFontBitmap(ttfBuffer,0, 15.0f, bmap,512,512, 32,96, g_cdata);
|
||||
|
||||
// can free ttf_buffer at this point
|
||||
glGenTextures(1, &g_ftex);
|
||||
glBindTexture(GL_TEXTURE_2D, g_ftex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, bmap);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
free(ttfBuffer);
|
||||
free(bmap);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void imguiRenderGLDestroy()
|
||||
{
|
||||
if (g_ftex)
|
||||
{
|
||||
glDeleteTextures(1, &g_ftex);
|
||||
g_ftex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void getBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index,
|
||||
float *xpos, float *ypos, stbtt_aligned_quad *q)
|
||||
{
|
||||
stbtt_bakedchar *b = chardata + char_index;
|
||||
int round_x = STBTT_ifloor(*xpos + b->xoff);
|
||||
int round_y = STBTT_ifloor(*ypos - b->yoff);
|
||||
|
||||
q->x0 = (float)round_x;
|
||||
q->y0 = (float)round_y;
|
||||
q->x1 = (float)round_x + b->x1 - b->x0;
|
||||
q->y1 = (float)round_y - b->y1 + b->y0;
|
||||
|
||||
q->s0 = b->x0 / (float)pw;
|
||||
q->t0 = b->y0 / (float)pw;
|
||||
q->s1 = b->x1 / (float)ph;
|
||||
q->t1 = b->y1 / (float)ph;
|
||||
|
||||
*xpos += b->xadvance;
|
||||
}
|
||||
|
||||
static const float g_tabStops[4] = {150, 210, 270, 330};
|
||||
|
||||
static float getTextLength(stbtt_bakedchar *chardata, const char* text)
|
||||
{
|
||||
float xpos = 0;
|
||||
float len = 0;
|
||||
while (*text)
|
||||
{
|
||||
int c = (unsigned char)*text;
|
||||
if (c == '\t')
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (xpos < g_tabStops[i])
|
||||
{
|
||||
xpos = g_tabStops[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c >= 32 && c < 128)
|
||||
{
|
||||
stbtt_bakedchar *b = chardata + c-32;
|
||||
int round_x = STBTT_ifloor((xpos + b->xoff) + 0.5);
|
||||
len = round_x + b->x1 - b->x0 + 0.5f;
|
||||
xpos += b->xadvance;
|
||||
}
|
||||
++text;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static void drawText(float x, float y, const char *text, int align, unsigned int col)
|
||||
{
|
||||
if (!g_ftex) return;
|
||||
if (!text) return;
|
||||
|
||||
if (align == IMGUI_ALIGN_CENTER)
|
||||
x -= getTextLength(g_cdata, text)/2;
|
||||
else if (align == IMGUI_ALIGN_RIGHT)
|
||||
x -= getTextLength(g_cdata, text);
|
||||
|
||||
glColor4ub(col&0xff, (col>>8)&0xff, (col>>16)&0xff, (col>>24)&0xff);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
// assume orthographic projection with units = screen pixels, origin at top left
|
||||
glBindTexture(GL_TEXTURE_2D, g_ftex);
|
||||
|
||||
glBegin(GL_TRIANGLES);
|
||||
|
||||
const float ox = x;
|
||||
|
||||
while (*text)
|
||||
{
|
||||
int c = (unsigned char)*text;
|
||||
if (c == '\t')
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (x < g_tabStops[i]+ox)
|
||||
{
|
||||
x = g_tabStops[i]+ox;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c >= 32 && c < 128)
|
||||
{
|
||||
stbtt_aligned_quad q;
|
||||
getBakedQuad(g_cdata, 512,512, c-32, &x,&y,&q);
|
||||
|
||||
glTexCoord2f(q.s0, q.t0);
|
||||
glVertex2f(q.x0, q.y0);
|
||||
glTexCoord2f(q.s1, q.t1);
|
||||
glVertex2f(q.x1, q.y1);
|
||||
glTexCoord2f(q.s1, q.t0);
|
||||
glVertex2f(q.x1, q.y0);
|
||||
|
||||
glTexCoord2f(q.s0, q.t0);
|
||||
glVertex2f(q.x0, q.y0);
|
||||
glTexCoord2f(q.s0, q.t1);
|
||||
glVertex2f(q.x0, q.y1);
|
||||
glTexCoord2f(q.s1, q.t1);
|
||||
glVertex2f(q.x1, q.y1);
|
||||
}
|
||||
++text;
|
||||
}
|
||||
|
||||
glEnd();
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
|
||||
void imguiRenderGLDraw()
|
||||
{
|
||||
const imguiGfxCmd* q = imguiGetRenderQueue();
|
||||
int nq = imguiGetRenderQueueSize();
|
||||
|
||||
const float s = 1.0f/8.0f;
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
for (int i = 0; i < nq; ++i)
|
||||
{
|
||||
const imguiGfxCmd& cmd = q[i];
|
||||
if (cmd.type == IMGUI_GFXCMD_RECT)
|
||||
{
|
||||
if (cmd.rect.r == 0)
|
||||
{
|
||||
drawRect((float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f,
|
||||
(float)cmd.rect.w*s-1, (float)cmd.rect.h*s-1,
|
||||
1.0f, cmd.col);
|
||||
}
|
||||
else
|
||||
{
|
||||
drawRoundedRect((float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f,
|
||||
(float)cmd.rect.w*s-1, (float)cmd.rect.h*s-1,
|
||||
(float)cmd.rect.r*s, 1.0f, cmd.col);
|
||||
}
|
||||
}
|
||||
else if (cmd.type == IMGUI_GFXCMD_LINE)
|
||||
{
|
||||
drawLine(cmd.line.x0*s, cmd.line.y0*s, cmd.line.x1*s, cmd.line.y1*s, cmd.line.r*s, 1.0f, cmd.col);
|
||||
}
|
||||
else if (cmd.type == IMGUI_GFXCMD_TRIANGLE)
|
||||
{
|
||||
if (cmd.flags == 1)
|
||||
{
|
||||
const float verts[3*2] =
|
||||
{
|
||||
(float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f,
|
||||
(float)cmd.rect.x*s+0.5f+(float)cmd.rect.w*s-1, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s/2-0.5f,
|
||||
(float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s-1,
|
||||
};
|
||||
drawPolygon(verts, 3, 1.0f, cmd.col);
|
||||
}
|
||||
if (cmd.flags == 2)
|
||||
{
|
||||
const float verts[3*2] =
|
||||
{
|
||||
(float)cmd.rect.x*s+0.5f, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s-1,
|
||||
(float)cmd.rect.x*s+0.5f+(float)cmd.rect.w*s/2-0.5f, (float)cmd.rect.y*s+0.5f,
|
||||
(float)cmd.rect.x*s+0.5f+(float)cmd.rect.w*s-1, (float)cmd.rect.y*s+0.5f+(float)cmd.rect.h*s-1,
|
||||
};
|
||||
drawPolygon(verts, 3, 1.0f, cmd.col);
|
||||
}
|
||||
}
|
||||
else if (cmd.type == IMGUI_GFXCMD_TEXT)
|
||||
{
|
||||
drawText(cmd.text.x, cmd.text.y, cmd.text.text, cmd.text.align, cmd.col);
|
||||
}
|
||||
else if (cmd.type == IMGUI_GFXCMD_SCISSOR)
|
||||
{
|
||||
if (cmd.flags)
|
||||
{
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h);
|
||||
}
|
||||
else
|
||||
{
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
@@ -0,0 +1,927 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.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.
|
||||
//
|
||||
|
||||
#include <cstdio>
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_opengl.h"
|
||||
#ifdef __APPLE__
|
||||
# include <OpenGL/glu.h>
|
||||
#else
|
||||
# include <GL/glu.h>
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imguiRenderGL.h"
|
||||
|
||||
#include "Recast.h"
|
||||
#include "RecastDebugDraw.h"
|
||||
#include "InputGeom.h"
|
||||
#include "TestCase.h"
|
||||
#include "Filelist.h"
|
||||
#include "Sample_SoloMesh.h"
|
||||
#include "Sample_TileMesh.h"
|
||||
#include "Sample_TempObstacles.h"
|
||||
#include "Sample_Debug.h"
|
||||
|
||||
#ifdef WIN32
|
||||
# define snprintf _snprintf
|
||||
# define putenv _putenv
|
||||
#endif
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
struct SampleItem
|
||||
{
|
||||
Sample* (*create)();
|
||||
const string name;
|
||||
};
|
||||
Sample* createSolo() { return new Sample_SoloMesh(); }
|
||||
Sample* createTile() { return new Sample_TileMesh(); }
|
||||
Sample* createTempObstacle() { return new Sample_TempObstacles(); }
|
||||
Sample* createDebug() { return new Sample_Debug(); }
|
||||
static SampleItem g_samples[] =
|
||||
{
|
||||
{ createSolo, "Solo Mesh" },
|
||||
{ createTile, "Tile Mesh" },
|
||||
{ createTempObstacle, "Temp Obstacles" },
|
||||
};
|
||||
static const int g_nsamples = sizeof(g_samples) / sizeof(SampleItem);
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/)
|
||||
{
|
||||
// Init SDL
|
||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
|
||||
{
|
||||
printf("Could not initialise SDL.\nError: %s\n", SDL_GetError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Enable depth buffer.
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
|
||||
// Set color channel depth.
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
|
||||
|
||||
// 4x MSAA.
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
|
||||
|
||||
SDL_DisplayMode displayMode;
|
||||
SDL_GetCurrentDisplayMode(0, &displayMode);
|
||||
|
||||
bool presentationMode = false;
|
||||
Uint32 flags = SDL_WINDOW_OPENGL;
|
||||
int width;
|
||||
int height;
|
||||
if (presentationMode)
|
||||
{
|
||||
// Create a fullscreen window at the native resolution.
|
||||
width = displayMode.w;
|
||||
height = displayMode.h;
|
||||
flags |= SDL_WINDOW_FULLSCREEN;
|
||||
}
|
||||
else
|
||||
{
|
||||
float aspect = 16.0f / 9.0f;
|
||||
width = rcMin(displayMode.w, (int)(displayMode.h * aspect)) - 80;
|
||||
height = displayMode.h - 80;
|
||||
}
|
||||
|
||||
SDL_Window* window;
|
||||
SDL_Renderer* renderer;
|
||||
int errorCode = SDL_CreateWindowAndRenderer(width, height, flags, &window, &renderer);
|
||||
|
||||
if (errorCode != 0 || !window || !renderer)
|
||||
{
|
||||
printf("Could not initialise SDL opengl\nError: %s\n", SDL_GetError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
SDL_GL_CreateContext(window);
|
||||
|
||||
if (!imguiRenderGLInit("DroidSans.ttf"))
|
||||
{
|
||||
printf("Could not init GUI renderer.\n");
|
||||
SDL_Quit();
|
||||
return -1;
|
||||
}
|
||||
|
||||
float t = 0.0f;
|
||||
float timeAcc = 0.0f;
|
||||
Uint32 prevFrameTime = SDL_GetTicks();
|
||||
int mousePos[2] = {0, 0};
|
||||
int origMousePos[2] = {0, 0}; // Used to compute mouse movement totals across frames.
|
||||
|
||||
float cameraEulers[] = {45, -45};
|
||||
float cameraPos[] = {0, 0, 0};
|
||||
float camr = 1000;
|
||||
float origCameraEulers[] = {0, 0}; // Used to compute rotational changes across frames.
|
||||
|
||||
float moveFront = 0.0f, moveBack = 0.0f, moveLeft = 0.0f, moveRight = 0.0f, moveUp = 0.0f, moveDown = 0.0f;
|
||||
|
||||
float scrollZoom = 0;
|
||||
bool rotate = false;
|
||||
bool movedDuringRotate = false;
|
||||
float rayStart[3];
|
||||
float rayEnd[3];
|
||||
bool mouseOverMenu = false;
|
||||
|
||||
bool showMenu = !presentationMode;
|
||||
bool showLog = false;
|
||||
bool showTools = true;
|
||||
bool showLevels = false;
|
||||
bool showSample = false;
|
||||
bool showTestCases = false;
|
||||
|
||||
// Window scroll positions.
|
||||
int propScroll = 0;
|
||||
int logScroll = 0;
|
||||
int toolsScroll = 0;
|
||||
|
||||
string sampleName = "Choose Sample...";
|
||||
|
||||
vector<string> files;
|
||||
const string meshesFolder = "Meshes";
|
||||
string meshName = "Choose Mesh...";
|
||||
|
||||
float markerPosition[3] = {0, 0, 0};
|
||||
bool markerPositionSet = false;
|
||||
|
||||
InputGeom* geom = 0;
|
||||
Sample* sample = 0;
|
||||
|
||||
const string testCasesFolder = "TestCases";
|
||||
TestCase* test = 0;
|
||||
|
||||
BuildContext ctx;
|
||||
|
||||
// Fog.
|
||||
float fogColor[4] = { 0.32f, 0.31f, 0.30f, 1.0f };
|
||||
glEnable(GL_FOG);
|
||||
glFogi(GL_FOG_MODE, GL_LINEAR);
|
||||
glFogf(GL_FOG_START, camr * 0.1f);
|
||||
glFogf(GL_FOG_END, camr * 1.25f);
|
||||
glFogfv(GL_FOG_COLOR, fogColor);
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
|
||||
bool done = false;
|
||||
while(!done)
|
||||
{
|
||||
// Handle input events.
|
||||
int mouseScroll = 0;
|
||||
bool processHitTest = false;
|
||||
bool processHitTestShift = false;
|
||||
SDL_Event event;
|
||||
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_KEYDOWN:
|
||||
// Handle any key presses here.
|
||||
if (event.key.keysym.sym == SDLK_ESCAPE)
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_t)
|
||||
{
|
||||
showLevels = false;
|
||||
showSample = false;
|
||||
showTestCases = true;
|
||||
scanDirectory(testCasesFolder, ".txt", files);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_TAB)
|
||||
{
|
||||
showMenu = !showMenu;
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_SPACE)
|
||||
{
|
||||
if (sample)
|
||||
sample->handleToggle();
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_1)
|
||||
{
|
||||
if (sample)
|
||||
sample->handleStep();
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_9)
|
||||
{
|
||||
if (sample && geom)
|
||||
{
|
||||
string savePath = meshesFolder + "/";
|
||||
BuildSettings settings;
|
||||
memset(&settings, 0, sizeof(settings));
|
||||
|
||||
rcVcopy(settings.navMeshBMin, geom->getNavMeshBoundsMin());
|
||||
rcVcopy(settings.navMeshBMax, geom->getNavMeshBoundsMax());
|
||||
|
||||
sample->collectSettings(settings);
|
||||
|
||||
geom->saveGeomSet(&settings);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
if (event.wheel.y < 0)
|
||||
{
|
||||
// wheel down
|
||||
if (mouseOverMenu)
|
||||
{
|
||||
mouseScroll++;
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollZoom += 1.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mouseOverMenu)
|
||||
{
|
||||
mouseScroll--;
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollZoom -= 1.0f;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
if (event.button.button == SDL_BUTTON_RIGHT)
|
||||
{
|
||||
if (!mouseOverMenu)
|
||||
{
|
||||
// Rotate view
|
||||
rotate = true;
|
||||
movedDuringRotate = false;
|
||||
origMousePos[0] = mousePos[0];
|
||||
origMousePos[1] = mousePos[1];
|
||||
origCameraEulers[0] = cameraEulers[0];
|
||||
origCameraEulers[1] = cameraEulers[1];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
// Handle mouse clicks here.
|
||||
if (event.button.button == SDL_BUTTON_RIGHT)
|
||||
{
|
||||
rotate = false;
|
||||
if (!mouseOverMenu)
|
||||
{
|
||||
if (!movedDuringRotate)
|
||||
{
|
||||
processHitTest = true;
|
||||
processHitTestShift = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (event.button.button == SDL_BUTTON_LEFT)
|
||||
{
|
||||
if (!mouseOverMenu)
|
||||
{
|
||||
processHitTest = true;
|
||||
processHitTestShift = (SDL_GetModState() & KMOD_SHIFT) ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
mousePos[0] = event.motion.x;
|
||||
mousePos[1] = height-1 - event.motion.y;
|
||||
|
||||
if (rotate)
|
||||
{
|
||||
int dx = mousePos[0] - origMousePos[0];
|
||||
int dy = mousePos[1] - origMousePos[1];
|
||||
cameraEulers[0] = origCameraEulers[0] - dy * 0.25f;
|
||||
cameraEulers[1] = origCameraEulers[1] + dx * 0.25f;
|
||||
if (dx * dx + dy * dy > 3 * 3)
|
||||
{
|
||||
movedDuringRotate = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_QUIT:
|
||||
done = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char mouseButtonMask = 0;
|
||||
if (SDL_GetMouseState(0, 0) & SDL_BUTTON_LMASK)
|
||||
mouseButtonMask |= IMGUI_MBUT_LEFT;
|
||||
if (SDL_GetMouseState(0, 0) & SDL_BUTTON_RMASK)
|
||||
mouseButtonMask |= IMGUI_MBUT_RIGHT;
|
||||
|
||||
Uint32 time = SDL_GetTicks();
|
||||
float dt = (time - prevFrameTime) / 1000.0f;
|
||||
prevFrameTime = time;
|
||||
|
||||
t += dt;
|
||||
|
||||
// Hit test mesh.
|
||||
if (processHitTest && geom && sample)
|
||||
{
|
||||
float hitTime;
|
||||
bool hit = geom->raycastMesh(rayStart, rayEnd, hitTime);
|
||||
|
||||
if (hit)
|
||||
{
|
||||
if (SDL_GetModState() & KMOD_CTRL)
|
||||
{
|
||||
// Marker
|
||||
markerPositionSet = true;
|
||||
markerPosition[0] = rayStart[0] + (rayEnd[0] - rayStart[0]) * hitTime;
|
||||
markerPosition[1] = rayStart[1] + (rayEnd[1] - rayStart[1]) * hitTime;
|
||||
markerPosition[2] = rayStart[2] + (rayEnd[2] - rayStart[2]) * hitTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
float pos[3];
|
||||
pos[0] = rayStart[0] + (rayEnd[0] - rayStart[0]) * hitTime;
|
||||
pos[1] = rayStart[1] + (rayEnd[1] - rayStart[1]) * hitTime;
|
||||
pos[2] = rayStart[2] + (rayEnd[2] - rayStart[2]) * hitTime;
|
||||
sample->handleClick(rayStart, pos, processHitTestShift);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SDL_GetModState() & KMOD_CTRL)
|
||||
{
|
||||
// Marker
|
||||
markerPositionSet = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update sample simulation.
|
||||
const float SIM_RATE = 20;
|
||||
const float DELTA_TIME = 1.0f / SIM_RATE;
|
||||
timeAcc = rcClamp(timeAcc + dt, -1.0f, 1.0f);
|
||||
int simIter = 0;
|
||||
while (timeAcc > DELTA_TIME)
|
||||
{
|
||||
timeAcc -= DELTA_TIME;
|
||||
if (simIter < 5 && sample)
|
||||
{
|
||||
sample->handleUpdate(DELTA_TIME);
|
||||
}
|
||||
simIter++;
|
||||
}
|
||||
|
||||
// Clamp the framerate so that we do not hog all the CPU.
|
||||
const float MIN_FRAME_TIME = 1.0f / 40.0f;
|
||||
if (dt < MIN_FRAME_TIME)
|
||||
{
|
||||
int ms = (int)((MIN_FRAME_TIME - dt) * 1000.0f);
|
||||
if (ms > 10) ms = 10;
|
||||
if (ms >= 0) SDL_Delay(ms);
|
||||
}
|
||||
|
||||
// Set the viewport.
|
||||
glViewport(0, 0, width, height);
|
||||
GLint viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
|
||||
// Clear the screen
|
||||
glClearColor(0.3f, 0.3f, 0.32f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
// Compute the projection matrix.
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluPerspective(50.0f, (float)width/(float)height, 1.0f, camr);
|
||||
GLdouble projectionMatrix[16];
|
||||
glGetDoublev(GL_PROJECTION_MATRIX, projectionMatrix);
|
||||
|
||||
// Compute the modelview matrix.
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
glRotatef(cameraEulers[0], 1, 0, 0);
|
||||
glRotatef(cameraEulers[1], 0, 1, 0);
|
||||
glTranslatef(-cameraPos[0], -cameraPos[1], -cameraPos[2]);
|
||||
GLdouble modelviewMatrix[16];
|
||||
glGetDoublev(GL_MODELVIEW_MATRIX, modelviewMatrix);
|
||||
|
||||
// Get hit ray position and direction.
|
||||
GLdouble x, y, z;
|
||||
gluUnProject(mousePos[0], mousePos[1], 0.0f, modelviewMatrix, projectionMatrix, viewport, &x, &y, &z);
|
||||
rayStart[0] = (float)x;
|
||||
rayStart[1] = (float)y;
|
||||
rayStart[2] = (float)z;
|
||||
gluUnProject(mousePos[0], mousePos[1], 1.0f, modelviewMatrix, projectionMatrix, viewport, &x, &y, &z);
|
||||
rayEnd[0] = (float)x;
|
||||
rayEnd[1] = (float)y;
|
||||
rayEnd[2] = (float)z;
|
||||
|
||||
// Handle keyboard movement.
|
||||
const Uint8* keystate = SDL_GetKeyboardState(NULL);
|
||||
moveFront = rcClamp(moveFront + dt * 4 * ((keystate[SDL_SCANCODE_W] || keystate[SDL_SCANCODE_UP ]) ? 1 : -1), 0.0f, 1.0f);
|
||||
moveLeft = rcClamp(moveLeft + dt * 4 * ((keystate[SDL_SCANCODE_A] || keystate[SDL_SCANCODE_LEFT ]) ? 1 : -1), 0.0f, 1.0f);
|
||||
moveBack = rcClamp(moveBack + dt * 4 * ((keystate[SDL_SCANCODE_S] || keystate[SDL_SCANCODE_DOWN ]) ? 1 : -1), 0.0f, 1.0f);
|
||||
moveRight = rcClamp(moveRight + dt * 4 * ((keystate[SDL_SCANCODE_D] || keystate[SDL_SCANCODE_RIGHT ]) ? 1 : -1), 0.0f, 1.0f);
|
||||
moveUp = rcClamp(moveUp + dt * 4 * ((keystate[SDL_SCANCODE_Q] || keystate[SDL_SCANCODE_PAGEUP ]) ? 1 : -1), 0.0f, 1.0f);
|
||||
moveDown = rcClamp(moveDown + dt * 4 * ((keystate[SDL_SCANCODE_E] || keystate[SDL_SCANCODE_PAGEDOWN ]) ? 1 : -1), 0.0f, 1.0f);
|
||||
|
||||
float keybSpeed = 22.0f;
|
||||
if (SDL_GetModState() & KMOD_SHIFT)
|
||||
{
|
||||
keybSpeed *= 4.0f;
|
||||
}
|
||||
|
||||
float movex = (moveRight - moveLeft) * keybSpeed * dt;
|
||||
float movey = (moveBack - moveFront) * keybSpeed * dt + scrollZoom * 2.0f;
|
||||
scrollZoom = 0;
|
||||
|
||||
cameraPos[0] += movex * (float)modelviewMatrix[0];
|
||||
cameraPos[1] += movex * (float)modelviewMatrix[4];
|
||||
cameraPos[2] += movex * (float)modelviewMatrix[8];
|
||||
|
||||
cameraPos[0] += movey * (float)modelviewMatrix[2];
|
||||
cameraPos[1] += movey * (float)modelviewMatrix[6];
|
||||
cameraPos[2] += movey * (float)modelviewMatrix[10];
|
||||
|
||||
cameraPos[1] += (moveUp - moveDown) * keybSpeed * dt;
|
||||
|
||||
glEnable(GL_FOG);
|
||||
|
||||
if (sample)
|
||||
sample->handleRender();
|
||||
if (test)
|
||||
test->handleRender();
|
||||
|
||||
glDisable(GL_FOG);
|
||||
|
||||
// Render GUI
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluOrtho2D(0, width, 0, height);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
mouseOverMenu = false;
|
||||
|
||||
imguiBeginFrame(mousePos[0], mousePos[1], mouseButtonMask, mouseScroll);
|
||||
|
||||
if (sample)
|
||||
{
|
||||
sample->handleRenderOverlay((double*)projectionMatrix, (double*)modelviewMatrix, (int*)viewport);
|
||||
}
|
||||
if (test)
|
||||
{
|
||||
if (test->handleRenderOverlay((double*)projectionMatrix, (double*)modelviewMatrix, (int*)viewport))
|
||||
mouseOverMenu = true;
|
||||
}
|
||||
|
||||
// Help text.
|
||||
if (showMenu)
|
||||
{
|
||||
const char msg[] = "W/S/A/D: Move RMB: Rotate";
|
||||
imguiDrawText(280, height-20, IMGUI_ALIGN_LEFT, msg, imguiRGBA(255,255,255,128));
|
||||
}
|
||||
|
||||
if (showMenu)
|
||||
{
|
||||
if (imguiBeginScrollArea("Properties", width-250-10, 10, 250, height-20, &propScroll))
|
||||
mouseOverMenu = true;
|
||||
|
||||
if (imguiCheck("Show Log", showLog))
|
||||
showLog = !showLog;
|
||||
if (imguiCheck("Show Tools", showTools))
|
||||
showTools = !showTools;
|
||||
|
||||
imguiSeparator();
|
||||
imguiLabel("Sample");
|
||||
if (imguiButton(sampleName.c_str()))
|
||||
{
|
||||
if (showSample)
|
||||
{
|
||||
showSample = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
showSample = true;
|
||||
showLevels = false;
|
||||
showTestCases = false;
|
||||
}
|
||||
}
|
||||
|
||||
imguiSeparator();
|
||||
imguiLabel("Input Mesh");
|
||||
if (imguiButton(meshName.c_str()))
|
||||
{
|
||||
if (showLevels)
|
||||
{
|
||||
showLevels = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
showSample = false;
|
||||
showTestCases = false;
|
||||
showLevels = true;
|
||||
scanDirectory(meshesFolder, ".obj", files);
|
||||
scanDirectoryAppend(meshesFolder, ".gset", files);
|
||||
}
|
||||
}
|
||||
if (geom)
|
||||
{
|
||||
char text[64];
|
||||
snprintf(text, 64, "Verts: %.1fk Tris: %.1fk",
|
||||
geom->getMesh()->getVertCount()/1000.0f,
|
||||
geom->getMesh()->getTriCount()/1000.0f);
|
||||
imguiValue(text);
|
||||
}
|
||||
imguiSeparator();
|
||||
|
||||
if (geom && sample)
|
||||
{
|
||||
imguiSeparatorLine();
|
||||
|
||||
sample->handleSettings();
|
||||
|
||||
if (imguiButton("Build"))
|
||||
{
|
||||
ctx.resetLog();
|
||||
if (!sample->handleBuild())
|
||||
{
|
||||
showLog = true;
|
||||
logScroll = 0;
|
||||
}
|
||||
ctx.dumpLog("Build log %s:", meshName.c_str());
|
||||
|
||||
// Clear test.
|
||||
delete test;
|
||||
test = 0;
|
||||
}
|
||||
|
||||
imguiSeparator();
|
||||
}
|
||||
|
||||
if (sample)
|
||||
{
|
||||
imguiSeparatorLine();
|
||||
sample->handleDebugMode();
|
||||
}
|
||||
|
||||
imguiEndScrollArea();
|
||||
}
|
||||
|
||||
// Sample selection dialog.
|
||||
if (showSample)
|
||||
{
|
||||
static int levelScroll = 0;
|
||||
if (imguiBeginScrollArea("Choose Sample", width-10-250-10-200, height-10-250, 200, 250, &levelScroll))
|
||||
mouseOverMenu = true;
|
||||
|
||||
Sample* newSample = 0;
|
||||
for (int i = 0; i < g_nsamples; ++i)
|
||||
{
|
||||
if (imguiItem(g_samples[i].name.c_str()))
|
||||
{
|
||||
newSample = g_samples[i].create();
|
||||
if (newSample)
|
||||
sampleName = g_samples[i].name;
|
||||
}
|
||||
}
|
||||
if (newSample)
|
||||
{
|
||||
delete sample;
|
||||
sample = newSample;
|
||||
sample->setContext(&ctx);
|
||||
if (geom)
|
||||
{
|
||||
sample->handleMeshChanged(geom);
|
||||
}
|
||||
showSample = false;
|
||||
}
|
||||
|
||||
if (geom || sample)
|
||||
{
|
||||
const float* bmin = 0;
|
||||
const float* bmax = 0;
|
||||
if (geom)
|
||||
{
|
||||
bmin = geom->getNavMeshBoundsMin();
|
||||
bmax = geom->getNavMeshBoundsMax();
|
||||
}
|
||||
// Reset camera and fog to match the mesh bounds.
|
||||
if (bmin && bmax)
|
||||
{
|
||||
camr = sqrtf(rcSqr(bmax[0]-bmin[0]) +
|
||||
rcSqr(bmax[1]-bmin[1]) +
|
||||
rcSqr(bmax[2]-bmin[2])) / 2;
|
||||
cameraPos[0] = (bmax[0] + bmin[0]) / 2 + camr;
|
||||
cameraPos[1] = (bmax[1] + bmin[1]) / 2 + camr;
|
||||
cameraPos[2] = (bmax[2] + bmin[2]) / 2 + camr;
|
||||
camr *= 3;
|
||||
}
|
||||
cameraEulers[0] = 45;
|
||||
cameraEulers[1] = -45;
|
||||
glFogf(GL_FOG_START, camr*0.1f);
|
||||
glFogf(GL_FOG_END, camr*1.25f);
|
||||
}
|
||||
|
||||
imguiEndScrollArea();
|
||||
}
|
||||
|
||||
// Level selection dialog.
|
||||
if (showLevels)
|
||||
{
|
||||
static int levelScroll = 0;
|
||||
if (imguiBeginScrollArea("Choose Level", width - 10 - 250 - 10 - 200, height - 10 - 450, 200, 450, &levelScroll))
|
||||
mouseOverMenu = true;
|
||||
|
||||
vector<string>::const_iterator fileIter = files.begin();
|
||||
vector<string>::const_iterator filesEnd = files.end();
|
||||
vector<string>::const_iterator levelToLoad = filesEnd;
|
||||
for (; fileIter != filesEnd; ++fileIter)
|
||||
{
|
||||
if (imguiItem(fileIter->c_str()))
|
||||
{
|
||||
levelToLoad = fileIter;
|
||||
}
|
||||
}
|
||||
|
||||
if (levelToLoad != filesEnd)
|
||||
{
|
||||
meshName = *levelToLoad;
|
||||
showLevels = false;
|
||||
|
||||
delete geom;
|
||||
geom = 0;
|
||||
|
||||
string path = meshesFolder + "/" + meshName;
|
||||
|
||||
geom = new InputGeom;
|
||||
if (!geom->load(&ctx, path))
|
||||
{
|
||||
delete geom;
|
||||
geom = 0;
|
||||
|
||||
// Destroy the sample if it already had geometry loaded, as we've just deleted it!
|
||||
if (sample && sample->getInputGeom())
|
||||
{
|
||||
delete sample;
|
||||
sample = 0;
|
||||
}
|
||||
|
||||
showLog = true;
|
||||
logScroll = 0;
|
||||
ctx.dumpLog("Geom load log %s:", meshName.c_str());
|
||||
}
|
||||
if (sample && geom)
|
||||
{
|
||||
sample->handleMeshChanged(geom);
|
||||
}
|
||||
|
||||
if (geom || sample)
|
||||
{
|
||||
const float* bmin = 0;
|
||||
const float* bmax = 0;
|
||||
if (geom)
|
||||
{
|
||||
bmin = geom->getNavMeshBoundsMin();
|
||||
bmax = geom->getNavMeshBoundsMax();
|
||||
}
|
||||
// Reset camera and fog to match the mesh bounds.
|
||||
if (bmin && bmax)
|
||||
{
|
||||
camr = sqrtf(rcSqr(bmax[0]-bmin[0]) +
|
||||
rcSqr(bmax[1]-bmin[1]) +
|
||||
rcSqr(bmax[2]-bmin[2])) / 2;
|
||||
cameraPos[0] = (bmax[0] + bmin[0]) / 2 + camr;
|
||||
cameraPos[1] = (bmax[1] + bmin[1]) / 2 + camr;
|
||||
cameraPos[2] = (bmax[2] + bmin[2]) / 2 + camr;
|
||||
camr *= 3;
|
||||
}
|
||||
cameraEulers[0] = 45;
|
||||
cameraEulers[1] = -45;
|
||||
glFogf(GL_FOG_START, camr * 0.1f);
|
||||
glFogf(GL_FOG_END, camr * 1.25f);
|
||||
}
|
||||
}
|
||||
|
||||
imguiEndScrollArea();
|
||||
|
||||
}
|
||||
|
||||
// Test cases
|
||||
if (showTestCases)
|
||||
{
|
||||
static int testScroll = 0;
|
||||
if (imguiBeginScrollArea("Choose Test To Run", width-10-250-10-200, height-10-450, 200, 450, &testScroll))
|
||||
mouseOverMenu = true;
|
||||
|
||||
vector<string>::const_iterator fileIter = files.begin();
|
||||
vector<string>::const_iterator filesEnd = files.end();
|
||||
vector<string>::const_iterator testToLoad = filesEnd;
|
||||
for (; fileIter != filesEnd; ++fileIter)
|
||||
{
|
||||
if (imguiItem(fileIter->c_str()))
|
||||
{
|
||||
testToLoad = fileIter;
|
||||
}
|
||||
}
|
||||
|
||||
if (testToLoad != filesEnd)
|
||||
{
|
||||
string path = testCasesFolder + "/" + *testToLoad;
|
||||
test = new TestCase;
|
||||
if (test)
|
||||
{
|
||||
// Load the test.
|
||||
if (!test->load(path))
|
||||
{
|
||||
delete test;
|
||||
test = 0;
|
||||
}
|
||||
|
||||
// Create sample
|
||||
Sample* newSample = 0;
|
||||
for (int i = 0; i < g_nsamples; ++i)
|
||||
{
|
||||
if (g_samples[i].name == test->getSampleName())
|
||||
{
|
||||
newSample = g_samples[i].create();
|
||||
if (newSample)
|
||||
sampleName = g_samples[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
delete sample;
|
||||
sample = newSample;
|
||||
|
||||
if (sample)
|
||||
{
|
||||
sample->setContext(&ctx);
|
||||
showSample = false;
|
||||
}
|
||||
|
||||
// Load geom.
|
||||
meshName = test->getGeomFileName();
|
||||
|
||||
|
||||
path = meshesFolder + "/" + meshName;
|
||||
|
||||
delete geom;
|
||||
geom = new InputGeom;
|
||||
if (!geom || !geom->load(&ctx, path))
|
||||
{
|
||||
delete geom;
|
||||
geom = 0;
|
||||
delete sample;
|
||||
sample = 0;
|
||||
showLog = true;
|
||||
logScroll = 0;
|
||||
ctx.dumpLog("Geom load log %s:", meshName.c_str());
|
||||
}
|
||||
if (sample && geom)
|
||||
{
|
||||
sample->handleMeshChanged(geom);
|
||||
}
|
||||
|
||||
// This will ensure that tile & poly bits are updated in tiled sample.
|
||||
if (sample)
|
||||
sample->handleSettings();
|
||||
|
||||
ctx.resetLog();
|
||||
if (sample && !sample->handleBuild())
|
||||
{
|
||||
ctx.dumpLog("Build log %s:", meshName.c_str());
|
||||
}
|
||||
|
||||
if (geom || sample)
|
||||
{
|
||||
const float* bmin = 0;
|
||||
const float* bmax = 0;
|
||||
if (geom)
|
||||
{
|
||||
bmin = geom->getNavMeshBoundsMin();
|
||||
bmax = geom->getNavMeshBoundsMax();
|
||||
}
|
||||
// Reset camera and fog to match the mesh bounds.
|
||||
if (bmin && bmax)
|
||||
{
|
||||
camr = sqrtf(rcSqr(bmax[0] - bmin[0]) +
|
||||
rcSqr(bmax[1] - bmin[1]) +
|
||||
rcSqr(bmax[2] - bmin[2])) / 2;
|
||||
cameraPos[0] = (bmax[0] + bmin[0]) / 2 + camr;
|
||||
cameraPos[1] = (bmax[1] + bmin[1]) / 2 + camr;
|
||||
cameraPos[2] = (bmax[2] + bmin[2]) / 2 + camr;
|
||||
camr *= 3;
|
||||
}
|
||||
cameraEulers[0] = 45;
|
||||
cameraEulers[1] = -45;
|
||||
glFogf(GL_FOG_START, camr * 0.2f);
|
||||
glFogf(GL_FOG_END, camr * 1.25f);
|
||||
}
|
||||
|
||||
// Do the tests.
|
||||
if (sample)
|
||||
test->doTests(sample->getNavMesh(), sample->getNavMeshQuery());
|
||||
}
|
||||
}
|
||||
|
||||
imguiEndScrollArea();
|
||||
}
|
||||
|
||||
|
||||
// Log
|
||||
if (showLog && showMenu)
|
||||
{
|
||||
if (imguiBeginScrollArea("Log", 250 + 20, 10, width - 300 - 250, 200, &logScroll))
|
||||
mouseOverMenu = true;
|
||||
for (int i = 0; i < ctx.getLogCount(); ++i)
|
||||
imguiLabel(ctx.getLogText(i));
|
||||
imguiEndScrollArea();
|
||||
}
|
||||
|
||||
// Left column tools menu
|
||||
if (!showTestCases && showTools && showMenu) // && geom && sample)
|
||||
{
|
||||
if (imguiBeginScrollArea("Tools", 10, 10, 250, height - 20, &toolsScroll))
|
||||
mouseOverMenu = true;
|
||||
|
||||
if (sample)
|
||||
sample->handleTools();
|
||||
|
||||
imguiEndScrollArea();
|
||||
}
|
||||
|
||||
// Marker
|
||||
if (markerPositionSet && gluProject((GLdouble)markerPosition[0], (GLdouble)markerPosition[1], (GLdouble)markerPosition[2],
|
||||
modelviewMatrix, projectionMatrix, viewport, &x, &y, &z))
|
||||
{
|
||||
// Draw marker circle
|
||||
glLineWidth(5.0f);
|
||||
glColor4ub(240,220,0,196);
|
||||
glBegin(GL_LINE_LOOP);
|
||||
const float r = 25.0f;
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
const float a = (float)i / 20.0f * RC_PI*2;
|
||||
const float fx = (float)x + cosf(a)*r;
|
||||
const float fy = (float)y + sinf(a)*r;
|
||||
glVertex2f(fx,fy);
|
||||
}
|
||||
glEnd();
|
||||
glLineWidth(1.0f);
|
||||
}
|
||||
|
||||
imguiEndFrame();
|
||||
imguiRenderGLDraw();
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
|
||||
imguiRenderGLDestroy();
|
||||
|
||||
SDL_Quit();
|
||||
|
||||
delete sample;
|
||||
delete geom;
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user