载入中...
搜索中...
未找到
CableChainRope.cpp
浏览该文件的文档.
2
5
6#include <algorithm>
7#include <cmath>
8#include <string>
9#include <vector>
10
11namespace eve::procgen {
12namespace {
13
14constexpr float kPi = 3.14159265358979323846f;
15
16struct V3 {
17 float x = 0.f, y = 0.f, z = 0.f;
18};
19
20V3 operator+(V3 a, V3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
21V3 operator-(V3 a, V3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
22V3 operator*(V3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
23float dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
24V3 cross(V3 a, V3 b) {
25 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};
26}
27V3 normalize(V3 a) {
28 const float l = std::sqrt(dot(a, a));
29 return l > 1e-8f ? V3{a.x / l, a.y / l, a.z / l} : V3{0.f, 1.f, 0.f};
30}
31
33void emitOpenTube(MeshBuild& out, const std::vector<V3>& path, float radius, int radialSegs, float uvScale,
34 float uOffset) {
35 if (path.size() < 2 || radius <= 0.f) return;
36 radialSegs = std::clamp(radialSegs, 3, 48);
37 const int rings = int(path.size());
38
39 std::vector<V3> tangents = std::vector<V3>(static_cast<size_t>(rings));
40 for (int i = 0; i < rings; ++i) {
41 if (i == 0)
42 tangents[size_t(i)] = normalize(path[1] - path[0]);
43 else if (i + 1 == rings)
44 tangents[size_t(i)] = normalize(path[size_t(i)] - path[size_t(i - 1)]);
45 else
46 tangents[size_t(i)] = normalize(path[size_t(i + 1)] - path[size_t(i - 1)]);
47 }
48
49 // Parallel-transport frames to avoid sudden flips.
50 std::vector<V3> frameN = std::vector<V3>(static_cast<size_t>(rings));
51 std::vector<V3> frameB = std::vector<V3>(static_cast<size_t>(rings));
52 V3 n0 = std::fabs(tangents[0].y) < 0.9f ? V3{0, 1, 0} : V3{0, 0, 1};
53 frameN[0] = normalize(n0 - tangents[0] * dot(n0, tangents[0]));
54 frameB[0] = cross(tangents[0], frameN[0]);
55 for (int i = 1; i < rings; ++i) {
56 const V3 axis = cross(tangents[size_t(i - 1)], tangents[size_t(i)]);
57 const float axisLen = std::sqrt(dot(axis, axis));
58 if (axisLen < 1e-6f) {
59 frameN[size_t(i)] = frameN[size_t(i - 1)];
60 frameB[size_t(i)] = frameB[size_t(i - 1)];
61 } else {
62 const V3 a = axis * (1.f / axisLen);
63 const float ang = std::atan2(axisLen, dot(tangents[size_t(i - 1)], tangents[size_t(i)]));
64 const float ca = std::cos(ang);
65 const float sa = std::sin(ang);
66 auto rotate = [&](V3 v) {
67 return v * ca + cross(a, v) * sa + a * (dot(a, v) * (1.f - ca));
68 };
69 frameN[size_t(i)] = normalize(rotate(frameN[size_t(i - 1)]));
70 frameB[size_t(i)] = cross(tangents[size_t(i)], frameN[size_t(i)]);
71 }
72 }
73
74 std::vector<float> arc = std::vector<float>(static_cast<size_t>(rings), 0.f);
75 for (int i = 1; i < rings; ++i) {
76 const V3 d = path[size_t(i)] - path[size_t(i - 1)];
77 arc[size_t(i)] = arc[size_t(i - 1)] + std::sqrt(dot(d, d));
78 }
79
80 const uint32_t base = uint32_t(out.getVertexCount());
81 for (int i = 0; i < rings; ++i) {
82 for (int j = 0; j <= radialSegs; ++j) {
83 const float a = 2.f * kPi * float(j) / float(radialSegs);
84 const float ca = std::cos(a);
85 const float sa = std::sin(a);
86 const V3 n = frameN[size_t(i)] * ca + frameB[size_t(i)] * sa;
87 const V3 p = path[size_t(i)] + n * radius;
88 const float u = (a * radius + uOffset) * uvScale;
89 const float v = arc[size_t(i)] * uvScale;
90 out.addVertex(p.x, p.y, p.z, n.x, n.y, n.z, u, v);
91 }
92 }
93 const int stride = radialSegs + 1;
94 for (int i = 0; i + 1 < rings; ++i) {
95 for (int j = 0; j < radialSegs; ++j) {
96 const uint32_t a = base + uint32_t(i * stride + j);
97 const uint32_t b = a + uint32_t(stride);
98 out.addTriangle(a, b, a + 1);
99 out.addTriangle(a + 1, b, b + 1);
100 }
101 }
102}
103
108void emitOvalLink(MeshBuild& out, V3 center, float majorX, float majorY, float minorR, int majorSegs, int minorSegs,
109 bool vertical, float uvScale) {
110 majorSegs = std::clamp(majorSegs, 8, 64);
111 minorSegs = std::clamp(minorSegs, 4, 24);
112 const uint32_t base = uint32_t(out.getVertexCount());
113 for (int i = 0; i <= majorSegs; ++i) {
114 const float u = float(i) / float(majorSegs);
115 const float a = 2.f * kPi * u;
116 const float ca = std::cos(a);
117 const float sa = std::sin(a);
118 // Tangent of the major ellipse and the plane normal of the link.
119 V3 majorPos, majorT, planeN;
120 if (vertical) {
121 majorPos = {center.x + majorX * ca, center.y + majorY * sa, center.z};
122 majorT = normalize(V3{-majorX * sa, majorY * ca, 0.f});
123 planeN = {0.f, 0.f, 1.f};
124 } else {
125 majorPos = {center.x + majorX * ca, center.y, center.z + majorY * sa};
126 majorT = normalize(V3{-majorX * sa, 0.f, majorY * ca});
127 planeN = {0.f, 1.f, 0.f};
128 }
129 const V3 radial = normalize(cross(planeN, majorT));
130 for (int j = 0; j <= minorSegs; ++j) {
131 const float v = float(j) / float(minorSegs);
132 const float b = 2.f * kPi * v;
133 const float cb = std::cos(b);
134 const float sb = std::sin(b);
135 const V3 n = radial * cb + planeN * sb;
136 const V3 p = majorPos + n * minorR;
137 out.addVertex(p.x, p.y, p.z, n.x, n.y, n.z, a * ((majorX + majorY) * 0.5f) * uvScale,
138 b * minorR * uvScale);
139 }
140 }
141 const int stride = minorSegs + 1;
142 for (int i = 0; i < majorSegs; ++i) {
143 for (int j = 0; j < minorSegs; ++j) {
144 const uint32_t a = base + uint32_t(i * stride + j);
145 const uint32_t b = a + uint32_t(stride);
146 out.addTriangle(a, a + 1, b);
147 out.addTriangle(a + 1, b + 1, b);
148 }
149 }
150}
151
152void buildTwistedStrands(MeshBuild& out, int segments, float L, float radius, float strandRadius, int strands,
153 int twists, int lengthSegs, int radialSegs, float uvScale, bool hempBulge) {
154 strands = std::clamp(strands, 2, 12);
155 twists = std::max(1, twists);
156 lengthSegs = std::clamp(lengthSegs, 4, 128);
157 radialSegs = std::clamp(radialSegs, 4, 24);
158 const float coreR = std::max(0.f, radius - strandRadius * 1.15f);
159
160 for (int s = 0; s < strands; ++s) {
161 const float phase = 2.f * kPi * float(s) / float(strands);
162 for (int seg = 0; seg < segments; ++seg) {
163 const float x0 = float(seg) * L;
164 std::vector<V3> path;
165 path.reserve(size_t(lengthSegs + 1));
166 for (int i = 0; i <= lengthSegs; ++i) {
167 const float t = float(i) / float(lengthSegs);
168 const float x = x0 + t * L;
169 // Integer twists per segment ⇒ phase at x=L matches x=0 for tiling.
170 const float a = phase + 2.f * kPi * float(twists) * t;
171 float r = coreR;
172 if (hempBulge) {
173 // Mild axial bulge so strands read as soft fiber bundles.
174 r *= 0.92f + 0.08f * std::sin(a * 3.f + float(s));
175 }
176 path.push_back({x, r * std::cos(a), r * std::sin(a)});
177 }
178 float sr = strandRadius;
179 if (hempBulge) sr *= 1.05f;
180 emitOpenTube(out, path, sr, radialSegs, uvScale, phase * radius);
181 }
182 }
183}
184
185void buildChain(MeshBuild& out, int segments, float L, float radius, float thickness, int majorSegs, int minorSegs,
186 float uvScale) {
187 // One tileable unit = two interlocking links (horizontal + vertical).
188 // Pitch between link centers is L/2; majorX sized so rings pass through each other.
189 const float pitch = L * 0.5f;
190 const float majorX = std::max(thickness * 2.5f, pitch * 0.72f);
191 const float majorY = std::max(thickness * 1.6f, radius);
192 const float minorR = std::clamp(thickness, 0.005f, majorY * 0.45f);
193
194 for (int seg = 0; seg < segments; ++seg) {
195 const float baseX = float(seg) * L + pitch * 0.5f;
196 // Horizontal link (major ellipse in XZ).
197 emitOvalLink(out, {baseX, 0.f, 0.f}, majorX, majorY, minorR, majorSegs, minorSegs, false, uvScale);
198 // Vertical link (major ellipse in XY), centered a half-pitch ahead.
199 emitOvalLink(out, {baseX + pitch, 0.f, 0.f}, majorX, majorY, minorR, majorSegs, minorSegs, true, uvScale);
200 }
201}
202
203RecipeDescriptor makeSchema(std::string id, std::string name, float radius, float thickness, int strands,
204 int twists) {
205 RecipeDescriptor descriptor{std::move(id), std::move(name), "Cable", {}};
206 descriptor.params.push_back(ParamDescriptor::integer("seed", "Seed", 1, 0, 2147483647));
207 descriptor.params.push_back(ParamDescriptor::integer("segments", "Segments", 6, 1, 256));
208 descriptor.params.push_back(ParamDescriptor::floating("segLength", "Segment Length", 1.f, 0.1f, 100.f, 0.05f));
209 descriptor.params.push_back(ParamDescriptor::floating("radius", "Radius", radius, 0.01f, 10.f, 0.01f));
210 descriptor.params.push_back(
211 ParamDescriptor::floating("thickness", "Strand / Bar Thickness", thickness, 0.005f, 5.f, 0.005f));
212 descriptor.params.push_back(ParamDescriptor::integer("strands", "Strand Count", strands, 2, 12));
213 descriptor.params.push_back(ParamDescriptor::integer("twists", "Twists Per Segment", twists, 1, 16));
214 descriptor.params.push_back(ParamDescriptor::integer("lengthSegs", "Length Segments", 16, 4, 128));
215 descriptor.params.push_back(ParamDescriptor::integer("radialSegs", "Radial Segments", 8, 3, 48));
216 descriptor.params.push_back(ParamDescriptor::integer("majorSegs", "Link Major Segments", 24, 8, 64));
217 descriptor.params.push_back(ParamDescriptor::integer("minorSegs", "Link Minor Segments", 8, 4, 24));
218 descriptor.params.push_back(ParamDescriptor::floating("uvRepeat", "UV Repeat", 2.f, 0.f, 100.f, 0.1f));
219 descriptor.params.push_back(ParamDescriptor::floating("scale", "Scale", 1.f, 0.01f, 100.f, 0.01f));
220 return descriptor;
221}
222
223} // namespace
224
225bool generateCableChainRope(const std::string& kind, const Params& params, MeshBuild& out, std::string& error) {
226 const int segments = std::clamp(params.getInt("segments", 6), 1, 256);
227 const float L = std::max(0.1f, params.getFloat("segLength", 1.f));
228 const float uvScale = std::max(0.f, params.getFloat("uvRepeat", 2.f));
229 const float scale = std::max(0.01f, params.getFloat("scale", 1.f));
230 const int lengthSegs = std::clamp(params.getInt("lengthSegs", 16), 4, 128);
231 const int radialSegs = std::clamp(params.getInt("radialSegs", 8), 3, 48);
232 const int majorSegs = std::clamp(params.getInt("majorSegs", 24), 8, 64);
233 const int minorSegs = std::clamp(params.getInt("minorSegs", 8), 4, 24);
234
235 out.clear();
236
237 if (kind == "mesh.cable") {
238 const float radius = std::max(0.01f, params.getFloat("radius", 0.08f));
239 const float thickness = std::max(0.005f, params.getFloat("thickness", 0.022f));
240 const int strands = std::clamp(params.getInt("strands", 6), 2, 12);
241 const int twists = std::max(1, params.getInt("twists", 1));
242 out.reserve(segments * strands * (lengthSegs + 1) * (radialSegs + 1),
243 segments * strands * lengthSegs * radialSegs * 6);
244 buildTwistedStrands(out, segments, L, radius, thickness, strands, twists, lengthSegs, radialSegs, uvScale,
245 false);
246 } else if (kind == "mesh.rope") {
247 const float radius = std::max(0.01f, params.getFloat("radius", 0.10f));
248 const float thickness = std::max(0.005f, params.getFloat("thickness", 0.045f));
249 const int strands = std::clamp(params.getInt("strands", 3), 2, 12);
250 const int twists = std::max(1, params.getInt("twists", 1));
251 out.reserve(segments * strands * (lengthSegs + 1) * (radialSegs + 1),
252 segments * strands * lengthSegs * radialSegs * 6);
253 buildTwistedStrands(out, segments, L, radius, thickness, strands, twists, lengthSegs, radialSegs, uvScale,
254 true);
255 } else if (kind == "mesh.chain") {
256 const float radius = std::max(0.01f, params.getFloat("radius", 0.12f));
257 const float thickness = std::max(0.005f, params.getFloat("thickness", 0.035f));
258 out.reserve(segments * 2 * (majorSegs + 1) * (minorSegs + 1),
259 segments * 2 * majorSegs * minorSegs * 6);
260 buildChain(out, segments, L, radius, thickness, majorSegs, minorSegs, uvScale);
261 } else {
262 error = "unknown cable/chain/rope kind '" + kind + "' (use mesh.cable|mesh.chain|mesh.rope)";
263 return false;
264 }
265
266 if (scale != 1.f) {
267 for (float& p : out.positions()) p *= scale;
268 }
269 if (out.empty()) {
270 error = kind + ": empty mesh (check segments/radius/thickness)";
271 return false;
272 }
273 out.setMeta("algorithm", kind);
274 out.setMeta("segments", std::to_string(segments));
275 return true;
276}
277
279 registry.registerRecipe(makeSchema("mesh.cable", "Steel Cable", 0.08f, 0.022f, 6, 1),
280 [](const Params& p, MeshBuild& o, std::string& e) {
281 return generateCableChainRope("mesh.cable", p, o, e);
282 });
283 registry.registerRecipe(makeSchema("mesh.chain", "Iron Chain", 0.12f, 0.035f, 2, 1),
284 [](const Params& p, MeshBuild& o, std::string& e) {
285 return generateCableChainRope("mesh.chain", p, o, e);
286 });
287 registry.registerRecipe(makeSchema("mesh.rope", "Hemp Rope", 0.10f, 0.045f, 3, 1),
288 [](const Params& p, MeshBuild& o, std::string& e) {
289 return generateCableChainRope("mesh.rope", p, o, e);
290 });
291}
292
293} // namespace eve::procgen
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string descriptor
const std::string & s
float ang
int strands
float phase
Definition CaveMesh.cpp:58
float thickness
Definition CaveMesh.cpp:83
glm::vec4 p[6]
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
double r
float v
TokenKind kind
std::array< float, 3 > scale
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string error
Definition Package.cpp:60
float radius
std::string path
Definition PlayHost.cpp:110
float d
float t
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:19
const std::vector< float > & positions() const
Positions.
Definition MeshBuild.h:125
void reserve(int vertexCount, int indexCount)
Reserve.
Definition MeshBuild.cpp:20
void setMeta(const std::string &key, const std::string &value)
Sets the meta.
bool empty() const
Empty.
void clear()
Clears .
Definition MeshBuild.cpp:8
EVENGINE_API_DOMAINS public API.
void registerRecipe(const std::string &id, MeshRecipeFn fn)
Register a recipe without metadata.
Owning, typed generation parameters.
Definition Params.h:27
std::vector< ParamSpec > params
Vec3 operator-(Vec3 lhs, Vec3 rhs)
Operator -.
Vec3 operator+(Vec3 lhs, Vec3 rhs)
Operator +.
Vec3 operator*(Vec3 value, float scale)
Operator *.
Vec2 normalize(const Vec2 &a)
Normalize.
Definition UrbanTypes.h:44
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
void registerCableChainRopeRecipes(MeshRecipeRegistry &registry)
Register mesh.cable / mesh.chain / mesh.rope mesh recipes.
bool generateCableChainRope(const std::string &kind, const Params &params, MeshBuild &out, std::string &error)
Procedural tileable cable / chain / rope meshes along +X.
int axis(int64_t a, size_t rank)
Axis.
static ParamDescriptor floating(std::string key, std::string label, float defaultValue, float minimum, float maximum, float step)
Construct a bounded floating-point descriptor.
static ParamDescriptor integer(std::string key, std::string label, int defaultValue, int minimum, int maximum, int step=1)
Construct a bounded integer descriptor.