#include "OwnerWorldLoader.h" #include "WorldMesher.h" #include "StandaloneResearchTunnels.h" #ifdef TEXT #undef TEXT #endif #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include #include #include #include #include #include #include #include #include #include #pragma comment(lib,"d3d11.lib") #pragma comment(lib,"dxgi.lib") #pragma comment(lib,"d3dcompiler.lib") #pragma comment(lib,"user32.lib") #pragma comment(lib,"gdi32.lib") namespace { struct V3 { float X=0.0f,Y=0.0f,Z=0.0f; }; 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 Normalize(V3 A){return Mul(A,1.0f/std::sqrt(std::max(1.0e-20f,Dot(A,A))));} struct M4 { float M[4][4]{}; }; M4 Identity(){M4 R{};for(int I=0;I<4;++I)R.M[I][I]=1.0f;return R;} 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;} M4 Perspective(float Fov,float Aspect,float NearZ,float FarZ) { M4 R{};const float Y=1.0f/std::tan(Fov*0.5f); R.M[0][0]=Y/Aspect;R.M[1][1]=Y;R.M[2][2]=FarZ/(FarZ-NearZ); R.M[2][3]=1.0f;R.M[3][2]=-NearZ*FarZ/(FarZ-NearZ);return R; } M4 View(V3 Position,V3 Forward,V3 Right,V3 Up) { M4 R=Identity(); R.M[0][0]=Right.X;R.M[1][0]=Right.Y;R.M[2][0]=Right.Z;R.M[3][0]=-Dot(Right,Position); R.M[0][1]=Up.X;R.M[1][1]=Up.Y;R.M[2][1]=Up.Z;R.M[3][1]=-Dot(Up,Position); R.M[0][2]=Forward.X;R.M[1][2]=Forward.Y;R.M[2][2]=Forward.Z;R.M[3][2]=-Dot(Forward,Position); return R; } struct FGpuVertex { float Position[3]; float Normal[3]; }; struct FConstants { M4 ViewProjection; float Camera[3]; float FogStart; float Forward[3]; float FogEnd; float LightDirection[3]; float Ambient; float Padding[4]; }; constexpr int32 MaxLevels=8; struct FLevelGpu { ID3D11Buffer* VertexBuffer=nullptr; ID3D11Buffer* IndexBuffer=nullptr; UINT IndexCount=0; uint64 VertexCount=0; double Seconds=0.0; uint64 Tiles=0; uint64 Samples=0; bool Ready=false; }; struct FReadyLevel { int32 Level=0; FStandaloneCpuLevel Mesh; }; struct FApp { HWND Window=nullptr; ID3D11Device* Device=nullptr; ID3D11DeviceContext* Context=nullptr; IDXGISwapChain* SwapChain=nullptr; ID3D11RenderTargetView* RenderTarget=nullptr; ID3D11DepthStencilView* DepthView=nullptr; ID3D11VertexShader* VertexShader=nullptr; ID3D11PixelShader* PixelShader=nullptr; ID3D11InputLayout* InputLayout=nullptr; ID3D11Buffer* Constants=nullptr; ID3D11RasterizerState* SolidRasterizer=nullptr; ID3D11RasterizerState* WireRasterizer=nullptr; FLevelGpu Levels[MaxLevels+1]; std::unique_ptr World; std::string DataPath; std::mutex ReadyMutex; std::vector ReadyLevels; std::thread GenerationThread; std::atomic GenerationDone{false}; std::atomic CancelGeneration{false}; FStandaloneCpuClipmap LastClipmap; bool bGenerationRunning=false; bool bRebuildPending=false; bool bParityChecked=false; bool bRoundTunnels=false; bool bWireframe=false; bool bCaptured=false; bool bShowLevel[MaxLevels+1]={true,true,true,true,true,true,true,true,true}; bool Keys[256]{}; int32 Seed=0; int32 Width=1280,Height=800; float Yaw=0.0f,Pitch=0.0f,MoveSpeed=8.0f; V3 Position{}; POINT LastMouse{}; std::chrono::steady_clock::time_point PreviousFrame=std::chrono::steady_clock::now(); double FrameMs=0.0; uint64 TotalVertices=0; uint64 TotalIndices=0; FIntVector BuildTargetChunk=FIntVector::ZeroValue; ~FApp(){ StopGeneration(true);ReleaseMeshes(); if(SolidRasterizer)SolidRasterizer->Release();if(WireRasterizer)WireRasterizer->Release(); if(Constants)Constants->Release();if(InputLayout)InputLayout->Release(); if(VertexShader)VertexShader->Release();if(PixelShader)PixelShader->Release(); if(RenderTarget)RenderTarget->Release();if(DepthView)DepthView->Release(); if(SwapChain)SwapChain->Release();if(Context)Context->Release();if(Device)Device->Release(); } void ReleaseMeshes() { for(FLevelGpu& L:Levels){if(L.VertexBuffer)L.VertexBuffer->Release();if(L.IndexBuffer)L.IndexBuffer->Release();L={};} TotalVertices=0;TotalIndices=0; } void StopGeneration(bool bCancel=false) { if(bCancel)CancelGeneration.store(true,std::memory_order_relaxed); if(GenerationThread.joinable())GenerationThread.join(); bGenerationRunning=false; } V3 Forward() const {return Normalize({std::sin(Yaw)*std::cos(Pitch),std::cos(Yaw)*std::cos(Pitch),std::sin(Pitch)});} FIntVector CameraChunk() const {return StandaloneWorldMesher::CameraChunk(FVector(Position.X*4.0,Position.Y*4.0,Position.Z*4.0));} bool NeedsRecenter(const FIntVector& Center) const { const FIntVector Current=CameraChunk(); const int32 Threshold=FMath::Max(1,(World?World->ClipRadius:6)/2); return FMath::Abs(Current.X-Center.X)>=Threshold ||FMath::Abs(Current.Y-Center.Y)>=Threshold ||FMath::Abs(Current.Z-Center.Z)>=Threshold; } bool MakeBuffer(ID3D11Buffer** Out,const void* Data,size_t Bytes,UINT Bind) { if(Bytes==0||Bytes>std::numeric_limits::max())return false; D3D11_BUFFER_DESC Desc{};Desc.ByteWidth=static_cast(Bytes); Desc.Usage=D3D11_USAGE_DEFAULT;Desc.BindFlags=Bind; D3D11_SUBRESOURCE_DATA Initial{};Initial.pSysMem=Data; return SUCCEEDED(Device->CreateBuffer(&Desc,&Initial,Out)); } bool Resize(UINT NewWidth,UINT NewHeight) { if(!Device||NewWidth==0||NewHeight==0)return false; Width=static_cast(NewWidth);Height=static_cast(NewHeight); if(RenderTarget){RenderTarget->Release();RenderTarget=nullptr;} if(DepthView){DepthView->Release();DepthView=nullptr;} if(SwapChain->ResizeBuffers(0,NewWidth,NewHeight,DXGI_FORMAT_UNKNOWN,0)!=S_OK)return false; ID3D11Texture2D* Back=nullptr; if(FAILED(SwapChain->GetBuffer(0,IID_PPV_ARGS(&Back))))return false; const HRESULT RtvResult=Device->CreateRenderTargetView(Back,nullptr,&RenderTarget);Back->Release(); if(FAILED(RtvResult))return false; D3D11_TEXTURE2D_DESC DepthDesc{};DepthDesc.Width=NewWidth;DepthDesc.Height=NewHeight; DepthDesc.MipLevels=1;DepthDesc.ArraySize=1;DepthDesc.Format=DXGI_FORMAT_D24_UNORM_S8_UINT; DepthDesc.SampleDesc.Count=1;DepthDesc.Usage=D3D11_USAGE_DEFAULT;DepthDesc.BindFlags=D3D11_BIND_DEPTH_STENCIL; ID3D11Texture2D* Depth=nullptr; if(FAILED(Device->CreateTexture2D(&DepthDesc,nullptr,&Depth)))return false; const HRESULT DsvResult=Device->CreateDepthStencilView(Depth,nullptr,&DepthView);Depth->Release(); return SUCCEEDED(DsvResult); } bool InitializeDevice() { DXGI_SWAP_CHAIN_DESC Desc{};Desc.BufferCount=1;Desc.BufferDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM; Desc.BufferUsage=DXGI_USAGE_RENDER_TARGET_OUTPUT;Desc.OutputWindow=Window;Desc.SampleDesc.Count=1; Desc.Windowed=TRUE;Desc.SwapEffect=DXGI_SWAP_EFFECT_DISCARD; D3D_FEATURE_LEVEL Feature{}; if(FAILED(D3D11CreateDeviceAndSwapChain(nullptr,D3D_DRIVER_TYPE_HARDWARE,nullptr,0,nullptr,0, D3D11_SDK_VERSION,&Desc,&SwapChain,&Device,&Feature,&Context)))return false; const char* ShaderSource=R"( cbuffer C:register(b0){float4x4 VP;float3 Camera;float FogStart;float3 Forward;float FogEnd;float3 LightDir;float Ambient;float4 Pad;} 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=normalize(i.n);return o;} float4 PS(O i):SV_TARGET{ float3 N=normalize(i.n);float3 V=normalize(Camera-i.world);float3 L=normalize(-LightDir); float Diffuse=saturate(dot(N,L));float Head=saturate(dot(N,V)); float Dist=length(Camera-i.world);float Atten=1.0-saturate(Dist/95.0);Head=Head*Head*Atten; float3 Rock=lerp(float3(0.28,0.34,0.38),float3(0.43,0.47,0.48),saturate(N.z*0.5+0.5)); float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.62,0.76,0.9)*Head*1.25; float Fog=saturate((Dist-FogStart)/(FogEnd-FogStart)); Lit=lerp(Lit,float3(0.10,0.14,0.19),Fog);return float4(Lit,1); })"; ID3DBlob* VsCode=nullptr;ID3DBlob* PsCode=nullptr;ID3DBlob* Errors=nullptr; HRESULT Hr=D3DCompile(ShaderSource,std::strlen(ShaderSource),nullptr,nullptr,nullptr,"VS","vs_5_0",0,0,&VsCode,&Errors); if(FAILED(Hr)){if(Errors)std::fprintf(stderr,"Vertex shader compile: %s\n",static_cast(Errors->GetBufferPointer()));if(Errors)Errors->Release();return false;} Hr=D3DCompile(ShaderSource,std::strlen(ShaderSource),nullptr,nullptr,nullptr,"PS","ps_5_0",0,0,&PsCode,&Errors); if(FAILED(Hr)){if(Errors)std::fprintf(stderr,"Pixel shader compile: %s\n",static_cast(Errors->GetBufferPointer()));if(Errors)Errors->Release();VsCode->Release();return false;} Hr=Device->CreateVertexShader(VsCode->GetBufferPointer(),VsCode->GetBufferSize(),nullptr,&VertexShader); if(SUCCEEDED(Hr))Hr=Device->CreatePixelShader(PsCode->GetBufferPointer(),PsCode->GetBufferSize(),nullptr,&PixelShader); D3D11_INPUT_ELEMENT_DESC Elements[]={{"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=Device->CreateInputLayout(Elements,2,VsCode->GetBufferPointer(),VsCode->GetBufferSize(),&InputLayout); VsCode->Release();PsCode->Release();if(FAILED(Hr))return false; D3D11_BUFFER_DESC ConstantDesc{};ConstantDesc.ByteWidth=sizeof(FConstants);ConstantDesc.Usage=D3D11_USAGE_DEFAULT; ConstantDesc.BindFlags=D3D11_BIND_CONSTANT_BUFFER; if(FAILED(Device->CreateBuffer(&ConstantDesc,nullptr,&Constants)))return false; D3D11_RASTERIZER_DESC Raster{};Raster.FillMode=D3D11_FILL_SOLID;Raster.CullMode=D3D11_CULL_NONE; Raster.DepthClipEnable=TRUE;if(FAILED(Device->CreateRasterizerState(&Raster,&SolidRasterizer)))return false; Raster.FillMode=D3D11_FILL_WIREFRAME;if(FAILED(Device->CreateRasterizerState(&Raster,&WireRasterizer)))return false; return Resize(Width,Height); } void PrintParity() { if(bParityChecked||!World)return; bParityChecked=true;size_t MismatchCount=0; std::map Kinds; for(size_t I=0;IDensitySamples.size();++I) { const FStandaloneParitySample& S=World->DensitySamples[I]; const float Actual=World->Generator->GetDensityAt(static_cast(S.PositionVoxels.X), static_cast(S.PositionVoxels.Y),static_cast(S.PositionVoxels.Z)); ++Kinds[S.Kind]; if(std::bit_cast(Actual)!=std::bit_cast(S.PluginDensity)) { ++MismatchCount; std::fprintf(stderr,"PARITY MISMATCH %s strate=%d voxels=(%.6f,%.6f,%.6f) plugin=%.9g standalone=%.9g\n", S.Kind.c_str(),S.StrateIndex,S.PositionVoxels.X,S.PositionVoxels.Y,S.PositionVoxels.Z,S.PluginDensity,Actual); } } std::printf("Density parity: %zu/%zu bit-identical; mismatches=%zu\n", World->DensitySamples.size()-MismatchCount,World->DensitySamples.size(),MismatchCount); for(const auto& [Kind,Count]:Kinds)std::printf(" %-24s %zu\n",Kind.c_str(),Count); std::fflush(stdout); std::fflush(stdout); } bool ConfigureWorld(std::string& Error) { World=std::make_unique(); if(!StandaloneOwnerLoader::Load(DataPath,*World,Error))return false; if(!bParityChecked&&Seed==World->Seed&&!bRoundTunnels)PrintParity(); World->Generator.reset();World->Manager.reset(); World->Settings->Seed=Seed;World->Seed=Seed; if(bRoundTunnels) { // The research tunnel graph replaces the owner's first-strate corridor graph. for(auto& Pair:World->Settings->FixedStrates) { UVoxelStrateDefinition* Definition=Pair.Value.Get(); if(Pair.Key==0&&Definition&&Definition->GeneratorType==ECaveGeneratorType::TunnelNetwork) { Definition->GenerationParams.TunnelDensity=0.0f; Definition->GenerationParams.TunnelWarpStrength=0.0f; } } FStandaloneResearchTunnels::Set(true,Position.Z*4.0f); } else FStandaloneResearchTunnels::Set(false,0.0f); World->Manager=std::make_unique(); if(!World->Manager->Initialize(World->Settings.get(),Seed)) {Error="standalone copied strate manager failed while applying viewer seed";return false;} World->Generator=std::make_unique(); World->Generator->InitializeSettings(World->Settings.get()); World->Generator->SetStrateManager(World->Manager.get()); return true; } void StartGeneration() { StopGeneration(); ReleaseMeshes(); std::string Error; if(!ConfigureWorld(Error)) {std::fprintf(stderr,"World load failed: %s\n",Error.c_str());MessageBoxA(Window,Error.c_str(),"VoxelForge viewer",MB_ICONERROR);return;} const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0); BuildTargetChunk=StandaloneWorldMesher::CameraChunk(CameraVoxels); ReadyLevels.clear();CancelGeneration.store(false,std::memory_order_relaxed); GenerationDone.store(false,std::memory_order_relaxed);bGenerationRunning=true;bRebuildPending=false; std::printf("Building seed %d%s full clipmap (six workers)...\n",Seed,bRoundTunnels?" with research round/winding tunnels":""); GenerationThread=std::thread([this,CameraVoxels]() { LastClipmap=StandaloneWorldMesher::Build(*World,CameraVoxels,6, [this](int32 Level,FStandaloneCpuLevel&& Mesh) { std::lock_guard Lock(ReadyMutex); ReadyLevels.push_back({Level,std::move(Mesh)}); },false,&CancelGeneration); GenerationDone.store(true,std::memory_order_release); }); } void RequestSeed(int32 Delta) { const uint32 Next=static_cast(Seed)+static_cast(Delta); Seed=static_cast(Next);bRebuildPending=true; CancelGeneration.store(true,std::memory_order_relaxed); std::printf("Seed changed to %d; rebuilding after the active clipmap finishes.\n",Seed); } bool UploadLevel(int32 Index,FStandaloneCpuLevel&& Mesh) { FLevelGpu& L=Levels[Index]; if(L.VertexBuffer)L.VertexBuffer->Release();if(L.IndexBuffer)L.IndexBuffer->Release();L={}; L.Seconds=Mesh.Seconds;L.Tiles=Mesh.TileCount;L.Samples=Mesh.DensitySamples; L.VertexCount=Mesh.VertexCount;L.IndexCount=static_cast(Mesh.Indices.size()); if(Mesh.Vertices.empty()||Mesh.Indices.empty()){L.Ready=true;return true;} if(Mesh.Indices.size()>std::numeric_limits::max()) {std::fprintf(stderr,"LOD %d has too many indices for one D3D11 draw.\n",Index);return false;} const bool bVertex=MakeBuffer(&L.VertexBuffer,Mesh.Vertices.data(),Mesh.Vertices.size()*sizeof(FStandaloneCpuVertex),D3D11_BIND_VERTEX_BUFFER); const bool bIndex=bVertex&&MakeBuffer(&L.IndexBuffer,Mesh.Indices.data(),Mesh.Indices.size()*sizeof(uint32),D3D11_BIND_INDEX_BUFFER); if(!bIndex){if(L.VertexBuffer){L.VertexBuffer->Release();L.VertexBuffer=nullptr;}std::fprintf(stderr,"D3D11 buffer upload failed for LOD %d.\n",Index);return false;} L.IndexCount=static_cast(Mesh.Indices.size());L.Ready=true; TotalVertices+=L.VertexCount;TotalIndices+=L.IndexCount; std::printf("LOD %d uploaded: %llu vertices, %.3f s, %llu tiles.\n",Index, static_cast(L.VertexCount),L.Seconds,static_cast(L.Tiles)); return true; } void PumpGeneration() { std::vector Ready; {std::lock_guard Lock(ReadyMutex);Ready.swap(ReadyLevels);} for(FReadyLevel& R:Ready)UploadLevel(R.Level,std::move(R.Mesh)); if(bGenerationRunning&&GenerationDone.load(std::memory_order_acquire)) { StopGeneration(); if(!LastClipmap.bCancelled) std::printf("Full clipmap ready: %.3f s on %d workers; %llu vertices, %llu indices.\n", LastClipmap.TotalSeconds,LastClipmap.WorkerCount, static_cast(TotalVertices),static_cast(TotalIndices)); for(int32 L=0;L<=World->MaxClipLevel;++L) std::printf(" LOD %d: %.3f s, %llu tiles, %llu vertices, %.2f M density samples\n",L, LastClipmap.Levels[L].Seconds,static_cast(LastClipmap.Levels[L].TileCount), static_cast(LastClipmap.Levels[L].VertexCount), static_cast(LastClipmap.Levels[L].DensitySamples)/1.0e6); std::fflush(stdout); if(bRebuildPending)StartGeneration(); } else if(!bGenerationRunning&&bRebuildPending)StartGeneration(); } void Capture(bool bOn) { bCaptured=bOn; if(bOn){SetCapture(Window);ShowCursor(FALSE);GetCursorPos(&LastMouse);POINT C{Width/2,Height/2};ClientToScreen(Window,&C);SetCursorPos(C.x,C.y);} else{ReleaseCapture();ShowCursor(TRUE);} } void UpdateTitle() { uint64 VisibleIndices=0;for(int32 I=0;I<=MaxLevels;++I)if(bShowLevel[I])VisibleIndices+=Levels[I].IndexCount; std::ostringstream S;S<<"VoxelForge standalone | seed "<(Now-PreviousFrame).count(),0.0f,0.1f); PreviousFrame=Now;FrameMs=Dt*1000.0; const V3 F=Forward(),R=Normalize(Cross(F,{0,0,1})),U=Normalize(Cross(R,F)); const float Step=MoveSpeed*Dt*(Keys[VK_SHIFT]?4.0f:1.0f); if(Keys['W'])Position=Add(Position,Mul(F,Step));if(Keys['S'])Position=Sub(Position,Mul(F,Step)); if(Keys['A'])Position=Sub(Position,Mul(R,Step));if(Keys['D'])Position=Add(Position,Mul(R,Step)); if(Keys[VK_SPACE]||Keys['E'])Position.Z+=Step;if(Keys[VK_CONTROL]||Keys['Q'])Position.Z-=Step; if(bGenerationRunning&&NeedsRecenter(BuildTargetChunk)) {bRebuildPending=true;CancelGeneration.store(true,std::memory_order_relaxed);} else if(!bGenerationRunning&&World&&NeedsRecenter(LastClipmap.CenterChunk)) {bRebuildPending=true;} const float Clear[4]={0.055f,0.075f,0.105f,1.0f}; Context->OMSetRenderTargets(1,&RenderTarget,DepthView);Context->ClearRenderTargetView(RenderTarget,Clear); Context->ClearDepthStencilView(DepthView,D3D11_CLEAR_DEPTH,1.0f,0); D3D11_VIEWPORT Viewport{0,0,static_cast(Width),static_cast(Height),0,1};Context->RSSetViewports(1,&Viewport); Context->RSSetState(bWireframe?WireRasterizer:SolidRasterizer);Context->IASetInputLayout(InputLayout); Context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); FConstants C{};const M4 V=View(Position,F,R,U);const M4 P=Perspective(1.04719755f,static_cast(Width)/std::max(1,Height),0.05f,5000.0f); M4 Combined{};for(int I=0;I<4;++I)for(int J=0;J<4;++J)for(int K=0;K<4;++K)Combined.M[I][J]+=V.M[I][K]*P.M[K][J]; C.ViewProjection=Transpose(Combined);C.Camera[0]=Position.X;C.Camera[1]=Position.Y;C.Camera[2]=Position.Z; C.Forward[0]=F.X;C.Forward[1]=F.Y;C.Forward[2]=F.Z;C.LightDirection[0]=-0.5f;C.LightDirection[1]=-0.72f;C.LightDirection[2]=-0.48f; C.FogStart=260.0f;C.FogEnd=2600.0f;C.Ambient=0.36f; Context->UpdateSubresource(Constants,0,nullptr,&C,0,0);Context->VSSetConstantBuffers(0,1,&Constants);Context->PSSetConstantBuffers(0,1,&Constants); Context->VSSetShader(VertexShader,nullptr,0);Context->PSSetShader(PixelShader,nullptr,0); for(int32 I=0;I<=MaxLevels;++I) { const FLevelGpu& L=Levels[I];if(!bShowLevel[I]||!L.VertexBuffer||!L.IndexBuffer||!L.IndexCount)continue; const UINT Stride=sizeof(FGpuVertex),Offset=0;Context->IASetVertexBuffers(0,1,&L.VertexBuffer,&Stride,&Offset); Context->IASetIndexBuffer(L.IndexBuffer,DXGI_FORMAT_R32_UINT,0);Context->DrawIndexed(L.IndexCount,0,0); } SwapChain->Present(1,0);UpdateTitle(); } }; FApp* GApp=nullptr; LRESULT CALLBACK WindowProc(HWND H,UINT Message,WPARAM W,LPARAM L) { if(!GApp)return DefWindowProc(H,Message,W,L); switch(Message) { case WM_SIZE:if(W!=SIZE_MINIMIZED)GApp->Resize(LOWORD(L),HIWORD(L));return 0; case WM_KEYDOWN: { const bool bWasDown=(L&(1ll<<30))!=0;if(W<256)GApp->Keys[W]=true; if(W==VK_ESCAPE){if(GApp->bCaptured)GApp->Capture(false);else DestroyWindow(H);} if(!bWasDown&&W=='F')GApp->bWireframe=!GApp->bWireframe; if(!bWasDown&&W=='T'){GApp->bRoundTunnels=!GApp->bRoundTunnels;GApp->bRebuildPending=true;GApp->CancelGeneration.store(true,std::memory_order_relaxed);} if(!bWasDown&&(W=='N'||W=='P'))GApp->RequestSeed(W=='N'?1:-1); if(!bWasDown&&W>='1'&&W<='8'){const int32 I=static_cast(W-'1');GApp->bShowLevel[I]=!GApp->bShowLevel[I];} if(!bWasDown&&W=='9')GApp->bShowLevel[8]=!GApp->bShowLevel[8]; if(!bWasDown&&W=='0')for(bool& B:GApp->bShowLevel)B=!B; return 0; } case WM_KEYUP:if(W<256)GApp->Keys[W]=false;return 0; case WM_RBUTTONDOWN:GApp->Capture(true);return 0; case WM_RBUTTONUP:GApp->Capture(false);return 0; case WM_MOUSEMOVE: if(GApp->bCaptured){POINT C{GApp->Width/2,GApp->Height/2};ClientToScreen(H,&C);POINT P{GET_X_LPARAM(L),GET_Y_LPARAM(L)},Screen=P;ClientToScreen(H,&Screen);const int32 DX=Screen.x-C.x,DY=Screen.y-C.y;if(DX||DY){GApp->Yaw+=DX*0.0035f;GApp->Pitch=std::clamp(GApp->Pitch-DY*0.0035f,-1.55f,1.55f);SetCursorPos(C.x,C.y);}}return 0; case WM_MOUSEWHEEL:GApp->MoveSpeed=std::clamp(GApp->MoveSpeed*(GET_WHEEL_DELTA_WPARAM(W)>0?1.25f:0.8f),0.25f,1000.0f);return 0; case WM_DESTROY:GApp->CancelGeneration.store(true,std::memory_order_relaxed);PostQuitMessage(0);return 0; } return DefWindowProc(H,Message,W,L); } std::filesystem::path DefaultDataPath() { wchar_t Module[MAX_PATH]{};GetModuleFileNameW(nullptr,Module,MAX_PATH); const std::filesystem::path Exe(Module); return Exe.parent_path().parent_path()/L"data"/L"owner_world.json"; } } int main(int Argc,char** Argv) { FApp App;App.DataPath=Argc>1?Argv[1]:DefaultDataPath().string(); std::string Error; App.World=std::make_unique(); if(!StandaloneOwnerLoader::Load(App.DataPath,*App.World,Error)){std::fprintf(stderr,"Owner data load failed: %s\n",Error.c_str());return 2;} App.Seed=App.World->Seed; App.Position={static_cast(App.World->ViewerStartVoxels.X*0.25), static_cast(App.World->ViewerStartVoxels.Y*0.25),static_cast(App.World->ViewerStartVoxels.Z*0.25)}; App.World.reset(); WNDCLASSA Class{};Class.lpfnWndProc=WindowProc;Class.hInstance=GetModuleHandleA(nullptr);Class.lpszClassName="VoxelForgeStandaloneWorld"; Class.hCursor=LoadCursor(nullptr,IDC_ARROW);RegisterClassA(&Class); RECT Rect{0,0,App.Width,App.Height};AdjustWindowRect(&Rect,WS_OVERLAPPEDWINDOW,FALSE); App.Window=CreateWindowA(Class.lpszClassName,"VoxelForge standalone world",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT, Rect.right-Rect.left,Rect.bottom-Rect.top,nullptr,nullptr,Class.hInstance,nullptr); GApp=&App; if(!App.Window||!App.InitializeDevice()){MessageBoxA(nullptr,"Direct3D 11 initialization failed.","VoxelForge standalone",MB_ICONERROR);return 3;} ShowWindow(App.Window,SW_SHOW);UpdateWindow(App.Window); std::printf("Controls: hold right mouse to look; WASD move; Space/Ctrl (E/Q) up/down; Shift faster; wheel changes speed; F wireframe; 1-9 toggle LOD; 0 toggle all levels; N/P change seed; T swap to round/winding research tunnels; Esc quit.\n"); std::printf("Owner data: %s; starting at first-strate open-space sample (%.2f, %.2f, %.2f) m.\n", App.DataPath.c_str(),App.Position.X,App.Position.Y,App.Position.Z); std::fflush(stdout); App.StartGeneration(); MSG Message{}; while(Message.message!=WM_QUIT) { if(PeekMessage(&Message,nullptr,0,0,PM_REMOVE)){TranslateMessage(&Message);DispatchMessage(&Message);} else App.Draw(); } GApp=nullptr; return 0; }