research(intervals): blends, dual contouring, surface relief -- look better, not there; new stages single-threaded

Bounded smooth blends at junctions (3.1% of cells), QEF dual contouring on the exact crossings and
analytic normals, band-limited surface relief (bound 0.60 m, observed 0.40-0.44 m, R toggles it),
sparser strata. Exact signs vs the blended reference at every node, thin features kept, same hash
across worker counts. Mesh has 0 boundary edges but 3-7 overused edge groups: not collision-ready.
Near CPU total, 6 workers: 241 -> 836 ms. The new stages all run on ONE thread: blend index 186 ms,
blend evaluation 201 ms, dual contouring 322 ms, relief 61 ms.
Look: slab stack gone, joins rounder; chambers read as broad crumpled facets, junction seams still
flaky. Viewer built as interval_viewer2.exe (the owner had the first one open).

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 04:23:38 +02:00
1 parent 8d29c09512
commit a443eb8546
8 files changed
+1118 -38

No files matched your search

@@ -5,15 +5,19 @@
#include <d3d11.h>
#include <d3dcompiler.h>
#include <dxgi.h>
#include <wincodec.h>
#include <windowsx.h>
#include <shellapi.h>
#include <memory>
#include <string>
#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")
#pragma comment(lib, "windowscodecs.lib")
#pragma comment(lib, "ole32.lib")
namespace {
struct V3 { float x,y,z; };
@@ -33,19 +37,22 @@ struct CB { M4 viewProj; float camera[3],pad; float light[3],fogStart; float fog
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;}
ID3D11Buffer* nearVB{}; ID3D11Buffer* nearReliefVB{}; 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,showDisplacement=true,captureFailed=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]{};std::filesystem::path captureTarget;
void release_mesh(){if(nearVB)nearVB->Release();if(nearReliefVB)nearReliefVB->Release();if(nearIB)nearIB->Release();if(farVB)farVB->Release();if(farIB)farIB->Release();nearVB=nearReliefVB=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);
BlendStats blendStats{};apply_blend_zones(corpus,axes,stats,blendStats);
std::vector<uint64_t> active;active_cell_masks(axes[2],active);MeshOutput nearMesh=emit_dual_contour_mesh(corpus,axes,axes[2],active);FarOutput farMesh=build_far_world(seed);
std::vector<Vertex> reliefVertices;double maxDisplacement=0;apply_surface_displacement(nearMesh,reliefVertices,maxDisplacement);
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> rv;rv.reserve(reliefVertices.size());for(const auto& v:reliefVertices)rv.push_back({{v.x,v.y,v.z},{v.nx,v.ny,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(!nv.empty()&&(!make_buffer(&nearVB,nv.data(),nv.size()*sizeof(GPUVertex),D3D11_BIND_VERTEX_BUFFER)||!make_buffer(&nearReliefVB,rv.data(),rv.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.
@@ -61,7 +68,7 @@ struct App {
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);} ";
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;}float hash3(float3 p){return frac(sin(dot(p,float3(127.1,311.7,74.7)))*43758.5453);}float noise3(float3 p){float3 i=floor(p),f=frac(p);f=f*f*(3-2*f);float a=lerp(hash3(i),hash3(i+float3(1,0,0)),f.x);float b=lerp(hash3(i+float3(0,1,0)),hash3(i+float3(1,1,0)),f.x);float c=lerp(hash3(i+float3(0,0,1)),hash3(i+float3(1,0,1)),f.x);float d=lerp(hash3(i+float3(0,1,1)),hash3(i+float3(1,1,1)),f.x);return lerp(lerp(a,b,f.y),lerp(c,d,f.y),f.z);}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;float mottling=0.68*noise3(i.world*0.28)+0.32*noise3(i.world*1.0);float shade=0.45+mottling;float3 rock=float3(0.46,0.39,0.30)*shade*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;
@@ -70,22 +77,62 @@ struct App {
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);}
bool save_backbuffer_png(const std::filesystem::path& path){
ID3D11Texture2D* back{};ID3D11Texture2D* staging{};IWICImagingFactory* factory{};IWICStream* stream{};IWICBitmapEncoder* encoder{};IWICBitmapFrameEncode* frame{};IPropertyBag2* props{};D3D11_TEXTURE2D_DESC desc{};D3D11_MAPPED_SUBRESOURCE mapped{};bool ok=false;HRESULT hr=S_OK;const char* stage="get swapchain buffer";
if(FAILED(hr=swap->GetBuffer(0,__uuidof(ID3D11Texture2D),reinterpret_cast<void**>(&back))))goto done;
back->GetDesc(&desc);desc.Usage=D3D11_USAGE_STAGING;desc.BindFlags=0;desc.CPUAccessFlags=D3D11_CPU_ACCESS_READ;desc.MiscFlags=0;
stage="create staging texture";if(FAILED(hr=dev->CreateTexture2D(&desc,nullptr,&staging)))goto done;
ctx->CopyResource(staging,back);
stage="map staging texture";if(FAILED(hr=ctx->Map(staging,0,D3D11_MAP_READ,0,&mapped)))goto done;
{
const UINT stride=desc.Width*4;std::vector<BYTE> pixels(static_cast<size_t>(stride)*desc.Height);
for(UINT y=0;y<desc.Height;++y){const BYTE* src=static_cast<const BYTE*>(mapped.pData)+static_cast<size_t>(y)*mapped.RowPitch;BYTE* dst=pixels.data()+static_cast<size_t>(y)*stride;for(UINT x=0;x<desc.Width;++x){dst[x*4]=src[x*4+2];dst[x*4+1]=src[x*4+1];dst[x*4+2]=src[x*4];dst[x*4+3]=src[x*4+3];}}
ctx->Unmap(staging,0);
stage="create WIC factory";if(FAILED(hr=CoCreateInstance(CLSID_WICImagingFactory,nullptr,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&factory))))goto done;
stage="create WIC stream";if(FAILED(hr=factory->CreateStream(&stream)))goto done;
stage="open PNG path";if(FAILED(hr=stream->InitializeFromFilename(path.c_str(),GENERIC_WRITE)))goto done;
stage="create PNG encoder";if(FAILED(hr=factory->CreateEncoder(GUID_ContainerFormatPng,nullptr,&encoder)))goto done;
stage="initialize PNG encoder";if(FAILED(hr=encoder->Initialize(stream,WICBitmapEncoderNoCache)))goto done;
stage="create PNG frame";if(FAILED(hr=encoder->CreateNewFrame(&frame,&props)))goto done;
stage="initialize PNG frame";if(FAILED(hr=frame->Initialize(props)))goto done;
stage="set PNG size";if(FAILED(hr=frame->SetSize(desc.Width,desc.Height)))goto done;
{WICPixelFormatGUID format=GUID_WICPixelFormat32bppBGRA;stage="set PNG pixel format";hr=frame->SetPixelFormat(&format);if(FAILED(hr)||format!=GUID_WICPixelFormat32bppBGRA){if(SUCCEEDED(hr))hr=WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT;goto done;}}
stage="write PNG pixels";if(FAILED(hr=frame->WritePixels(desc.Height,stride,static_cast<UINT>(pixels.size()),pixels.data())))goto done;
stage="commit PNG";if(FAILED(hr=frame->Commit())||FAILED(hr=encoder->Commit()))goto done;ok=true;
}
done:
if(!ok)std::fprintf(stderr,"Capture failed at %s (HRESULT 0x%08lx)\n",stage,static_cast<unsigned long>(hr));
if(staging)staging->Release();if(back)back->Release();if(frame)frame->Release();if(props)props->Release();if(encoder)encoder->Release();if(stream)stream->Release();if(factory)factory->Release();return ok;
}
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 title(){char b[256];snprintf(b,sizeof(b),"Interval spike | %.2f ms/frame | %u tris | relief %s | (%.1f, %.1f, %.1f) m | gen %.1f ms | seed %llu",frameMs,((showNear?nearIndices:0)+(showFar?farIndices:0))/3,showDisplacement?"ON":"OFF",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();}
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(showDisplacement?nearReliefVB:nearVB,nearIB,nearIndices);if(showFar)mesh(farVB,farIB,farIndices);if(!captureTarget.empty()){if(save_backbuffer_png(captureTarget))std::printf("Captured %ls\n",captureTarget.c_str());else{std::fprintf(stderr,"Capture failed: %ls\n",captureTarget.c_str());captureFailed=true;}captureTarget.clear();}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);}
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=='R')g->showDisplacement=!g->showDisplacement;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;
int argc=0;LPWSTR* argv=CommandLineToArgvW(GetCommandLineW(),&argc);uint64_t seed=1;int workers=6,positional=0;std::filesystem::path captureDir;bool captureMode=false;
if(argv)for(int i=1;i<argc;++i){if(std::wcscmp(argv[i],L"--capture-dir")==0&&i+1<argc){captureDir=argv[++i];captureMode=true;}else if(std::wcscmp(argv[i],L"--seed")==0&&i+1<argc)seed=_wcstoui64(argv[++i],nullptr,10);else if(std::wcscmp(argv[i],L"--workers")==0&&i+1<argc)workers=std::clamp(_wtoi(argv[++i]),1,8);else if(positional==0){seed=_wcstoui64(argv[i],nullptr,10);++positional;}else if(positional==1){workers=std::clamp(_wtoi(argv[i]),1,8);++positional;}}
if(FAILED(CoInitializeEx(nullptr,COINIT_MULTITHREADED))){if(argv)LocalFree(argv);return 1;}
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; R relief toggle; 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="IntervalSpikeViewer2";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 2",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);CoUninitialize();if(argv)LocalFree(argv);return 1;}ShowWindow(app.hwnd,SW_SHOW);UpdateWindow(app.hwnd);if(!app.generate()){MessageBoxA(app.hwnd,"World generation or GPU buffer creation failed.","Viewer error",MB_ICONERROR);CoUninitialize();if(argv)LocalFree(argv);return 2;}
if(captureMode){
std::error_code ec;std::filesystem::create_directories(captureDir,ec);if(ec){std::fprintf(stderr,"Could not create capture directory: %ls\n",captureDir.c_str());CoUninitialize();if(argv)LocalFree(argv);return 3;}
auto capture=[&](const wchar_t* name){app.captureTarget=captureDir/name;app.draw();};
capture(L"chamber.png");
app.showDisplacement=false;capture(L"chamber_relief_off.png");app.showDisplacement=true;
app.pos={kWorldOriginCm[0]*.01f-2.5f,kWorldOriginCm[1]*.01f-.75f,kWorldOriginCm[2]*.01f-3.5f};app.yaw=0;app.pitch=0;capture(L"junction.png");
Rng rng(seed ^ 0x0A57A20261002ull);int lx=-1300+rng.range(-170,170),ly=-1300+rng.range(-170,170),lz=-850+rng.range(-120,120);
app.pos={kWorldOriginCm[0]*.01f+(lx+500)*.01f,kWorldOriginCm[1]*.01f+ly*.01f,kWorldOriginCm[2]*.01f+lz*.01f};app.yaw=1.5707963f;app.pitch=0;capture(L"wall.png");
const bool ok=!app.captureFailed;DestroyWindow(app.hwnd);g=nullptr;CoUninitialize();if(argv)LocalFree(argv);return ok?0:4;
}
if(argv)LocalFree(argv);MSG msg{};while(msg.message!=WM_QUIT){if(PeekMessage(&msg,nullptr,0,0,PM_REMOVE)){TranslateMessage(&msg);DispatchMessage(&msg);}else app.draw();}CoUninitialize();return 0;
}