62 case '"':
o <<
"\\\"";
break;
63 case '\\':
o <<
"\\\\";
break;
64 case '\n':
o <<
"\\n";
break;
65 case '\r':
o <<
"\\r";
break;
66 case '\t':
o <<
"\\t";
break;
68 if (
static_cast<unsigned char>(
ch) < 0x20) {
69 o <<
"\\u" << std::hex << std::setw(4) << std::setfill(
'0')
70 <<
static_cast<int>(
ch) << std::dec;
80inline std::string
fmt(
float v) {
86inline std::string
join_impl(
const std::vector<std::string> &
v);
100 const Color *color) {
101 pm.positions.push_back(
v.x);
102 pm.positions.push_back(
v.y);
103 pm.positions.push_back(
v.z);
104 if (normals !=
nullptr && !normals->empty()) {
106 pm.normals.push_back(
n.x);
107 pm.normals.push_back(
n.y);
108 pm.normals.push_back(
n.z);
110 if (color !=
nullptr) {
111 pm.colors.push_back(
static_cast<uint8_t>(std::min(1.0f, std::max(0.0f, color->
r)) * 255.0f + 0.5f));
112 pm.colors.push_back(
static_cast<uint8_t>(std::min(1.0f, std::max(0.0f, color->
g)) * 255.0f + 0.5f));
113 pm.colors.push_back(
static_cast<uint8_t>(std::min(1.0f, std::max(0.0f, color->
b)) * 255.0f + 0.5f));
114 pm.colors.push_back(
static_cast<uint8_t>(std::min(1.0f, std::max(0.0f, color->
a)) * 255.0f + 0.5f));
125 const std::vector<Vec3> *normals =
nullptr;
139 pm.has_colors =
true;
145 for (
int k = 0;
k < 3; ++
k) {
152 for (
size_t i = 0;
i < mesh.
vertices.size(); ++
i) {
158 pm.indices.push_back(
tri.v0);
159 pm.indices.push_back(
tri.v1);
160 pm.indices.push_back(
tri.v2);
174 while (bin.size() %
align != 0) bin.push_back(0);
178 std::vector<PackedMesh> meshes;
179 meshes.reserve(
scene.meshes.size());
180 for (
size_t i = 0;
i <
scene.meshes.size(); ++
i) {
181 if (!
scene.meshes[
i].empty())
186 std::vector<uint8_t> &bin =
out.
bin;
189 std::vector<size_t>
pos_off(meshes.size()),
nor_off(meshes.size()),
191 std::vector<size_t>
pos_cnt(meshes.size()),
idx_cnt(meshes.size());
192 for (
size_t i = 0;
i < meshes.size(); ++
i) {
196 for (
float f :
m.positions) {
const uint8_t *
p =
reinterpret_cast<const uint8_t *
>(&
f); bin.insert(bin.end(),
p,
p + 4); }
199 if (!
m.normals.empty()) {
202 for (
float f :
m.normals) {
const uint8_t *
p =
reinterpret_cast<const uint8_t *
>(&
f); bin.insert(bin.end(),
p,
p + 4); }
204 if (!
m.colors.empty()) {
207 bin.insert(bin.end(),
m.colors.begin(),
m.colors.end());
213 bin.insert(bin.end(),
p,
p + 4);
219 std::ostringstream
j;
222 j <<
" \"asset\": {\"version\": \"2.0\", \"generator\": \"scimesh\"},\n";
225 j <<
" \"buffers\": [{\"byteLength\": " << bin.size();
233 for (
size_t i = 0;
i < meshes.size(); ++
i) {
235 std::vector<int>
acc;
238 float mn[3] = {0,0,0},
mx[3] = {0,0,0};
239 for (
size_t k = 0;
k <
m.positions.size();
k += 3) {
240 for (
int d = 0;
d < 3; ++
d) {
246 views_json.push_back(
"{\"buffer\": 0, \"byteOffset\": " + std::to_string(
pos_off[
i]) +
247 ", \"byteLength\": " + std::to_string(
m.positions.size() * 4) +
"}");
249 "{\"bufferView\": " + std::to_string(
views_json.size() - 1) +
250 ", \"componentType\": 5126, \"count\": " + std::to_string(
pos_cnt[
i]) +
251 ", \"type\": \"VEC3\", \"min\": [" +
fmt(
mn[0]) +
", " +
fmt(
mn[1]) +
", " +
fmt(
mn[2]) +
252 "], \"max\": [" +
fmt(
mx[0]) +
", " +
fmt(
mx[1]) +
", " +
fmt(
mx[2]) +
"]}");
256 if (!
m.normals.empty()) {
257 views_json.push_back(
"{\"buffer\": 0, \"byteOffset\": " + std::to_string(
nor_off[
i]) +
258 ", \"byteLength\": " + std::to_string(
m.normals.size() * 4) +
"}");
260 "{\"bufferView\": " + std::to_string(
views_json.size() - 1) +
261 ", \"componentType\": 5126, \"count\": " + std::to_string(
pos_cnt[
i]) +
262 ", \"type\": \"VEC3\"}");
268 if (!
m.colors.empty()) {
269 views_json.push_back(
"{\"buffer\": 0, \"byteOffset\": " + std::to_string(
col_off[
i]) +
270 ", \"byteLength\": " + std::to_string(
m.colors.size()) +
"}");
272 "{\"bufferView\": " + std::to_string(
views_json.size() - 1) +
273 ", \"componentType\": 5121, \"count\": " + std::to_string(
pos_cnt[
i]) +
274 ", \"normalized\": true, \"type\": \"VEC4\"}");
280 views_json.push_back(
"{\"buffer\": 0, \"byteOffset\": " + std::to_string(
idx_off[
i]) +
281 ", \"byteLength\": " + std::to_string(
m.indices.size() * 4) +
"}");
283 "{\"bufferView\": " + std::to_string(
views_json.size() - 1) +
284 ", \"componentType\": 5125, \"count\": " + std::to_string(
idx_cnt[
i]) +
285 ", \"type\": \"SCALAR\"}");
296 float r =
m.has_colors ? 1.0f :
m.base_r;
297 float g =
m.has_colors ? 1.0f :
m.base_g;
298 float b =
m.has_colors ? 1.0f :
m.base_b;
299 float a =
m.has_colors ? 1.0f :
m.base_a;
300 std::ostringstream
mat;
304 mat <<
"{\"pbrMetallicRoughness\": {\"baseColorFactor\": ["
305 <<
fmt(r) <<
", " <<
fmt(g) <<
", " <<
fmt(b) <<
", " <<
fmt(a)
306 <<
"], \"metallicFactor\": 0.0, \"roughnessFactor\": 1.0}}";
313 for (
size_t i = 0;
i < meshes.size(); ++
i) {
315 std::ostringstream
attrs;
316 attrs <<
"\"POSITION\": " <<
acc[0];
317 if (
acc[1] >= 0)
attrs <<
", \"NORMAL\": " <<
acc[1];
318 if (
acc[2] >= 0)
attrs <<
", \"COLOR_0\": " <<
acc[2];
319 std::ostringstream
prim;
320 prim <<
"{\"attributes\": {" <<
attrs.str() <<
"}, \"indices\": "
321 <<
acc[3] <<
", \"material\": " <<
i <<
"}";
328 if (
out.has_camera) {
329 float yfov =
camera->fov_degrees * 3.14159265358979323846f / 180.0f;
330 j <<
" \"cameras\": [{\"type\": \"perspective\", \"perspective\": {\"yfov\": "
331 <<
fmt(
yfov) <<
", \"znear\": 0.1}}],\n";
336 for (
size_t i = 0;
i < meshes.size(); ++
i) {
337 std::ostringstream
n;
338 n <<
"{\"mesh\": " <<
i;
339 if (!
scene.name(
i).empty())
344 n <<
", \"matrix\": [";
345 for (
int c = 0;
c < 4; ++
c)
346 for (
int r = 0; r < 4; ++r) {
347 if (
c != 0 || r != 0)
n <<
", ";
355 if (
out.has_camera) {
356 std::ostringstream
n;
357 n <<
"{\"camera\": 0, \"name\": \"camera\"}";
366 j <<
" \"scene\": 0\n";
373inline std::string
join_impl(
const std::vector<std::string> &
v) {
374 std::ostringstream
o;
375 for (
size_t i = 0;
i <
v.size(); ++
i) {
383 size_t pos =
path.find_last_of(
"/\\");
384 return pos == std::string::npos ?
path :
path.substr(pos + 1);
388 size_t pos =
path.find_last_of(
"/\\");
389 return pos == std::string::npos ? std::string() :
path.substr(0, pos + 1);
394 size_t dot = b.find_last_of(
'.');
395 return (
dot == std::string::npos) ? b : b.substr(0,
dot);
399 std::ofstream
f(
path, std::ios::binary);
400 if (!
f)
throw std::runtime_error(
"glTF: cannot open '" +
path +
"' for writing");
401 f.write(
reinterpret_cast<const char *
>(data.data()),
402 static_cast<std::streamsize
>(data.size()));
425 std::string
needle =
"\"byteLength\": " + std::to_string(
out.bin.size()) +
"}";
426 std::string
repl =
"\"byteLength\": " + std::to_string(
out.bin.size()) +
429 if (pos != std::string::npos)
435 if (!
jf)
throw std::runtime_error(
"glTF: cannot open '" +
path +
"' for writing");
454 std::vector<uint8_t> bin =
out.bin;
455 while (bin.size() % 4 != 0) bin.push_back(0);
462 8 +
static_cast<uint32_t>(bin.size());
492 size_t dot = b.find_last_of(
'.');
493 if (
dot != std::string::npos)
495 for (
auto &
c :
ext)
c =
static_cast<char>(std::tolower(
static_cast<unsigned char>(
c)));
Camera definition and helper functions for view setup.
The Mesh — the central data structure for 3D geometry in scimesh.
void write_bytes(const std::string &path, const std::vector< uint8_t > &data)
GltfOutput build(const Scene &scene, const Camera *camera)
std::string json_escape(const std::string &s)
std::string fmt(float v)
Print a float compactly with enough digits to round-trip a float32.
void pad_bin(std::vector< uint8_t > &bin, size_t align=4)
std::string dirname(const std::string &path)
std::string basename(const std::string &path)
PackedMesh pack_mesh(const Mesh &mesh)
void append_vertex(PackedMesh &pm, const Vec3 &v, const std::vector< Vec3 > *normals, uint32_t idx, const Color *color)
std::string join_impl(const std::vector< std::string > &v)
std::string stem(const std::string &path)
void write(const std::string &path, const Scene &scene, const Camera *camera=nullptr)
Write a scene as glTF, choosing the format from the file extension.
void write_gltf(const std::string &path, const Scene &scene, const Camera *camera=nullptr)
Write a scene as glTF 2.0 (JSON document + external .bin file).
void write_glb(const std::string &path, const Scene &scene, const Camera *camera=nullptr)
Write a scene as a single binary glTF file (.glb).
glm::mat4 Mat4
4×4 floating-point matrix.
void compute_vertex_normals(const Mesh &mesh, std::vector< Vec3 > &normals)
Compute per-vertex normals by averaging adjacent face normals.
glm::vec3 Vec3
3-component floating-point vector (xyz).
@ PERSPECTIVE
Perspective projection: objects farther away appear smaller.
Compute per-vertex surface normals for lighting.
The Scene — a collection of meshes rendered together.
A virtual camera that defines the viewpoint for rendering.
An RGBA color with floating-point components.
float g
Green channel, [0, 1].
float r
Red channel, [0, 1].
float b
Blue channel, [0, 1].
float a
Alpha (opacity) channel, [0, 1]. 1.0 = fully opaque.
A 3D triangle mesh using an indexed face set representation.
bool has_face_colors() const
Does the mesh have per-face colors?
std::vector< Color > face_colors
Per-face RGBA colors.
std::vector< Color > colors
Per-vertex RGBA colors.
bool has_colors() const
Does the mesh have per-vertex colors?
std::vector< Vec3 > normals
Per-vertex surface normals (unit-length direction vectors).
bool has_normals() const
Does the mesh have per-vertex normals?
std::vector< Vec3 > vertices
3D vertex positions.
std::vector< Triangle > triangles
Triangle index triplets.
Color default_color
Fallback color when no per-vertex or per-face color is set.
A collection of Mesh objects to be rendered together.
A triangle defined by three vertex indices.
Serialized glTF scene: the JSON document plus the binary buffer.
std::vector< uint8_t > bin
One mesh, packed into glTF-ready flat arrays.
std::vector< uint32_t > indices
std::vector< float > normals
std::vector< float > positions
std::vector< uint8_t > colors
int x
Left edge of the bitmap, in image pixels.