载入中...
搜索中...
未找到
GeometryStroke.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <string>
6
7namespace eve::procgen {
8namespace {
9
10struct V3 {
11 float x, y, z;
12};
13
14V3 operator+(V3 a, V3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
15V3 operator-(V3 a, V3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
16V3 operator*(V3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
17float dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
18V3 cross(V3 a, V3 b) { return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; }
19float length(V3 a) { return std::sqrt(dot(a, a)); }
20V3 normalized(V3 a) {
21 const float magnitude = length(a);
22 return magnitude > 1e-8f ? a * (1.f / magnitude) : V3{1.f, 0.f, 0.f};
23}
24
25void emitTriangle(MeshBuild& mesh, V3 a, V3 b, V3 c, float u0 = 0.f, float u1 = 1.f, float u2 = 1.f) {
26 const V3 normal = normalized(cross(b - a, c - a));
27 const uint32_t base = static_cast<uint32_t>(mesh.getVertexCount());
28 mesh.addVertex(a.x, a.y, a.z, normal.x, normal.y, normal.z, u0, 0.f);
29 mesh.addVertex(b.x, b.y, b.z, normal.x, normal.y, normal.z, u1, 0.f);
30 mesh.addVertex(c.x, c.y, c.z, normal.x, normal.y, normal.z, u2, 1.f);
31 mesh.addTriangle(base, base + 1, base + 2);
32}
33
34void emitQuad(MeshBuild& mesh, V3 a, V3 b, V3 c, V3 d) {
35 emitTriangle(mesh, a, b, c, 0.f, 1.f, 1.f);
36 emitTriangle(mesh, a, c, d, 0.f, 1.f, 0.f);
37}
38
39} // namespace
40
42 if (shape != "quad" && shape != "triangularPrism" && shape != "cube")
44 "geometry stroke shape must be quad, triangularPrism, or cube",
45 "shape", {}, "procgen.geometryStroke"));
46 shape_ = shape;
47 ++revision_;
48 return Result<void>::success();
49}
50
51Result<void> GeometryStroke::setInputSpaceResult(std::string_view inputSpace, float planeY) {
52 if (inputSpace != "spatial" && inputSpace != "planar")
54 "geometry stroke input space must be spatial or planar",
55 "inputSpace", {}, "procgen.geometryStroke"));
56 if (!std::isfinite(planeY))
58 "geometry stroke planeY must be finite", "planeY", {},
59 "procgen.geometryStroke"));
60 inputSpace_ = inputSpace;
61 planeY_ = planeY;
62 if (inputSpace_ == "planar")
63 for (auto& point : points_) point.y = planeY_;
64 ++revision_;
65 return Result<void>::success();
66}
67
69 if (!std::isfinite(width) || !std::isfinite(depth) || width <= 0.f || depth <= 0.f)
71 "geometry stroke width and depth must be finite and positive",
72 "size", {}, "procgen.geometryStroke"));
73 width_ = width;
74 depth_ = depth;
75 ++revision_;
76 return Result<void>::success();
77}
78
80 if (!std::isfinite(spacing) || spacing < 0.f)
82 DiagnosticCode::InvalidArgument, "geometry stroke minimum spacing must be finite and non-negative",
83 "spacing", {}, "procgen.geometryStroke"));
84 spacing_ = spacing;
85 ++revision_;
86 return Result<void>::success();
87}
88
90 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z))
92 "geometry stroke point must be finite", "point", {},
93 "procgen.geometryStroke"));
94 if (points_.size() >= 65536)
96 "geometry stroke point budget exceeded", "point", {},
97 "procgen.geometryStroke"));
98 GeometryStrokePoint point{x, inputSpace_ == "planar" ? planeY_ : y, z};
99 if (!points_.empty()) {
100 const auto& previous = points_.back();
101 if (length({point.x - previous.x, point.y - previous.y, point.z - previous.z}) < spacing_)
102 return Result<bool>::success(false);
103 }
104 points_.push_back(point);
105 ++revision_;
106 return Result<bool>::success(true);
107}
108
110 if (points_.empty())
111 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "geometry stroke has no point to undo",
112 "points", {}, "procgen.geometryStroke"));
113 points_.pop_back();
114 ++revision_;
115 return Result<void>::success();
116}
117
118void GeometryStroke::clear() noexcept {
119 if (points_.empty()) return;
120 points_.clear();
121 ++revision_;
122}
123
125 if (points_.size() < 2)
127 "geometry stroke requires at least two accepted points",
128 "points", {}, "procgen.geometryStroke"));
130 output.setActiveGroup(shape_);
131 std::vector<std::vector<V3>> rings;
132 rings.reserve(points_.size());
133 for (std::size_t index = 0; index < points_.size(); ++index) {
134 const auto& point = points_[index];
135 const V3 center{point.x, point.y, point.z};
136 const auto& previousPoint = points_[index == 0 ? 0 : index - 1];
137 const auto& nextPoint = points_[std::min(index + 1, points_.size() - 1)];
138 V3 forward{nextPoint.x - previousPoint.x, nextPoint.y - previousPoint.y, nextPoint.z - previousPoint.z};
139 forward = normalized(forward);
140 const V3 reference = std::abs(forward.y) < 0.95f ? V3{0.f, 1.f, 0.f} : V3{1.f, 0.f, 0.f};
141 const V3 side = normalized(cross(reference, forward));
142 const V3 up = normalized(cross(forward, side));
143 const float halfWidth = width_ * 0.5f;
144 const float halfDepth = depth_ * 0.5f;
145 if (shape_ == "quad") {
146 rings.push_back({center - side * halfWidth, center + side * halfWidth});
147 } else if (shape_ == "triangularPrism") {
148 rings.push_back({center - side * halfWidth - up * halfDepth, center + side * halfWidth - up * halfDepth,
149 center + up * halfDepth});
150 } else {
151 rings.push_back({center - side * halfWidth - up * halfDepth, center + side * halfWidth - up * halfDepth,
152 center + side * halfWidth + up * halfDepth, center - side * halfWidth + up * halfDepth});
153 }
154 }
155 const std::size_t ringSize = rings.front().size();
156 for (std::size_t index = 1; index < rings.size(); ++index) {
157 if (shape_ == "quad") {
158 emitQuad(output, rings[index - 1][0], rings[index][0], rings[index][1], rings[index - 1][1]);
159 emitQuad(output, rings[index - 1][1], rings[index][1], rings[index][0], rings[index - 1][0]);
160 continue;
161 }
162 for (std::size_t side = 0; side < ringSize; ++side) {
163 const std::size_t next = (side + 1) % ringSize;
164 emitQuad(output, rings[index - 1][side], rings[index][side], rings[index][next], rings[index - 1][next]);
165 }
166 }
167 if (shape_ == "triangularPrism") {
168 emitTriangle(output, rings.front()[2], rings.front()[1], rings.front()[0]);
169 emitTriangle(output, rings.back()[0], rings.back()[1], rings.back()[2]);
170 } else if (shape_ == "cube") {
171 emitQuad(output, rings.front()[3], rings.front()[2], rings.front()[1], rings.front()[0]);
172 emitQuad(output, rings.back()[0], rings.back()[1], rings.back()[2], rings.back()[3]);
173 }
174 output.setMeta("generator", "mesh.geometryStroke");
175 output.setMeta("shape", shape_);
176 output.setMeta("inputSpace", inputSpace_);
177 output.setMeta("pointCount", std::to_string(points_.size()));
178 return Result<MeshBuild>::success(std::move(output));
179}
180
181} // namespace eve::procgen
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
const std::string & s
float length
Definition CaveMesh.cpp:94
ShaderImageInput shape
HexVec3 up
std::int32_t c
std::uint32_t width
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
Texture * normal
graphics::Canvas * previous
float halfWidth
float d
Mesh * mesh
ecs::EntityHandle side
int spacing
uint32_t index
std::uint32_t depth
glm::vec3 point
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.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Result< MeshBuild > buildMeshResult() const
Build an owning triangle mesh without mutating the stroke.
Result< void > undoResult()
Remove the most recently accepted point.
Result< void > setMinimumSpacingResult(float spacing)
Set the non-negative minimum accepted distance between input points.
Result< bool > addPointResult(float x, float y, float z)
Append a finite point; returns false when minimum spacing rejects it.
std::string_view shape() const noexcept
Return the selected cross-section name.
Result< void > setInputSpaceResult(std::string_view inputSpace, float planeY=0.f)
Select spatial input or planar input constrained to planeY.
Result< void > setSizeResult(float width, float depth)
Set positive stroke width and depth.
void clear() noexcept
Clear all input points.
std::string_view inputSpace() const noexcept
Return the selected input-space name.
Result< void > setShapeResult(std::string_view shape)
Select quad, triangularPrism, or cube cross-section generation.
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:19
int setActiveGroup(const std::string &name)
Select/create the named group assigned to subsequently added triangles.
Definition MeshBuild.cpp:53
Vec3 operator-(Vec3 lhs, Vec3 rhs)
Operator -.
Vec3 operator+(Vec3 lhs, Vec3 rhs)
Operator +.
Vec3 operator*(Vec3 value, float scale)
Operator *.
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Definition UrbanTypes.h:36
One input sample owned by a procedural geometry stroke.