import { type SurfaceFlag } from '@quake2ts/shared'; import { ShaderProgram } from './shaderProgram.js'; import { DLight } from './dlight.js'; import { RenderModeConfig } from './frame.js'; import { BspSurfaceGeometry } from './bsp.js'; import { IndexBuffer } from './resources.js'; export interface SurfaceRenderState { readonly alpha: number; readonly blend: boolean; readonly depthWrite: boolean; readonly warp: boolean; readonly flowOffset: readonly [number, number]; readonly sky: boolean; } export interface BspSurfaceBindOptions { readonly modelViewProjection: Float32List; readonly styleIndices?: readonly number[]; readonly styleValues?: ReadonlyArray; readonly styleLayers?: readonly number[]; readonly diffuseSampler?: number; readonly lightmapSampler?: number; readonly refractionSampler?: number; readonly surfaceFlags?: SurfaceFlag; readonly timeSeconds?: number; readonly texScroll?: readonly [number, number]; readonly alpha?: number; readonly warp?: boolean; readonly dlights?: readonly DLight[]; readonly renderMode?: RenderModeConfig; readonly lightmapOnly?: boolean; readonly brightness?: number; readonly gamma?: number; readonly fullbright?: boolean; readonly ambient?: number; } export declare const BSP_SURFACE_VERTEX_SOURCE = "#version 300 es\nprecision highp float;\n\nlayout(location = 0) in vec3 a_position;\nlayout(location = 1) in vec2 a_texCoord;\nlayout(location = 2) in vec2 a_lightmapCoord;\nlayout(location = 3) in float a_lightmapStep;\n\nuniform mat4 u_modelViewProjection;\nuniform vec2 u_texScroll;\nuniform vec2 u_lightmapScroll;\nuniform float u_time;\nuniform bool u_warp;\n\nout vec2 v_texCoord;\nout vec2 v_lightmapCoord;\nout float v_lightmapStep;\nout vec3 v_position;\nout vec4 v_screenPos; // For refraction\n\n// Match gl_warp.c TURBSCALE\nconst float TURBSCALE = (256.0 / (2.0 * 3.14159));\n\nvec2 applyScroll(vec2 uv, vec2 scroll) {\n return uv + scroll;\n}\n\nvoid main() {\n vec3 pos = a_position;\n vec2 tex = a_texCoord;\n vec2 lm = a_lightmapCoord;\n\n // Vertex Warping (match gl_warp.c)\n\n if (u_warp) {\n // Simple sine wave distortion\n float amp = 0.125;\n\n // Let's just use sin directly.\n float s = tex.x + sin((tex.y * 0.125 + u_time) * 1.0) * amp;\n float t = tex.y + sin((tex.x * 0.125 + u_time) * 1.0) * amp;\n\n tex = vec2(s, t);\n }\n\n v_texCoord = applyScroll(tex, u_texScroll);\n v_lightmapCoord = applyScroll(lm, u_lightmapScroll);\n v_lightmapStep = a_lightmapStep;\n v_position = pos;\n gl_Position = u_modelViewProjection * vec4(pos, 1.0);\n v_screenPos = gl_Position;\n}"; export declare const BSP_SURFACE_FRAGMENT_SOURCE = "#version 300 es\nprecision highp float;\n\nstruct DLight {\n vec3 position;\n vec3 color;\n float intensity;\n};\n\nconst int MAX_DLIGHTS = 32;\n\nin vec2 v_texCoord;\nin vec2 v_lightmapCoord;\nin float v_lightmapStep;\nin vec3 v_position;\nin vec4 v_screenPos;\n\nuniform sampler2D u_diffuseMap;\nuniform sampler2D u_lightmapAtlas;\nuniform sampler2D u_refractionMap; // New: Refraction map\nuniform vec4 u_lightStyleFactors;\nuniform vec4 u_styleLayerMapping; // 0, 1, 2... or -1 if invalid\nuniform float u_alpha;\nuniform bool u_applyLightmap;\nuniform bool u_warp;\nuniform bool u_lightmapOnly;\nuniform bool u_hasRefraction; // New: Flag to enable refraction\nuniform float u_time;\n\nuniform int u_renderMode; // 0: Textured, 1: Solid, 2: Solid Faceted\nuniform vec4 u_solidColor;\n\nuniform int u_numDlights;\nuniform DLight u_dlights[MAX_DLIGHTS];\n\n// Lighting controls\nuniform float u_brightness;\nuniform float u_gamma;\nuniform bool u_fullbright;\nuniform float u_ambient;\n\nout vec4 o_color;\n\nvoid main() {\n vec4 finalColor;\n\n if (u_renderMode == 0) {\n // TEXTURED MODE\n vec4 base = vec4(1.0);\n if (!u_lightmapOnly) {\n base = texture(u_diffuseMap, v_texCoord);\n }\n\n // Refraction Logic\n if (u_warp && u_hasRefraction) {\n vec2 ndc = (v_screenPos.xy / v_screenPos.w) * 0.5 + 0.5;\n\n // Calculate distortion based on texture coordinates time\n // Simple turbulent distortion\n float distortionStrength = 0.05;\n vec2 distortion = vec2(\n sin(v_texCoord.y * 10.0 + u_time * 2.0),\n cos(v_texCoord.x * 10.0 + u_time * 2.0)\n ) * distortionStrength;\n\n vec3 refractColor = texture(u_refractionMap, ndc + distortion).rgb;\n\n // Blend base texture with refraction\n // Quake 2 water usually is quite opaque but let's try a blend\n // Or just tint the refraction\n\n // If it's water (warp), we usually want some transparency + refraction\n // Let's mix refraction into the base color\n base.rgb = mix(base.rgb, refractColor, 0.4);\n base.a = 0.7; // Ensure some alpha for water\n }\n\n vec3 totalLight = vec3(1.0);\n\n if (u_applyLightmap && !u_fullbright) {\n // Multi-style lightmap accumulation\n vec3 light = vec3(0.0);\n bool hasLight = false;\n\n // Loop unrolled-ish\n for (int i = 0; i < 4; i++) {\n float layer = u_styleLayerMapping[i];\n float factor = u_lightStyleFactors[i];\n\n if (layer >= -0.5) { // Valid layer (check >= 0 approx)\n // Offset V by layer * step\n // Since we packed vertically\n vec2 offset = vec2(0.0, layer * v_lightmapStep);\n light += texture(u_lightmapAtlas, v_lightmapCoord + offset).rgb * factor;\n hasLight = true;\n }\n }\n\n if (!hasLight) light = vec3(1.0);\n\n totalLight = light;\n\n // Add dynamic lights\n for (int i = 0; i < MAX_DLIGHTS; i++) {\n if (i >= u_numDlights) break;\n DLight dlight = u_dlights[i];\n\n float dist = distance(v_position, dlight.position);\n // Quake 2 dlight formula\n if (dist < dlight.intensity) {\n float contribution = (dlight.intensity - dist) * (1.0 / 255.0);\n totalLight += dlight.color * contribution;\n }\n }\n } else if (u_fullbright) {\n totalLight = vec3(1.0);\n }\n\n // Apply ambient minimum\n totalLight = max(totalLight, vec3(u_ambient));\n\n // Apply brightness\n totalLight *= u_brightness;\n\n base.rgb *= totalLight;\n\n // Gamma correction\n if (u_gamma != 1.0) {\n base.rgb = pow(base.rgb, vec3(1.0 / u_gamma));\n }\n\n finalColor = vec4(base.rgb, base.a * u_alpha);\n } else {\n // SOLID / WIREFRAME / FACETED\n vec3 color = u_solidColor.rgb;\n if (u_renderMode == 2) {\n // FACETED: simple lighting based on face normal\n vec3 fdx = dFdx(v_position);\n vec3 fdy = dFdy(v_position);\n vec3 faceNormal = normalize(cross(fdx, fdy));\n\n // Simple directional light from \"camera\" or fixed\n vec3 lightDir = normalize(vec3(0.5, 0.5, 1.0));\n float diff = max(dot(faceNormal, lightDir), 0.2); // Ambient 0.2\n color *= diff;\n }\n finalColor = vec4(color, u_solidColor.a * u_alpha);\n }\n\n o_color = finalColor;\n}"; export declare function resolveLightStyles(styleIndices?: readonly number[], styleValues?: ReadonlyArray): Float32Array; export declare function deriveSurfaceRenderState(surfaceFlags?: SurfaceFlag, timeSeconds?: number): SurfaceRenderState; declare module './bsp.js' { interface BspSurfaceGeometry { wireframeIndexBuffer?: IndexBuffer; wireframeIndexCount?: number; } } export declare class BspSurfacePipeline { readonly gl: WebGL2RenderingContext; readonly program: ShaderProgram; private readonly uniformMvp; private readonly uniformTexScroll; private readonly uniformLmScroll; private readonly uniformLightStyles; private readonly uniformStyleLayerMapping; private readonly uniformAlpha; private readonly uniformApplyLightmap; private readonly uniformWarp; private readonly uniformLightmapOnly; private readonly uniformDiffuse; private readonly uniformLightmap; private readonly uniformRefraction; private readonly uniformHasRefraction; private readonly uniformTime; private readonly uniformRenderMode; private readonly uniformSolidColor; private readonly uniformNumDlights; private readonly uniformDlights; private readonly uniformBrightness; private readonly uniformGamma; private readonly uniformFullbright; private readonly uniformAmbient; constructor(gl: WebGL2RenderingContext); get shaderSize(): number; bind(options: BspSurfaceBindOptions): SurfaceRenderState; draw(geometry: BspSurfaceGeometry, renderMode?: RenderModeConfig): void; dispose(): void; } export declare function applySurfaceState(gl: WebGL2RenderingContext, state: SurfaceRenderState): void; //# sourceMappingURL=bspPipeline.d.ts.map