16constexpr float kEpsilon = 1e-6f;
24Vec3 subtract(
const Vec3 &
a,
const Vec3 &
b) {
return {
a.x -
b.x,
a.y -
b.y,
a.z -
b.z}; }
27 return {
a.y *
b.z -
a.z *
b.y,
a.z *
b.x -
a.x *
b.z,
28 a.x *
b.y -
a.y *
b.x};
37 const std::string &
subject = {}) {
44StaticMeshSurface::StaticMeshSurface(Config config, std::vector<float>
vertices,
47 : config_(
std::move(config)),
51 const uint32_t triangleCount =
static_cast<uint32_t
>(indices_.size() / 3);
52 triangleOrder_.resize(triangleCount);
53 std::iota(triangleOrder_.begin(), triangleOrder_.end(), uint32_t{0});
54 triangleBounds_.reserve(triangleCount);
56 Bounds
bounds{std::numeric_limits<float>::max(), std::numeric_limits<float>::max(),
57 std::numeric_limits<float>::max(), std::numeric_limits<float>::lowest(),
58 std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest()};
59 for (uint32_t corner = 0; corner < 3; ++corner) {
60 const size_t vertex =
static_cast<size_t>(indices_[
triangle * 3 + corner]) * 3;
62 bounds.minY = std::min(
bounds.minY, vertices_[vertex + 1]);
63 bounds.minZ = std::min(
bounds.minZ, vertices_[vertex + 2]);
65 bounds.maxY = std::max(
bounds.maxY, vertices_[vertex + 1]);
66 bounds.maxZ = std::max(
bounds.maxZ, vertices_[vertex + 2]);
68 triangleBounds_.push_back(
bounds);
70 nodes_.reserve(triangleCount * 2);
71 buildNode(0, triangleCount);
79 return meshFailure<std::shared_ptr<const StaticMeshSurface>>(
81 "static mesh requires packed XYZ vertices and complete indexed triangles",
84 if (
indices.size() / 3 > std::numeric_limits<uint32_t>::max()) {
85 return meshFailure<std::shared_ptr<const StaticMeshSurface>>(
90 return meshFailure<std::shared_ptr<const StaticMeshSurface>>(
92 "static mesh normals must be empty or match packed vertices", config.
surfaceId);
96 return meshFailure<std::shared_ptr<const StaticMeshSurface>>(
98 "static mesh requires a finite reference height and leaf size in [1, 64]",
102 if (!std::isfinite(
value))
103 return meshFailure<std::shared_ptr<const StaticMeshSurface>>(
108 if (!std::isfinite(
value))
109 return meshFailure<std::shared_ptr<const StaticMeshSurface>>(
115 std::array<Vec3, 3>
points;
116 for (
size_t corner = 0; corner < 3; ++corner) {
119 return meshFailure<std::shared_ptr<const StaticMeshSurface>>(
121 "static mesh contains an out-of-range vertex index", config.
surfaceId);
122 const size_t offset =
static_cast<size_t>(
index) * 3;
126 subtract(
points[2],
points[0]))) <= kEpsilon * kEpsilon) {
127 return meshFailure<std::shared_ptr<const StaticMeshSurface>>(
129 "static mesh contains a degenerate triangle", config.
surfaceId);
137uint32_t StaticMeshSurface::buildNode(uint32_t
first, uint32_t
count) {
138 Bounds
bounds{std::numeric_limits<float>::max(), std::numeric_limits<float>::max(),
139 std::numeric_limits<float>::max(), std::numeric_limits<float>::lowest(),
140 std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest()};
141 Bounds centroids =
bounds;
143 const Bounds &
triangle = triangleBounds_[triangleOrder_[i]];
153 centroids.minX = std::min(centroids.minX,
cx);
154 centroids.minY = std::min(centroids.minY,
cy);
155 centroids.minZ = std::min(centroids.minZ, cz);
156 centroids.maxX = std::max(centroids.maxX,
cx);
157 centroids.maxY = std::max(centroids.maxY,
cy);
158 centroids.maxZ = std::max(centroids.maxZ, cz);
160 const uint32_t
nodeIndex =
static_cast<uint32_t
>(nodes_.size());
164 const std::array<float, 3> extents{centroids.maxX - centroids.minX,
165 centroids.maxY - centroids.minY,
166 centroids.maxZ - centroids.minZ};
167 const int axis =
static_cast<int>(
168 std::distance(extents.begin(), std::max_element(extents.begin(), extents.end())));
171 if (axis == 0)
return value.minX +
value.maxX;
172 if (axis == 1)
return value.minY +
value.maxY;
176 std::nth_element(triangleOrder_.begin() +
first, triangleOrder_.begin() + middle,
178 [&](uint32_t
a, uint32_t
b) {
179 const float ac = center(a);
180 const float bc = center(b);
181 return ac == bc ? a < b : ac < bc;
194 if (!std::isfinite(planeX) || !std::isfinite(planeY))
195 return meshFailure<PlacementSystem::PlacementHit>(
199 const bool xz =
world.getGrid().plane == grid::GridPlane::XZ;
201 float bestHeight = 0.f;
202 float bestDistance = 0.f;
203 uint32_t bestTriangle = 0;
207 std::vector<uint32_t>
pending{0};
211 const bool inside = planeX >=
node.bounds.minX - kEpsilon &&
212 planeX <=
node.bounds.maxX + kEpsilon &&
213 planeY >= (xz ?
node.bounds.minZ :
node.bounds.minY) - kEpsilon &&
214 planeY <= (xz ?
node.bounds.maxZ :
node.bounds.maxY) + kEpsilon;
221 for (uint32_t ordered =
node.first; ordered <
node.first +
node.count; ++ordered) {
222 const uint32_t
triangle = triangleOrder_[ordered];
223 std::array<Vec3, 3>
p;
224 for (uint32_t corner = 0; corner < 3; ++corner) {
225 const size_t offset =
static_cast<size_t>(indices_[
triangle * 3 + corner]) * 3;
228 const float p0x =
p[0].x;
229 const float p0y = xz ?
p[0].z :
p[0].y;
230 const float p1x =
p[1].x;
231 const float p1y = xz ?
p[1].z :
p[1].y;
232 const float p2x =
p[2].x;
233 const float p2y = xz ?
p[2].z :
p[2].y;
234 const float denominator =
235 (p1y - p2y) * (p0x - p2x) + (p2x - p1x) * (p0y - p2y);
236 if (std::fabs(denominator) <= kEpsilon)
continue;
237 const float a = ((p1y - p2y) * (planeX - p2x) +
238 (p2x - p1x) * (planeY - p2y)) /
240 const float b = ((p2y - p0y) * (planeX - p2x) +
241 (p0x - p2x) * (planeY - p2y)) /
243 const float c = 1.f -
a -
b;
244 if (
a < -kEpsilon ||
b < -kEpsilon ||
c < -kEpsilon)
continue;
245 const float height = xz ?
a *
p[0].y +
b *
p[1].y +
c *
p[2].y
246 :
a *
p[0].z +
b *
p[1].z +
c *
p[2].z;
247 const float distance = std::fabs(
height - config_.referenceHeight);
248 bool better = !
found;
249 if (
found && config_.hitSelection == HitSelection::Highest)
250 better =
height > bestHeight + kEpsilon;
251 else if (
found && config_.hitSelection == HitSelection::Lowest)
252 better =
height < bestHeight - kEpsilon;
253 else if (
found && config_.hitSelection == HitSelection::ClosestToReference)
254 better =
distance < bestDistance - kEpsilon;
255 const bool tied = config_.hitSelection == HitSelection::ClosestToReference
256 ? std::fabs(
distance - bestDistance) <= kEpsilon
257 : std::fabs(
height - bestHeight) <= kEpsilon;
258 if (!better &&
found && tied) better =
triangle < bestTriangle;
259 if (!better)
continue;
270 return meshFailure<PlacementSystem::PlacementHit>(
272 "no projectable mesh triangle exists at the plane coordinate", config_.surfaceId);
274 std::array<Vec3, 3>
p;
275 std::array<Vec3, 3>
n;
276 for (uint32_t corner = 0; corner < 3; ++corner) {
277 const size_t offset =
static_cast<size_t>(indices_[bestTriangle * 3 + corner]) * 3;
279 if (!normals_.empty())
282 Vec3
normal = normals_.empty()
283 ? cross(subtract(
p[1],
p[0]), subtract(
p[2],
p[0]))
284 : Vec3{bestA *
n[0].x + bestB *
n[1].x + bestC *
n[2].x,
285 bestA *
n[0].y + bestB *
n[1].y + bestC *
n[2].y,
286 bestA *
n[0].z + bestB *
n[1].z + bestC *
n[2].z};
287 if (lengthSquared(
normal) <= kEpsilon * kEpsilon)
288 normal = cross(subtract(
p[1],
p[0]), subtract(
p[2],
p[0]));
289 if (config_.orientNormalsToGridUp && (xz ?
normal.y :
normal.z) < 0.f) {
295 if (lengthSquared(
tangent) <= kEpsilon * kEpsilon)
tangent = subtract(
p[2],
p[0]);
299 hit.worldY = xz ? bestHeight : planeY;
300 hit.worldZ = xz ? planeY : bestHeight;
307 hit.surfaceId = config_.surfaceId;
308 hit.surfaceRevision = config_.surfaceRevision;
309 hit.primitiveId = bestTriangle;
310 hit.tags = config_.tags;
std::vector< QuestEvent > pending
std::uint32_t vertexCount
Module-neutral grid topology and sizing (no building/map dependency).
std::vector< std::uint32_t > indices
std::vector< float > normals
std::vector< Point > vertices
std::shared_ptr< const std::vector< glm::vec2 > > points
std::vector< char > inside
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
Immutable world-space triangle mesh accelerated for placement-surface projection.
建筑放置模块入口:定义 / 放置世界 / 鬼影 / 变更事件的脚本绑定点。 设计文档:docs/dev/建筑放置系统设计.md
float lengthSquared(Vec3 value)
Length squared.
double cross(const Vec2 &a, const Vec2 &b)
Cross.
int axis(int64_t a, size_t rank)
Axis.
DiagnosticCode
Stable machine-readable diagnostic codes.
One sampled point and orthonormal frame on a placement surface.
Immutable identity, hit policy and metadata for one static mesh snapshot.
uint32_t leafTriangleCount