research(intervals): OBJ export switch and a D3D11 fly-through viewer for the spike

Viewer generates the world at startup with the spike's code (timing shown), free-fly camera,
wireframe, near/far toggles, seed cycling. Benchmark hashes unchanged (e5ad62303de5f641).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-02 01:27:18 +02:00
1 parent 61b1c9c329
commit 8d29c09512
2 files changed
+190 -2

No files matched your search

+99 -2
View File
@@ -10,6 +10,9 @@
#include <cstdlib>
#include <cstring>
#include <functional>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <limits>
#include <numeric>
#include <span>
@@ -1323,11 +1326,104 @@ void print_axis_stats(const char* name,const AxisStats& s) {
s.runsP50,s.runsP95,s.runsMax);
}
bool write_obj(const std::filesystem::path& path, const std::vector<Vertex>& vertices,
const std::vector<uint32_t>& indices, const std::vector<PageRange>* pages = nullptr) {
std::ofstream file(path, std::ios::binary | std::ios::trunc);
if (!file) return false;
file << "# VoxelForge interval spike; Unreal centimetres, Z up, relative to spike world origin\n";
std::vector<uint32_t> remap;
std::vector<uint8_t> used;
if (pages) {
remap.assign(vertices.size(), 0);
used.assign(vertices.size(), 0);
for (const PageRange& page : *pages) {
if (static_cast<uint64_t>(page.indexStart) + page.indexCount > indices.size()) return false;
for (uint32_t i = 0; i < page.indexCount; ++i) {
const uint32_t index = indices[page.indexStart + i];
if (index >= vertices.size()) return false;
used[index] = 1;
}
}
uint32_t next = 1;
for (size_t i = 0; i < vertices.size(); ++i) if (used[i]) remap[i] = next++;
}
file << std::fixed << std::setprecision(6);
for (size_t i = 0; i < vertices.size(); ++i) {
if (pages && !used[i]) continue;
file << "v " << vertices[i].x * 100.0f << ' ' << vertices[i].y * 100.0f << ' '
<< vertices[i].z * 100.0f << '\n';
}
for (size_t i = 0; i < vertices.size(); ++i) {
if (pages && !used[i]) continue;
file << "vn " << vertices[i].nx << ' ' << vertices[i].ny << ' ' << vertices[i].nz << '\n';
}
auto emit_face = [&](uint32_t a, uint32_t b, uint32_t c) {
if (pages) { a = remap[a]; b = remap[b]; c = remap[c]; }
file << "f " << a << "//" << a << ' ' << b << "//" << b << ' ' << c << "//" << c << '\n';
};
if (pages) {
for (const PageRange& page : *pages)
for (uint32_t i = 0; i < page.indexCount; i += 3)
emit_face(indices[page.indexStart + i], indices[page.indexStart + i + 1], indices[page.indexStart + i + 2]);
} else {
if (indices.size() % 3 != 0) return false;
for (size_t i = 0; i < indices.size(); i += 3) {
if (indices[i] >= vertices.size() || indices[i + 1] >= vertices.size() || indices[i + 2] >= vertices.size()) return false;
emit_face(indices[i] + 1, indices[i + 1] + 1, indices[i + 2] + 1);
}
}
file.flush();
return file.good();
}
bool export_obj_files(const std::filesystem::path& directory, const MeshOutput& nearMesh, const FarOutput& farMesh) {
std::error_code ec;
std::filesystem::create_directories(directory, ec);
if (ec) { std::fprintf(stderr, "OBJ export: cannot create %s: %s\n", directory.string().c_str(), ec.message().c_str()); return false; }
std::vector<Vertex> nearVertices;
nearVertices.reserve(nearMesh.vertices.size());
for (const TempVertex& v : nearMesh.vertices) {
Vertex local = v.v;
local.x -= kWorldOriginCm[0] * 0.01f;
local.y -= kWorldOriginCm[1] * 0.01f;
local.z -= kWorldOriginCm[2] * 0.01f;
nearVertices.push_back(local);
}
if (!write_obj(directory / "near.obj", nearVertices, nearMesh.indices)) {
std::fprintf(stderr, "OBJ export: failed writing %s\n", (directory / "near.obj").string().c_str()); return false;
}
std::vector<Vertex> farVertices = farMesh.vertices;
for (Vertex& v : farVertices) {
v.x -= kWorldOriginCm[0] * 0.01f;
v.y -= kWorldOriginCm[1] * 0.01f;
v.z -= kWorldOriginCm[2] * 0.01f;
}
if (!write_obj(directory / "far.obj", farVertices, farMesh.indices)) {
std::fprintf(stderr, "OBJ export: failed writing %s\n", (directory / "far.obj").string().c_str()); return false;
}
for (uint32_t pageId = 0; pageId < static_cast<uint32_t>(kPagesPerAxis*kPagesPerAxis*kPagesPerAxis); ++pageId) {
std::vector<PageRange> ranges;
for (const PageRange& range : nearMesh.pages) if (range.page == pageId) ranges.push_back(range);
if (std::none_of(ranges.begin(), ranges.end(), [](const PageRange& range) { return range.indexCount != 0; })) continue;
char filename[32]; std::snprintf(filename, sizeof(filename), "near_page_%02u.obj", pageId);
if (!write_obj(directory / filename, nearVertices, nearMesh.indices, &ranges)) {
std::fprintf(stderr, "OBJ export: failed writing %s\n", (directory / filename).string().c_str()); return false;
}
}
return true;
}
} // namespace
int main(int argc,char** argv) {
const uint64_t seed=argc>1?std::strtoull(argv[1],nullptr,10):1;
const int workers=argc>2?std::clamp(std::atoi(argv[2]),1,8):1;
uint64_t seed=1; int workers=1; std::filesystem::path exportDirectory; bool exportObj=false;
int positional=0;
for (int i=1;i<argc;++i) {
if (std::strcmp(argv[i], "--export-obj") == 0 && i+1<argc) { exportObj=true; exportDirectory=argv[++i]; }
else if (positional++ == 0) seed=std::strtoull(argv[i],nullptr,10);
else if (positional == 2) workers=std::clamp(std::atoi(argv[i]),1,8);
else { std::fprintf(stderr,"Usage: interval_spike.exe [seed] [workers] [--export-obj directory]\n"); return 1; }
}
double corpusMs=0,intervalMs=0,activeMs=0,meshMs=0,farMs=0,validationMs=0;
Clock::time_point t0=Clock::now();
Corpus corpus;
@@ -1387,5 +1483,6 @@ int main(int argc,char** argv) {
std::printf("\"output_bytes_total\":%llu,\"combined_hash\":\"%016llx\",\"working_set_bytes\":%llu,\"validation_pass\":%s}\n",
static_cast<unsigned long long>(totalBytes),static_cast<unsigned long long>(combinedHash),
static_cast<unsigned long long>(process_working_set()),validationPass?"true":"false");
if (exportObj && !export_obj_files(exportDirectory, mesh, farOutput)) return 3;
return validationPass?0:2;
}