26 bool write_via_registry(
27 const std::string& filepath,
28 const std::vector<Kakshya::MeshData>& meshes,
29 const ModelWriteOptions& options)
34 "save_mesh: no writer registered for '{}'", filepath);
38 const bool ok = writer->write(filepath, meshes, options);
41 "save_mesh: writer failed for '{}': {}", filepath, writer->get_last_error());
44 "save_mesh: wrote '{}'", filepath);
61 std::string pack_diffuse_texture(
62 const std::shared_ptr<Core::VKImage>& texture,
63 const std::string& model_filepath,
64 const std::string& suffix)
70 const std::filesystem::path model_path(model_filepath);
71 std::string
name = model_path.stem().string() +
"_diffuse";
72 if (!suffix.empty()) {
77 const auto image_path = (model_path.parent_path() /
name).
string();
80 "save_mesh: failed to write diffuse texture to '{}'", image_path);
90 void ensure_submesh_region(Kakshya::MeshData& mesh_data,
const std::string&
name)
92 if (mesh_data.submeshes && !mesh_data.submeshes->regions.empty()) {
96 Kakshya::MeshSubrange sub;
98 sub.index_count = mesh_data.face_count() * 3;
101 Kakshya::RegionGroup rg(
"submeshes");
102 rg.add_region(sub.to_region());
103 mesh_data.submeshes = std::move(rg);
111 std::optional<Kakshya::MeshData> pack_node(
112 const std::shared_ptr<Nodes::GpuSync::MeshWriterNode>& node,
113 const glm::mat4& world,
114 const std::string&
name,
117 const auto& src_verts = node->get_mesh_vertices();
118 const auto& indices = node->get_mesh_indices();
119 if (src_verts.empty() || indices.empty()) {
123 const glm::mat3 normal_matrix(world);
125 std::vector<Kakshya::MeshVertex> verts(src_verts.begin(), src_verts.end());
126 for (
auto& v : verts) {
127 v.position = glm::vec3(world * glm::vec4(v.position, 1.0F));
128 v.normal = glm::normalize(normal_matrix * v.normal);
129 v.tangent = glm::normalize(normal_matrix * v.tangent);
133 Kakshya::MeshInsertion ins(mesh_data.vertex_variant, mesh_data.index_variant);
135 std::span<const uint8_t>(
136 reinterpret_cast<const uint8_t*
>(verts.data()),
137 verts.size() *
sizeof(Kakshya::MeshVertex)),
138 std::span<const uint32_t>(indices),
140 auto access = ins.build();
144 mesh_data.layout = access->layout;
146 Kakshya::MeshSubrange sub;
148 sub.index_count =
static_cast<uint32_t
>(indices.size());
151 Kakshya::RegionGroup rg(
"submeshes");
152 rg.add_region(sub.to_region());
153 mesh_data.submeshes = std::move(rg);
158 std::string splice_timestamp(
const std::string& pattern)
160 const auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
161 std::chrono::system_clock::now().time_since_epoch())
169 const std::shared_ptr<Buffers::MeshBuffer>&
buffer,
170 const std::string& filepath,
178 auto mesh_data =
buffer->get_mesh_data();
179 if (
buffer->has_diffuse_texture()) {
180 const auto image_path = pack_diffuse_texture(
buffer->get_diffuse_texture(), filepath,
"");
181 if (!image_path.empty()) {
182 ensure_submesh_region(mesh_data,
"mesh");
183 mesh_data.submeshes->regions.front().set_attribute(
"diffuse_path", image_path);
187 return write_via_registry(filepath, { mesh_data }, options);
191 const std::shared_ptr<Nodes::Network::MeshNetwork>&
network,
192 const std::string& filepath,
200 std::vector<Kakshya::MeshData> meshes;
201 meshes.reserve(
network->slots().size());
202 for (
const auto& slot :
network->slots()) {
207 if (slot.diffuse_texture) {
208 const auto suffix = !slot.name.empty() ? slot.name : std::to_string(slot.index);
209 diffuse_path = pack_diffuse_texture(slot.diffuse_texture, filepath, suffix);
211 if (
auto packed = pack_node(slot.node, slot.world_transform, slot.name,
diffuse_path)) {
212 meshes.push_back(std::move(*packed));
216 if (meshes.empty()) {
218 "save_mesh: no exportable slots in network");
222 return write_via_registry(filepath, meshes, options);
226 const std::shared_ptr<Buffers::MeshNetworkBuffer>& network_buffer,
227 const std::string& filepath,
230 if (!network_buffer) {
235 auto net = network_buffer->get_network();
238 "save_mesh: MeshNetworkBuffer has no network");
242 std::vector<Kakshya::MeshData> meshes;
243 meshes.reserve(net->slots().size());
244 const std::string shared_diffuse_path = network_buffer->has_diffuse_texture()
245 ? pack_diffuse_texture(network_buffer->get_diffuse_texture(), filepath,
"")
247 for (
const auto& slot : net->slots()) {
252 if (slot.diffuse_texture) {
253 const auto suffix = !slot.name.empty() ? slot.name : std::to_string(slot.index);
254 diffuse_path = pack_diffuse_texture(slot.diffuse_texture, filepath, suffix);
256 if (
auto packed = pack_node(slot.node, slot.world_transform, slot.name,
diffuse_path)) {
257 meshes.push_back(std::move(*packed));
261 if (meshes.empty()) {
263 "save_mesh: no exportable slots in network");
267 return write_via_registry(filepath, meshes, options);
271 const std::shared_ptr<Nodes::GpuSync::MeshWriterNode>& node,
272 const std::string& filepath,
280 auto packed = pack_node(node, glm::mat4(1.0F),
"mesh");
283 "save_mesh: node has no vertices/indices yet");
287 return write_via_registry(filepath, { *packed }, options);
291 const std::shared_ptr<Buffers::ComputeMeshBuffer>&
buffer)
298 auto processor =
buffer->get_mesh_processor();
301 "download_compute_mesh: no mesh processor; setup_processors() has not been called");
305 auto counter_buf = processor->counter_buf();
306 const auto* counter_ptr = counter_buf
307 ?
static_cast<const uint32_t*
>(counter_buf->get_mapped_ptr())
311 "download_compute_mesh: counter buffer is not host-visible or not allocated yet");
315 const uint32_t vertex_count = *counter_ptr;
316 if (vertex_count == 0) {
318 "download_compute_mesh: live vertex count is zero");
322 std::vector<Kakshya::MeshVertex> verts(vertex_count);
323 std::shared_ptr<Buffers::VKBuffer> staging;
327 std::vector<uint32_t> indices(vertex_count);
328 std::ranges::iota(indices, 0
U);
333 std::span<const uint8_t>(
334 reinterpret_cast<const uint8_t*
>(verts.data()),
336 std::span<const uint32_t>(indices),
338 auto access = ins.
build();
341 "download_compute_mesh: MeshInsertion::build() failed");
344 mesh_data.layout = access->layout;
350 const std::shared_ptr<Buffers::ComputeMeshBuffer>&
buffer,
351 const std::string& filepath,
359 if (
buffer->has_diffuse_texture()) {
360 const auto image_path = pack_diffuse_texture(
buffer->get_diffuse_texture(), filepath,
"");
361 if (!image_path.empty()) {
362 ensure_submesh_region(*mesh_data,
"mesh");
363 mesh_data->submeshes->regions.front().set_attribute(
"diffuse_path", image_path);
367 return write_via_registry(filepath, { *mesh_data }, options);
371 const std::shared_ptr<Buffers::ComputeMeshBuffer>&
buffer,
372 const std::string& path_pattern,
379 const std::shared_ptr<Buffers::MeshBuffer>&
buffer,
380 const std::string& path_pattern,
387 const std::shared_ptr<Buffers::MeshNetworkBuffer>& network_buffer,
388 const std::string& path_pattern,
391 return save_mesh(network_buffer, splice_timestamp(path_pattern), options);
395 const std::shared_ptr<Nodes::Network::MeshNetwork>&
network,
396 const std::string& path_pattern,
403 const std::shared_ptr<Nodes::GpuSync::MeshWriterNode>& node,
404 const std::string& path_pattern,
407 return save_mesh(node, splice_timestamp(path_pattern), options);
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
Core::GlobalNetworkConfig network
std::shared_ptr< NetworkGeometryBuffer > buffer
std::unique_ptr< ModelWriter > create_writer(const std::string &filepath) const
static ModelWriterRegistry & instance()
std::optional< MeshAccess > build() const
Produce a MeshAccess over the current variant contents.
void insert_flat(std::span< const uint8_t > vertex_bytes, std::span< const uint32_t > index_data, const VertexLayout &layout)
Insert a single flat mesh (no submesh tracking).
Write counterpart to MeshAccess.
void download_from_gpu_async(const std::shared_ptr< VKBuffer > &source, void *data, size_t size, std::shared_ptr< VKBuffer > &staging)
Download from a device-local GPU buffer without stalling the graphics queue.
bool save_image(const std::shared_ptr< Core::VKImage > &image, const std::string &filepath, const ImageWriteOptions &options)
Save a VKImage directly to disk via the ImageWriter registry.
bool save_mesh_snapshot(const std::shared_ptr< Buffers::ComputeMeshBuffer > &buffer, const std::string &path_pattern, const ModelWriteOptions &options)
Save a ComputeMeshBuffer with a millisecond epoch timestamp spliced into the path.
std::string resolve_sequence_path(std::string_view pattern, uint64_t frame)
Substitute a frame index into a numbered output pattern.
bool save_mesh(const std::shared_ptr< Buffers::MeshBuffer > &buffer, const std::string &filepath, const ModelWriteOptions &options)
Save a MeshBuffer's current mesh data to disk via ModelWriterRegistry.
std::optional< Kakshya::MeshData > download_compute_mesh(const std::shared_ptr< Buffers::ComputeMeshBuffer > &buffer)
Download a ComputeMeshBuffer's current live geometry to CPU.
@ FileIO
Filesystem I/O operations.
@ IO
Networking, file handling, streaming.
Configuration for model writing.
static MeshData empty()
Construct an empty MeshData with the canonical 60-byte mesh layout.
Vertex type for indexed triangle mesh primitives (TRIANGLE_LIST topology)
static VertexLayout for_meshes(uint32_t stride=60)
Factory: layout for MeshVertex (position, color, weight, uv, normal, tangent)