// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
priv struct LightingShaderState {
shader : @raylib.Shader
ambient_light_loc : Int
view_position_loc : Int
view_forward_loc : Int
directional_count_loc : Int
directional_directions_loc : Int
directional_colors_loc : Int
point_count_loc : Int
point_positions_loc : Int
point_colors_loc : Int
point_ranges_loc : Int
spot_count_loc : Int
spot_positions_loc : Int
spot_directions_loc : Int
spot_colors_loc : Int
spot_params_loc : Int
material_base_color_loc : Int
material_emissive_color_loc : Int
material_normal_scale_loc : Int
material_occlusion_strength_loc : Int
material_metallic_loc : Int
material_roughness_loc : Int
material_alpha_mode_loc : Int
material_alpha_cutoff_loc : Int
material_has_base_texture_loc : Int
material_has_emissive_texture_loc : Int
material_has_metallic_roughness_texture_loc : Int
material_has_occlusion_texture_loc : Int
material_has_normal_texture_loc : Int
material_has_tangents_loc : Int
material_unlit_loc : Int
material_receive_shadows_loc : Int
shadow_enabled_locs : Array[Int]
shadow_cascade_count_locs : Array[Int]
shadow_matrix_locs : Array[Int]
shadow_rect_locs : Array[Int]
shadow_bound_locs : Array[Int]
shadow_depth_bias_locs : Array[Int]
shadow_normal_bias_locs : Array[Int]
shadow_sampler_locs : Array[Int]
directional_shadow_texel_size_loc : Int
spot_shadow_enabled_locs : Array[Int]
spot_shadow_matrix_locs : Array[Int]
spot_shadow_rect_locs : Array[Int]
spot_shadow_depth_bias_locs : Array[Int]
spot_shadow_normal_bias_locs : Array[Int]
spot_shadow_sampler_loc : Int
spot_shadow_texel_size_loc : Int
point_shadow_enabled_locs : Array[Int]
point_shadow_depth_bias_locs : Array[Int]
point_shadow_normal_bias_locs : Array[Int]
point_shadow_matrix_locs : Array[Int]
point_shadow_rect_locs : Array[Int]
point_shadow_sampler_loc : Int
point_shadow_texel_size_loc : Int
base_uv_offset_scale_loc : Int
base_uv_rotation_loc : Int
base_uv_set_loc : Int
metallic_roughness_uv_offset_scale_loc : Int
metallic_roughness_uv_rotation_loc : Int
metallic_roughness_uv_set_loc : Int
normal_uv_offset_scale_loc : Int
normal_uv_rotation_loc : Int
normal_uv_set_loc : Int
occlusion_uv_offset_scale_loc : Int
occlusion_uv_rotation_loc : Int
occlusion_uv_set_loc : Int
emissive_uv_offset_scale_loc : Int
emissive_uv_rotation_loc : Int
emissive_uv_set_loc : Int
}
///|
priv struct MaterialTextureBundle3D {
base_texture : @raylib.Texture
emissive_texture : @raylib.Texture
metallic_roughness_texture : @raylib.Texture
occlusion_texture : @raylib.Texture
normal_texture : @raylib.Texture
has_base_texture : Bool
has_emissive_texture : Bool
has_metallic_roughness_texture : Bool
has_occlusion_texture : Bool
has_normal_texture : Bool
base_transform : @render3d_types.TextureTransform3D
emissive_transform : @render3d_types.TextureTransform3D
metallic_roughness_transform : @render3d_types.TextureTransform3D
occlusion_transform : @render3d_types.TextureTransform3D
normal_transform : @render3d_types.TextureTransform3D
base_texcoord_set : Int
emissive_texcoord_set : Int
metallic_roughness_texcoord_set : Int
occlusion_texcoord_set : Int
normal_texcoord_set : Int
}
///|
priv struct MaterialTextureSource3D {
texture : @raylib.Texture
texcoord_set : Int
transform : @render3d_types.TextureTransform3D
}
///|
priv struct MaterialTextureSlotBinding {
shader_loc : Int
uniform_name : String
slot : Int
material_map : Int
}
///|
priv struct CosmicRasterImage {
width : Int
height : Int
pixels : Array[Int]
}
///|
priv struct CosmicTextTexture {
texture : @raylib.Texture
width : Int
height : Int
}
///|
priv struct OffscreenTarget2D {
render_texture : @raylib.RenderTexture
width : Int
height : Int
}
///|
priv struct RaylibBackend {
mut time_scale : Double
mut image_smooth : Bool
mut realtime_delta : Double
mut embedded_asset_lookup : (String) -> Bytes?
mut key_events : Set[@inputs.Code]?
mut mouse_state : @inputs.Mouse?
mut mouse_movement : @inputs.MouseMovement?
mut mouse_wheel : @inputs.MouseWheel?
mut gamepads_state : Set[@inputs.Gamepad]?
mut gamepad_buttons_state : Set[@inputs.GamepadButtonInput]?
mut gamepad_axes_state : Map[@inputs.GamepadAxisInput, Double]?
mut lock_state : Ref[Bool]?
mut lock_requested : Bool
mut cursor_locked : Bool
mut close_requested : Bool
mut frame_has_draw_commands : Bool
textures : Map[String, @raylib.Texture]
sounds : Map[String, @raylib.Sound]
audio_instances : Map[AudioInstance, AudioPlayback]
mut next_audio_instance : UInt
font_alias : Map[String, String]
fonts_by_path : Map[String, @raylib.Font]
cosmic_loaded_families : Set[String]
cosmic_textures : Map[String, CosmicTextTexture]
offscreen_targets_2d : Map[String, OffscreenTarget2D]
mut active_2d_offscreen_target : String?
mut embedded_asset_cache : Map[String, Bytes]
meshes3d : Map[@render3d_types.MeshHandle, @render3d_types.MeshAsset]
materials3d : Map[
@render3d_types.MaterialHandle,
@render3d_types.StandardMaterial3D,
]
images3d : Map[@render3d_types.ImageHandle, @render3d_types.ImageAsset]
primitive_meshes : Map[String, @raylib.Mesh]
triangle_mesh_cache : Map[String, @raylib.Mesh]
mut lighting_shader : LightingShaderState?
mut lit_material : @raylib.Material?
mut lit_textured_material : @raylib.Material?
mut shadow_shader : ShadowShaderState?
mut shadow_material : @raylib.Material?
shadow_render_textures : Array[@raylib.RenderTexture]
directional_shadow_tile_sizes : Array[Int]
directional_shadow_atlas_columns : Array[Int]
directional_shadow_atlas_rows : Array[Int]
mut spot_shadow_render_texture : @raylib.RenderTexture?
mut spot_shadow_map_size : Int
mut point_shadow_render_texture : @raylib.RenderTexture?
mut point_shadow_map_size : Int
texture_warning_keys : Set[String]
}
///|
pub type AudioInstance = Int
///|
priv enum AudioPlaybackKind {
Sound(@raylib.Sound)
Music(@raylib.Music)
}
///|
priv struct AudioPlayback {
kind : AudioPlaybackKind
mut loop_ : Bool
mut paused : Bool
mut started : Bool
mut finished : Bool
}
///|
let backend : RaylibBackend = {
time_scale: 1.0,
image_smooth: true,
realtime_delta: 0.016,
embedded_asset_lookup: default_embedded_asset_lookup,
key_events: None,
mouse_state: None,
mouse_movement: None,
mouse_wheel: None,
gamepads_state: None,
gamepad_buttons_state: None,
gamepad_axes_state: None,
lock_state: None,
lock_requested: false,
cursor_locked: false,
close_requested: false,
frame_has_draw_commands: false,
textures: Map::new(),
sounds: Map::new(),
audio_instances: Map::new(),
next_audio_instance: 1U,
font_alias: Map::new(),
fonts_by_path: Map::new(),
cosmic_loaded_families: Set::new(),
cosmic_textures: Map::new(),
offscreen_targets_2d: Map::new(),
active_2d_offscreen_target: None,
embedded_asset_cache: Map::new(),
meshes3d: Map::new(),
materials3d: Map::new(),
images3d: Map::new(),
primitive_meshes: Map::new(),
triangle_mesh_cache: Map::new(),
lighting_shader: None,
lit_material: None,
lit_textured_material: None,
shadow_shader: None,
shadow_material: None,
shadow_render_textures: [],
directional_shadow_tile_sizes: [],
directional_shadow_atlas_columns: [],
directional_shadow_atlas_rows: [],
spot_shadow_render_texture: None,
spot_shadow_map_size: 0,
point_shadow_render_texture: None,
point_shadow_map_size: 0,
texture_warning_keys: Set::new(),
}
///|
let clip_stack : Array[@smath.Rect] = []
///|
let cosmic_font_system : Ref[@cosmic.FontSystem] = Ref::new(
@cosmic.FontSystem::new(),
)
///|
let cosmic_swash_cache : Ref[@cosmic.SwashCache] = Ref::new(
@cosmic.SwashCache::new(),
)
///|
const MAX_DIRECTIONAL_LIGHTS : Int = 4
///|
const MAX_DIRECTIONAL_CASCADES : Int = 4
///|
const MAX_POINT_LIGHTS : Int = 8
///|
const MAX_SPOT_LIGHTS : Int = 4
///|
let raylib_lighting_vertex_shader : String = "#version 330\n" +
"in vec3 vertexPosition;\n" +
"in vec2 vertexTexCoord;\n" +
"in vec2 vertexTexCoord2;\n" +
"in vec3 vertexNormal;\n" +
"in vec4 vertexTangent;\n" +
"in vec4 vertexColor;\n" +
"uniform mat4 mvp;\n" +
"uniform mat4 matModel;\n" +
"uniform mat4 matNormal;\n" +
"out vec3 fragPosition;\n" +
"out vec2 fragTexCoord;\n" +
"out vec2 fragTexCoord2;\n" +
"out vec3 fragNormal;\n" +
"out vec4 fragTangent;\n" +
"out vec4 fragVertexColor;\n" +
"void main() {\n" +
" vec4 worldPosition = matModel * vec4(vertexPosition, 1.0);\n" +
" fragPosition = worldPosition.xyz;\n" +
" fragTexCoord = vertexTexCoord;\n" +
" fragTexCoord2 = vertexTexCoord2;\n" +
" fragNormal = normalize((matNormal * vec4(vertexNormal, 0.0)).xyz);\n" +
" vec3 tangent = normalize((matNormal * vec4(vertexTangent.xyz, 0.0)).xyz);\n" +
" fragTangent = vec4(tangent, vertexTangent.w);\n" +
" fragVertexColor = vertexColor;\n" +
" gl_Position = mvp * vec4(vertexPosition, 1.0);\n" +
"}\n"
///|
let raylib_lighting_fragment_shader : String = "#version 330\n" +
"in vec3 fragPosition;\n" +
"in vec2 fragTexCoord;\n" +
"in vec2 fragTexCoord2;\n" +
"in vec3 fragNormal;\n" +
"in vec4 fragTangent;\n" +
"in vec4 fragVertexColor;\n" +
"uniform sampler2D texture0;\n" +
"uniform sampler2D texture1;\n" +
"uniform sampler2D texture2;\n" +
"uniform sampler2D texture4;\n" +
"uniform sampler2D texture5;\n" +
"uniform vec3 ambientLight;\n" +
"uniform vec3 viewPosition;\n" +
"uniform vec3 viewForward;\n" +
"uniform int directionalCount;\n" +
"uniform vec3 directionalDirections[4];\n" +
"uniform vec3 directionalColors[4];\n" +
"uniform int pointCount;\n" +
"uniform vec3 pointPositions[8];\n" +
"uniform vec3 pointColors[8];\n" +
"uniform float pointRanges[8];\n" +
"uniform int spotCount;\n" +
"uniform vec3 spotPositions[4];\n" +
"uniform vec3 spotDirections[4];\n" +
"uniform vec3 spotColors[4];\n" +
"uniform vec3 spotParams[4];\n" +
"uniform vec4 materialBaseColor;\n" +
"uniform vec3 materialEmissiveColor;\n" +
"uniform float materialNormalScale;\n" +
"uniform float materialOcclusionStrength;\n" +
"uniform float materialMetallic;\n" +
"uniform float materialRoughness;\n" +
"uniform int materialAlphaMode;\n" +
"uniform float materialAlphaCutoff;\n" +
"uniform int materialHasBaseTexture;\n" +
"uniform int materialHasEmissiveTexture;\n" +
"uniform int materialHasMetallicRoughnessTexture;\n" +
"uniform int materialHasOcclusionTexture;\n" +
"uniform int materialHasNormalTexture;\n" +
"uniform int materialHasTangents;\n" +
"uniform int materialUnlit;\n" +
"uniform int materialReceiveShadows;\n" +
"uniform int shadowEnabled[4];\n" +
"uniform int shadowCascadeCounts[4];\n" +
"uniform sampler2D shadowMap0;\n" +
"uniform sampler2D shadowMap1;\n" +
"uniform sampler2D shadowMap2;\n" +
"uniform sampler2D shadowMap3;\n" +
"uniform mat4 shadowMatrices[16];\n" +
"uniform vec4 shadowRects[16];\n" +
"uniform vec2 shadowBounds[16];\n" +
"uniform float shadowDepthBiases[4];\n" +
"uniform float shadowNormalBiases[4];\n" +
"uniform vec2 directionalShadowTexelSize;\n" +
"uniform int spotShadowEnabled[4];\n" +
"uniform sampler2D spotShadowAtlas;\n" +
"uniform mat4 spotShadowMatrices[4];\n" +
"uniform vec4 spotShadowRects[4];\n" +
"uniform float spotShadowDepthBiases[4];\n" +
"uniform float spotShadowNormalBiases[4];\n" +
"uniform vec2 spotShadowTexelSize;\n" +
"uniform int pointShadowEnabled[8];\n" +
"uniform sampler2D pointShadowAtlas;\n" +
"uniform mat4 pointShadowMatrices[48];\n" +
"uniform vec4 pointShadowRects[48];\n" +
"uniform float pointShadowDepthBiases[8];\n" +
"uniform float pointShadowNormalBiases[8];\n" +
"uniform vec2 pointShadowTexelSize;\n" +
"uniform vec4 baseUvOffsetScale;\n" +
"uniform vec2 baseUvRotation;\n" +
"uniform int baseUvSet;\n" +
"uniform vec4 metallicRoughnessUvOffsetScale;\n" +
"uniform vec2 metallicRoughnessUvRotation;\n" +
"uniform int metallicRoughnessUvSet;\n" +
"uniform vec4 normalUvOffsetScale;\n" +
"uniform vec2 normalUvRotation;\n" +
"uniform int normalUvSet;\n" +
"uniform vec4 occlusionUvOffsetScale;\n" +
"uniform vec2 occlusionUvRotation;\n" +
"uniform int occlusionUvSet;\n" +
"uniform vec4 emissiveUvOffsetScale;\n" +
"uniform vec2 emissiveUvRotation;\n" +
"uniform int emissiveUvSet;\n" +
"out vec4 finalColor;\n" +
"float rangeAttenuation(float distanceToLight, float lightRange) {\n" +
" if (lightRange <= 0.000001) return 1.0;\n" +
" if (distanceToLight >= lightRange) return 0.0;\n" +
" float t = 1.0 - distanceToLight / lightRange;\n" +
" return t * t;\n" +
"}\n" +
"vec2 selectUv(int setIndex) {\n" +
" if (setIndex == 1) return fragTexCoord2;\n" +
" return fragTexCoord;\n" +
"}\n" +
"vec2 applyTextureTransform(vec2 uv, vec4 offsetScale, vec2 rotationSinCos) {\n" +
" vec2 scaled = uv * offsetScale.zw;\n" +
" return vec2(\n" +
" scaled.x * rotationSinCos.y - scaled.y * rotationSinCos.x + offsetScale.x,\n" +
" scaled.x * rotationSinCos.x + scaled.y * rotationSinCos.y + offsetScale.y\n" +
" );\n" +
"}\n" +
"float spotAttenuation(vec3 spotDirection, vec3 lightToFragment, vec3 params) {\n" +
" float inner = max(params.y, params.z);\n" +
" float outer = min(params.y, params.z);\n" +
" float cosTheta = dot(normalize(spotDirection), normalize(lightToFragment));\n" +
" float delta = inner - outer;\n" +
" if (cosTheta <= outer) return 0.0;\n" +
" if (cosTheta >= inner || abs(delta) <= 0.000001) return 1.0;\n" +
" return (cosTheta - outer) / delta;\n" +
"}\n" +
"mat3 cotangentFrame(vec3 normal, vec3 worldPosition, vec2 uv) {\n" +
" vec3 dp1 = dFdx(worldPosition);\n" +
" vec3 dp2 = dFdy(worldPosition);\n" +
" vec2 duv1 = dFdx(uv);\n" +
" vec2 duv2 = dFdy(uv);\n" +
" vec3 dp2perp = cross(dp2, normal);\n" +
" vec3 dp1perp = cross(normal, dp1);\n" +
" vec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x;\n" +
" vec3 bitangent = dp2perp * duv1.y + dp1perp * duv2.y;\n" +
" float scale = max(dot(tangent, tangent), dot(bitangent, bitangent));\n" +
" if (scale <= 0.000001) return mat3(1.0);\n" +
" float invScale = inversesqrt(scale);\n" +
" return mat3(tangent * invScale, bitangent * invScale, normal);\n" +
"}\n" +
"vec3 applyNormalMap(vec3 normal, vec3 worldPosition, vec2 uv) {\n" +
" vec3 mapNormal = texture(texture2, uv).xyz * 2.0 - 1.0;\n" +
" mapNormal.xy *= materialNormalScale;\n" +
" mapNormal = normalize(mapNormal);\n" +
" mat3 tbn = cotangentFrame(normal, worldPosition, uv);\n" +
" if (materialHasTangents != 0) {\n" +
" vec3 tangent = normalize(fragTangent.xyz);\n" +
" vec3 bitangent = normalize(cross(normal, tangent) * fragTangent.w);\n" +
" if (dot(tangent, tangent) > 0.000001 && dot(bitangent, bitangent) > 0.000001) {\n" +
" tbn = mat3(tangent, bitangent, normal);\n" +
" }\n" +
" }\n" +
" return normalize(tbn * mapNormal);\n" +
"}\n" +
"void addLight(\n" +
" vec3 lightDir,\n" +
" vec3 lightColor,\n" +
" float attenuation,\n" +
" vec3 normal,\n" +
" vec3 viewDir,\n" +
" float shininess,\n" +
" float specularStrength,\n" +
" inout vec3 diffuseLight,\n" +
" inout vec3 specularLight\n" +
") {\n" +
" if (attenuation <= 0.0) return;\n" +
" float lambert = max(dot(normal, lightDir), 0.0);\n" +
" if (lambert <= 0.0) return;\n" +
" diffuseLight += lightColor * (lambert * attenuation);\n" +
" vec3 halfway = normalize(lightDir + viewDir);\n" +
" float spec = pow(max(dot(normal, halfway), 0.0), shininess) * specularStrength;\n" +
" specularLight += lightColor * (spec * attenuation);\n" +
"}\n" +
"float sampleShadowMapDepth(int index, vec2 uv) {\n" +
" if (index == 0) return texture(shadowMap0, uv).r;\n" +
" if (index == 1) return texture(shadowMap1, uv).r;\n" +
" if (index == 2) return texture(shadowMap2, uv).r;\n" +
" if (index == 3) return texture(shadowMap3, uv).r;\n" +
" return 1.0;\n" +
"}\n" +
"float sampleShadowAtlasDepth(sampler2D atlasTexture, vec2 uv, vec4 atlasRect) {\n" +
" return texture(atlasTexture, atlasRect.xy + uv * atlasRect.zw).r;\n" +
"}\n" +
"float sampleDirectionalShadowCascade(int lightIndex, int cascadeIndex, vec3 normal, vec3 lightDir) {\n" +
" int matrixIndex = lightIndex * 4 + cascadeIndex;\n" +
" vec4 atlasRect = shadowRects[matrixIndex];\n" +
" if (atlasRect.z <= 0.0 || atlasRect.w <= 0.0) return 1.0;\n" +
" vec4 shadowClip = shadowMatrices[matrixIndex] * vec4(fragPosition, 1.0);\n" +
" if (abs(shadowClip.w) <= 0.00001) return 1.0;\n" +
" vec3 shadowNdc = shadowClip.xyz / shadowClip.w;\n" +
" vec2 shadowUv = shadowNdc.xy * 0.5 + 0.5;\n" +
" float shadowDepth = shadowNdc.z * 0.5 + 0.5;\n" +
" if (shadowDepth <= 0.0 || shadowDepth >= 1.0) return 1.0;\n" +
" if (shadowUv.x <= 0.0 || shadowUv.x >= 1.0 || shadowUv.y <= 0.0 || shadowUv.y >= 1.0) return 1.0;\n" +
" float texel = max(directionalShadowTexelSize.x, directionalShadowTexelSize.y);\n" +
" float ndotl = max(dot(normal, lightDir), 0.0);\n" +
" float bias = max(0.0, shadowDepthBiases[lightIndex]) + max(0.0, shadowNormalBiases[lightIndex]) * texel * (1.0 - ndotl);\n" +
" float filterRadius = 1.25;\n" +
" float lit = 0.0;\n" +
" float totalWeight = 0.0;\n" +
" for (int x = -1; x <= 1; x++) {\n" +
" for (int y = -1; y <= 1; y++) {\n" +
" float weight = 1.0 / (1.0 + abs(float(x)) + abs(float(y)));\n" +
" vec2 sampleUv = shadowUv + vec2(float(x), float(y)) * directionalShadowTexelSize * filterRadius;\n" +
" float closest = sampleShadowMapDepth(lightIndex, clamp(atlasRect.xy + sampleUv * atlasRect.zw, vec2(0.0), vec2(1.0)));\n" +
" lit += (shadowDepth - bias <= closest ? 1.0 : 0.0) * weight;\n" +
" totalWeight += weight;\n" +
" }\n" +
" }\n" +
" return totalWeight <= 0.00001 ? 1.0 : lit / totalWeight;\n" +
"}\n" +
"float sampleDirectionalShadow(int index, vec3 normal, vec3 lightDir) {\n" +
" if (materialReceiveShadows == 0 || index < 0 || index >= 4 || shadowEnabled[index] == 0) return 1.0;\n" +
" int cascadeCount = shadowCascadeCounts[index];\n" +
" if (cascadeCount <= 0) return 1.0;\n" +
" float viewDepth = dot(fragPosition - viewPosition, viewForward);\n" +
" int baseIndex = index * 4;\n" +
" float firstNear = shadowBounds[baseIndex].x;\n" +
" float lastFar = shadowBounds[baseIndex + cascadeCount - 1].y;\n" +
" if (viewDepth < firstNear || viewDepth > lastFar) return 1.0;\n" +
" int selected = cascadeCount - 1;\n" +
" for (int i = 0; i < 4; i++) {\n" +
" if (i >= cascadeCount) break;\n" +
" if (viewDepth <= shadowBounds[baseIndex + i].y) {\n" +
" selected = i;\n" +
" break;\n" +
" }\n" +
" }\n" +
" float shadow = sampleDirectionalShadowCascade(index, selected, normal, lightDir);\n" +
" if (selected > 0) {\n" +
" int previous = selected - 1;\n" +
" float overlapNear = shadowBounds[baseIndex + selected].x;\n" +
" float overlapFar = shadowBounds[baseIndex + previous].y;\n" +
" if (overlapNear < overlapFar && viewDepth >= overlapNear && viewDepth <= overlapFar) {\n" +
" float blend = clamp((viewDepth - overlapNear) / (overlapFar - overlapNear), 0.0, 1.0);\n" +
" shadow = mix(sampleDirectionalShadowCascade(index, previous, normal, lightDir), shadow, blend);\n" +
" }\n" +
" }\n" +
" return shadow;\n" +
"}\n" +
"float sampleSpotShadow(int index, vec3 normal, vec3 lightDir) {\n" +
" if (materialReceiveShadows == 0 || index < 0 || index >= 4 || spotShadowEnabled[index] == 0) return 1.0;\n" +
" vec4 shadowClip = spotShadowMatrices[index] * vec4(fragPosition, 1.0);\n" +
" if (abs(shadowClip.w) <= 0.00001) return 1.0;\n" +
" vec3 shadowNdc = shadowClip.xyz / shadowClip.w;\n" +
" vec2 shadowUv = shadowNdc.xy * 0.5 + 0.5;\n" +
" float shadowDepth = shadowNdc.z * 0.5 + 0.5;\n" +
" if (shadowDepth <= 0.0 || shadowDepth >= 1.0) return 1.0;\n" +
" if (shadowUv.x <= 0.0 || shadowUv.x >= 1.0 || shadowUv.y <= 0.0 || shadowUv.y >= 1.0) return 1.0;\n" +
" float texel = max(spotShadowTexelSize.x, spotShadowTexelSize.y);\n" +
" float ndotl = max(dot(normal, lightDir), 0.0);\n" +
" float bias = max(0.0, spotShadowDepthBiases[index]) + max(0.0, spotShadowNormalBiases[index]) * texel * (1.0 - ndotl);\n" +
" float filterRadius = 1.25;\n" +
" float lit = 0.0;\n" +
" float totalWeight = 0.0;\n" +
" for (int x = -1; x <= 1; x++) {\n" +
" for (int y = -1; y <= 1; y++) {\n" +
" float weight = 1.0 / (1.0 + abs(float(x)) + abs(float(y)));\n" +
" vec2 sampleUv = shadowUv + vec2(float(x), float(y)) * spotShadowTexelSize * filterRadius;\n" +
" float closest = sampleShadowAtlasDepth(spotShadowAtlas, clamp(sampleUv, vec2(0.0), vec2(1.0)), spotShadowRects[index]);\n" +
" lit += (shadowDepth - bias <= closest ? 1.0 : 0.0) * weight;\n" +
" totalWeight += weight;\n" +
" }\n" +
" }\n" +
" return totalWeight <= 0.00001 ? 1.0 : lit / totalWeight;\n" +
"}\n" +
"int selectPointShadowFace(vec3 dir) {\n" +
" vec3 absDir = abs(dir);\n" +
" if (absDir.x >= absDir.y && absDir.x >= absDir.z) return dir.x >= 0.0 ? 0 : 1;\n" +
" if (absDir.y >= absDir.x && absDir.y >= absDir.z) return dir.y >= 0.0 ? 2 : 3;\n" +
" return dir.z >= 0.0 ? 4 : 5;\n" +
"}\n" +
"float samplePointShadow(int index, vec3 normal, vec3 lightDir, vec3 lightToFragment) {\n" +
" if (materialReceiveShadows == 0 || index < 0 || index >= 8 || pointShadowEnabled[index] == 0) return 1.0;\n" +
" int face = selectPointShadowFace(lightToFragment);\n" +
" int matrixIndex = index * 6 + face;\n" +
" vec4 shadowClip = pointShadowMatrices[matrixIndex] * vec4(fragPosition, 1.0);\n" +
" if (abs(shadowClip.w) <= 0.00001) return 1.0;\n" +
" vec3 shadowNdc = shadowClip.xyz / shadowClip.w;\n" +
" vec2 shadowUv = shadowNdc.xy * 0.5 + 0.5;\n" +
" float shadowDepth = shadowNdc.z * 0.5 + 0.5;\n" +
" if (shadowDepth <= 0.0 || shadowDepth >= 1.0) return 1.0;\n" +
" if (shadowUv.x <= 0.0 || shadowUv.x >= 1.0 || shadowUv.y <= 0.0 || shadowUv.y >= 1.0) return 1.0;\n" +
" float texel = max(pointShadowTexelSize.x, pointShadowTexelSize.y);\n" +
" float ndotl = max(dot(normal, lightDir), 0.0);\n" +
" float bias = max(0.0, pointShadowDepthBiases[index]) + max(0.0, pointShadowNormalBiases[index]) * texel * (1.0 - ndotl);\n" +
" float filterRadius = 1.0;\n" +
" float lit = 0.0;\n" +
" float totalWeight = 0.0;\n" +
" for (int x = -1; x <= 1; x++) {\n" +
" for (int y = -1; y <= 1; y++) {\n" +
" float weight = 1.0 / (1.0 + abs(float(x)) + abs(float(y)));\n" +
" vec2 sampleUv = shadowUv + vec2(float(x), float(y)) * pointShadowTexelSize * filterRadius;\n" +
" float closest = sampleShadowAtlasDepth(pointShadowAtlas, clamp(sampleUv, vec2(0.0), vec2(1.0)), pointShadowRects[matrixIndex]);\n" +
" lit += (shadowDepth - bias <= closest ? 1.0 : 0.0) * weight;\n" +
" totalWeight += weight;\n" +
" }\n" +
" }\n" +
" return totalWeight <= 0.00001 ? 1.0 : lit / totalWeight;\n" +
"}\n" +
"void main() {\n" +
" vec2 baseUv = applyTextureTransform(selectUv(baseUvSet), baseUvOffsetScale, baseUvRotation);\n" +
" vec2 metallicRoughnessUv = applyTextureTransform(selectUv(metallicRoughnessUvSet), metallicRoughnessUvOffsetScale, metallicRoughnessUvRotation);\n" +
" vec2 normalUv = applyTextureTransform(selectUv(normalUvSet), normalUvOffsetScale, normalUvRotation);\n" +
" vec2 occlusionUv = applyTextureTransform(selectUv(occlusionUvSet), occlusionUvOffsetScale, occlusionUvRotation);\n" +
" vec2 emissiveUv = applyTextureTransform(selectUv(emissiveUvSet), emissiveUvOffsetScale, emissiveUvRotation);\n" +
" vec4 albedoSample = vec4(1.0);\n" +
" if (materialHasBaseTexture != 0) albedoSample = texture(texture0, baseUv);\n" +
" vec4 albedo = albedoSample * fragVertexColor * materialBaseColor;\n" +
" if (materialAlphaMode == 0) {\n" +
" albedo.a = 1.0;\n" +
" } else if (materialAlphaMode == 1) {\n" +
" if (albedo.a < materialAlphaCutoff) discard;\n" +
" albedo.a = 1.0;\n" +
" } else if (albedo.a <= 0.0001) discard;\n" +
" if (materialUnlit != 0) {\n" +
" finalColor = vec4(albedo.rgb, albedo.a);\n" +
" return;\n" +
" }\n" +
" vec4 metallicRoughnessSample = vec4(1.0);\n" +
" if (materialHasMetallicRoughnessTexture != 0) {\n" +
" metallicRoughnessSample = texture(texture1, metallicRoughnessUv);\n" +
" }\n" +
" vec3 normal = normalize(fragNormal);\n" +
" if (length(normal) <= 0.00001) normal = vec3(0.0, 1.0, 0.0);\n" +
" vec3 geometricNormal = normal;\n" +
" if (materialHasNormalTexture != 0) normal = applyNormalMap(normal, fragPosition, normalUv);\n" +
" vec3 viewDir = normalize(viewPosition - fragPosition);\n" +
" if (length(viewDir) <= 0.00001) viewDir = vec3(0.0, 0.0, 1.0);\n" +
" float roughness = clamp(materialRoughness * metallicRoughnessSample.g, 0.089, 1.0);\n" +
" float metallic = clamp(materialMetallic * metallicRoughnessSample.b, 0.0, 1.0);\n" +
" float shininess = mix(96.0, 8.0, roughness);\n" +
" float specularStrength = mix(0.04, 1.0, metallic);\n" +
" vec3 diffuseLight = ambientLight;\n" +
" vec3 specularLight = vec3(0.0);\n" +
" for (int i = 0; i < directionalCount; i++) {\n" +
" vec3 lightDir = normalize(-directionalDirections[i]);\n" +
" float attenuation = sampleDirectionalShadow(i, geometricNormal, lightDir);\n" +
" addLight(lightDir, directionalColors[i], attenuation, normal, viewDir, shininess, specularStrength, diffuseLight, specularLight);\n" +
" }\n" +
" for (int i = 0; i < pointCount; i++) {\n" +
" vec3 toLight = pointPositions[i] - fragPosition;\n" +
" float distanceToLight = length(toLight);\n" +
" if (distanceToLight <= 0.00001) continue;\n" +
" vec3 lightDir = toLight / distanceToLight;\n" +
" float attenuation = rangeAttenuation(distanceToLight, pointRanges[i]) * samplePointShadow(i, geometricNormal, lightDir, fragPosition - pointPositions[i]);\n" +
" addLight(lightDir, pointColors[i], attenuation, normal, viewDir, shininess, specularStrength, diffuseLight, specularLight);\n" +
" }\n" +
" for (int i = 0; i < spotCount; i++) {\n" +
" vec3 toLight = spotPositions[i] - fragPosition;\n" +
" float distanceToLight = length(toLight);\n" +
" if (distanceToLight <= 0.00001) continue;\n" +
" vec3 lightDir = toLight / distanceToLight;\n" +
" float attenuation = rangeAttenuation(distanceToLight, spotParams[i].x);\n" +
" float cone = spotAttenuation(spotDirections[i], -lightDir, spotParams[i]);\n" +
" addLight(lightDir, spotColors[i], attenuation * cone * sampleSpotShadow(i, geometricNormal, lightDir), normal, viewDir, shininess, specularStrength, diffuseLight, specularLight);\n" +
" }\n" +
" if (directionalCount == 0 && pointCount == 0 && spotCount == 0 && dot(ambientLight, ambientLight) <= 0.000001) {\n" +
" diffuseLight = vec3(1.0);\n" +
" }\n" +
" float occlusion = 1.0;\n" +
" if (materialHasOcclusionTexture != 0) {\n" +
" occlusion = mix(1.0, texture(texture4, occlusionUv).r, clamp(materialOcclusionStrength, 0.0, 1.0));\n" +
" }\n" +
" vec3 emissive = materialEmissiveColor;\n" +
" if (materialHasEmissiveTexture != 0) {\n" +
" emissive = texture(texture5, emissiveUv).rgb * materialEmissiveColor;\n" +
" }\n" +
" vec3 litColor = albedo.rgb * diffuseLight * occlusion + specularLight * occlusion + emissive;\n" +
" finalColor = vec4(litColor, albedo.a);\n" +
"}\n"
///|
fn default_font_codepoints() -> Array[Int] {
let codepoints : Array[Int] = []
// Basic printable ASCII.
for cp in 32..<127 {
codepoints.push(cp)
}
// Latin-1 supplement (includes multiplication sign U+00D7).
for cp in 160..<256 {
codepoints.push(cp)
}
codepoints
}
///|
fn default_embedded_asset_lookup(_path : String) -> Bytes? {
None
}