载入中...
搜索中...
未找到
ModelNormals.cpp
浏览该文件的文档.
1#include "model3d/ModelData.h"
2
3#include "common/Diagnostic.h"
4#include "common/Result.h"
5#include "image/ImageData.h"
6
7#include <assimp/mesh.h>
8#include <assimp/vector3.h>
9
10#include <algorithm>
11#include <cmath>
12#include <memory>
13#include <string>
14#include <string_view>
15#include <utility>
16
17namespace eve {
18namespace model3d {
19namespace {
20
21eve::Diagnostic invalidArg(std::string message, std::string path) {
23 path);
24}
25
26eve::Diagnostic unsupported(std::string message, std::string path) {
28 path);
29}
30
31aiVector3D unitOrUp(float x, float y, float z) {
32 const float length = std::sqrt(x * x + y * y + z * z);
33 if (!std::isfinite(length) || length <= 1e-12f)
34 return aiVector3D(0.f, 0.f, 1.f);
35 return aiVector3D(x / length, y / length, z / length);
36}
37
38bool ensureNormals(aiMesh *mesh) {
39 if (!mesh || mesh->mNumVertices == 0 || !mesh->mVertices)
40 return false;
41 if (mesh->mNormals)
42 return true;
43 mesh->mNormals = new aiVector3D[mesh->mNumVertices];
44 for (unsigned i = 0; i < mesh->mNumVertices; ++i)
45 mesh->mNormals[i] = aiVector3D(0.f, 0.f, 1.f);
46 return true;
47}
48
49bool meshAabbCenter(const aiMesh *mesh, aiVector3D *out) {
50 if (!mesh || mesh->mNumVertices == 0 || !mesh->mVertices)
51 return false;
52 aiVector3D lo = mesh->mVertices[0];
53 aiVector3D hi = mesh->mVertices[0];
54 for (unsigned i = 1; i < mesh->mNumVertices; ++i) {
55 const aiVector3D &p = mesh->mVertices[i];
56 lo.x = std::min(lo.x, p.x);
57 lo.y = std::min(lo.y, p.y);
58 lo.z = std::min(lo.z, p.z);
59 hi.x = std::max(hi.x, p.x);
60 hi.y = std::max(hi.y, p.y);
61 hi.z = std::max(hi.z, p.z);
62 }
63 *out = aiVector3D((lo.x + hi.x) * 0.5f, (lo.y + hi.y) * 0.5f, (lo.z + hi.z) * 0.5f);
64 return true;
65}
66
67void applyRadial(aiMesh *mesh, const aiVector3D &origin) {
68 for (unsigned i = 0; i < mesh->mNumVertices; ++i) {
69 const aiVector3D &p = mesh->mVertices[i];
70 mesh->mNormals[i] = unitOrUp(p.x - origin.x, p.y - origin.y, p.z - origin.z);
71 }
72}
73
74struct Vec2 {
75 float x = 0.f;
76 float y = 0.f;
77};
78
79struct Vec3 {
80 float x = 0.f;
81 float y = 0.f;
82 float z = 0.f;
83};
84
85Vec3 add(const Vec3 &a, const Vec3 &b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
86Vec3 sub(const Vec3 &a, const Vec3 &b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
87Vec3 scale(const Vec3 &a, float s) { return {a.x * s, a.y * s, a.z * s}; }
88float dot3(const Vec3 &a, const Vec3 &b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
89Vec3 cross3(const Vec3 &a, const Vec3 &b) {
90 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};
91}
92Vec3 normalize3(const Vec3 &a) {
93 const aiVector3D n = unitOrUp(a.x, a.y, a.z);
94 return {n.x, n.y, n.z};
95}
96
97bool barycentric2(const Vec2 &p, const Vec2 &a, const Vec2 &b, const Vec2 &c, float *wa, float *wb,
98 float *wc) {
99 const float den = (b.y - c.y) * (a.x - c.x) + (c.x - b.x) * (a.y - c.y);
100 if (!std::isfinite(den) || std::fabs(den) <= 1e-12f)
101 return false;
102 *wa = ((b.y - c.y) * (p.x - c.x) + (c.x - b.x) * (p.y - c.y)) / den;
103 *wb = ((c.y - a.y) * (p.x - c.x) + (a.x - c.x) * (p.y - c.y)) / den;
104 *wc = 1.f - *wa - *wb;
105 return true;
106}
107
108image::ImageData::Colorf encodeNormal(const Vec3 &n) {
110 c.r = std::clamp(n.x * 0.5f + 0.5f, 0.f, 1.f);
111 c.g = std::clamp(n.y * 0.5f + 0.5f, 0.f, 1.f);
112 c.b = std::clamp(n.z * 0.5f + 0.5f, 0.f, 1.f);
113 c.a = 1.f;
114 return c;
115}
116
117Vec3 toTangent(const Vec3 &objectNormal, const Vec3 &pos0, const Vec3 &pos1, const Vec3 &pos2,
118 const Vec2 &uv0, const Vec2 &uv1, const Vec2 &uv2) {
119 const Vec3 e1 = sub(pos1, pos0);
120 const Vec3 e2 = sub(pos2, pos0);
121 const float du1 = uv1.x - uv0.x;
122 const float dv1 = uv1.y - uv0.y;
123 const float du2 = uv2.x - uv0.x;
124 const float dv2 = uv2.y - uv0.y;
125 const float det = du1 * dv2 - du2 * dv1;
126 Vec3 geometric = normalize3(cross3(e1, e2));
127 if (dot3(geometric, objectNormal) < 0.f)
128 geometric = scale(geometric, -1.f);
130 Vec3 bitangent;
131 if (!std::isfinite(det) || std::fabs(det) <= 1e-12f) {
132 tangent = {1.f, 0.f, 0.f};
133 if (std::fabs(dot3(tangent, geometric)) > 0.9f)
134 tangent = {0.f, 1.f, 0.f};
135 tangent = normalize3(sub(tangent, scale(geometric, dot3(tangent, geometric))));
136 bitangent = normalize3(cross3(geometric, tangent));
137 } else {
138 const float inv = 1.f / det;
139 tangent = normalize3(add(scale(e1, dv2 * inv), scale(e2, -dv1 * inv)));
140 bitangent = normalize3(add(scale(e1, -du2 * inv), scale(e2, du1 * inv)));
141 tangent = normalize3(sub(tangent, scale(geometric, dot3(tangent, geometric))));
142 const Vec3 reconstructed = cross3(geometric, tangent);
143 if (dot3(reconstructed, bitangent) < 0.f)
144 bitangent = scale(reconstructed, -1.f);
145 else
146 bitangent = reconstructed;
147 }
148 return {dot3(objectNormal, tangent), dot3(objectNormal, bitangent),
149 dot3(objectNormal, geometric)};
150}
151
152} // namespace
153
154eve::Result<void> ModelData::setVertexNormal(int meshIndex, int vertexIndex, float x, float y,
155 float z) {
156 aiMesh *mesh = meshAtMutable(meshIndex);
157 if (!mesh || vertexIndex < 0 || static_cast<unsigned>(vertexIndex) >= mesh->mNumVertices)
159 invalidArg("invalid mesh or vertex index", "model3d.setVertexNormal"));
160 if (!ensureNormals(mesh))
162 invalidArg("mesh has no vertices", "model3d.setVertexNormal"));
163 mesh->mNormals[vertexIndex] = unitOrUp(x, y, z);
165}
166
167eve::Result<void> ModelData::applyVertexNormals(int meshIndex, std::string_view kind) {
168 aiMesh *mesh = meshAtMutable(meshIndex);
169 if (!mesh)
171 invalidArg("invalid mesh index", "model3d.applyVertexNormals"));
172 aiVector3D origin;
173 if (!meshAabbCenter(mesh, &origin))
175 invalidArg("mesh has no vertices", "model3d.applyVertexNormals"));
176 return applyVertexNormalsFrom(meshIndex, kind, origin.x, origin.y, origin.z);
177}
178
180 float originX, float originY, float originZ) {
181 aiMesh *mesh = meshAtMutable(meshIndex);
182 if (!mesh)
184 invalidArg("invalid mesh index", "model3d.applyVertexNormalsFrom"));
185 if (kind != "radial")
187 unsupported("unknown vertex-normal kind", "model3d.applyVertexNormalsFrom.kind"));
188 if (!ensureNormals(mesh))
190 invalidArg("mesh has no vertices", "model3d.applyVertexNormalsFrom"));
191 applyRadial(mesh, aiVector3D(originX, originY, originZ));
193}
194
196 int meshIndex, int width, int height, int uvChannel, std::string_view space) const {
197 using ImagePtr = std::unique_ptr<image::ImageData>;
198 const aiMesh *mesh = meshAt(meshIndex);
199 if (!mesh)
201 invalidArg("invalid mesh index", "model3d.bakeNormalMap"));
202 if (width <= 0 || height <= 0)
204 invalidArg("normal map size must be positive", "model3d.bakeNormalMap.size"));
205 if (space != "tangent" && space != "object")
207 unsupported("space must be \"tangent\" or \"object\"", "model3d.bakeNormalMap.space"));
208 if (!mesh->HasNormals())
209 return eve::Result<ImagePtr>::failure(unsupported(
210 "mesh has no vertex normals; apply or set them before baking",
211 "model3d.bakeNormalMap.normals"));
212 if (uvChannel < 0 || uvChannel >= AI_MAX_NUMBER_OF_TEXTURECOORDS ||
213 !mesh->HasTextureCoords(static_cast<unsigned>(uvChannel)))
215 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "requested UV channel is not present",
216 "model3d.bakeNormalMap.channel"));
217
218 for (unsigned f = 0; f < mesh->mNumFaces; ++f) {
219 if (mesh->mFaces[f].mNumIndices != 3)
220 return eve::Result<ImagePtr>::failure(unsupported(
221 "normal-map baking requires triangulated faces", "model3d.bakeNormalMap.triangle"));
222 }
223
224 auto image = std::make_unique<image::ImageData>(width, height, "RGBA8");
226 fill.r = 0.5f;
227 fill.g = 0.5f;
228 fill.b = 1.f;
229 fill.a = 1.f;
230 for (int y = 0; y < height; ++y)
231 for (int x = 0; x < width; ++x)
232 image->setPixel(x, y, fill);
233
234 const float widthF = static_cast<float>(width);
235 const float heightF = static_cast<float>(height);
236 const bool tangentSpace = space == "tangent";
237
238 for (unsigned f = 0; f < mesh->mNumFaces; ++f) {
239 const aiFace &face = mesh->mFaces[f];
240 const unsigned i0 = face.mIndices[0];
241 const unsigned i1 = face.mIndices[1];
242 const unsigned i2 = face.mIndices[2];
243 const Vec3 p0{mesh->mVertices[i0].x, mesh->mVertices[i0].y, mesh->mVertices[i0].z};
244 const Vec3 p1{mesh->mVertices[i1].x, mesh->mVertices[i1].y, mesh->mVertices[i1].z};
245 const Vec3 p2{mesh->mVertices[i2].x, mesh->mVertices[i2].y, mesh->mVertices[i2].z};
246 const Vec3 n0{mesh->mNormals[i0].x, mesh->mNormals[i0].y, mesh->mNormals[i0].z};
247 const Vec3 n1{mesh->mNormals[i1].x, mesh->mNormals[i1].y, mesh->mNormals[i1].z};
248 const Vec3 n2{mesh->mNormals[i2].x, mesh->mNormals[i2].y, mesh->mNormals[i2].z};
249 const aiVector3D &t0 = mesh->mTextureCoords[uvChannel][i0];
250 const aiVector3D &t1 = mesh->mTextureCoords[uvChannel][i1];
251 const aiVector3D &t2 = mesh->mTextureCoords[uvChannel][i2];
252 const Vec2 uv0{t0.x, t0.y};
253 const Vec2 uv1{t1.x, t1.y};
254 const Vec2 uv2{t2.x, t2.y};
255 const Vec2 pix0{uv0.x * widthF, (1.f - uv0.y) * heightF};
256 const Vec2 pix1{uv1.x * widthF, (1.f - uv1.y) * heightF};
257 const Vec2 pix2{uv2.x * widthF, (1.f - uv2.y) * heightF};
258
259 const int minX = std::max(0, static_cast<int>(std::floor(std::min({pix0.x, pix1.x, pix2.x}))));
260 const int maxX =
261 std::min(width - 1, static_cast<int>(std::ceil(std::max({pix0.x, pix1.x, pix2.x}))));
262 const int minY = std::max(0, static_cast<int>(std::floor(std::min({pix0.y, pix1.y, pix2.y}))));
263 const int maxY =
264 std::min(height - 1, static_cast<int>(std::ceil(std::max({pix0.y, pix1.y, pix2.y}))));
265
266 for (int y = minY; y <= maxY; ++y) {
267 for (int x = minX; x <= maxX; ++x) {
268 const Vec2 sample{static_cast<float>(x) + 0.5f, static_cast<float>(y) + 0.5f};
269 float wa = 0.f, wb = 0.f, wc = 0.f;
270 if (!barycentric2(sample, pix0, pix1, pix2, &wa, &wb, &wc))
271 continue;
272 constexpr float kEps = -1e-4f;
273 if (wa < kEps || wb < kEps || wc < kEps)
274 continue;
275 Vec3 object = normalize3(add(add(scale(n0, wa), scale(n1, wb)), scale(n2, wc)));
276 const Vec3 encoded =
277 tangentSpace ? toTangent(object, p0, p1, p2, uv0, uv1, uv2) : object;
278 image->setPixel(x, y, encodeNormal(encoded));
279 }
280 }
281 }
282
283 return eve::Result<ImagePtr>::success(std::move(image));
284}
285
286} // namespace model3d
287} // namespace eve
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
const std::string & s
Vec3 tangent
Definition CaveMesh.cpp:80
float length
Definition CaveMesh.cpp:94
glm::vec4 p[6]
Stable, structured diagnostics shared by engine modules.
std::string message
vk::UniqueImage image
uint32_t i1
Definition Grass.cpp:61
uint32_t i2
Definition Grass.cpp:61
uint32_t i0
Definition Grass.cpp:61
glm::vec3 n
Definition Grass.cpp:63
std::int32_t c
std::uint32_t height
std::uint32_t width
TokenKind kind
std::array< float, 3 > scale
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float f
std::string path
Definition PlayHost.cpp:110
Mesh * mesh
Move-only, checked operation results for the common layer.
V3 origin
Definition RoadBake.cpp:138
Json object
A structured explanation of a failed, degraded, or noteworthy result.
Definition Diagnostic.h:94
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
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
medialoader::Colorf Colorf
Definition ImageData.h:41
eve::Result< void > applyVertexNormalsFrom(int meshIndex, std::string_view kind, float originX, float originY, float originZ)
Like applyVertexNormals, but "radial" uses an explicit origin.
eve::Result< void > applyVertexNormals(int meshIndex, std::string_view kind)
Replace every vertex normal on a mesh from a named procedure.
eve::Result< void > setVertexNormal(int meshIndex, int vertexIndex, float x, float y, float z)
Write one unit (or zero) object-space vertex normal. Allocates the normal stream when the mesh has po...
eve::Result< std::unique_ptr< image::ImageData > > bakeNormalMap(int meshIndex, int width, int height, int uvChannel=0, std::string_view space="tangent") const
Rasterize current object-space vertex normals into an RGBA8 normal map.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16