From f2e8b0b45e1db4b19ef68c1fe1da0ef0148ef353 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 22 May 2026 07:54:36 -0700 Subject: [PATCH] Added screensapce decal system and stamp placement system in Radiant. --- neo/doom3/game/gamesys/GameTypeInfo.h | 44 +++- neo/engine/opengl/gl_d3d12raylight.cpp | 8 +- neo/engine/opengl/gl_d3d12shim.cpp | 256 ++++++++++++++++++++++- neo/engine/opengl/opengl.h | 17 ++ neo/engine/renderer/RenderWorld.cpp | 4 + neo/engine/renderer/RenderWorld.h | 20 ++ neo/engine/renderer/RenderWorld_load.cpp | 187 +++++++++++++++++ neo/engine/renderer/RenderWorld_local.h | 8 + neo/engine/renderer/draw_dx.cpp | 16 +- neo/engine/renderer/tr_light.cpp | 6 +- neo/engine/tools/radiant/CamWnd.cpp | 205 +++++++++++++++++- neo/engine/tools/radiant/EditorMap.cpp | 210 +++++++++++++++++++ neo/engine/tools/radiant/EditorMap.h | 13 ++ 13 files changed, 962 insertions(+), 32 deletions(-) diff --git a/neo/doom3/game/gamesys/GameTypeInfo.h b/neo/doom3/game/gamesys/GameTypeInfo.h index a4eedee2..b72050ac 100644 --- a/neo/doom3/game/gamesys/GameTypeInfo.h +++ b/neo/doom3/game/gamesys/GameTypeInfo.h @@ -9,7 +9,7 @@ 1186 constants 133 enums - 579 classes/structs/unions + 581 classes/structs/unions 38 templates 7 max inheritance level for 'idAI_Vagary' @@ -4077,6 +4077,21 @@ static classVariableInfo_t glRaytracingLightingPassDesc_t_typeInfo[] = { { NULL, 0 } }; +static classVariableInfo_t glScreenSpaceDecal_t_typeInfo[] = { + { "glRaytracingVec3_t", "origin", (intptr_t)(&((glScreenSpaceDecal_t *)0)->origin), sizeof( ((glScreenSpaceDecal_t *)0)->origin ) }, + { "float", "halfSize", (intptr_t)(&((glScreenSpaceDecal_t *)0)->halfSize), sizeof( ((glScreenSpaceDecal_t *)0)->halfSize ) }, + { "glRaytracingVec3_t", "normal", (intptr_t)(&((glScreenSpaceDecal_t *)0)->normal), sizeof( ((glScreenSpaceDecal_t *)0)->normal ) }, + { "float", "depth", (intptr_t)(&((glScreenSpaceDecal_t *)0)->depth), sizeof( ((glScreenSpaceDecal_t *)0)->depth ) }, + { "glRaytracingVec3_t", "axisU", (intptr_t)(&((glScreenSpaceDecal_t *)0)->axisU), sizeof( ((glScreenSpaceDecal_t *)0)->axisU ) }, + { "float", "opacity", (intptr_t)(&((glScreenSpaceDecal_t *)0)->opacity), sizeof( ((glScreenSpaceDecal_t *)0)->opacity ) }, + { "glRaytracingVec3_t", "axisV", (intptr_t)(&((glScreenSpaceDecal_t *)0)->axisV), sizeof( ((glScreenSpaceDecal_t *)0)->axisV ) }, + { "float", "pad0", (intptr_t)(&((glScreenSpaceDecal_t *)0)->pad0), sizeof( ((glScreenSpaceDecal_t *)0)->pad0 ) }, + { "float[4]", "color", (intptr_t)(&((glScreenSpaceDecal_t *)0)->color), sizeof( ((glScreenSpaceDecal_t *)0)->color ) }, + { "uint32_t", "textureId", (intptr_t)(&((glScreenSpaceDecal_t *)0)->textureId), sizeof( ((glScreenSpaceDecal_t *)0)->textureId ) }, + { "float[3]", "pad1", (intptr_t)(&((glScreenSpaceDecal_t *)0)->pad1), sizeof( ((glScreenSpaceDecal_t *)0)->pad1 ) }, + { NULL, 0 } +}; + static classVariableInfo_t QD3D12NeuralPOMImageRGBA8_typeInfo[] = { { "uint32_t", "width", (intptr_t)(&((QD3D12NeuralPOMImageRGBA8 *)0)->width), sizeof( ((QD3D12NeuralPOMImageRGBA8 *)0)->width ) }, { "uint32_t", "height", (intptr_t)(&((QD3D12NeuralPOMImageRGBA8 *)0)->height), sizeof( ((QD3D12NeuralPOMImageRGBA8 *)0)->height ) }, @@ -4538,6 +4553,19 @@ static classVariableInfo_t modelTrace_t_typeInfo[] = { { NULL, 0 } }; +static classVariableInfo_t renderWorldDecal_t_typeInfo[] = { + { "idStr", "material", (intptr_t)(&((renderWorldDecal_t *)0)->material), sizeof( ((renderWorldDecal_t *)0)->material ) }, + { "idVec3", "origin", (intptr_t)(&((renderWorldDecal_t *)0)->origin), sizeof( ((renderWorldDecal_t *)0)->origin ) }, + { "idVec3", "normal", (intptr_t)(&((renderWorldDecal_t *)0)->normal), sizeof( ((renderWorldDecal_t *)0)->normal ) }, + { "idVec3", "axisU", (intptr_t)(&((renderWorldDecal_t *)0)->axisU), sizeof( ((renderWorldDecal_t *)0)->axisU ) }, + { "idVec3", "axisV", (intptr_t)(&((renderWorldDecal_t *)0)->axisV), sizeof( ((renderWorldDecal_t *)0)->axisV ) }, + { "float", "size", (intptr_t)(&((renderWorldDecal_t *)0)->size), sizeof( ((renderWorldDecal_t *)0)->size ) }, + { "float", "depth", (intptr_t)(&((renderWorldDecal_t *)0)->depth), sizeof( ((renderWorldDecal_t *)0)->depth ) }, + { "float", "opacity", (intptr_t)(&((renderWorldDecal_t *)0)->opacity), sizeof( ((renderWorldDecal_t *)0)->opacity ) }, + { "idVec4", "color", (intptr_t)(&((renderWorldDecal_t *)0)->color), sizeof( ((renderWorldDecal_t *)0)->color ) }, + { NULL, 0 } +}; + static classVariableInfo_t idRenderWorld_typeInfo[] = { { NULL, 0 } }; @@ -5168,11 +5196,11 @@ static classVariableInfo_t frameLookup_t_typeInfo[] = { { NULL, 0 } }; -static classVariableInfo_t class_283_class_283_typeInfo[] = { -// { "const idSoundShader *", "soundShader", (intptr_t)(&((class_283::class_283 *)0)->soundShader), sizeof( ((class_283::class_283 *)0)->soundShader ) }, -// { "const function_t *", "function", (intptr_t)(&((class_283::class_283 *)0)->function), sizeof( ((class_283::class_283 *)0)->function ) }, -// { "const idDeclSkin *", "skin", (intptr_t)(&((class_283::class_283 *)0)->skin), sizeof( ((class_283::class_283 *)0)->skin ) }, -// { "int", "index", (intptr_t)(&((class_283::class_283 *)0)->index), sizeof( ((class_283::class_283 *)0)->index ) }, +static classVariableInfo_t class_285_class_285_typeInfo[] = { +// { "const idSoundShader *", "soundShader", (intptr_t)(&((class_285::class_285 *)0)->soundShader), sizeof( ((class_285::class_285 *)0)->soundShader ) }, +// { "const function_t *", "function", (intptr_t)(&((class_285::class_285 *)0)->function), sizeof( ((class_285::class_285 *)0)->function ) }, +// { "const idDeclSkin *", "skin", (intptr_t)(&((class_285::class_285 *)0)->skin), sizeof( ((class_285::class_285 *)0)->skin ) }, +// { "int", "index", (intptr_t)(&((class_285::class_285 *)0)->index), sizeof( ((class_285::class_285 *)0)->index ) }, { NULL, 0 } }; @@ -8709,6 +8737,7 @@ static classTypeInfo_t classTypeInfo[] = { // { "glRaytracingLight_s::class_173", "", sizeof(glRaytracingLight_s::class_173), glRaytracingLight_s_class_173_typeInfo }, { "glRaytracingLight_t", "", sizeof(glRaytracingLight_t), glRaytracingLight_t_typeInfo }, { "glRaytracingLightingPassDesc_t", "", sizeof(glRaytracingLightingPassDesc_t), glRaytracingLightingPassDesc_t_typeInfo }, + { "glScreenSpaceDecal_t", "", sizeof(glScreenSpaceDecal_t), glScreenSpaceDecal_t_typeInfo }, { "QD3D12NeuralPOMImageRGBA8", "", sizeof(QD3D12NeuralPOMImageRGBA8), QD3D12NeuralPOMImageRGBA8_typeInfo }, { "QD3D12NeuralPOMTrainDesc", "", sizeof(QD3D12NeuralPOMTrainDesc), QD3D12NeuralPOMTrainDesc_typeInfo }, { "QD3D12NeuralPOMTrainStats", "", sizeof(QD3D12NeuralPOMTrainStats), QD3D12NeuralPOMTrainStats_typeInfo }, @@ -8743,6 +8772,7 @@ static classTypeInfo_t classTypeInfo[] = { { "exitPortal_t", "", sizeof(exitPortal_t), exitPortal_t_typeInfo }, { "guiPoint_t", "", sizeof(guiPoint_t), guiPoint_t_typeInfo }, { "modelTrace_t", "", sizeof(modelTrace_t), modelTrace_t_typeInfo }, + { "renderWorldDecal_t", "", sizeof(renderWorldDecal_t), renderWorldDecal_t_typeInfo }, { "idRenderWorld", "", sizeof(idRenderWorld), idRenderWorld_typeInfo }, { "idDeviceContext", "", sizeof(idDeviceContext), idDeviceContext_typeInfo }, { "jointInfo_t", "", sizeof(jointInfo_t), jointInfo_t_typeInfo }, @@ -8816,7 +8846,7 @@ static classTypeInfo_t classTypeInfo[] = { { "jointAnimInfo_t", "", sizeof(jointAnimInfo_t), jointAnimInfo_t_typeInfo }, { "jointMod_t", "", sizeof(jointMod_t), jointMod_t_typeInfo }, { "frameLookup_t", "", sizeof(frameLookup_t), frameLookup_t_typeInfo }, -// { "class_283::class_283", "", sizeof(class_283::class_283), class_283_class_283_typeInfo }, +// { "class_285::class_285", "", sizeof(class_285::class_285), class_285_class_285_typeInfo }, { "frameCommand_t", "", sizeof(frameCommand_t), frameCommand_t_typeInfo }, { "animFlags_t", "", sizeof(animFlags_t), animFlags_t_typeInfo }, { "idModelExport", "", sizeof(idModelExport), idModelExport_typeInfo }, diff --git a/neo/engine/opengl/gl_d3d12raylight.cpp b/neo/engine/opengl/gl_d3d12raylight.cpp index e4f38370..f8ed755d 100644 --- a/neo/engine/opengl/gl_d3d12raylight.cpp +++ b/neo/engine/opengl/gl_d3d12raylight.cpp @@ -2461,6 +2461,7 @@ struct glRaytracingLightingConstants_t float denoisePhiNormal; float denoisePhiPosition; float bumpStrength; + }; struct glRaytracingMeshMetadata_t @@ -2560,7 +2561,6 @@ struct glRaytracingLightingState_t static glRaytracingLightingState_t g_glRaytracingLighting; static const DXGI_FORMAT GL_RAYTRACING_DENOISE_FORMAT = DXGI_FORMAT_R16G16B16A16_FLOAT; - enum glRaytracingLightingDescriptorIndex_t { GLR_DESC_LIGHTS_SRV = 0, @@ -3101,6 +3101,8 @@ float3 Doom3SafeNormalizeOr(float3 v, float3 fallbackDir) return (lenSq > 1e-8) ? (v * rsqrt(lenSq)) : fallbackDir; } +)" +R"( float Doom3QuadraticFalloffImage(float texCoord) { // Math version of Doom 3 BFG's built-in _quadratic lookup table. @@ -3122,7 +3124,8 @@ float Doom3QuadraticFalloffImage(float texCoord) d = saturate(d); return d * d; } - +)" +R"( float Doom3QuadraticCentered(float centeredCoord) { // centeredCoord is -1 at one side of the light volume, 0 at the light center, @@ -7220,7 +7223,6 @@ void glRaytracingLightingSetShadowBias(float bias) glRaytracingLightingUpdateConstants(); } - void glRaytracingLightingSetPathTracingOptions(uint32_t samplesPerPixel, uint32_t maxBounces, int enableDenoiser, float denoiseStrength) { std::lock_guard lock(g_glRaytracingMutex); diff --git a/neo/engine/opengl/gl_d3d12shim.cpp b/neo/engine/opengl/gl_d3d12shim.cpp index 0db73866..24acdfd2 100644 --- a/neo/engine/opengl/gl_d3d12shim.cpp +++ b/neo/engine/opengl/gl_d3d12shim.cpp @@ -466,10 +466,11 @@ static const UINT QD3D12_UploadBufferSize = 128 * 1024 * 1024; // MSAA has been removed from the G-buffer path. Temporal AA now owns edge cleanup // after lighting, so all scene/deferred render targets remain single-sample. static const UINT QD3D12_RequestedGBufferSampleCount = 1; -static constexpr UINT QD3D12_PostSrvCount = 11; +static constexpr UINT QD3D12_PostSrvCount = 14; static constexpr UINT QD3D12_PostRootConstants = QD3D12_PostSrvCount; static constexpr UINT QD3D12_PostConstantDwords = 24; static constexpr UINT QD3D12_ToneMapTileDim = 64; +static constexpr UINT QD3D12_MaxScreenSpaceDecals = 64; enum QD3D12RTVSlotGroup { @@ -1517,6 +1518,13 @@ struct GLState uint32_t pathTracingSamplesPerPixel = 1; uint32_t pathTracingFallbackSamplesPerPixel = 2; uint32_t pathTracingMaxBounces = 2; + glScreenSpaceDecal_t screenSpaceDecals[QD3D12_MaxScreenSpaceDecals] = {}; + uint32_t screenSpaceDecalCount = 0; + ComPtr screenSpaceDecalBuffer; + void* screenSpaceDecalBufferMapped = nullptr; + UINT screenSpaceDecalSrvIndex = UINT_MAX; + D3D12_CPU_DESCRIPTOR_HANDLE screenSpaceDecalSrvCpu{}; + D3D12_GPU_DESCRIPTOR_HANDLE screenSpaceDecalSrvGpu{}; float jitterX = 0.0f; float jitterY = 0.0f; @@ -1696,6 +1704,7 @@ struct GLState ComPtr postDepthCopyPSO; ComPtr postDepthResolveMsaaPSO; ComPtr postGBufferPointResolveMsaaPSO; + ComPtr postScreenDecalPSO; ComPtr vsMainBlob; ComPtr hsMainBlob; @@ -1717,6 +1726,7 @@ struct GLState ComPtr postPsDepthCopyBlob; ComPtr postPsDepthResolveMsaaBlob; ComPtr postPsGBufferPointResolveMsaaBlob; + ComPtr postPsScreenDecalBlob; TextureResource whiteTexture; @@ -4381,8 +4391,27 @@ Texture2D gTaaHistory : register(t7); Texture2D gTaaVelocity : register(t8); Texture2D gFogPosition : register(t9); Texture2D gFogDepth : register(t10); +Texture2D gScreenDecalNormal : register(t11); +Texture2D gScreenDecalPosition : register(t12); SamplerState gSamp0 : register(s0); +struct ScreenSpaceDecal +{ + float3 origin; + float halfSize; + float3 normal; + float depth; + float3 axisU; + float opacity; + float3 axisV; + float pad0; + float4 color; + uint textureId; + float3 pad1; +}; + +StructuredBuffer gScreenDecals : register(t13); + cbuffer PostCB : register(b0) { // gPostParams0: x = sharpen strength, y = history weight, z = TAA reset flag. @@ -4400,6 +4429,7 @@ cbuffer PostCB : register(b0) #define gPostSharpness gPostParams0.x #define gTaaHistoryWeight gPostParams0.y #define gTaaReset gPostParams0.z +#define gScreenDecalIndex gPostParams0.w #define gToneMapExposure gPostParams1.x #define gToneMapWhiteScale gPostParams1.y #define gToneMapBrightness gPostParams1.z @@ -4409,6 +4439,7 @@ cbuffer PostCB : register(b0) #define gPostFogStart gPostParams2.w #define gPostFogEnd gPostParams3.x #define gPostFogCameraValid gPostParams3.y +#define gScreenDecalCount gPostFogCameraWorldPos.w #define QD3D12_TONEMAP_TILE_DIM 64u struct VSOut @@ -4470,6 +4501,53 @@ float4 QD3D12_ApplyPostFog(float4 color, float2 uv) return color; } +float3 QD3D12_SafeNormalizeOr(float3 v, float3 fallbackDir) +{ + float lenSq = dot(v, v); + return (lenSq > 1e-8) ? (v * rsqrt(lenSq)) : fallbackDir; +} + +float4 PSScreenDecals(VSOut i) : SV_Target0 +{ + uint2 pixel = uint2(i.pos.xy); + float3 worldPos = gScreenDecalPosition.Load(int3(pixel, 0)).xyz; + float3 normal = QD3D12_SafeNormalizeOr(gScreenDecalNormal.Load(int3(pixel, 0)).xyz, float3(0.0, 0.0, 1.0)); + float3 geometricNormal = QD3D12_SafeNormalizeOr(cross(ddx(worldPos), ddy(worldPos)), normal); + float3 decalColor = 0.0; + float decalAlpha = 0.0; + uint decalIndex = (uint)gScreenDecalIndex; + if (decalIndex >= min((uint)gScreenDecalCount, 64u)) + return float4(0.0, 0.0, 0.0, 0.0); + + ScreenSpaceDecal d = gScreenDecals[decalIndex]; + float3 decalN = QD3D12_SafeNormalizeOr(d.normal, normal); + float facing = saturate(abs(dot(geometricNormal, decalN)) * 4.0); + if (facing <= 0.0) + return float4(0.0, 0.0, 0.0, 0.0); + + float3 rel = worldPos - d.origin; + float decalDepth = max(d.depth, 1.0e-3); + float depthDistance = abs(dot(rel, decalN)); + float depthFade = 1.0 - smoothstep(decalDepth, decalDepth + max(decalDepth * 0.25, 1.0), depthDistance); + if (depthFade <= 0.0) + return float4(0.0, 0.0, 0.0, 0.0); + + float3 axisU = QD3D12_SafeNormalizeOr(d.axisU, float3(1.0, 0.0, 0.0)); + float3 axisV = QD3D12_SafeNormalizeOr(d.axisV, cross(decalN, axisU)); + float2 uv = float2(dot(rel, axisU), dot(rel, axisV)) / max(d.halfSize, 1.0e-3); + if (abs(uv.x) >= 1.0 || abs(uv.y) >= 1.0) + return float4(0.0, 0.0, 0.0, 0.0); + + float2 texUv = float2(uv.x * 0.5 + 0.5, -uv.y * 0.5 + 0.5); + float4 texel = gTex0.Sample(gSamp0, texUv); + float edge = smoothstep(1.0, 0.75, max(abs(uv.x), abs(uv.y))); + float mask = saturate(d.opacity) * facing * depthFade * edge * saturate(d.color.a) * saturate(texel.a); + decalColor = texel.rgb * saturate(d.color.rgb); + decalAlpha = mask; + + return float4(decalColor, decalAlpha); +} + float4 PSCopy(VSOut i) : SV_Target0 { return QD3D12_ApplyPostFog(gTex0.Sample(gSamp0, i.uv), i.uv); @@ -4773,7 +4851,8 @@ struct PSGBufferPointResolveOut float4 emissive : SV_Target3; float4 specular : SV_Target4; }; - +)HLSL" +R"HLSL( PSGBufferPointResolveOut PSGBufferPointResolveMS(VSOut i) { uint srcW, srcH, sampleCount; @@ -7617,7 +7696,9 @@ static void QD3D12_SetPostConstants( float toneMapExposure = 1.0f, float toneMapWhiteScale = 1.0f, float toneMapBrightness = 1.0f, - bool applySceneFog = false) + bool applySceneFog = false, + float screenDecalCount = 0.0f, + float screenDecalIndex = 0.0f) { if (!cl) return; @@ -7626,6 +7707,7 @@ static void QD3D12_SetPostConstants( constants[0] = sharpness; constants[1] = taaHistoryWeight; constants[2] = taaReset; + constants[3] = screenDecalIndex; constants[4] = toneMapExposure; constants[5] = toneMapWhiteScale; constants[6] = toneMapBrightness; @@ -7645,6 +7727,7 @@ static void QD3D12_SetPostConstants( constants[21] = g_gl.sceneFogCameraWorldPos[1]; constants[22] = g_gl.sceneFogCameraWorldPos[2]; } + constants[23] = screenDecalCount; cl->SetGraphicsRoot32BitConstants(QD3D12_PostRootConstants, QD3D12_PostConstantDwords, constants, 0); } @@ -7661,6 +7744,77 @@ static void QD3D12_BindPostFogResources(ID3D12GraphicsCommandList* cl, QD3D12Win cl->SetGraphicsRootDescriptorTable(10, w.depthSrvGpu); } +static bool QD3D12_EnsureScreenSpaceDecalBuffer() +{ + if (!g_gl.device || !g_gl.srvHeap) + return false; + + const UINT64 bufferSize = sizeof(glScreenSpaceDecal_t) * QD3D12_MaxScreenSpaceDecals; + if (!g_gl.screenSpaceDecalBuffer) + { + D3D12_HEAP_PROPERTIES hp{}; + hp.Type = D3D12_HEAP_TYPE_UPLOAD; + hp.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + hp.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + hp.CreationNodeMask = 1; + hp.VisibleNodeMask = 1; + + D3D12_RESOURCE_DESC rd{}; + rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + rd.Alignment = 0; + rd.Width = bufferSize; + rd.Height = 1; + rd.DepthOrArraySize = 1; + rd.MipLevels = 1; + rd.Format = DXGI_FORMAT_UNKNOWN; + rd.SampleDesc.Count = 1; + rd.SampleDesc.Quality = 0; + rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + rd.Flags = D3D12_RESOURCE_FLAG_NONE; + + QD3D12_CHECK(g_gl.device->CreateCommittedResource( + &hp, + D3D12_HEAP_FLAG_NONE, + &rd, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS(&g_gl.screenSpaceDecalBuffer))); + + QD3D12_CHECK(g_gl.screenSpaceDecalBuffer->Map(0, nullptr, &g_gl.screenSpaceDecalBufferMapped)); + } + + if (g_gl.screenSpaceDecalSrvIndex == UINT_MAX) + { + g_gl.screenSpaceDecalSrvIndex = g_gl.nextSrvIndex++; + g_gl.screenSpaceDecalSrvCpu = QD3D12_SrvCpu(g_gl.screenSpaceDecalSrvIndex); + g_gl.screenSpaceDecalSrvGpu = QD3D12_SrvGpu(g_gl.screenSpaceDecalSrvIndex); + + D3D12_SHADER_RESOURCE_VIEW_DESC sd{}; + sd.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; + sd.Format = DXGI_FORMAT_UNKNOWN; + sd.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + sd.Buffer.FirstElement = 0; + sd.Buffer.NumElements = QD3D12_MaxScreenSpaceDecals; + sd.Buffer.StructureByteStride = sizeof(glScreenSpaceDecal_t); + sd.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE; + g_gl.device->CreateShaderResourceView(g_gl.screenSpaceDecalBuffer.Get(), &sd, g_gl.screenSpaceDecalSrvCpu); + } + + if (g_gl.screenSpaceDecalBufferMapped) + { + memset(g_gl.screenSpaceDecalBufferMapped, 0, bufferSize); + if (g_gl.screenSpaceDecalCount > 0) + { + memcpy( + g_gl.screenSpaceDecalBufferMapped, + g_gl.screenSpaceDecals, + sizeof(glScreenSpaceDecal_t) * g_gl.screenSpaceDecalCount); + } + } + + return true; +} + static void QD3D12_PostFullscreenPass(ID3D12GraphicsCommandList* cl, ID3D12PipelineState* pso, D3D12_GPU_DESCRIPTOR_HANDLE inputSrv, @@ -7843,6 +7997,91 @@ static void QD3D12_CompositeEmissiveIntoSceneColor(QD3D12Window& w) scissor); } +void APIENTRY glSetScreenSpaceDecalsQD3D12(const glScreenSpaceDecal_t* decals, uint32_t decalCount) +{ + const uint32_t count = std::min(decalCount, QD3D12_MaxScreenSpaceDecals); + g_gl.screenSpaceDecalCount = count; + memset(g_gl.screenSpaceDecals, 0, sizeof(g_gl.screenSpaceDecals)); + if (decals && count > 0) + { + memcpy(g_gl.screenSpaceDecals, decals, sizeof(glScreenSpaceDecal_t) * count); + } +} + +void APIENTRY glDrawScreenSpaceDecalsQD3D12(void) +{ + if (g_gl.screenSpaceDecalCount == 0) + return; + + QD3D12Window* window = g_currentWindow; + if (!window || !g_gl.device || !g_gl.cmdList || !g_gl.postScreenDecalPSO) + return; + if (!window->sceneColorBuffers[window->frameIndex] || !window->normalBuffers[window->frameIndex] || !window->positionBuffers[window->frameIndex]) + return; + if (!QD3D12_EnsureScreenSpaceDecalBuffer()) + return; + + QD3D12_EnsureFrameOpen(); + QD3D12_FlushQueuedBatches(); + QD3D12_ResolveGBufferForCurrentFrame(*window); + + const UINT frame = window->frameIndex; + ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); + QD3D12_TransitionResource(cl, window->sceneColorBuffers[frame].Get(), window->sceneColorState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); + QD3D12_TransitionResource(cl, window->normalBuffers[frame].Get(), window->normalBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + QD3D12_TransitionResource(cl, window->positionBuffers[frame].Get(), window->positionBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + D3D12_VIEWPORT viewport{}; + viewport.TopLeftX = 0.0f; + viewport.TopLeftY = 0.0f; + viewport.Width = (float)window->renderWidth; + viewport.Height = (float)window->renderHeight; + viewport.MinDepth = 0.0f; + viewport.MaxDepth = 1.0f; + + D3D12_RECT scissor{}; + scissor.left = 0; + scissor.top = 0; + scissor.right = (LONG)window->renderWidth; + scissor.bottom = (LONG)window->renderHeight; + + ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; + cl->SetDescriptorHeaps(_countof(heaps), heaps); + cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); + QD3D12_SetPostConstants(cl, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, false, (float)g_gl.screenSpaceDecalCount); + cl->SetPipelineState(g_gl.postScreenDecalPSO.Get()); + D3D12_CPU_DESCRIPTOR_HANDLE sceneColorRtv = CurrentResolvedSceneColorRTV(); + cl->OMSetRenderTargets(1, &sceneColorRtv, FALSE, nullptr); + cl->RSSetViewports(1, &viewport); + cl->RSSetScissorRects(1, &scissor); + cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + for (uint32_t decalIndex = 0; decalIndex < g_gl.screenSpaceDecalCount; ++decalIndex) + { + D3D12_GPU_DESCRIPTOR_HANDLE decalTextureSrv = g_gl.whiteTexture.srvGpu; + const glScreenSpaceDecal_t& decal = g_gl.screenSpaceDecals[decalIndex]; + TextureResource* decalTexture = decal.textureId ? QD3D12_FindTextureResource(decal.textureId) : nullptr; + if (decalTexture) + { + EnsureTextureResource(*decalTexture); + if (decalTexture->texture && !decalTexture->gpuValid) + { + UploadTexture(*decalTexture); + } + if (decalTexture->texture && decalTexture->srvIndex != UINT_MAX) + { + decalTextureSrv = decalTexture->srvGpu; + } + } + + QD3D12_SetPostConstants(cl, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, false, (float)g_gl.screenSpaceDecalCount, (float)decalIndex); + cl->SetGraphicsRootDescriptorTable(0, decalTextureSrv); + cl->SetGraphicsRootDescriptorTable(11, window->normalSrvGpu[frame]); + cl->SetGraphicsRootDescriptorTable(12, window->positionSrvGpu[frame]); + cl->SetGraphicsRootDescriptorTable(13, g_gl.screenSpaceDecalSrvGpu); + cl->DrawInstanced(3, 1, 0, 0); + } +} + static ID3D12Resource* QD3D12_PrepareStreamlineOutputForWrite(QD3D12Window& w) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); @@ -9338,6 +9577,7 @@ static void QD3D12_CompileShaders() g_gl.postPsDepthCopyBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSDepthCopy", "ps_6_0"); g_gl.postPsDepthResolveMsaaBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSDepthResolveMS", "ps_6_0"); g_gl.postPsGBufferPointResolveMsaaBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSGBufferPointResolveMS", "ps_6_0"); + g_gl.postPsScreenDecalBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSScreenDecals", "ps_6_0"); } static const D3D12_INPUT_ELEMENT_DESC kGLVertexInputLayout[] = @@ -9547,6 +9787,14 @@ static void QD3D12_CreatePSOs() d.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ONE; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&d, IID_PPV_ARGS(&g_gl.postAdditivePSO))); + d.PS = { g_gl.postPsScreenDecalBlob->GetBufferPointer(), g_gl.postPsScreenDecalBlob->GetBufferSize() }; + d.BlendState.RenderTarget[0].BlendEnable = TRUE; + d.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA; + d.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA; + d.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE; + d.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA; + QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&d, IID_PPV_ARGS(&g_gl.postScreenDecalPSO))); + D3D12_GRAPHICS_PIPELINE_STATE_DESC depthDesc{}; depthDesc.pRootSignature = g_gl.postRootSig.Get(); depthDesc.VS = { g_gl.postVsBlob->GetBufferPointer(), g_gl.postVsBlob->GetBufferSize() }; @@ -16328,6 +16576,8 @@ PROC WINAPI qd3d12_wglGetProcAddress(LPCSTR name) { { "glRaytracingMaterialFlagQD3D12", (PROC)glRaytracingMaterialFlagQD3D12 }, { "glResolveGBufferQD3D12", (PROC)glResolveGBufferQD3D12 }, { "QD3D12_ResolveGBufferNow", (PROC)QD3D12_ResolveGBufferNow }, + { "glSetScreenSpaceDecalsQD3D12",(PROC)glSetScreenSpaceDecalsQD3D12 }, + { "glDrawScreenSpaceDecalsQD3D12",(PROC)glDrawScreenSpaceDecalsQD3D12 }, { "QD3D12_SetUpscalerBackend", (PROC)QD3D12_SetUpscalerBackend }, { "QD3D12_SetUpscalerQuality", (PROC)QD3D12_SetUpscalerQuality }, { "QD3D12_SetUpscalerSharpness", (PROC)QD3D12_SetUpscalerSharpness }, diff --git a/neo/engine/opengl/opengl.h b/neo/engine/opengl/opengl.h index d55277e3..383cf6d1 100644 --- a/neo/engine/opengl/opengl.h +++ b/neo/engine/opengl/opengl.h @@ -1728,6 +1728,21 @@ typedef struct glRaytracingLightingPassDesc_s uint32_t height; } glRaytracingLightingPassDesc_t; +typedef struct glScreenSpaceDecal_s +{ + glRaytracingVec3_t origin; + float halfSize; + glRaytracingVec3_t normal; + float depth; + glRaytracingVec3_t axisU; + float opacity; + glRaytracingVec3_t axisV; + float pad0; + float color[4]; + uint32_t textureId; + float pad1[3]; +} glScreenSpaceDecal_t; + int glRaytracingInit(void); void glRaytracingShutdown(void); void glRaytracingClear(void); @@ -1821,6 +1836,8 @@ glRaytracingLight_t glRaytracingLightingMakeRectLight( uint32_t glRaytracingLightingGetLightCount(void); void glLightScene(glRaytracingSceneHandle_t sceneHandle); +void glSetScreenSpaceDecalsQD3D12(const glScreenSpaceDecal_t* decals, uint32_t decalCount); +void glDrawScreenSpaceDecalsQD3D12(void); ID3D12Device* QD3D12_GetDevice(void); ID3D12CommandQueue* QD3D12_GetQueue(void); diff --git a/neo/engine/renderer/RenderWorld.cpp b/neo/engine/renderer/RenderWorld.cpp index 16132fca..c05e0aed 100644 --- a/neo/engine/renderer/RenderWorld.cpp +++ b/neo/engine/renderer/RenderWorld.cpp @@ -491,6 +491,7 @@ idRenderWorldLocal::ProjectDecalOntoWorld ================ */ void idRenderWorldLocal::ProjectDecalOntoWorld( const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) { +#if 0 int i, areas[10], numAreas; const areaReference_t *ref; const portalArea_t *area; @@ -542,6 +543,7 @@ void idRenderWorldLocal::ProjectDecalOntoWorld( const idFixedWinding &winding, c def->decals->CreateDecal( model, localInfo ); } } +#endif } /* @@ -550,6 +552,7 @@ idRenderWorldLocal::ProjectDecal ==================== */ void idRenderWorldLocal::ProjectDecal( qhandle_t entityHandle, const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) { +#if 0 decalProjectionInfo_t info, localInfo; if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { @@ -588,6 +591,7 @@ void idRenderWorldLocal::ProjectDecal( qhandle_t entityHandle, const idFixedWind def->decals = idRenderModelDecal::Alloc(); } def->decals->CreateDecal( model, localInfo ); +#endif } /* diff --git a/neo/engine/renderer/RenderWorld.h b/neo/engine/renderer/RenderWorld.h index a67a5490..22c3b51e 100644 --- a/neo/engine/renderer/RenderWorld.h +++ b/neo/engine/renderer/RenderWorld.h @@ -579,6 +579,18 @@ typedef struct modelTrace_s { #endif } modelTrace_t; +typedef struct renderWorldDecal_s { + idStr material; + idVec3 origin; + idVec3 normal; + idVec3 axisU; + idVec3 axisV; + float size; + float depth; + float opacity; + idVec4 color; +} renderWorldDecal_t; + #ifdef QUAKE4 // RAVEN BEGIN @@ -817,6 +829,14 @@ public: // Traces vs the world model bsp tree. virtual bool FastWorldTrace(modelTrace_t& trace, const idVec3& start, const idVec3& end) const = 0; + // Screen-space world decals consumed by the path tracer. + virtual void ClearWorldDecals() = 0; + virtual int AddWorldDecal(const renderWorldDecal_t& decal) = 0; + virtual void RemoveWorldDecal(int decalIndex) = 0; + virtual int NumWorldDecals() const = 0; + virtual const renderWorldDecal_t* GetWorldDecal(int decalIndex) const = 0; + virtual void SubmitWorldDecalsToScreenSpace() const = 0; + //-------------- Demo Control ----------------- // Writes a loadmap command to the demo, and clears archive counters. diff --git a/neo/engine/renderer/RenderWorld_load.cpp b/neo/engine/renderer/RenderWorld_load.cpp index fe07bc5f..af52d8d8 100644 --- a/neo/engine/renderer/RenderWorld_load.cpp +++ b/neo/engine/renderer/RenderWorld_load.cpp @@ -96,6 +96,7 @@ void idRenderWorldLocal::FreeWorld() { delete localModels[i]; } localModels.Clear(); + worldDecals.Clear(); areaReferenceAllocator.Shutdown(); //interactionAllocator.Shutdown(); @@ -104,6 +105,190 @@ void idRenderWorldLocal::FreeWorld() { mapName = ""; } +static void R_NormalizeDecalBasis(renderWorldDecal_t& decal) { + if (decal.normal.LengthSqr() < 0.0001f) { + decal.normal.Set(0.0f, 0.0f, 1.0f); + } + decal.normal.Normalize(); + + decal.axisU -= decal.normal * (decal.axisU * decal.normal); + if (decal.axisU.LengthSqr() < 0.0001f) { + decal.axisU = decal.normal.Cross(idVec3(0.0f, 0.0f, 1.0f)); + if (decal.axisU.LengthSqr() < 0.0001f) { + decal.axisU = decal.normal.Cross(idVec3(0.0f, 1.0f, 0.0f)); + } + } + decal.axisU.Normalize(); + decal.axisV = decal.normal.Cross(decal.axisU); + decal.axisV.Normalize(); + if (decal.size <= 0.0f) { + decal.size = 64.0f; + } + if (decal.depth <= 0.0f) { + decal.depth = 8.0f; + } + if (decal.opacity <= 0.0f) { + decal.opacity = 1.0f; + } +} + +void idRenderWorldLocal::ClearWorldDecals() { + worldDecals.Clear(); +} + +int idRenderWorldLocal::AddWorldDecal(const renderWorldDecal_t& inDecal) { + renderWorldDecal_t decal = inDecal; + R_NormalizeDecalBasis(decal); + return worldDecals.Append(decal); +} + +void idRenderWorldLocal::RemoveWorldDecal(int decalIndex) { + if (decalIndex >= 0 && decalIndex < worldDecals.Num()) { + worldDecals.RemoveIndex(decalIndex); + } +} + +int idRenderWorldLocal::NumWorldDecals() const { + return worldDecals.Num(); +} + +const renderWorldDecal_t* idRenderWorldLocal::GetWorldDecal(int decalIndex) const { + if (decalIndex < 0 || decalIndex >= worldDecals.Num()) { + return NULL; + } + return &worldDecals[decalIndex]; +} + +void idRenderWorldLocal::SubmitWorldDecalsToScreenSpace() const { + glScreenSpaceDecal_t gpuDecals[64]; + const int count = min(worldDecals.Num(), 64); + + for (int i = 0; i < count; ++i) { + const renderWorldDecal_t& src = worldDecals[i]; + glScreenSpaceDecal_t& dst = gpuDecals[i]; + memset(&dst, 0, sizeof(dst)); + dst.origin.x = src.origin.x; + dst.origin.y = src.origin.y; + dst.origin.z = src.origin.z; + dst.normal.x = src.normal.x; + dst.normal.y = src.normal.y; + dst.normal.z = src.normal.z; + dst.axisU.x = src.axisU.x; + dst.axisU.y = src.axisU.y; + dst.axisU.z = src.axisU.z; + dst.axisV.x = src.axisV.x; + dst.axisV.y = src.axisV.y; + dst.axisV.z = src.axisV.z; + dst.halfSize = src.size * 0.5f; + dst.depth = src.depth; + dst.opacity = src.opacity; + dst.color[0] = src.color.x; + dst.color[1] = src.color.y; + dst.color[2] = src.color.z; + dst.color[3] = src.color.w; + dst.textureId = 0; + + const idMaterial* mat = declManager->FindMaterial(src.material.c_str()); + idImage* image = mat ? mat->GetEditorImage() : NULL; +#ifndef QUAKE4 + if (mat && src.color.x == 1.0f && src.color.y == 1.0f && src.color.z == 1.0f && src.color.w == 1.0f) { + decalInfo_t decalInfo = mat->GetDecalInfo(); + dst.color[0] *= decalInfo.start[0]; + dst.color[1] *= decalInfo.start[1]; + dst.color[2] *= decalInfo.start[2]; + dst.color[3] *= decalInfo.start[3]; + } +#endif + if (image) { + image->Bind(); + dst.textureId = image->texnum; + } + } + + glSetScreenSpaceDecalsQD3D12(gpuDecals, count); + glDrawScreenSpaceDecalsQD3D12(); +} + +static idStr R_WorldDecalFileName(const char* mapName) { + idStr filename = mapName ? mapName : ""; + filename.StripFileExtension(); + filename.SetFileExtension(".stamp"); + return filename; +} + +void idRenderWorldLocal::LoadWorldDecals() { + ClearWorldDecals(); + + idStr filename = R_WorldDecalFileName(mapName.c_str()); + const bool osPath = strstr(filename.c_str(), ":") != NULL; + idLexer src(LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE | LEXFL_ALLOWPATHNAMES); + src.LoadFile(filename.c_str(), osPath); + if (!src.IsLoaded()) { + return; + } + + idToken token; + if (!src.ReadToken(&token) || token.Icmp("worldDecals")) { + return; + } + + src.ReadToken(&token); + if (token != "{") { + src.ExpectTokenString("{"); + } + + while (src.ReadToken(&token)) { + if (token == "}") { + break; + } + if (token.Icmp("decal")) { + src.Error("idRenderWorldLocal::LoadWorldDecals: bad token \"%s\"", token.c_str()); + break; + } + + renderWorldDecal_t decal; + decal.material = "textures/decals/default"; + decal.origin.Zero(); + decal.normal.Set(0.0f, 0.0f, 1.0f); + decal.axisU.Set(1.0f, 0.0f, 0.0f); + decal.axisV.Set(0.0f, 1.0f, 0.0f); + decal.size = 64.0f; + decal.depth = 8.0f; + decal.opacity = 1.0f; + decal.color.Set(0.05f, 0.05f, 0.05f, 1.0f); + + src.ExpectTokenString("{"); + while (src.ReadToken(&token)) { + if (token == "}") { + break; + } + if (!token.Icmp("material")) { + src.ExpectAnyToken(&token); + decal.material = token; + } else if (!token.Icmp("origin")) { + src.Parse1DMatrix(3, decal.origin.ToFloatPtr()); + } else if (!token.Icmp("normal")) { + src.Parse1DMatrix(3, decal.normal.ToFloatPtr()); + } else if (!token.Icmp("axisU")) { + src.Parse1DMatrix(3, decal.axisU.ToFloatPtr()); + } else if (!token.Icmp("axisV")) { + src.Parse1DMatrix(3, decal.axisV.ToFloatPtr()); + } else if (!token.Icmp("size")) { + decal.size = src.ParseFloat(); + } else if (!token.Icmp("depth")) { + decal.depth = src.ParseFloat(); + } else if (!token.Icmp("opacity")) { + decal.opacity = src.ParseFloat(); + } else if (!token.Icmp("color")) { + src.Parse1DMatrix(4, decal.color.ToFloatPtr()); + } else { + src.Error("idRenderWorldLocal::LoadWorldDecals: bad decal key \"%s\"", token.c_str()); + } + } + AddWorldDecal(decal); + } +} + /* ================ idRenderWorldLocal::TouchWorldModels @@ -546,6 +731,7 @@ bool idRenderWorldLocal::InitFromMap( const char *name ) { common->Printf( "idRenderWorldLocal::InitFromMap: retaining existing map\n" ); FreeDefs(); TouchWorldModels(); + LoadWorldDecals(); AddWorldModelEntities(); ClearPortalStates(); return true; @@ -565,6 +751,7 @@ bool idRenderWorldLocal::InitFromMap( const char *name ) { mapName = name; mapTimeStamp = currentTimeStamp; + LoadWorldDecals(); // if we are writing a demo, archive the load command if ( session->writeDemo ) { diff --git a/neo/engine/renderer/RenderWorld_local.h b/neo/engine/renderer/RenderWorld_local.h index 28d9585e..7b03258f 100644 --- a/neo/engine/renderer/RenderWorld_local.h +++ b/neo/engine/renderer/RenderWorld_local.h @@ -123,6 +123,12 @@ public: virtual bool ModelTrace( modelTrace_t &trace, qhandle_t entityHandle, const idVec3 &start, const idVec3 &end, const float radius ) const; virtual bool Trace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, bool skipDynamic = true, bool skipPlayer = false ) const; virtual bool FastWorldTrace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; + virtual void ClearWorldDecals(); + virtual int AddWorldDecal( const renderWorldDecal_t &decal ); + virtual void RemoveWorldDecal( int decalIndex ); + virtual int NumWorldDecals() const; + virtual const renderWorldDecal_t *GetWorldDecal( int decalIndex ) const; + virtual void SubmitWorldDecalsToScreenSpace() const; virtual void DebugClearLines( int time ); virtual void DebugLine( const idVec4 &color, const idVec3 &start, const idVec3 &end, const int lifetime = 0, const bool depthTest = false ); @@ -160,6 +166,7 @@ public: int numInterAreaPortals; idList localModels; + idList worldDecals; idList entityDefs; idList lightDefs; @@ -182,6 +189,7 @@ public: void TouchWorldModels( void ); void AddWorldModelEntities(); void ClearPortalStates(); + void LoadWorldDecals(); virtual bool InitFromMap( const char *mapName ); //-------------------------- diff --git a/neo/engine/renderer/draw_dx.cpp b/neo/engine/renderer/draw_dx.cpp index 02fca405..f0ec9266 100644 --- a/neo/engine/renderer/draw_dx.cpp +++ b/neo/engine/renderer/draw_dx.cpp @@ -156,7 +156,6 @@ RB_DXDrawInteractions void RB_DXDrawInteractions(void) { viewLight_t* vLight; - bool hasLight = false; QD3D12_SetToneMapBrightness(r_brightness.GetFloat()); QD3D12_SetFrameGenerationMultiplier(r_frameGen.GetInteger()); @@ -219,18 +218,15 @@ void RB_DXDrawInteractions(void) common->Warning("Parallel lights are not needed with raytracing! Just place a skybox."); } - if (supported && glRaytracingLightingAddLight(&light)) - { - hasLight = true; + if (supported) { + glRaytracingLightingAddLight(&light); } } - if (!hasLight) + if (backEnd.viewDef->renderWorld) { - return; + backEnd.viewDef->renderWorld->SubmitWorldDecalsToScreenSpace(); + glLightScene(backEnd.viewDef->renderWorld->dxrWorldId); + glRaytracingLightingClearLights(false); } - -// glFinish(); - glLightScene(backEnd.viewDef->renderWorld->dxrWorldId); - glRaytracingLightingClearLights(false); } diff --git a/neo/engine/renderer/tr_light.cpp b/neo/engine/renderer/tr_light.cpp index bc363c37..9683271f 100644 --- a/neo/engine/renderer/tr_light.cpp +++ b/neo/engine/renderer/tr_light.cpp @@ -1359,10 +1359,8 @@ static void R_AddAmbientDrawsurfs( viewEntity_t *vEntity ) { } } - // add the lightweight decal surfaces - for ( idRenderModelDecal *decal = def->decals; decal; decal = decal->Next() ) { - decal->AddDecalDrawSurf( vEntity ); - } + // Decals are now submitted as screen-space world projectors before the + // path-traced light pass instead of as projected render-model surfaces. } /* diff --git a/neo/engine/tools/radiant/CamWnd.cpp b/neo/engine/tools/radiant/CamWnd.cpp index e9c80abd..08a76535 100644 --- a/neo/engine/tools/radiant/CamWnd.cpp +++ b/neo/engine/tools/radiant/CamWnd.cpp @@ -31,6 +31,7 @@ If you have questions concerning this license or the applicable additional terms #include "qe3.h" #include "Radiant.h" +#include "EditorMap.h" #include "XYWnd.h" #include "CamWnd.h" #include "OutlinerDlg.h" @@ -118,6 +119,52 @@ struct cameraNavState_t { static cameraNavConfig_t s_cameraNavConfig; static cameraNavState_t s_cameraNavState; +static void CameraNav_GetIniPath(char* path, int pathSize); + +static bool CamWnd_DecalPlacementEnabled() { + char iniPath[MAX_PATH]; + CameraNav_GetIniPath(iniPath, sizeof(iniPath)); + return GetPrivateProfileInt("decals", "enabled", 0, iniPath) != 0; +} + +static float CamWnd_DecalFloat(const char* key, const char* defaultValue) { + char iniPath[MAX_PATH]; + char value[64]; + CameraNav_GetIniPath(iniPath, sizeof(iniPath)); + GetPrivateProfileString("decals", key, defaultValue, value, sizeof(value), iniPath); + return (float)atof(value); +} + +static void CamWnd_DecalMaterial(idStr& material) { + const char* selected = g_qeglobals.d_texturewin.texdef.name; + if (selected && selected[0] && selected[0] != '(') { + material = selected; + return; + } + material = "textures/decals/default"; +} + +static void CamWnd_DecalColorFromSelectedMaterial(const idStr& material, idVec4& color) { + color.Set( + CamWnd_DecalFloat("red", "1"), + CamWnd_DecalFloat("green", "1"), + CamWnd_DecalFloat("blue", "1"), + CamWnd_DecalFloat("alpha", "1")); + + const idMaterial* mat = declManager->FindMaterial(material.c_str()); + if (mat) { +#ifndef QUAKE4 + decalInfo_t decalInfo = mat->GetDecalInfo(); + color.x *= decalInfo.start[0]; + color.y *= decalInfo.start[1]; + color.z *= decalInfo.start[2]; + color.w *= decalInfo.start[3]; +#endif + if (mat->GetEditorImage()) { + mat->GetEditorImage()->Bind(); + } + } +} static void CameraNav_GetIniPath(char* path, int pathSize) { if (pathSize <= 0) { @@ -1010,6 +1057,7 @@ static const char* CAMWND_PROP_RENDER_MODE = "QER_CamWnd_RenderMode"; static const char* CAMWND_PROP_REBUILD_MODE = "QER_CamWnd_RebuildMode"; static const char* CAMWND_PROP_ENTITY_MODE = "QER_CamWnd_EntityMode"; static const char* CAMWND_PROP_FILTER_MASK = "QER_CamWnd_FilterMask"; +static const char* CAMWND_PROP_DECAL_EDITING = "QER_CamWnd_DecalEditing"; #define CAMWND_CAPTION_HEIGHT 30 #define CAMWND_MENU_HEIGHT 22 @@ -1025,6 +1073,7 @@ static const char* CAMWND_PROP_FILTER_MASK = "QER_CamWnd_FilterMask"; #define CAMWND_MENU_CMD_SHOW_TARGET_LINES 62107 #define CAMWND_MENU_CMD_SHOW_TRIGGER_BRUSHES 62108 #define CAMWND_MENU_CMD_CUBIC_CLIPPING 62109 +#define CAMWND_MENU_CMD_DECAL_EDITING 62110 #define CAMWND_FILTER_HIDE_WORLD 0x00000001 #define CAMWND_FILTER_HIDE_PATCHES 0x00000002 @@ -1120,15 +1169,19 @@ static void CamWnd_GetMenuItemRect(HWND hWnd, int item, RECT& rect) { rect.left = 4; rect.right = 86; } - else { + else if (item == 1) { rect.left = 86; rect.right = 156; } + else { + rect.left = 156; + rect.right = 236; + } } static int CamWnd_MenuHitTest(HWND hWnd, int x, int y) { RECT rect; - for (int i = 0; i < 2; i++) { + for (int i = 0; i < 3; i++) { CamWnd_GetMenuItemRect(hWnd, i, rect); if (x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom) { return i; @@ -1179,6 +1232,25 @@ static void CamWnd_ToggleRealtimeLighting(CCamWnd* cam) { CamWnd_SetRealtimeLighting(cam, !CamWnd_RealtimeLightingEnabled(cam)); } +static bool CamWnd_DecalEditingEnabled(CCamWnd* cam) { + if (!cam || !cam->GetSafeHwnd()) { + return false; + } + return CamWnd_GetIntProp(cam->GetSafeHwnd(), CAMWND_PROP_DECAL_EDITING, CamWnd_DecalPlacementEnabled() ? 1 : 0) != 0; +} + +static void CamWnd_SetDecalEditing(CCamWnd* cam, bool enabled) { + if (!cam || !cam->GetSafeHwnd()) { + return; + } + CamWnd_SetIntProp(cam->GetSafeHwnd(), CAMWND_PROP_DECAL_EDITING, enabled ? 1 : 0); + CamWnd_RequestRedraw(cam); +} + +static void CamWnd_ToggleDecalEditing(CCamWnd* cam) { + CamWnd_SetDecalEditing(cam, !CamWnd_DecalEditingEnabled(cam)); +} + static void CamWnd_ToggleFilterBit(CCamWnd* cam, int bit) { int mask = CamWnd_GetFilterMask(cam); mask ^= bit; @@ -1211,6 +1283,9 @@ static void CamWnd_HandleMenuCommand(CCamWnd* cam, int command) { case CAMWND_MENU_CMD_CUBIC_CLIPPING: CamWnd_ToggleCubicClipping(cam); break; + case CAMWND_MENU_CMD_DECAL_EDITING: + CamWnd_ToggleDecalEditing(cam); + break; case CAMWND_MENU_CMD_SHOW_WORLD: CamWnd_ToggleFilterBit(cam, CAMWND_FILTER_HIDE_WORLD); break; @@ -1268,6 +1343,28 @@ static void CamWnd_ShowLightingMenu(HWND hWnd, CCamWnd* cam) { } } +static void CamWnd_ShowDecalsMenu(HWND hWnd, CCamWnd* cam) { + RECT itemRect; + POINT pt; + HMENU menu; + int command; + + CamWnd_GetMenuItemRect(hWnd, 2, itemRect); + pt.x = itemRect.left; + pt.y = itemRect.bottom; + ClientToScreen(hWnd, &pt); + + menu = CreatePopupMenu(); + AppendMenu(menu, MF_STRING | (CamWnd_DecalEditingEnabled(cam) ? MF_CHECKED : 0), CAMWND_MENU_CMD_DECAL_EDITING, "Enable decal editing"); + + command = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, hWnd, NULL); + DestroyMenu(menu); + + if (command) { + CamWnd_HandleMenuCommand(cam, command); + } +} + static void CamWnd_ShowFiltersMenu(HWND hWnd, CCamWnd* cam) { RECT itemRect; POINT pt; @@ -1313,6 +1410,9 @@ static void CamWnd_ShowTopMenu(HWND hWnd, int item) { else if (item == 1) { CamWnd_ShowFiltersMenu(hWnd, cam); } + else if (item == 2) { + CamWnd_ShowDecalsMenu(hWnd, cam); + } } static void CamWnd_RegisterMenuBarClass() { @@ -1397,6 +1497,7 @@ static void CamWnd_DestroyMenuBar(CCamWnd* cam) { RemoveProp(cam->GetSafeHwnd(), CAMWND_PROP_REBUILD_MODE); RemoveProp(cam->GetSafeHwnd(), CAMWND_PROP_ENTITY_MODE); RemoveProp(cam->GetSafeHwnd(), CAMWND_PROP_FILTER_MASK); + RemoveProp(cam->GetSafeHwnd(), CAMWND_PROP_DECAL_EDITING); } static bool CamWnd_PointInMenuBar(CCamWnd* cam, const CPoint& point) { @@ -1703,9 +1804,11 @@ static void CamWnd_FaceExtrudeBuildRay(CCamWnd* cam, const CPoint& point, idVec3 float u = 0.0f; float r = 0.0f; if (camera.width > 0) { - u = (float)(y - camera.height / 2) / (camera.width / 2); r = (float)(x - camera.width / 2) / (camera.width / 2); } + if (camera.width > 0) { + u = (float)(y - camera.height / 2) / (camera.width / 2); + } for (int i = 0; i < 3; i++) { dir[i] = camera.vpn[i] + camera.vright[i] * r + camera.vup[i] * u; @@ -1713,6 +1816,75 @@ static void CamWnd_FaceExtrudeBuildRay(CCamWnd* cam, const CPoint& point, idVec3 dir.Normalize(); } +static bool CamWnd_DecalClick(CCamWnd* cam, const CPoint& point, UINT nFlags) { + if (!CamWnd_DecalEditingEnabled(cam) || cam == NULL) { + return false; + } + + idVec3 dir; + CamWnd_FaceExtrudeBuildRay(cam, point, dir); + + if (nFlags & MK_SHIFT) { + Map_SelectDecalByRay(cam->Camera().origin, dir); + return true; + } + + idVec3 hitPoint; + idVec3 hitNormal; + float hitDist = HUGE_DISTANCE; + bool hitBrush = false; + const char* hitSource = "none"; + + qertrace_t trace = Test_Ray(cam->Camera().origin, dir, 0); + if (trace.brush != NULL && trace.face != NULL && trace.dist > 0.0f && trace.dist < hitDist) { + hitBrush = true; + hitSource = "brush"; + hitDist = trace.dist; + hitPoint = cam->Camera().origin + dir * trace.dist; + hitNormal = trace.face->plane.Normal(); + } + + if (g_qeglobals.rw) { + modelTrace_t modelTrace; + idVec3 end = cam->Camera().origin + dir * HUGE_DISTANCE; + if (g_qeglobals.rw->Trace(modelTrace, cam->Camera().origin, end, 0.0f, false, false)) { + float modelDist = (modelTrace.point - cam->Camera().origin) * dir; + if (!hitBrush && modelDist > 0.0f && modelDist < hitDist) { + hitSource = "render"; + hitDist = modelDist; + hitPoint = modelTrace.point; + hitNormal = modelTrace.normal; + } + } + } + + if (hitDist <= 0.0f || hitDist >= HUGE_DISTANCE || hitNormal.LengthSqr() < 0.0001f) { + return true; + } + + renderWorldDecal_t decal; + CamWnd_DecalMaterial(decal.material); + CamWnd_DecalColorFromSelectedMaterial(decal.material, decal.color); + decal.origin = hitPoint; + decal.normal = hitNormal; + if ((decal.normal * dir) > 0.0f) { + decal.normal = -decal.normal; + } + decal.axisU = decal.normal.Cross(cam->Camera().vup); + if (decal.axisU.LengthSqr() < 0.0001f) { + decal.axisU = decal.normal.Cross(cam->Camera().vright); + } + decal.axisV = decal.normal.Cross(decal.axisU); + decal.size = CamWnd_DecalFloat("size", "64"); + decal.depth = CamWnd_DecalFloat("depth", "8"); + decal.opacity = CamWnd_DecalFloat("opacity", "1"); + + Map_AddDecal(decal); + Sys_Status(va("Placed decal using %s on %s at %.2f %.2f %.2f\n", decal.material.c_str(), hitSource, decal.origin.x, decal.origin.y, decal.origin.z), 0); + Sys_UpdateWindows(W_CAMERA); + return true; +} + static bool CamWnd_FaceExtrudeBrushAllowed(CCamWnd* cam, brush_t* brush) { if (brush == NULL || brush->owner == NULL || brush->owner->eclass == NULL) { return false; @@ -2665,6 +2837,9 @@ static LRESULT CALLBACK CamWnd_MenuBarProc(HWND hWnd, UINT uMsg, WPARAM wParam, CamWnd_GetMenuItemRect(hWnd, 1, itemRect); RoundRect(hDC, itemRect.left + 3, itemRect.top + 2, itemRect.right - 3, itemRect.bottom - 2, 6, 6); DrawText(hDC, "Filters", -1, &itemRect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + CamWnd_GetMenuItemRect(hWnd, 2, itemRect); + RoundRect(hDC, itemRect.left + 3, itemRect.top + 2, itemRect.right - 3, itemRect.bottom - 2, 6, 6); + DrawText(hDC, "Decals", -1, &itemRect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); ::SelectObject(hDC, oldPen); ::SelectObject(hDC, oldBrush); ::DeleteObject(borderPen); @@ -2849,6 +3024,10 @@ void CCamWnd::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { CamWnd_FaceExtrudeEnd(this, false); return; } + if (nChar == VK_DELETE && CamWnd_DecalEditingEnabled(this) && Map_GetSelectedDecal() >= 0) { + Map_DeleteSelectedDecal(); + return; + } if (CameraNav_HandleKeyDown(this, nChar)) { return; } @@ -2975,6 +3154,9 @@ void CCamWnd::OnLButtonDown(UINT nFlags, CPoint point) { if ((nFlags & MK_CONTROL) && (nFlags & MK_SHIFT) && CamWnd_FaceExtrudeBegin(this, point)) { return; } + if (CamWnd_DecalClick(this, point, nFlags)) { + return; + } if (CamWnd_GizmoBegin(this, point, nFlags)) { return; } @@ -3857,7 +4039,6 @@ static void CamWnd_DrawMoveSelectedBrushSides(const camWndBrushFilterContext_t& glPopAttrib(); } - extern void glLabeledPoint(idVec4& color, idVec3& point, float size, const char* label); void DrawAxial(face_t* selFace) { if (g_bAxialMode) { @@ -4084,6 +4265,9 @@ void CCamWnd::Cam_Draw() { } CamWnd_DrawMoveSelectedBrushSides(brushFilter); + if (!renderMode && g_qeglobals.rw && CamWnd_DecalEditingEnabled(this)) { + g_qeglobals.rw->SubmitWorldDecalsToScreenSpace(); + } CamWnd_FaceExtrudeDrawPreview(this); CamWnd_GizmoDraw(this); @@ -4141,7 +4325,7 @@ void CCamWnd::Cam_Draw() { CameraNav_DrawOverlay(this); glDisable(GL_SCISSOR_TEST); - glFinish(); + //glFinish(); QE_CheckOpenGLForErrors(); if (!renderMode) { @@ -5682,6 +5866,17 @@ void CCamWnd::Cam_Render() { } Cam_SetGlobalFogFromWorldspawn(refdef); + if (g_qeglobals.rw && CamWnd_DecalEditingEnabled(this)) { + for (int i = 0; i < g_qeglobals.rw->NumWorldDecals(); ++i) { + const renderWorldDecal_t* decal = g_qeglobals.rw->GetWorldDecal(i); + if (!decal) { + continue; + } + idVec4 color = (i == Map_GetSelectedDecal()) ? idVec4(1.0f, 0.85f, 0.1f, 1.0f) : idVec4(0.1f, 0.8f, 1.0f, 1.0f); + g_qeglobals.rw->DebugCircle(color, decal->origin, decal->normal, decal->size * 0.5f, 32, 0, false); + } + } + g_qeglobals.rw->RenderScene(&refdef); int frontEnd, backEnd; diff --git a/neo/engine/tools/radiant/EditorMap.cpp b/neo/engine/tools/radiant/EditorMap.cpp index 7e37095f..3620ebaa 100644 --- a/neo/engine/tools/radiant/EditorMap.cpp +++ b/neo/engine/tools/radiant/EditorMap.cpp @@ -82,6 +82,209 @@ void Map_SaveBetween(void) { } +static int s_selectedWorldDecal = -1; + +static idStr Map_DecalFileName(const char* filename) { + idStr decalFile = filename ? filename : currentmap; + decalFile.StripFileExtension(); + decalFile.SetFileExtension(".stamp"); + return decalFile; +} + +static void Map_NormalizeDecal(renderWorldDecal_t& decal) { + if (decal.normal.LengthSqr() < 0.0001f) { + decal.normal.Set(0.0f, 0.0f, 1.0f); + } + decal.normal.Normalize(); + decal.axisU -= decal.normal * (decal.axisU * decal.normal); + if (decal.axisU.LengthSqr() < 0.0001f) { + decal.axisU = decal.normal.Cross(idVec3(0.0f, 0.0f, 1.0f)); + if (decal.axisU.LengthSqr() < 0.0001f) { + decal.axisU = decal.normal.Cross(idVec3(0.0f, 1.0f, 0.0f)); + } + } + decal.axisU.Normalize(); + decal.axisV = decal.normal.Cross(decal.axisU); + decal.axisV.Normalize(); + if (decal.size <= 0.0f) { + decal.size = 64.0f; + } + if (decal.depth <= 0.0f) { + decal.depth = 8.0f; + } + if (decal.opacity <= 0.0f) { + decal.opacity = 1.0f; + } +} + +int Map_AddDecal(const renderWorldDecal_t& inDecal) { + if (!g_qeglobals.rw) { + return -1; + } + renderWorldDecal_t decal = inDecal; + Map_NormalizeDecal(decal); + s_selectedWorldDecal = g_qeglobals.rw->AddWorldDecal(decal); + mapModified = 1; + return s_selectedWorldDecal; +} + +int Map_GetSelectedDecal(void) { + return s_selectedWorldDecal; +} + +void Map_DeleteSelectedDecal(void) { + if (!g_qeglobals.rw || s_selectedWorldDecal < 0) { + return; + } + g_qeglobals.rw->RemoveWorldDecal(s_selectedWorldDecal); + s_selectedWorldDecal = -1; + mapModified = 1; + Sys_UpdateWindows(W_ALL); +} + +int Map_SelectDecalByRay(const idVec3& origin, const idVec3& dir) { + if (!g_qeglobals.rw) { + return -1; + } + + float bestT = 1e30f; + int bestIndex = -1; + for (int i = 0; i < g_qeglobals.rw->NumWorldDecals(); ++i) { + const renderWorldDecal_t* decal = g_qeglobals.rw->GetWorldDecal(i); + if (!decal) { + continue; + } + float denom = dir * decal->normal; + if (fabs(denom) < 0.0001f) { + continue; + } + float t = ((decal->origin - origin) * decal->normal) / denom; + if (t <= 0.0f || t >= bestT) { + continue; + } + idVec3 hit = origin + dir * t; + idVec3 rel = hit - decal->origin; + float u = rel * decal->axisU; + float v = rel * decal->axisV; + if (fabs(u) <= decal->size * 0.5f && fabs(v) <= decal->size * 0.5f) { + bestT = t; + bestIndex = i; + } + } + s_selectedWorldDecal = bestIndex; + Sys_UpdateWindows(W_CAMERA); + return bestIndex; +} + +void Map_SaveDecals(const char* filename) { + if (!g_qeglobals.rw) { + return; + } + + idStr decalFile = Map_DecalFileName(filename); + const bool osPath = strstr(decalFile.c_str(), ":") != NULL; + idFile* file = osPath ? fileSystem->OpenExplicitFileWrite(decalFile.c_str()) : fileSystem->OpenFileWrite(decalFile.c_str()); + if (!file) { + common->Warning("Map_SaveDecals: couldn't write %s", decalFile.c_str()); + return; + } + + file->WriteFloatString("worldDecals 1 {\n"); + for (int i = 0; i < g_qeglobals.rw->NumWorldDecals(); ++i) { + const renderWorldDecal_t* d = g_qeglobals.rw->GetWorldDecal(i); + if (!d) { + continue; + } + file->WriteFloatString("\tdecal {\n"); + file->WriteFloatString("\t\tmaterial \"%s\"\n", d->material.c_str()); + file->WriteFloatString("\t\torigin ( %f %f %f )\n", d->origin.x, d->origin.y, d->origin.z); + file->WriteFloatString("\t\tnormal ( %f %f %f )\n", d->normal.x, d->normal.y, d->normal.z); + file->WriteFloatString("\t\taxisU ( %f %f %f )\n", d->axisU.x, d->axisU.y, d->axisU.z); + file->WriteFloatString("\t\taxisV ( %f %f %f )\n", d->axisV.x, d->axisV.y, d->axisV.z); + file->WriteFloatString("\t\tsize %f\n", d->size); + file->WriteFloatString("\t\tdepth %f\n", d->depth); + file->WriteFloatString("\t\topacity %f\n", d->opacity); + file->WriteFloatString("\t\tcolor ( %f %f %f %f )\n", d->color.x, d->color.y, d->color.z, d->color.w); + file->WriteFloatString("\t}\n"); + } + file->WriteFloatString("}\n"); + fileSystem->CloseFile(file); +} + +void Map_LoadDecals(const char* filename) { + if (!g_qeglobals.rw) { + return; + } + g_qeglobals.rw->ClearWorldDecals(); + s_selectedWorldDecal = -1; + + idStr decalFile = Map_DecalFileName(filename); + const bool osPath = strstr(decalFile.c_str(), ":") != NULL; + idLexer src(LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE | LEXFL_ALLOWPATHNAMES); + src.LoadFile(decalFile.c_str(), osPath); + if (!src.IsLoaded()) { + return; + } + + idToken token; + if (!src.ReadToken(&token) || token.Icmp("worldDecals")) { + return; + } + src.ReadToken(&token); + if (token != "{") { + src.ExpectTokenString("{"); + } + + while (src.ReadToken(&token)) { + if (token == "}") { + break; + } + if (token.Icmp("decal")) { + break; + } + renderWorldDecal_t decal; + decal.material = "textures/decals/default"; + decal.origin.Zero(); + decal.normal.Set(0.0f, 0.0f, 1.0f); + decal.axisU.Set(1.0f, 0.0f, 0.0f); + decal.axisV.Set(0.0f, 1.0f, 0.0f); + decal.size = 64.0f; + decal.depth = 8.0f; + decal.opacity = 1.0f; + decal.color.Set(0.05f, 0.05f, 0.05f, 1.0f); + + src.ExpectTokenString("{"); + while (src.ReadToken(&token)) { + if (token == "}") { + break; + } + if (!token.Icmp("material")) { + src.ExpectAnyToken(&token); + decal.material = token; + } else if (!token.Icmp("origin")) { + src.Parse1DMatrix(3, decal.origin.ToFloatPtr()); + } else if (!token.Icmp("normal")) { + src.Parse1DMatrix(3, decal.normal.ToFloatPtr()); + } else if (!token.Icmp("axisU")) { + src.Parse1DMatrix(3, decal.axisU.ToFloatPtr()); + } else if (!token.Icmp("axisV")) { + src.Parse1DMatrix(3, decal.axisV.ToFloatPtr()); + } else if (!token.Icmp("size")) { + decal.size = src.ParseFloat(); + } else if (!token.Icmp("depth")) { + decal.depth = src.ParseFloat(); + } else if (!token.Icmp("opacity")) { + decal.opacity = src.ParseFloat(); + } else if (!token.Icmp("color")) { + src.Parse1DMatrix(4, decal.color.ToFloatPtr()); + } + } + Map_AddDecal(decal); + } + s_selectedWorldDecal = -1; + mapModified = 0; +} + /* ======================================================================================================================= ======================================================================================================================= @@ -546,6 +749,7 @@ void Map_LoadFile(const char *filename) { if (g_pParentWnd->GetCamera()->GetRenderMode()) { g_pParentWnd->GetCamera()->BuildRendererState(); } + Map_LoadDecals(filename); Sys_EndWait(); Sys_UpdateWindows(W_ALL); @@ -790,6 +994,9 @@ bool Map_SaveFile(const char *filename, bool use_region, bool autosave) { sprintf(status, "Writing file %s.%s...", mapFile.c_str(), mapExt.c_str()); dlg.SetText(status); map.Write(mapFile, mapExt, !(autosave || localFile)); + if (!use_region) { + Map_SaveDecals(filename); + } mapModified = 0; if (use_region) { @@ -821,6 +1028,9 @@ void Map_New(void) { world_entity->brushes.onext = world_entity->brushes.oprev = &world_entity->brushes; SetKeyValue(world_entity, "classname", "worldspawn"); world_entity->eclass = Eclass_ForName("worldspawn", true); + if (g_qeglobals.rw) { + g_qeglobals.rw->ClearWorldDecals(); + } g_pParentWnd->GetCamera()->Camera().angles[YAW] = 0; g_pParentWnd->GetCamera()->Camera().angles[PITCH] = 0; diff --git a/neo/engine/tools/radiant/EditorMap.h b/neo/engine/tools/radiant/EditorMap.h index bba310b7..dd5e8d47 100644 --- a/neo/engine/tools/radiant/EditorMap.h +++ b/neo/engine/tools/radiant/EditorMap.h @@ -26,6 +26,11 @@ If you have questions concerning this license or the applicable additional terms =========================================================================== */ +#ifndef __EDITORMAP_H__ +#define __EDITORMAP_H__ + +#include "../../renderer/RenderWorld.h" + extern char currentmap[1024]; // head/tail of doubly linked lists @@ -52,6 +57,12 @@ void Map_LoadFile (const char *filename); bool Map_SaveFile (const char *filename, bool use_region, bool autosave = false); void Map_New (void); void Map_BuildBrushData(void); +void Map_LoadDecals(const char *filename); +void Map_SaveDecals(const char *filename); +int Map_AddDecal(const renderWorldDecal_t& decal); +void Map_DeleteSelectedDecal(void); +int Map_SelectDecalByRay(const idVec3& origin, const idVec3& dir); +int Map_GetSelectedDecal(void); void Map_RegionOff (void); void Map_RegionXY (void); @@ -65,3 +76,5 @@ void Map_ImportBuffer (char* buf, bool renameEntities = true); int Map_GetUniqueEntityID(const char *prefix, const char *eclass); idMapPrimitive *BrushToMapPrimitive( const brush_t *b, const idVec3 &origin ); + +#endif