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:
1 parent
61b1c9c329
commit
8d29c09512
2 files changed
+190
-2
No files matched your search
@@ -10,6 +10,9 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <span>
|
#include <span>
|
||||||
@@ -1323,11 +1326,104 @@ void print_axis_stats(const char* name,const AxisStats& s) {
|
|||||||
s.runsP50,s.runsP95,s.runsMax);
|
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
|
} // namespace
|
||||||
|
|
||||||
int main(int argc,char** argv) {
|
int main(int argc,char** argv) {
|
||||||
const uint64_t seed=argc>1?std::strtoull(argv[1],nullptr,10):1;
|
uint64_t seed=1; int workers=1; std::filesystem::path exportDirectory; bool exportObj=false;
|
||||||
const int workers=argc>2?std::clamp(std::atoi(argv[2]),1,8):1;
|
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;
|
double corpusMs=0,intervalMs=0,activeMs=0,meshMs=0,farMs=0,validationMs=0;
|
||||||
Clock::time_point t0=Clock::now();
|
Clock::time_point t0=Clock::now();
|
||||||
Corpus corpus;
|
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",
|
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>(totalBytes),static_cast<unsigned long long>(combinedHash),
|
||||||
static_cast<unsigned long long>(process_working_set()),validationPass?"true":"false");
|
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;
|
return validationPass?0:2;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
#define main interval_spike_benchmark_main
|
||||||
|
#include "../interval_spike.cpp"
|
||||||
|
#undef main
|
||||||
|
|
||||||
|
#include <d3d11.h>
|
||||||
|
#include <d3dcompiler.h>
|
||||||
|
#include <dxgi.h>
|
||||||
|
#include <windowsx.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#pragma comment(lib, "d3d11.lib")
|
||||||
|
#pragma comment(lib, "dxgi.lib")
|
||||||
|
#pragma comment(lib, "d3dcompiler.lib")
|
||||||
|
#pragma comment(lib, "shell32.lib")
|
||||||
|
#pragma comment(lib, "user32.lib")
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct V3 { float x,y,z; };
|
||||||
|
V3 add(V3 a,V3 b){return {a.x+b.x,a.y+b.y,a.z+b.z};}
|
||||||
|
V3 sub(V3 a,V3 b){return {a.x-b.x,a.y-b.y,a.z-b.z};}
|
||||||
|
V3 mul(V3 a,float s){return {a.x*s,a.y*s,a.z*s};}
|
||||||
|
float dot(V3 a,V3 b){return a.x*b.x+a.y*b.y+a.z*b.z;}
|
||||||
|
V3 cross(V3 a,V3 b){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};}
|
||||||
|
V3 norm(V3 a){float n=std::sqrt(std::max(1e-20f,dot(a,a)));return mul(a,1.0f/n);}
|
||||||
|
struct M4 { float m[4][4]{}; };
|
||||||
|
M4 identity(){M4 a{};for(int i=0;i<4;++i)a.m[i][i]=1;return a;}
|
||||||
|
M4 perspective(float fov,float aspect,float zn,float zf){M4 a{};float y=1/std::tan(fov*.5f);a.m[0][0]=y/aspect;a.m[1][1]=y;a.m[2][2]=zf/(zf-zn);a.m[2][3]=1;a.m[3][2]=-zn*zf/(zf-zn);return a;}
|
||||||
|
M4 view_matrix(V3 p,V3 f,V3 r,V3 u){M4 a=identity();a.m[0][0]=r.x;a.m[1][0]=r.y;a.m[2][0]=r.z;a.m[3][0]=-dot(r,p);a.m[0][1]=u.x;a.m[1][1]=u.y;a.m[2][1]=u.z;a.m[3][1]=-dot(u,p);a.m[0][2]=f.x;a.m[1][2]=f.y;a.m[2][2]=f.z;a.m[3][2]=-dot(f,p);return a;}
|
||||||
|
M4 transpose(M4 a){M4 b{};for(int i=0;i<4;++i)for(int j=0;j<4;++j)b.m[i][j]=a.m[j][i];return b;}
|
||||||
|
struct GPUVertex { float p[3],n[3]; };
|
||||||
|
struct CB { M4 viewProj; float camera[3],pad; float light[3],fogStart; float fogEnd,ambient,pad2[2]; };
|
||||||
|
struct App {
|
||||||
|
HWND hwnd{}; ID3D11Device* dev{}; ID3D11DeviceContext* ctx{}; IDXGISwapChain* swap{}; ID3D11RenderTargetView* rtv{}; ID3D11DepthStencilView* dsv{};
|
||||||
|
ID3D11VertexShader* vs{}; ID3D11PixelShader* ps{}; ID3D11InputLayout* layout{}; ID3D11Buffer* cb{}; ID3D11RasterizerState* solid{}; ID3D11RasterizerState* wire{};
|
||||||
|
ID3D11Buffer* nearVB{}; ID3D11Buffer* nearIB{}; ID3D11Buffer* farVB{}; ID3D11Buffer* farIB{}; UINT nearIndices{},farIndices{};
|
||||||
|
uint64_t seed=1;int workers=6;double generationMs{};bool showNear=true,showFar=true,wireframe=false,captured=false;float yaw=0,pitch=0,speed=8;V3 pos{};POINT last{};int width=1280,height=800;Clock::time_point prev=Clock::now();double frameMs=0;bool keys[256]{};
|
||||||
|
void release_mesh(){if(nearVB)nearVB->Release();if(nearIB)nearIB->Release();if(farVB)farVB->Release();if(farIB)farIB->Release();nearVB=nearIB=farVB=farIB=nullptr;nearIndices=farIndices=0;}
|
||||||
|
~App(){release_mesh();if(solid)solid->Release();if(wire)wire->Release();if(cb)cb->Release();if(layout)layout->Release();if(vs)vs->Release();if(ps)ps->Release();if(rtv)rtv->Release();if(dsv)dsv->Release();if(swap)swap->Release();if(ctx)ctx->Release();if(dev)dev->Release();}
|
||||||
|
bool make_buffer(ID3D11Buffer** out,const void* data,size_t bytes,UINT bind){D3D11_BUFFER_DESC d{};d.ByteWidth=static_cast<UINT>(bytes);d.Usage=D3D11_USAGE_DEFAULT;d.BindFlags=bind;D3D11_SUBRESOURCE_DATA s{};s.pSysMem=data;return SUCCEEDED(dev->CreateBuffer(&d,&s,out));}
|
||||||
|
bool generate(){
|
||||||
|
auto begin=Clock::now(); Corpus corpus;build_corpus(corpus,seed);AxisData axes[3];AxisStats stats[3];bool overflow=false;
|
||||||
|
for(int a=0;a<3;++a)axes[a]=solve_axis(corpus,a,workers,stats[a],overflow);
|
||||||
|
std::vector<uint64_t> active;active_cell_masks(axes[2],active);MeshOutput nearMesh=emit_near_mesh(corpus,axes,axes[2],active,workers);FarOutput farMesh=build_far_world(seed);
|
||||||
|
generationMs=ms(begin,Clock::now());release_mesh();
|
||||||
|
std::vector<GPUVertex> nv;nv.reserve(nearMesh.vertices.size());for(const auto& v:nearMesh.vertices)nv.push_back({{v.v.x,v.v.y,v.v.z},{v.v.nx,v.v.ny,v.v.nz}});
|
||||||
|
std::vector<GPUVertex> fv;fv.reserve(farMesh.vertices.size());for(const auto& v:farMesh.vertices)fv.push_back({{v.x,v.y,v.z},{v.nx,v.ny,v.nz}});
|
||||||
|
if(!nv.empty()&&(!make_buffer(&nearVB,nv.data(),nv.size()*sizeof(GPUVertex),D3D11_BIND_VERTEX_BUFFER)||!make_buffer(&nearIB,nearMesh.indices.data(),nearMesh.indices.size()*sizeof(uint32_t),D3D11_BIND_INDEX_BUFFER)))return false;
|
||||||
|
if(!fv.empty()&&(!make_buffer(&farVB,fv.data(),fv.size()*sizeof(GPUVertex),D3D11_BIND_VERTEX_BUFFER)||!make_buffer(&farIB,farMesh.indices.data(),farMesh.indices.size()*sizeof(uint32_t),D3D11_BIND_INDEX_BUFFER)))return false;
|
||||||
|
nearIndices=static_cast<UINT>(nearMesh.indices.size());farIndices=static_cast<UINT>(farMesh.indices.size());
|
||||||
|
// The first generated room is centred near local (-13, -13, -8.5) metres.
|
||||||
|
// These offsets match its deterministic seed-1 center and keep the camera in its air volume.
|
||||||
|
Rng rng(seed ^ 0x0A57A20261002ull);int lx=-1300+rng.range(-170,170),ly=-1300+rng.range(-170,170),lz=-850+rng.range(-120,120);
|
||||||
|
pos={kWorldOriginCm[0]*.01f+lx*.01f,kWorldOriginCm[1]*.01f+ly*.01f,kWorldOriginCm[2]*.01f+lz*.01f};yaw=0;pitch=0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool init_device(){
|
||||||
|
DXGI_SWAP_CHAIN_DESC sd{};sd.BufferCount=1;sd.BufferDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM;sd.BufferUsage=DXGI_USAGE_RENDER_TARGET_OUTPUT;sd.OutputWindow=hwnd;sd.SampleDesc.Count=1;sd.Windowed=TRUE;sd.SwapEffect=DXGI_SWAP_EFFECT_DISCARD;
|
||||||
|
UINT flags=0;
|
||||||
|
#if defined(_DEBUG)
|
||||||
|
flags|=D3D11_CREATE_DEVICE_DEBUG;
|
||||||
|
#endif
|
||||||
|
D3D_FEATURE_LEVEL fl{};if(FAILED(D3D11CreateDeviceAndSwapChain(nullptr,D3D_DRIVER_TYPE_HARDWARE,nullptr,flags,nullptr,0,D3D11_SDK_VERSION,&sd,&swap,&dev,&fl,&ctx)))return false;
|
||||||
|
const char* shader="cbuffer C:register(b0){float4x4 VP;float3 Camera;float Pad;float3 LightDir;float FogStart;float FogEnd;float Ambient;float2 Pad2;} struct I{float3 p:POSITION;float3 n:NORMAL;};struct O{float4 p:SV_POSITION;float3 world:TEXCOORD0;float3 n:NORMAL;};O VS(I i){O o;o.p=mul(float4(i.p,1),VP);o.world=i.p;o.n=i.n;return o;}float4 PS(O i):SV_TARGET{float3 N=normalize(i.n);float3 L=normalize(-LightDir);float diffuse=saturate(dot(N,L))*0.72;float3 toCam=Camera-i.world;float dist=length(toCam);float head=saturate(dot(N,normalize(toCam)))*0.85/(1+dist*0.018);float light=Ambient+diffuse+head;float3 rock=float3(0.55,0.57,0.60)*light;float fog=saturate((dist-FogStart)/(FogEnd-FogStart));return float4(lerp(rock,float3(0.075,0.095,0.12),fog),1);} ";
|
||||||
|
ID3DBlob *vb=nullptr,*pb=nullptr,*err=nullptr;if(FAILED(D3DCompile(shader,strlen(shader),nullptr,nullptr,nullptr,"VS","vs_4_0",0,0,&vb,&err)))return false;if(FAILED(D3DCompile(shader,strlen(shader),nullptr,nullptr,nullptr,"PS","ps_4_0",0,0,&pb,&err))){if(vb)vb->Release();return false;}
|
||||||
|
HRESULT hr=dev->CreateVertexShader(vb->GetBufferPointer(),vb->GetBufferSize(),nullptr,&vs);if(SUCCEEDED(hr))hr=dev->CreatePixelShader(pb->GetBufferPointer(),pb->GetBufferSize(),nullptr,&ps);
|
||||||
|
D3D11_INPUT_ELEMENT_DESC il[]={{"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0},{"NORMAL",0,DXGI_FORMAT_R32G32B32_FLOAT,0,12,D3D11_INPUT_PER_VERTEX_DATA,0}};if(SUCCEEDED(hr))hr=dev->CreateInputLayout(il,2,vb->GetBufferPointer(),vb->GetBufferSize(),&layout);vb->Release();pb->Release();if(FAILED(hr))return false;
|
||||||
|
D3D11_BUFFER_DESC cbd{};cbd.ByteWidth=sizeof(CB);cbd.Usage=D3D11_USAGE_DEFAULT;cbd.BindFlags=D3D11_BIND_CONSTANT_BUFFER;if(FAILED(dev->CreateBuffer(&cbd,nullptr,&cb)))return false;
|
||||||
|
D3D11_RASTERIZER_DESC rd{};rd.FillMode=D3D11_FILL_SOLID;rd.CullMode=D3D11_CULL_NONE;rd.DepthClipEnable=TRUE;if(FAILED(dev->CreateRasterizerState(&rd,&solid)))return false;rd.FillMode=D3D11_FILL_WIREFRAME;if(FAILED(dev->CreateRasterizerState(&rd,&wire)))return false;
|
||||||
|
return resize(width,height);
|
||||||
|
}
|
||||||
|
bool resize(int w,int h){if(!dev||w<1||h<1)return true;width=w;height=h;if(rtv){rtv->Release();rtv=nullptr;}if(dsv){dsv->Release();dsv=nullptr;}if(FAILED(swap->ResizeBuffers(0,w,h,DXGI_FORMAT_UNKNOWN,0)))return false;ID3D11Texture2D* back{};if(FAILED(swap->GetBuffer(0,__uuidof(ID3D11Texture2D),reinterpret_cast<void**>(&back))))return false;HRESULT hr=dev->CreateRenderTargetView(back,nullptr,&rtv);back->Release();if(FAILED(hr))return false;D3D11_TEXTURE2D_DESC dd{};dd.Width=w;dd.Height=h;dd.MipLevels=1;dd.ArraySize=1;dd.Format=DXGI_FORMAT_D24_UNORM_S8_UINT;dd.SampleDesc.Count=1;dd.BindFlags=D3D11_BIND_DEPTH_STENCIL;ID3D11Texture2D* depth{};if(FAILED(dev->CreateTexture2D(&dd,nullptr,&depth)))return false;hr=dev->CreateDepthStencilView(depth,nullptr,&dsv);depth->Release();return SUCCEEDED(hr);}
|
||||||
|
V3 forward(){return norm({std::sin(yaw)*std::cos(pitch),std::cos(yaw)*std::cos(pitch),std::sin(pitch)});}
|
||||||
|
void title(){char b[256];snprintf(b,sizeof(b),"Interval spike | %.2f ms/frame | %u tris | (%.1f, %.1f, %.1f) m | gen %.1f ms | seed %llu",frameMs,((showNear?nearIndices:0)+(showFar?farIndices:0))/3,pos.x,pos.y,pos.z,generationMs,(unsigned long long)seed);SetWindowTextA(hwnd,b);}
|
||||||
|
void draw(){auto now=Clock::now();float dt=std::clamp(std::chrono::duration<float>(now-prev).count(),0.0f,.1f);prev=now;frameMs=dt*1000;
|
||||||
|
V3 f=forward(),r=norm(cross(f,{0,0,1})),u=norm(cross(r,f));float step=speed*dt*(keys[VK_SHIFT]?4.0f:1.0f);if(keys['W'])pos=add(pos,mul(f,step));if(keys['S'])pos=sub(pos,mul(f,step));if(keys['A'])pos=sub(pos,mul(r,step));if(keys['D'])pos=add(pos,mul(r,step));if(keys[VK_SPACE]||keys['E'])pos.z+=step;if(keys[VK_CONTROL]||keys['Q'])pos.z-=step;
|
||||||
|
float clear[]={.075f,.095f,.12f,1};ctx->OMSetRenderTargets(1,&rtv,dsv);ctx->ClearRenderTargetView(rtv,clear);ctx->ClearDepthStencilView(dsv,D3D11_CLEAR_DEPTH,1,0);D3D11_VIEWPORT vp{0,0,(float)width,(float)height,0,1};ctx->RSSetViewports(1,&vp);ctx->RSSetState(wireframe?wire:solid);ctx->IASetInputLayout(layout);ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||||
|
CB c{};M4 v=view_matrix(pos,f,r,u);M4 p=perspective(1.0472f,(float)width/height,.05f,20000); // compose view and projection for row-vector shader
|
||||||
|
M4 out{};for(int i=0;i<4;++i)for(int j=0;j<4;++j)for(int k=0;k<4;++k)out.m[i][j]+=v.m[i][k]*p.m[k][j];c.viewProj=transpose(out);c.camera[0]=pos.x;c.camera[1]=pos.y;c.camera[2]=pos.z;c.light[0]=-.45f;c.light[1]=-.8f;c.light[2]=-.35f;c.fogStart=80;c.fogEnd=1200;c.ambient=.28f;ctx->UpdateSubresource(cb,0,nullptr,&c,0,0);ctx->VSSetConstantBuffers(0,1,&cb);ctx->PSSetConstantBuffers(0,1,&cb);ctx->VSSetShader(vs,nullptr,0);ctx->PSSetShader(ps,nullptr,0);
|
||||||
|
auto mesh=[&](ID3D11Buffer* vb,ID3D11Buffer* ib,UINT count){if(!vb||!ib||!count)return;UINT stride=sizeof(GPUVertex),offset=0;ctx->IASetVertexBuffers(0,1,&vb,&stride,&offset);ctx->IASetIndexBuffer(ib,DXGI_FORMAT_R32_UINT,0);ctx->DrawIndexed(count,0,0);};if(showNear)mesh(nearVB,nearIB,nearIndices);if(showFar)mesh(farVB,farIB,farIndices);swap->Present(1,0);title();}
|
||||||
|
void capture(bool on){captured=on;if(on){SetCapture(hwnd);ShowCursor(FALSE);GetCursorPos(&last);POINT c{width/2,height/2};ClientToScreen(hwnd,&c);SetCursorPos(c.x,c.y);}else{ReleaseCapture();ShowCursor(TRUE);}}
|
||||||
|
};
|
||||||
|
App* g{};
|
||||||
|
LRESULT CALLBACK wndproc(HWND h,UINT m,WPARAM w,LPARAM l){if(!g)return DefWindowProc(h,m,w,l);switch(m){case WM_SIZE:if(w!=SIZE_MINIMIZED)g->resize(LOWORD(l),HIWORD(l));return 0;case WM_KEYDOWN:if(w<256)g->keys[w]=true;if(w==VK_ESCAPE){if(g->captured)g->capture(false);else DestroyWindow(h);}if(w=='F')g->wireframe=!g->wireframe;if(w=='N'||w=='P'){g->seed+=(w=='N'?1:static_cast<uint64_t>(-1));if(!g->generate())MessageBoxA(h,"Could not create mesh buffers.","Viewer error",MB_ICONERROR);}if(w=='M')g->showNear=!g->showNear;if(w=='B')g->showFar=!g->showFar;return 0;case WM_KEYUP:if(w<256)g->keys[w]=false;return 0;case WM_RBUTTONDOWN:g->capture(true);return 0;case WM_RBUTTONUP:g->capture(false);return 0;case WM_MOUSEMOVE:if(g->captured){POINT c{g->width/2,g->height/2};ClientToScreen(h,&c);POINT p{GET_X_LPARAM(l),GET_Y_LPARAM(l)};POINT sc=p;ClientToScreen(h,&sc);int dx=sc.x-c.x,dy=sc.y-c.y;if(dx||dy){g->yaw+=dx*.0035f;g->pitch=std::clamp(g->pitch-dy*.0035f,-1.55f,1.55f);SetCursorPos(c.x,c.y);}}return 0;case WM_MOUSEWHEEL:g->speed=std::clamp(g->speed*(GET_WHEEL_DELTA_WPARAM(w)>0?1.25f:.8f),.25f,1000.0f);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(h,m,w,l);}
|
||||||
|
}
|
||||||
|
|
||||||
|
int WINAPI WinMain(HINSTANCE instance,HINSTANCE,LPSTR,int){
|
||||||
|
int argc=0;LPWSTR* argv=CommandLineToArgvW(GetCommandLineW(),&argc);uint64_t seed=1;int workers=6,positional=0;if(argv){for(int i=1;i<argc;++i){if(positional==0){seed=_wcstoui64(argv[i],nullptr,10);++positional;}else if(positional==1){workers=std::clamp(_wtoi(argv[i]),1,8);++positional;}}LocalFree(argv);}
|
||||||
|
AllocConsole();FILE* stream{};freopen_s(&stream,"CONOUT$","w",stdout);std::printf("Controls: hold right mouse button to look; WASD move; Space/Ctrl (E/Q) up/down; Shift faster; wheel speed; F wireframe; M near; B far; N/P next/previous seed; Esc quit.\n");
|
||||||
|
App app;g=&app;app.seed=seed;app.workers=workers;WNDCLASSA wc{};wc.lpfnWndProc=wndproc;wc.hInstance=instance;wc.lpszClassName="IntervalSpikeViewer";wc.hCursor=LoadCursor(nullptr,IDC_ARROW);RegisterClassA(&wc);RECT rc{0,0,app.width,app.height};AdjustWindowRect(&rc,WS_OVERLAPPEDWINDOW,FALSE);app.hwnd=CreateWindowA(wc.lpszClassName,"Interval spike viewer",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,rc.right-rc.left,rc.bottom-rc.top,nullptr,nullptr,instance,nullptr);if(!app.hwnd||!app.init_device()){MessageBoxA(nullptr,"Direct3D 11 initialization failed.","Viewer error",MB_ICONERROR);return 1;}ShowWindow(app.hwnd,SW_SHOW);if(!app.generate()){MessageBoxA(app.hwnd,"World generation or GPU buffer creation failed.","Viewer error",MB_ICONERROR);return 2;}MSG msg{};while(msg.message!=WM_QUIT){if(PeekMessage(&msg,nullptr,0,0,PM_REMOVE)){TranslateMessage(&msg);DispatchMessage(&msg);}else app.draw();}return 0;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user