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) {
82 o << std::setprecision(9) << static_cast<double>(v);
86inline std::string
join_impl(
const std::vector<std::string> &v);
99 const std::vector<Vec3> *normals, uint32_t idx,
100 const Color *color) {
104 if (normals !=
nullptr && !normals->empty()) {
105 const Vec3 &n = (*normals)[idx];
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;
126 std::vector<Vec3> computed_normals;
133 normals = &computed_normals;
140 for (
size_t t = 0; t < mesh.
triangles.size(); ++t) {
143 uint32_t base =
static_cast<uint32_t
>(pm.
positions.size() / 3);
144 const uint32_t idxs[3] = {tri.
v0, tri.
v1, tri.
v2};
145 for (
int k = 0; k < 3; ++k) {
147 pm.
indices.push_back(base +
static_cast<uint32_t
>(k));
152 for (
size_t i = 0; i < mesh.
vertices.size(); ++i) {
153 const Color *c =
nullptr;
173inline void pad_bin(std::vector<uint8_t> &bin,
size_t align = 4) {
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()),
190 col_off(meshes.size()), idx_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) {
195 pos_off[i] = bin.size();
196 for (
float f : m.
positions) {
const uint8_t *p =
reinterpret_cast<const uint8_t *
>(&f); bin.insert(bin.end(), p, p + 4); }
201 nor_off[i] = bin.size();
202 for (
float f : m.
normals) {
const uint8_t *p =
reinterpret_cast<const uint8_t *
>(&f); bin.insert(bin.end(), p, p + 4); }
206 col_off[i] = bin.size();
207 bin.insert(bin.end(), m.
colors.begin(), m.
colors.end());
210 idx_off[i] = bin.size();
212 const uint8_t *p =
reinterpret_cast<const uint8_t *
>(&v);
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();
229 int next_accessor = 0;
230 std::vector<std::string> accessors_json;
231 std::vector<std::string> views_json;
232 std::vector<std::vector<int>> mesh_accessors;
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) {
242 if (k == 0 || v < mn[d]) mn[d] = v;
243 if (k == 0 || v > mx[d]) mx[d] = v;
246 views_json.push_back(
"{\"buffer\": 0, \"byteOffset\": " + std::to_string(pos_off[i]) +
247 ", \"byteLength\": " + std::to_string(m.
positions.size() * 4) +
"}");
248 accessors_json.push_back(
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]) +
"]}");
253 acc.push_back(next_accessor++);
257 views_json.push_back(
"{\"buffer\": 0, \"byteOffset\": " + std::to_string(nor_off[i]) +
258 ", \"byteLength\": " + std::to_string(m.
normals.size() * 4) +
"}");
259 accessors_json.push_back(
260 "{\"bufferView\": " + std::to_string(views_json.size() - 1) +
261 ", \"componentType\": 5126, \"count\": " + std::to_string(pos_cnt[i]) +
262 ", \"type\": \"VEC3\"}");
263 acc.push_back(next_accessor++);
269 views_json.push_back(
"{\"buffer\": 0, \"byteOffset\": " + std::to_string(col_off[i]) +
270 ", \"byteLength\": " + std::to_string(m.
colors.size()) +
"}");
271 accessors_json.push_back(
272 "{\"bufferView\": " + std::to_string(views_json.size() - 1) +
273 ", \"componentType\": 5121, \"count\": " + std::to_string(pos_cnt[i]) +
274 ", \"normalized\": true, \"type\": \"VEC4\"}");
275 acc.push_back(next_accessor++);
280 views_json.push_back(
"{\"buffer\": 0, \"byteOffset\": " + std::to_string(idx_off[i]) +
281 ", \"byteLength\": " + std::to_string(m.
indices.size() * 4) +
"}");
282 accessors_json.push_back(
283 "{\"bufferView\": " + std::to_string(views_json.size() - 1) +
284 ", \"componentType\": 5125, \"count\": " + std::to_string(idx_cnt[i]) +
285 ", \"type\": \"SCALAR\"}");
286 acc.push_back(next_accessor++);
287 mesh_accessors.push_back(acc);
290 j <<
" \"bufferViews\": [\n " <<
join_impl(views_json) <<
"\n ],\n";
291 j <<
" \"accessors\": [\n " <<
join_impl(accessors_json) <<
"\n ],\n";
294 std::vector<std::string> materials_json;
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}}";
307 materials_json.push_back(mat.str());
309 j <<
" \"materials\": [\n " <<
join_impl(materials_json) <<
"\n ],\n";
312 std::vector<std::string> meshes_json;
313 for (
size_t i = 0; i < meshes.size(); ++i) {
314 const auto &acc = mesh_accessors[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 <<
"}";
322 meshes_json.push_back(
"{\"primitives\": [" + prim.str() +
"]}");
324 j <<
" \"meshes\": [\n " <<
join_impl(meshes_json) <<
"\n ],\n";
329 float yfov = camera->
fov_degrees * 3.14159265358979323846f / 180.0f;
330 j <<
" \"cameras\": [{\"type\": \"perspective\", \"perspective\": {\"yfov\": "
331 <<
fmt(yfov) <<
", \"znear\": 0.1}}],\n";
335 std::vector<std::string> nodes_json;
336 for (
size_t i = 0; i < meshes.size(); ++i) {
337 std::ostringstream n;
338 n <<
"{\"mesh\": " << i;
339 if (!scene.
name(i).empty())
342 bool identity = (m ==
Mat4(1.0f));
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 <<
", ";
353 nodes_json.push_back(n.str());
356 std::ostringstream n;
357 n <<
"{\"camera\": 0, \"name\": \"camera\"}";
358 nodes_json.push_back(n.str());
360 j <<
" \"nodes\": [\n " <<
join_impl(nodes_json) <<
"\n ],\n";
363 std::vector<std::string> node_ids;
364 for (
size_t i = 0; i < nodes_json.size(); ++i) node_ids.push_back(std::to_string(i));
365 j <<
" \"scenes\": [{\"nodes\": [" <<
join_impl(node_ids) <<
"]}],\n";
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) {
382inline std::string
basename(
const std::string &path) {
383 size_t pos = path.find_last_of(
"/\\");
384 return pos == std::string::npos ? path : path.substr(pos + 1);
387inline std::string
dirname(
const std::string &path) {
388 size_t pos = path.find_last_of(
"/\\");
389 return pos == std::string::npos ? std::string() : path.substr(0, pos + 1);
392inline std::string
stem(
const std::string &path) {
394 size_t dot = b.find_last_of(
'.');
395 return (dot == std::string::npos) ? b : b.substr(0, dot);
398inline void write_bytes(
const std::string &path,
const std::vector<uint8_t> &data) {
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()));
417 const Camera *camera =
nullptr) {
423 std::string uri_escaped = uri;
425 std::string needle =
"\"byteLength\": " + std::to_string(out.
bin.size()) +
"}";
426 std::string repl =
"\"byteLength\": " + std::to_string(out.
bin.size()) +
427 ", \"uri\": \"" + uri_escaped +
"\"}";
428 size_t pos = out.
json.find(needle);
429 if (pos != std::string::npos)
430 out.
json.replace(pos, needle.size(), repl);
434 std::ofstream jf(path);
435 if (!jf)
throw std::runtime_error(
"glTF: cannot open '" + path +
"' for writing");
447 const Camera *camera =
nullptr) {
451 std::vector<uint8_t> json_chunk(out.
json.begin(), out.
json.end());
452 while (json_chunk.size() % 4 != 0) json_chunk.push_back(0x20);
454 std::vector<uint8_t> bin = out.
bin;
455 while (bin.size() % 4 != 0) bin.push_back(0);
457 const uint32_t kMagic = 0x46546C67;
458 const uint32_t kVersion = 2;
459 const uint32_t kJsonType = 0x4E4F534A;
460 const uint32_t kBinType = 0x004E4942;
461 uint32_t total = 12 + 8 +
static_cast<uint32_t
>(json_chunk.size()) +
462 8 +
static_cast<uint32_t
>(bin.size());
464 std::vector<uint8_t> out_bytes;
465 auto put_u32 = [&out_bytes](uint32_t v) {
466 out_bytes.push_back(v & 0xFF);
467 out_bytes.push_back((v >> 8) & 0xFF);
468 out_bytes.push_back((v >> 16) & 0xFF);
469 out_bytes.push_back((v >> 24) & 0xFF);
474 put_u32(
static_cast<uint32_t
>(json_chunk.size()));
476 out_bytes.insert(out_bytes.end(), json_chunk.begin(), json_chunk.end());
477 put_u32(
static_cast<uint32_t
>(bin.size()));
479 out_bytes.insert(out_bytes.end(), bin.begin(), bin.end());
488inline void write(
const std::string &path,
const Scene &scene,
489 const Camera *camera =
nullptr) {
492 size_t dot = b.find_last_of(
'.');
493 if (dot != std::string::npos)
494 ext = b.substr(dot + 1);
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.
ProjectionType projection
Which projection type to use.
float fov_degrees
Vertical field of view in degrees (perspective only).
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.
std::vector< Mesh > meshes
The meshes in this scene, drawn in order.
const Mat4 & transform(size_t index) const
The placement transform of the mesh at index (identity when unset).
const std::string & name(size_t index) const
The name of the mesh at index (empty when unset).
A triangle defined by three vertex indices.
uint32_t v2
Index of the third vertex in Mesh::vertices.
uint32_t v1
Index of the second vertex in Mesh::vertices.
uint32_t v0
Index of the first vertex in Mesh::vertices.
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