standalone: the owner's world outside Unreal -- bit-identical density, D3D11 fly-through viewer

Standalone/ (C++20, MSVC /O2 /arch:AVX2 /fp:precise, Build.ps1): copied density pipeline, op stack,
cave morphology, passages, strate manager/composer, noise, DetSinCos, mesher, clipmap selection,
with a UE compatibility layer (Compat/). Owner data exported once to Standalone/data/owner_world.json
by a default-off explore-commandlet exporter (the only plugin-source change). 25 cm voxels.
Parity: seed 0, 3,436 samples (strate edges, negative/far coords, rooms, tunnels, passages,
landings): bit-identical. Viewer: full 7-level clipmap, per-level toggles 1-7, T = round winding
tunnels (standalone option), N/P seed, F wireframe.
Timing NOT yet a baseline: 515 s for 12,054 tiles (L0 70 s, L6 296 s); the compat TMap/TSet are
vector-backed with linear lookup, which the plugin's caches do not expect.

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 12:14:04 +02:00
1 parent a443eb8546
commit 05f62cff36
114 files changed
+68206 -3

No files matched your search

+492
View File
@@ -0,0 +1,492 @@
#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 <d3d11.h>
#include <d3dcompiler.h>
#include <dxgi.h>
#include <windowsx.h>
#include <bit>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <sstream>
#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<FStandaloneOwnerWorld> World;
std::string DataPath;
std::mutex ReadyMutex;
std::vector<FReadyLevel> ReadyLevels;
std::thread GenerationThread;
std::atomic<bool> GenerationDone{false};
std::atomic<bool> 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<UINT>::max())return false;
D3D11_BUFFER_DESC Desc{};Desc.ByteWidth=static_cast<UINT>(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<int32>(NewWidth);Height=static_cast<int32>(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<const char*>(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<const char*>(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<std::string,size_t> Kinds;
for(size_t I=0;I<World->DensitySamples.size();++I)
{
const FStandaloneParitySample& S=World->DensitySamples[I];
const float Actual=World->Generator->GetDensityAt(static_cast<float>(S.PositionVoxels.X),
static_cast<float>(S.PositionVoxels.Y),static_cast<float>(S.PositionVoxels.Z));
++Kinds[S.Kind];
if(std::bit_cast<uint32>(Actual)!=std::bit_cast<uint32>(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<FStandaloneOwnerWorld>();
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<UVoxelStrateManager>();
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<UVoxelGenerator>();
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<std::mutex> 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<uint32>(Seed)+static_cast<uint32>(Delta);
Seed=static_cast<int32>(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<uint64>(Mesh.Indices.size());
if(Mesh.Vertices.empty()||Mesh.Indices.empty()){L.Ready=true;return true;}
if(Mesh.Indices.size()>std::numeric_limits<UINT>::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<UINT>(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<unsigned long long>(L.VertexCount),L.Seconds,static_cast<unsigned long long>(L.Tiles));
return true;
}
void PumpGeneration()
{
std::vector<FReadyLevel> Ready;
{std::lock_guard<std::mutex> 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<unsigned long long>(TotalVertices),static_cast<unsigned long long>(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<unsigned long long>(LastClipmap.Levels[L].TileCount),
static_cast<unsigned long long>(LastClipmap.Levels[L].VertexCount),
static_cast<double>(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 "<<Seed<<" | "<<(bRoundTunnels?"round/winding research tunnels":"base world")
<<" | "<<VisibleIndices/3<<" tris | "<<FrameMs<<" ms | "<<(bGenerationRunning?"generating":"ready")
<<" | pos "<<std::fixed<<std::setprecision(1)<<Position.X<<", "<<Position.Y<<", "<<Position.Z<<" m";
SetWindowTextA(Window,S.str().c_str());
}
void Draw()
{
PumpGeneration();
const auto Now=std::chrono::steady_clock::now();
const float Dt=std::clamp(std::chrono::duration<float>(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<float>(Width),static_cast<float>(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<float>(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<int32>(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<FStandaloneOwnerWorld>();
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<float>(App.World->ViewerStartVoxels.X*0.25),
static_cast<float>(App.World->ViewerStartVoxels.Y*0.25),static_cast<float>(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;
}