|
|
|
@@ -461,9 +461,9 @@ void RoadSystem::buildPageMeshes(RoadPageGeometry &pg)
|
|
|
|
|
|
|
|
|
|
auto buffer = std::make_shared<Procedural::TriangleBuffer>();
|
|
|
|
|
for (const RoadWedge &w : pg.wedges)
|
|
|
|
|
buildWedgeGeometry(w, rg, *buffer);
|
|
|
|
|
buildWedgeGeometry(w, rg, getRoadTemplate(rg.config), *buffer);
|
|
|
|
|
for (const RoadStraightSegment &s : pg.segments)
|
|
|
|
|
buildSegmentGeometry(s, rg, *buffer);
|
|
|
|
|
buildSegmentGeometry(s, rg, getRoadTemplate(rg.config), *buffer);
|
|
|
|
|
|
|
|
|
|
if (buffer->getIndices().empty()) {
|
|
|
|
|
/* No road content seeded on this page. */
|
|
|
|
@@ -1072,12 +1072,18 @@ bool RoadSystem::loadTemplateFromMesh(const std::string &meshName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RoadSystem::buildFallbackTemplate(float roadThickness)
|
|
|
|
|
{
|
|
|
|
|
m_templateBuffer = makeFallbackTemplate(roadThickness);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Procedural::TriangleBuffer
|
|
|
|
|
RoadSystem::makeFallbackTemplate(float roadThickness)
|
|
|
|
|
{
|
|
|
|
|
float h = std::max(0.01f, roadThickness) * 0.5f;
|
|
|
|
|
|
|
|
|
|
m_templateBuffer = Procedural::TriangleBuffer();
|
|
|
|
|
auto &verts = m_templateBuffer.getVertices();
|
|
|
|
|
auto &indices = m_templateBuffer.getIndices();
|
|
|
|
|
Procedural::TriangleBuffer tb;
|
|
|
|
|
auto &verts = tb.getVertices();
|
|
|
|
|
auto &indices = tb.getIndices();
|
|
|
|
|
verts.reserve(24);
|
|
|
|
|
indices.reserve(36);
|
|
|
|
|
|
|
|
|
@@ -1132,6 +1138,8 @@ void RoadSystem::buildFallbackTemplate(float roadThickness)
|
|
|
|
|
addFace({ { 0, -h, 0 }, { 0, 0 } }, { { 0, -h, 1 }, { 1, 0 } },
|
|
|
|
|
{ { 0, h, 1 }, { 1, 1 } }, { { 0, h, 0 }, { 0, 1 } },
|
|
|
|
|
Ogre::Vector3::NEGATIVE_UNIT_X);
|
|
|
|
|
|
|
|
|
|
return tb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1197,103 +1205,214 @@ static float halfEdgeU(const RoadHalfEdge &he, const RoadGraph &graph,
|
|
|
|
|
return t;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** One center-surface triangle with UVs, input to emitSlab(). */
|
|
|
|
|
struct RoadSurfTri {
|
|
|
|
|
Ogre::Vector3 p[3];
|
|
|
|
|
Ogre::Vector2 uv[3];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** One exposed center-surface boundary edge that gets a side skirt. */
|
|
|
|
|
struct RoadSkirtEdge {
|
|
|
|
|
Ogre::Vector3 p0, p1;
|
|
|
|
|
Ogre::Vector2 uv0, uv1;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** Append one triangle; degenerate (zero-area) triangles are skipped. */
|
|
|
|
|
static void emitTri(Procedural::TriangleBuffer &out, const Ogre::Vector3 &p0,
|
|
|
|
|
const Ogre::Vector3 &p1, const Ogre::Vector3 &p2,
|
|
|
|
|
const Ogre::Vector2 &uv0, const Ogre::Vector2 &uv1,
|
|
|
|
|
const Ogre::Vector2 &uv2)
|
|
|
|
|
{
|
|
|
|
|
Ogre::Vector3 n = (p1 - p0).crossProduct(p2 - p0);
|
|
|
|
|
if (n.squaredLength() < 1e-10f)
|
|
|
|
|
return;
|
|
|
|
|
n.normalise();
|
|
|
|
|
|
|
|
|
|
int base = (int)out.getVertices().size();
|
|
|
|
|
const Ogre::Vector3 *pp[3] = { &p0, &p1, &p2 };
|
|
|
|
|
const Ogre::Vector2 *uu[3] = { &uv0, &uv1, &uv2 };
|
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
|
|
|
Procedural::TriangleBuffer::Vertex v;
|
|
|
|
|
v.mPosition = *pp[i];
|
|
|
|
|
v.mNormal = n;
|
|
|
|
|
v.mUV = *uu[i];
|
|
|
|
|
out.getVertices().push_back(v);
|
|
|
|
|
out.getIndices().push_back(base + i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Turn a set of center-surface triangles into a solid slab.
|
|
|
|
|
* Build a straight strip by concatenating road template copies end-to-end
|
|
|
|
|
* along the strip's +Z axis and side-by-side along +X for multiple lanes.
|
|
|
|
|
*
|
|
|
|
|
* Every triangle is emitted twice: offset by +halfThick along Y with the
|
|
|
|
|
* winding chosen so the normal points up, and offset by -halfThick with
|
|
|
|
|
* the opposite winding. Each listed boundary edge grows a vertical
|
|
|
|
|
* skirt quad whose normal points away from @p refPoint (an interior
|
|
|
|
|
* reference, e.g. the primitive's centroid).
|
|
|
|
|
* Strip local coordinates:
|
|
|
|
|
* X in [0, numLanes * laneWidth] — lateral, from outer curb inward
|
|
|
|
|
* Z in [0, stripLen] — along sweep (polyline) direction
|
|
|
|
|
* Y in [-halfThick, halfThick] — vertical, from template
|
|
|
|
|
*
|
|
|
|
|
* Template vertex mapping (template → strip):
|
|
|
|
|
* Template +X (along road) → strip +Z
|
|
|
|
|
* Template +Z (lateral) → strip +X
|
|
|
|
|
*
|
|
|
|
|
* UVs are set to tile continuously: u = sweep distance, v = lane index +
|
|
|
|
|
* template Z, so they are phase-continuous across copies and lanes.
|
|
|
|
|
*
|
|
|
|
|
* @param tmpl Road template buffer (unit-box).
|
|
|
|
|
* @param laneWidth World-space width of one lane.
|
|
|
|
|
* @param numLanes Number of lanes placed side-by-side.
|
|
|
|
|
* @param stripLen Total length of the strip along Z (world units).
|
|
|
|
|
* @param out Receives concatenated vertices and indices.
|
|
|
|
|
*/
|
|
|
|
|
static void emitSlab(Procedural::TriangleBuffer &out,
|
|
|
|
|
const std::vector<RoadSurfTri> &tris,
|
|
|
|
|
const std::vector<RoadSkirtEdge> &skirts, float halfThick,
|
|
|
|
|
const Ogre::Vector3 &refPoint)
|
|
|
|
|
static void buildTemplateStrip(const Procedural::TriangleBuffer &tmpl,
|
|
|
|
|
float laneWidth, int numLanes,
|
|
|
|
|
float stripLen,
|
|
|
|
|
Procedural::TriangleBuffer &out)
|
|
|
|
|
{
|
|
|
|
|
Ogre::Vector3 up(0.0f, halfThick, 0.0f);
|
|
|
|
|
const auto &sv = tmpl.getVertices();
|
|
|
|
|
const auto &si = tmpl.getIndices();
|
|
|
|
|
if (sv.empty() || si.empty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (const RoadSurfTri &t : tris) {
|
|
|
|
|
Ogre::Vector3 n =
|
|
|
|
|
(t.p[1] - t.p[0]).crossProduct(t.p[2] - t.p[0]);
|
|
|
|
|
if (n.squaredLength() < 1e-10f)
|
|
|
|
|
int copies = std::max(1, (int)std::ceil(stripLen));
|
|
|
|
|
|
|
|
|
|
for (int ci = 0; ci < copies; ++ci) {
|
|
|
|
|
float segLen = 1.0f;
|
|
|
|
|
if (ci == copies - 1 && stripLen > 0.0f)
|
|
|
|
|
segLen = stripLen - (float)ci;
|
|
|
|
|
if (segLen <= 0.0f)
|
|
|
|
|
continue;
|
|
|
|
|
int i1 = n.y >= 0.0f ? 1 : 2;
|
|
|
|
|
int i2 = n.y >= 0.0f ? 2 : 1;
|
|
|
|
|
|
|
|
|
|
/* Top surface. */
|
|
|
|
|
emitTri(out, t.p[0] + up, t.p[i1] + up, t.p[i2] + up,
|
|
|
|
|
t.uv[0], t.uv[i1], t.uv[i2]);
|
|
|
|
|
/* Bottom surface, flipped. */
|
|
|
|
|
emitTri(out, t.p[0] - up, t.p[i2] - up, t.p[i1] - up,
|
|
|
|
|
t.uv[0], t.uv[i2], t.uv[i1]);
|
|
|
|
|
}
|
|
|
|
|
for (int lane = 0; lane < numLanes; ++lane) {
|
|
|
|
|
int base = (int)out.getVertices().size();
|
|
|
|
|
float xOff = (float)lane * laneWidth;
|
|
|
|
|
|
|
|
|
|
float thickness = 2.0f * halfThick;
|
|
|
|
|
for (const RoadSkirtEdge &e : skirts) {
|
|
|
|
|
Ogre::Vector3 t0 = e.p0 + up;
|
|
|
|
|
Ogre::Vector3 t1 = e.p1 + up;
|
|
|
|
|
Ogre::Vector3 b0 = e.p0 - up;
|
|
|
|
|
Ogre::Vector3 b1 = e.p1 - up;
|
|
|
|
|
Ogre::Vector2 uvB0(e.uv0.x, e.uv0.y - thickness);
|
|
|
|
|
Ogre::Vector2 uvB1(e.uv1.x, e.uv1.y - thickness);
|
|
|
|
|
|
|
|
|
|
Ogre::Vector3 n = (t1 - t0).crossProduct(b0 - t0);
|
|
|
|
|
if (n.squaredLength() < 1e-10f)
|
|
|
|
|
continue;
|
|
|
|
|
Ogre::Vector3 mid = 0.5f * (t0 + t1);
|
|
|
|
|
bool outward = n.dotProduct(mid - refPoint) >= 0.0f;
|
|
|
|
|
if (outward) {
|
|
|
|
|
emitTri(out, t0, t1, b1, e.uv0, e.uv1, uvB1);
|
|
|
|
|
emitTri(out, t0, b1, b0, e.uv0, uvB1, uvB0);
|
|
|
|
|
} else {
|
|
|
|
|
emitTri(out, t0, b1, t1, e.uv0, uvB1, e.uv1);
|
|
|
|
|
emitTri(out, t0, b0, b1, e.uv0, uvB0, uvB1);
|
|
|
|
|
for (const auto &v : sv) {
|
|
|
|
|
Procedural::TriangleBuffer::Vertex nv;
|
|
|
|
|
nv.mPosition = Ogre::Vector3(
|
|
|
|
|
v.mPosition.z * laneWidth + xOff,
|
|
|
|
|
v.mPosition.y,
|
|
|
|
|
v.mPosition.x * segLen + (float)ci);
|
|
|
|
|
nv.mUV = Ogre::Vector2(
|
|
|
|
|
v.mPosition.x * segLen + (float)ci,
|
|
|
|
|
v.mPosition.z + (float)lane);
|
|
|
|
|
nv.mNormal = v.mNormal;
|
|
|
|
|
out.getVertices().push_back(nv);
|
|
|
|
|
}
|
|
|
|
|
for (int idx : si)
|
|
|
|
|
out.getIndices().push_back(base + idx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Overlap of strip quads behind the node so no seam gap shows. */
|
|
|
|
|
/**
|
|
|
|
|
* Sweep-deform a template strip along a world-space polyline.
|
|
|
|
|
*
|
|
|
|
|
* Each vertex at strip position (x, y, z) is mapped to world space:
|
|
|
|
|
* 1. Find world point Q on the polyline at distance z.
|
|
|
|
|
* 2. Compute horizontal radial r = (Q - O).normalised().
|
|
|
|
|
* 3. worldPos = Q - x * r; worldPos.y += polylineHeight(z) + y.
|
|
|
|
|
*
|
|
|
|
|
* Strip Z outside [0, polyLen] is clamped to the nearest endpoint for
|
|
|
|
|
* polyline lookup, providing seam overlap at both ends.
|
|
|
|
|
*
|
|
|
|
|
* @param verts Strip vertices (mutated in place to world positions).
|
|
|
|
|
* @param poly World-space polyline control points [P1, X, P2].
|
|
|
|
|
* @param polyD Cumulative distances along polyline: [0, d1, d1+d2].
|
|
|
|
|
* @param polyY Road surface height at each polyline control point.
|
|
|
|
|
* @param O Seed node world position.
|
|
|
|
|
*/
|
|
|
|
|
static void sweepDeform(
|
|
|
|
|
std::vector<Procedural::TriangleBuffer::Vertex> &verts,
|
|
|
|
|
const std::vector<Ogre::Vector3> &poly,
|
|
|
|
|
const std::vector<float> &polyD,
|
|
|
|
|
const std::vector<float> &polyY,
|
|
|
|
|
const Ogre::Vector3 &O)
|
|
|
|
|
{
|
|
|
|
|
if (poly.size() < 2 || polyD.size() != poly.size() ||
|
|
|
|
|
polyY.size() != poly.size())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
float totalLen = polyD.back();
|
|
|
|
|
|
|
|
|
|
for (auto &v : verts) {
|
|
|
|
|
float z = v.mPosition.z;
|
|
|
|
|
float x = v.mPosition.x;
|
|
|
|
|
float y = v.mPosition.y;
|
|
|
|
|
|
|
|
|
|
/* Clamp z to valid range for polyline lookup. */
|
|
|
|
|
float cz = std::max(0.0f, std::min(totalLen, z));
|
|
|
|
|
|
|
|
|
|
/* Locate segment containing cz. */
|
|
|
|
|
Ogre::Vector3 Q = poly.back();
|
|
|
|
|
float hQ = polyY.back();
|
|
|
|
|
for (size_t i = 0; i + 1 < polyD.size(); ++i) {
|
|
|
|
|
if (cz <= polyD[i + 1]) {
|
|
|
|
|
float t = (cz - polyD[i]) /
|
|
|
|
|
std::max(1e-6f,
|
|
|
|
|
polyD[i + 1] - polyD[i]);
|
|
|
|
|
Q = poly[i] + (poly[i + 1] - poly[i]) * t;
|
|
|
|
|
hQ = polyY[i] +
|
|
|
|
|
(polyY[i + 1] - polyY[i]) * t;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Radial direction from node to polyline point. */
|
|
|
|
|
Ogre::Vector3 radial(Q.x - O.x, 0.0f, Q.z - O.z);
|
|
|
|
|
float rlen = radial.length();
|
|
|
|
|
if (rlen < 1e-4f)
|
|
|
|
|
radial = Ogre::Vector3::UNIT_X;
|
|
|
|
|
else
|
|
|
|
|
radial /= rlen;
|
|
|
|
|
|
|
|
|
|
/* If z is behind the polyline start, extend along d1. */
|
|
|
|
|
Ogre::Vector3 wp;
|
|
|
|
|
if (z < 0.0f) {
|
|
|
|
|
/* Push back from P1 toward the node along the
|
|
|
|
|
* direction from P1 to O. */
|
|
|
|
|
Ogre::Vector3 toNode(poly[0].x - O.x, 0.0f,
|
|
|
|
|
poly[0].z - O.z);
|
|
|
|
|
float tnLen = toNode.length();
|
|
|
|
|
if (tnLen < 1e-4f)
|
|
|
|
|
toNode = radial;
|
|
|
|
|
else
|
|
|
|
|
toNode /= tnLen;
|
|
|
|
|
wp = poly[0] + toNode * (-z) - x * toNode;
|
|
|
|
|
wp.y = hQ + y;
|
|
|
|
|
} else if (z > totalLen) {
|
|
|
|
|
/* Extend past P2 outward along the direction from O
|
|
|
|
|
* to P2. */
|
|
|
|
|
Ogre::Vector3 outDir(poly.back().x - O.x, 0.0f,
|
|
|
|
|
poly.back().z - O.z);
|
|
|
|
|
float odLen = outDir.length();
|
|
|
|
|
if (odLen < 1e-4f)
|
|
|
|
|
outDir = radial;
|
|
|
|
|
else
|
|
|
|
|
outDir /= odLen;
|
|
|
|
|
wp = poly.back() + outDir * (z - totalLen) -
|
|
|
|
|
x * outDir;
|
|
|
|
|
wp.y = hQ + y;
|
|
|
|
|
} else {
|
|
|
|
|
wp = Q - x * radial;
|
|
|
|
|
wp.y = hQ + y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v.mPosition = wp;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Recompute per-vertex normals from triangle faces. */
|
|
|
|
|
static void
|
|
|
|
|
recomputeNormals(std::vector<Procedural::TriangleBuffer::Vertex> &verts,
|
|
|
|
|
const std::vector<int> &indices)
|
|
|
|
|
{
|
|
|
|
|
for (auto &v : verts)
|
|
|
|
|
v.mNormal = Ogre::Vector3::ZERO;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i + 2 < indices.size(); i += 3) {
|
|
|
|
|
int i0 = indices[i];
|
|
|
|
|
int i1 = indices[i + 1];
|
|
|
|
|
int i2 = indices[i + 2];
|
|
|
|
|
if (i0 < 0 || i1 < 0 || i2 < 0 ||
|
|
|
|
|
i0 >= (int)verts.size() ||
|
|
|
|
|
i1 >= (int)verts.size() ||
|
|
|
|
|
i2 >= (int)verts.size())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
Ogre::Vector3 e1 =
|
|
|
|
|
verts[i1].mPosition - verts[i0].mPosition;
|
|
|
|
|
Ogre::Vector3 e2 =
|
|
|
|
|
verts[i2].mPosition - verts[i0].mPosition;
|
|
|
|
|
Ogre::Vector3 n = e1.crossProduct(e2);
|
|
|
|
|
float len2 = n.squaredLength();
|
|
|
|
|
if (len2 < 1e-10f)
|
|
|
|
|
continue;
|
|
|
|
|
n /= std::sqrt(len2);
|
|
|
|
|
|
|
|
|
|
verts[i0].mNormal += n;
|
|
|
|
|
verts[i1].mNormal += n;
|
|
|
|
|
verts[i2].mNormal += n;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto &v : verts) {
|
|
|
|
|
float len2 = v.mNormal.squaredLength();
|
|
|
|
|
if (len2 > 1e-10f)
|
|
|
|
|
v.mNormal /= std::sqrt(len2);
|
|
|
|
|
else
|
|
|
|
|
v.mNormal = Ogre::Vector3::UNIT_Y;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Overlap distance to close gaps at wedge boundaries (spec 5.4 step 4). */
|
|
|
|
|
static const float ROAD_SEAM_OVERLAP = 0.05f;
|
|
|
|
|
|
|
|
|
|
bool RoadSystem::buildWedgeGeometry(const RoadWedge &wedge,
|
|
|
|
|
const RoadGraph &graph,
|
|
|
|
|
const Procedural::TriangleBuffer &tmpl,
|
|
|
|
|
Procedural::TriangleBuffer &out)
|
|
|
|
|
{
|
|
|
|
|
if (wedge.degenerate) {
|
|
|
|
@@ -1315,158 +1434,109 @@ bool RoadSystem::buildWedgeGeometry(const RoadWedge &wedge,
|
|
|
|
|
Ogre::Vector3 r1 = roadRightVec(d1);
|
|
|
|
|
Ogre::Vector3 r2 = roadRightVec(d2);
|
|
|
|
|
float lw = graph.config.laneWidth;
|
|
|
|
|
float halfThick =
|
|
|
|
|
std::max(0.01f, graph.config.roadThickness) * 0.5f;
|
|
|
|
|
float out1 = h1.lanesOut * lw;
|
|
|
|
|
float in1 = h1.lanesIn * lw;
|
|
|
|
|
float in2 = h2.lanesIn * lw;
|
|
|
|
|
float L1 = h1.halfLength;
|
|
|
|
|
float L2 = h2.halfLength;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The wedge region is the union of two one-sided band strips:
|
|
|
|
|
* strip1 = O + t*d1 + s*r1, t in [0, L1], s in [0, out1] (h1's
|
|
|
|
|
* outbound side) and strip2 = O + t*d2 + s*r2, t in [0, L2],
|
|
|
|
|
* s in [-in2, 0] (h2's inbound side). For swept angles up to
|
|
|
|
|
* 180 degrees the union is an L-shaped hexagon with a single
|
|
|
|
|
* outer corner X where the two outer curbs intersect; emit it as
|
|
|
|
|
* a triangle fan around X. Otherwise fall back to two
|
|
|
|
|
* independent strip quads with their own curb and cap skirts.
|
|
|
|
|
* Wedge polyline (outer curb boundary):
|
|
|
|
|
* P1 — outbound curb of h1 at midpoint
|
|
|
|
|
* X — intersection of the two curb lines (if it exists)
|
|
|
|
|
* P2 — inbound curb of h2 at midpoint
|
|
|
|
|
*
|
|
|
|
|
* The road surface is a single combined mesh: templates are
|
|
|
|
|
* concatenated into a straight strip, then sweep-deformed so the
|
|
|
|
|
* strip's +Z follows the polyline and the strip's +X extends
|
|
|
|
|
* inward toward the seed node O.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Outer corner: X = O + t1*d1 + out1*r1 = O + t2*d2 - in2*r2. */
|
|
|
|
|
/* --- Polyline endpoints --- */
|
|
|
|
|
Ogre::Vector3 P1 = O + L1 * d1 + out1 * r1;
|
|
|
|
|
Ogre::Vector3 P2 = O + L2 * d2 - in2 * r2;
|
|
|
|
|
|
|
|
|
|
/* --- Corner X: intersection of the two curb lines --- */
|
|
|
|
|
Ogre::Vector3 rhs = -in2 * r2 - out1 * r1;
|
|
|
|
|
float det = d1.z * d2.x - d1.x * d2.z;
|
|
|
|
|
float t1 = 0.0f, t2 = 0.0f;
|
|
|
|
|
float t1x = 0.0f, t2x = 0.0f;
|
|
|
|
|
bool cornerOk = false;
|
|
|
|
|
if (std::fabs(det) >= 0.05f) {
|
|
|
|
|
t1 = (-rhs.x * d2.z + d2.x * rhs.z) / det;
|
|
|
|
|
t2 = (d1.x * rhs.z - rhs.x * d1.z) / det;
|
|
|
|
|
cornerOk = t1 >= 0.0f && t1 <= L1 && t2 >= 0.0f &&
|
|
|
|
|
t2 <= L2;
|
|
|
|
|
t1x = (-rhs.x * d2.z + d2.x * rhs.z) / det;
|
|
|
|
|
t2x = (d1.x * rhs.z - rhs.x * d1.z) / det;
|
|
|
|
|
cornerOk = t1x >= 0.0f && t1x <= L1 &&
|
|
|
|
|
t2x >= 0.0f && t2x <= L2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cornerOk && wedge.sweptAngleDeg <= 180.0f) {
|
|
|
|
|
float yNode1, yMid1, yNode2, yMid2;
|
|
|
|
|
halfEdgeHeights(h1, graph, yNode1, yMid1);
|
|
|
|
|
halfEdgeHeights(h2, graph, yNode2, yMid2);
|
|
|
|
|
/* --- Polyline and cumulative distances --- */
|
|
|
|
|
std::vector<Ogre::Vector3> poly;
|
|
|
|
|
std::vector<float> polyY;
|
|
|
|
|
std::vector<float> polyD;
|
|
|
|
|
|
|
|
|
|
Ogre::Vector3 vX = O + t1 * d1 + out1 * r1;
|
|
|
|
|
float yO = 0.5f * (yNode1 + yNode2);
|
|
|
|
|
float yX = 0.5f * (halfEdgeHeightAt(h1, graph, t1) +
|
|
|
|
|
halfEdgeHeightAt(h2, graph, t2));
|
|
|
|
|
float yP1 = halfEdgeHeightAt(h1, graph, L1);
|
|
|
|
|
float yP2 = halfEdgeHeightAt(h2, graph, L2);
|
|
|
|
|
poly.push_back(P1);
|
|
|
|
|
polyY.push_back(yP1);
|
|
|
|
|
polyD.push_back(0.0f);
|
|
|
|
|
|
|
|
|
|
Ogre::Vector3 poly[6] = {
|
|
|
|
|
O, O + L1 * d1, O + L1 * d1 + out1 * r1,
|
|
|
|
|
vX, O + L2 * d2 - in2 * r2, O + L2 * d2
|
|
|
|
|
};
|
|
|
|
|
float polyY[6] = { yO, yMid1, yMid1, yX, yMid2, yMid2 };
|
|
|
|
|
|
|
|
|
|
/* Planar UVs in the (d1, r1) frame. */
|
|
|
|
|
Ogre::Vector2 polyUV[6];
|
|
|
|
|
for (int i = 0; i < 6; ++i) {
|
|
|
|
|
Ogre::Vector3 rel = poly[i] - O;
|
|
|
|
|
polyUV[i] = Ogre::Vector2(rel.dotProduct(d1),
|
|
|
|
|
rel.dotProduct(r1) + in1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<RoadSurfTri> tris;
|
|
|
|
|
for (int i = 0; i < 6; ++i) {
|
|
|
|
|
int j = (i + 1) % 6;
|
|
|
|
|
RoadSurfTri tri;
|
|
|
|
|
tri.p[0] = Ogre::Vector3(vX.x, yX, vX.z);
|
|
|
|
|
tri.p[1] =
|
|
|
|
|
Ogre::Vector3(poly[i].x, polyY[i], poly[i].z);
|
|
|
|
|
tri.p[2] =
|
|
|
|
|
Ogre::Vector3(poly[j].x, polyY[j], poly[j].z);
|
|
|
|
|
tri.uv[0] = polyUV[3];
|
|
|
|
|
tri.uv[1] = polyUV[i];
|
|
|
|
|
tri.uv[2] = polyUV[j];
|
|
|
|
|
tris.push_back(tri);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The fan closes the whole L-shape; the only exposed
|
|
|
|
|
* boundary is the two outer curbs meeting at X.
|
|
|
|
|
*/
|
|
|
|
|
std::vector<RoadSkirtEdge> skirts;
|
|
|
|
|
RoadSkirtEdge curb1;
|
|
|
|
|
curb1.p0 = Ogre::Vector3(poly[2].x, polyY[2], poly[2].z);
|
|
|
|
|
curb1.p1 = Ogre::Vector3(vX.x, yX, vX.z);
|
|
|
|
|
curb1.uv0 = polyUV[2];
|
|
|
|
|
curb1.uv1 = polyUV[3];
|
|
|
|
|
skirts.push_back(curb1);
|
|
|
|
|
RoadSkirtEdge curb2;
|
|
|
|
|
curb2.p0 = Ogre::Vector3(vX.x, yX, vX.z);
|
|
|
|
|
curb2.p1 = Ogre::Vector3(poly[4].x, polyY[4], poly[4].z);
|
|
|
|
|
curb2.uv0 = polyUV[3];
|
|
|
|
|
curb2.uv1 = polyUV[4];
|
|
|
|
|
skirts.push_back(curb2);
|
|
|
|
|
|
|
|
|
|
emitSlab(out, tris, skirts, halfThick, O);
|
|
|
|
|
return true;
|
|
|
|
|
if (cornerOk) {
|
|
|
|
|
Ogre::Vector3 X = O + t1x * d1 + out1 * r1;
|
|
|
|
|
float yX = 0.5f * (halfEdgeHeightAt(h1, graph, t1x) +
|
|
|
|
|
halfEdgeHeightAt(h2, graph, t2x));
|
|
|
|
|
poly.push_back(X);
|
|
|
|
|
polyY.push_back(yX);
|
|
|
|
|
polyD.push_back((X - P1).length());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Fallback: two independent one-sided strip quads. */
|
|
|
|
|
float t0 = -ROAD_SEAM_OVERLAP;
|
|
|
|
|
poly.push_back(P2);
|
|
|
|
|
polyY.push_back(yP2);
|
|
|
|
|
polyD.push_back(polyD.back() +
|
|
|
|
|
(P2 - poly[poly.size() - 2]).length());
|
|
|
|
|
|
|
|
|
|
struct StripDef {
|
|
|
|
|
const RoadHalfEdge &he;
|
|
|
|
|
const Ogre::Vector3 &d;
|
|
|
|
|
const Ogre::Vector3 &r;
|
|
|
|
|
float s0, s1; /* lateral range along r */
|
|
|
|
|
float L;
|
|
|
|
|
};
|
|
|
|
|
StripDef strips[2] = { { h1, d1, r1, 0.0f, out1, L1 },
|
|
|
|
|
{ h2, d2, r2, -in2, 0.0f, L2 } };
|
|
|
|
|
float polyLen = polyD.back();
|
|
|
|
|
if (polyLen < 1e-4f)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
for (const StripDef &sd : strips) {
|
|
|
|
|
Ogre::Vector3 c00 = O + t0 * sd.d + sd.s0 * sd.r;
|
|
|
|
|
Ogre::Vector3 c10 = O + sd.L * sd.d + sd.s0 * sd.r;
|
|
|
|
|
Ogre::Vector3 c11 = O + sd.L * sd.d + sd.s1 * sd.r;
|
|
|
|
|
Ogre::Vector3 c01 = O + t0 * sd.d + sd.s1 * sd.r;
|
|
|
|
|
float y0 = halfEdgeHeightAt(sd.he, graph, t0);
|
|
|
|
|
float yL = halfEdgeHeightAt(sd.he, graph, sd.L);
|
|
|
|
|
c00.y = c01.y = y0;
|
|
|
|
|
c10.y = c11.y = yL;
|
|
|
|
|
/* --- Determine lane count --- */
|
|
|
|
|
if (tmpl.getVertices().empty() || tmpl.getIndices().empty())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
float inW = sd.he.lanesIn * lw;
|
|
|
|
|
Ogre::Vector2 uv00(halfEdgeU(sd.he, graph, t0), sd.s0 + inW);
|
|
|
|
|
Ogre::Vector2 uv10(halfEdgeU(sd.he, graph, sd.L),
|
|
|
|
|
sd.s0 + inW);
|
|
|
|
|
Ogre::Vector2 uv11(halfEdgeU(sd.he, graph, sd.L),
|
|
|
|
|
sd.s1 + inW);
|
|
|
|
|
Ogre::Vector2 uv01(halfEdgeU(sd.he, graph, t0), sd.s1 + inW);
|
|
|
|
|
|
|
|
|
|
std::vector<RoadSurfTri> tris;
|
|
|
|
|
RoadSurfTri tA;
|
|
|
|
|
tA.p[0] = c00; tA.p[1] = c10; tA.p[2] = c11;
|
|
|
|
|
tA.uv[0] = uv00; tA.uv[1] = uv10; tA.uv[2] = uv11;
|
|
|
|
|
tris.push_back(tA);
|
|
|
|
|
RoadSurfTri tB;
|
|
|
|
|
tB.p[0] = c00; tB.p[1] = c11; tB.p[2] = c01;
|
|
|
|
|
tB.uv[0] = uv00; tB.uv[1] = uv11; tB.uv[2] = uv01;
|
|
|
|
|
tris.push_back(tB);
|
|
|
|
|
int numLanes = std::max(h1.lanesOut, h2.lanesIn);
|
|
|
|
|
if (numLanes < 1)
|
|
|
|
|
numLanes = 1;
|
|
|
|
|
|
|
|
|
|
std::vector<RoadSkirtEdge> skirts;
|
|
|
|
|
RoadSkirtEdge curb;
|
|
|
|
|
curb.p0 = c01; curb.p1 = c11;
|
|
|
|
|
curb.uv0 = uv01; curb.uv1 = uv11;
|
|
|
|
|
skirts.push_back(curb);
|
|
|
|
|
RoadSkirtEdge cap;
|
|
|
|
|
cap.p0 = c00; cap.p1 = c01;
|
|
|
|
|
cap.uv0 = uv00; cap.uv1 = uv01;
|
|
|
|
|
skirts.push_back(cap);
|
|
|
|
|
/* --- Build straight template strip --- */
|
|
|
|
|
Procedural::TriangleBuffer strip;
|
|
|
|
|
buildTemplateStrip(tmpl, lw, numLanes, polyLen, strip);
|
|
|
|
|
|
|
|
|
|
Ogre::Vector3 ref = 0.25f * (c00 + c10 + c11 + c01);
|
|
|
|
|
emitSlab(out, tris, skirts, halfThick, ref);
|
|
|
|
|
/* --- Seam suppression: extend strip ends by overlap --- */
|
|
|
|
|
auto &sverts = strip.getVertices();
|
|
|
|
|
for (auto &v : sverts) {
|
|
|
|
|
if (v.mPosition.z < ROAD_SEAM_OVERLAP)
|
|
|
|
|
v.mPosition.z -= ROAD_SEAM_OVERLAP;
|
|
|
|
|
else if (v.mPosition.z > polyLen - ROAD_SEAM_OVERLAP)
|
|
|
|
|
v.mPosition.z += ROAD_SEAM_OVERLAP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* --- Sweep-deform along polyline --- */
|
|
|
|
|
sweepDeform(sverts, poly, polyD, polyY, O);
|
|
|
|
|
|
|
|
|
|
/* --- Recompute normals --- */
|
|
|
|
|
recomputeNormals(sverts, strip.getIndices());
|
|
|
|
|
|
|
|
|
|
/* --- Append to output --- */
|
|
|
|
|
int base = (int)out.getVertices().size();
|
|
|
|
|
for (const auto &v : sverts)
|
|
|
|
|
out.getVertices().push_back(v);
|
|
|
|
|
for (int idx : strip.getIndices())
|
|
|
|
|
out.getIndices().push_back(base + idx);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool RoadSystem::buildSegmentGeometry(const RoadStraightSegment &segment,
|
|
|
|
|
const RoadGraph &graph,
|
|
|
|
|
const Procedural::TriangleBuffer &tmpl,
|
|
|
|
|
Procedural::TriangleBuffer &out)
|
|
|
|
|
{
|
|
|
|
|
const RoadNode *node = graph.findNodeById(segment.nodeId);
|
|
|
|
@@ -1478,55 +1548,68 @@ bool RoadSystem::buildSegmentGeometry(const RoadStraightSegment &segment,
|
|
|
|
|
Ogre::Vector3 d = he.direction;
|
|
|
|
|
Ogre::Vector3 r = roadRightVec(d);
|
|
|
|
|
float lw = graph.config.laneWidth;
|
|
|
|
|
float halfThick =
|
|
|
|
|
std::max(0.01f, graph.config.roadThickness) * 0.5f;
|
|
|
|
|
float inW = he.lanesIn * lw;
|
|
|
|
|
float outW = he.lanesOut * lw;
|
|
|
|
|
float L = he.halfLength;
|
|
|
|
|
int numLanes = he.lanesIn + he.lanesOut;
|
|
|
|
|
if (numLanes < 1)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (tmpl.getVertices().empty() || tmpl.getIndices().empty())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
/* Straight road strip along the half-edge direction.
|
|
|
|
|
* Templates are placed directly in world space:
|
|
|
|
|
* world = O + d * (t) + r * (lateral) + Y offset.
|
|
|
|
|
* t starts at t0 = -ROAD_SEAM_OVERLAP for seam suppression. */
|
|
|
|
|
float t0 = -ROAD_SEAM_OVERLAP;
|
|
|
|
|
float stripLen = L - t0;
|
|
|
|
|
int copies = std::max(1, (int)std::ceil(stripLen));
|
|
|
|
|
|
|
|
|
|
/* Full-width band: s in [-inW, +outW], t in [t0, L]. */
|
|
|
|
|
Ogre::Vector3 c00 = O + t0 * d - inW * r;
|
|
|
|
|
Ogre::Vector3 c10 = O + L * d - inW * r;
|
|
|
|
|
Ogre::Vector3 c11 = O + L * d + outW * r;
|
|
|
|
|
Ogre::Vector3 c01 = O + t0 * d + outW * r;
|
|
|
|
|
float y0 = halfEdgeHeightAt(he, graph, t0);
|
|
|
|
|
float yL = halfEdgeHeightAt(he, graph, L);
|
|
|
|
|
c00.y = c01.y = y0;
|
|
|
|
|
c10.y = c11.y = yL;
|
|
|
|
|
const auto &sv = tmpl.getVertices();
|
|
|
|
|
const auto &si = tmpl.getIndices();
|
|
|
|
|
float inW = he.lanesIn * lw;
|
|
|
|
|
|
|
|
|
|
Ogre::Vector2 uv00(halfEdgeU(he, graph, t0), 0.0f);
|
|
|
|
|
Ogre::Vector2 uv10(halfEdgeU(he, graph, L), 0.0f);
|
|
|
|
|
Ogre::Vector2 uv11(halfEdgeU(he, graph, L), inW + outW);
|
|
|
|
|
Ogre::Vector2 uv01(halfEdgeU(he, graph, t0), inW + outW);
|
|
|
|
|
for (int ci = 0; ci < copies; ++ci) {
|
|
|
|
|
float segLen = 1.0f;
|
|
|
|
|
if (ci == copies - 1 && stripLen > 0.0f)
|
|
|
|
|
segLen = stripLen - (float)ci;
|
|
|
|
|
if (segLen <= 0.0f)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
std::vector<RoadSurfTri> tris;
|
|
|
|
|
RoadSurfTri tA;
|
|
|
|
|
tA.p[0] = c00; tA.p[1] = c10; tA.p[2] = c11;
|
|
|
|
|
tA.uv[0] = uv00; tA.uv[1] = uv10; tA.uv[2] = uv11;
|
|
|
|
|
tris.push_back(tA);
|
|
|
|
|
RoadSurfTri tB;
|
|
|
|
|
tB.p[0] = c00; tB.p[1] = c11; tB.p[2] = c01;
|
|
|
|
|
tB.uv[0] = uv00; tB.uv[1] = uv11; tB.uv[2] = uv01;
|
|
|
|
|
tris.push_back(tB);
|
|
|
|
|
float zStart = t0 + (float)ci;
|
|
|
|
|
|
|
|
|
|
/* Skirts: node-end cap plus both curbs (not the far end). */
|
|
|
|
|
std::vector<RoadSkirtEdge> skirts;
|
|
|
|
|
RoadSkirtEdge cap;
|
|
|
|
|
cap.p0 = c00; cap.p1 = c01;
|
|
|
|
|
cap.uv0 = uv00; cap.uv1 = uv01;
|
|
|
|
|
skirts.push_back(cap);
|
|
|
|
|
RoadSkirtEdge curbIn;
|
|
|
|
|
curbIn.p0 = c00; curbIn.p1 = c10;
|
|
|
|
|
curbIn.uv0 = uv00; curbIn.uv1 = uv10;
|
|
|
|
|
skirts.push_back(curbIn);
|
|
|
|
|
RoadSkirtEdge curbOut;
|
|
|
|
|
curbOut.p0 = c01; curbOut.p1 = c11;
|
|
|
|
|
curbOut.uv0 = uv01; curbOut.uv1 = uv11;
|
|
|
|
|
skirts.push_back(curbOut);
|
|
|
|
|
for (int lane = 0; lane < numLanes; ++lane) {
|
|
|
|
|
int base = (int)out.getVertices().size();
|
|
|
|
|
float xOff = -inW + (float)lane * lw;
|
|
|
|
|
|
|
|
|
|
for (const auto &v : sv) {
|
|
|
|
|
Procedural::TriangleBuffer::Vertex nv;
|
|
|
|
|
/* t = distance along half-edge from O. */
|
|
|
|
|
float t = zStart + v.mPosition.x * segLen;
|
|
|
|
|
float tClamped =
|
|
|
|
|
std::max(0.0f, std::min(L, t));
|
|
|
|
|
float roadY =
|
|
|
|
|
halfEdgeHeightAt(he, graph,
|
|
|
|
|
tClamped);
|
|
|
|
|
|
|
|
|
|
nv.mPosition =
|
|
|
|
|
O + d * t +
|
|
|
|
|
r * (v.mPosition.z * lw + xOff);
|
|
|
|
|
nv.mPosition.y += v.mPosition.y + roadY;
|
|
|
|
|
nv.mUV = Ogre::Vector2(
|
|
|
|
|
t, v.mPosition.z + (float)lane);
|
|
|
|
|
/* Rotate template normal to world frame:
|
|
|
|
|
* template +X → d, +Z → r, +Y → world Y. */
|
|
|
|
|
nv.mNormal =
|
|
|
|
|
d * v.mNormal.x +
|
|
|
|
|
r * v.mNormal.z +
|
|
|
|
|
Ogre::Vector3::UNIT_Y * v.mNormal.y;
|
|
|
|
|
out.getVertices().push_back(nv);
|
|
|
|
|
}
|
|
|
|
|
for (int idx : si)
|
|
|
|
|
out.getIndices().push_back(base + idx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ogre::Vector3 ref = 0.25f * (c00 + c10 + c11 + c01);
|
|
|
|
|
emitSlab(out, tris, skirts, halfThick, ref);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1612,7 +1695,7 @@ void RoadSystem::complyTerrain(TerrainSystem *terrainSystem,
|
|
|
|
|
|
|
|
|
|
for (const RoadWedge &wedge : pg.wedges) {
|
|
|
|
|
Procedural::TriangleBuffer buf;
|
|
|
|
|
if (!buildWedgeGeometry(wedge, m_world.entity(m_terrainEntityId).get<TerrainComponent>().roadGraph, buf))
|
|
|
|
|
if (!buildWedgeGeometry(wedge, m_world.entity(m_terrainEntityId).get<TerrainComponent>().roadGraph, getRoadTemplate(m_world.entity(m_terrainEntityId).get<TerrainComponent>().roadGraph.config), buf))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const auto &verts = buf.getVertices();
|
|
|
|
@@ -1631,7 +1714,7 @@ void RoadSystem::complyTerrain(TerrainSystem *terrainSystem,
|
|
|
|
|
|
|
|
|
|
for (const RoadStraightSegment &seg : pg.segments) {
|
|
|
|
|
Procedural::TriangleBuffer buf;
|
|
|
|
|
if (!buildSegmentGeometry(seg, m_world.entity(m_terrainEntityId).get<TerrainComponent>().roadGraph, buf))
|
|
|
|
|
if (!buildSegmentGeometry(seg, m_world.entity(m_terrainEntityId).get<TerrainComponent>().roadGraph, getRoadTemplate(m_world.entity(m_terrainEntityId).get<TerrainComponent>().roadGraph.config), buf))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const auto &verts = buf.getVertices();
|
|
|
|
@@ -1721,25 +1804,25 @@ void RoadSystem::rebuildDebugWedge()
|
|
|
|
|
if (m_debugWedgeIndex < 0) {
|
|
|
|
|
for (const auto &w : nodeWedges) {
|
|
|
|
|
Procedural::TriangleBuffer wedgeTb;
|
|
|
|
|
if (buildWedgeGeometry(w, tc.roadGraph, wedgeTb))
|
|
|
|
|
if (buildWedgeGeometry(w, tc.roadGraph, getRoadTemplate(tc.roadGraph.config), wedgeTb))
|
|
|
|
|
emitGeom(wedgeTb);
|
|
|
|
|
}
|
|
|
|
|
for (const auto &s : nodeSegs) {
|
|
|
|
|
Procedural::TriangleBuffer segTb;
|
|
|
|
|
if (buildSegmentGeometry(s, tc.roadGraph, segTb))
|
|
|
|
|
if (buildSegmentGeometry(s, tc.roadGraph, getRoadTemplate(tc.roadGraph.config), segTb))
|
|
|
|
|
emitGeom(segTb);
|
|
|
|
|
}
|
|
|
|
|
} else if (m_debugWedgeIndex < (int)nodeWedges.size()) {
|
|
|
|
|
Procedural::TriangleBuffer wedgeTb;
|
|
|
|
|
if (buildWedgeGeometry(nodeWedges[m_debugWedgeIndex],
|
|
|
|
|
tc.roadGraph, wedgeTb))
|
|
|
|
|
tc.roadGraph, getRoadTemplate(tc.roadGraph.config), wedgeTb))
|
|
|
|
|
emitGeom(wedgeTb);
|
|
|
|
|
} else {
|
|
|
|
|
int segIdx = m_debugWedgeIndex - (int)nodeWedges.size();
|
|
|
|
|
if (segIdx >= 0 && segIdx < (int)nodeSegs.size()) {
|
|
|
|
|
Procedural::TriangleBuffer segTb;
|
|
|
|
|
if (buildSegmentGeometry(nodeSegs[segIdx],
|
|
|
|
|
tc.roadGraph, segTb))
|
|
|
|
|
tc.roadGraph, getRoadTemplate(tc.roadGraph.config), segTb))
|
|
|
|
|
emitGeom(segTb);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|