// 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_relative_delta : Ref[@smath.Vec2?]?
  mut mouse_wheel : @inputs.MouseWheel?
  mut touch_state : Map[@inputs.TouchId, @inputs.TouchPoint]?
  mut touch_changes_state : Array[@inputs.TouchEvent]?
  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]
  cubemaps3d : Map[
    @render3d_types.CubemapHandle,
    @render3d_types.CubemapAsset3D,
  ]
  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]
  asset_io_warning_keys : Set[String]
  last_touches : Map[@inputs.TouchId, @inputs.TouchPoint]
}

///|
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_relative_delta: None,
  mouse_wheel: None,
  touch_state: None,
  touch_changes_state: 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([]),
  sounds: Map([]),
  audio_instances: Map([]),
  next_audio_instance: 1U,
  font_alias: Map([]),
  fonts_by_path: Map([]),
  cosmic_loaded_families: Set([]),
  cosmic_textures: Map([]),
  offscreen_targets_2d: Map([]),
  active_2d_offscreen_target: None,
  embedded_asset_cache: Map([]),
  meshes3d: Map([]),
  materials3d: Map([]),
  images3d: Map([]),
  cubemaps3d: Map([]),
  primitive_meshes: Map([]),
  triangle_mesh_cache: Map([]),
  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([]),
  asset_io_warning_keys: Set([]),
  last_touches: Map([]),
}

///|
let clip_stack : Array[@smath.Rect] = []

///|
let cosmic_font_system : Ref[@cosmic.FontSystem] = Ref(
  @cosmic.FontSystem::new(),
)

///|
let cosmic_swash_cache : Ref[@cosmic.SwashCache] = Ref(
  @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
  #|in vec3 vertexPosition;
  #|in vec2 vertexTexCoord;
  #|in vec2 vertexTexCoord2;
  #|in vec3 vertexNormal;
  #|in vec4 vertexTangent;
  #|in vec4 vertexColor;
  #|uniform mat4 mvp;
  #|uniform mat4 matModel;
  #|uniform mat4 matNormal;
  #|out vec3 fragPosition;
  #|out vec2 fragTexCoord;
  #|out vec2 fragTexCoord2;
  #|out vec3 fragNormal;
  #|out vec4 fragTangent;
  #|out vec4 fragVertexColor;
  #|void main() {
  #|  vec4 worldPosition = matModel * vec4(vertexPosition, 1.0);
  #|  fragPosition = worldPosition.xyz;
  #|  fragTexCoord = vertexTexCoord;
  #|  fragTexCoord2 = vertexTexCoord2;
  #|  fragNormal = normalize((matNormal * vec4(vertexNormal, 0.0)).xyz);
  #|  vec3 tangent = normalize((matNormal * vec4(vertexTangent.xyz, 0.0)).xyz);
  #|  fragTangent = vec4(tangent, vertexTangent.w);
  #|  fragVertexColor = vertexColor;
  #|  gl_Position = mvp * vec4(vertexPosition, 1.0);
  #|}
  #|

///|
let raylib_lighting_fragment_shader : String =
  #|#version 330
  #|in vec3 fragPosition;
  #|in vec2 fragTexCoord;
  #|in vec2 fragTexCoord2;
  #|in vec3 fragNormal;
  #|in vec4 fragTangent;
  #|in vec4 fragVertexColor;
  #|uniform sampler2D texture0;
  #|uniform sampler2D texture1;
  #|uniform sampler2D texture2;
  #|uniform sampler2D texture4;
  #|uniform sampler2D texture5;
  #|uniform vec3 ambientLight;
  #|uniform vec3 viewPosition;
  #|uniform vec3 viewForward;
  #|uniform int directionalCount;
  #|uniform vec3 directionalDirections[4];
  #|uniform vec3 directionalColors[4];
  #|uniform int pointCount;
  #|uniform vec3 pointPositions[8];
  #|uniform vec3 pointColors[8];
  #|uniform float pointRanges[8];
  #|uniform int spotCount;
  #|uniform vec3 spotPositions[4];
  #|uniform vec3 spotDirections[4];
  #|uniform vec3 spotColors[4];
  #|uniform vec3 spotParams[4];
  #|uniform vec4 materialBaseColor;
  #|uniform vec3 materialEmissiveColor;
  #|uniform float materialNormalScale;
  #|uniform float materialOcclusionStrength;
  #|uniform float materialMetallic;
  #|uniform float materialRoughness;
  #|uniform int materialAlphaMode;
  #|uniform float materialAlphaCutoff;
  #|uniform int materialHasBaseTexture;
  #|uniform int materialHasEmissiveTexture;
  #|uniform int materialHasMetallicRoughnessTexture;
  #|uniform int materialHasOcclusionTexture;
  #|uniform int materialHasNormalTexture;
  #|uniform int materialHasTangents;
  #|uniform int materialUnlit;
  #|uniform int materialReceiveShadows;
  #|uniform int shadowEnabled[4];
  #|uniform int shadowCascadeCounts[4];
  #|uniform sampler2D shadowMap0;
  #|uniform sampler2D shadowMap1;
  #|uniform sampler2D shadowMap2;
  #|uniform sampler2D shadowMap3;
  #|uniform mat4 shadowMatrices[16];
  #|uniform vec4 shadowRects[16];
  #|uniform vec2 shadowBounds[16];
  #|uniform float shadowDepthBiases[4];
  #|uniform float shadowNormalBiases[4];
  #|uniform vec2 directionalShadowTexelSize;
  #|uniform int spotShadowEnabled[4];
  #|uniform sampler2D spotShadowAtlas;
  #|uniform mat4 spotShadowMatrices[4];
  #|uniform vec4 spotShadowRects[4];
  #|uniform float spotShadowDepthBiases[4];
  #|uniform float spotShadowNormalBiases[4];
  #|uniform vec2 spotShadowTexelSize;
  #|uniform int pointShadowEnabled[8];
  #|uniform sampler2D pointShadowAtlas;
  #|uniform mat4 pointShadowMatrices[48];
  #|uniform vec4 pointShadowRects[48];
  #|uniform float pointShadowDepthBiases[8];
  #|uniform float pointShadowNormalBiases[8];
  #|uniform vec2 pointShadowTexelSize;
  #|uniform vec4 baseUvOffsetScale;
  #|uniform vec2 baseUvRotation;
  #|uniform int baseUvSet;
  #|uniform vec4 metallicRoughnessUvOffsetScale;
  #|uniform vec2 metallicRoughnessUvRotation;
  #|uniform int metallicRoughnessUvSet;
  #|uniform vec4 normalUvOffsetScale;
  #|uniform vec2 normalUvRotation;
  #|uniform int normalUvSet;
  #|uniform vec4 occlusionUvOffsetScale;
  #|uniform vec2 occlusionUvRotation;
  #|uniform int occlusionUvSet;
  #|uniform vec4 emissiveUvOffsetScale;
  #|uniform vec2 emissiveUvRotation;
  #|uniform int emissiveUvSet;
  #|out vec4 finalColor;
  #|float rangeAttenuation(float distanceToLight, float lightRange) {
  #|  if (lightRange <= 0.000001) return 1.0;
  #|  if (distanceToLight >= lightRange) return 0.0;
  #|  float t = 1.0 - distanceToLight / lightRange;
  #|  return t * t;
  #|}
  #|vec2 selectUv(int setIndex) {
  #|  if (setIndex == 1) return fragTexCoord2;
  #|  return fragTexCoord;
  #|}
  #|vec2 applyTextureTransform(vec2 uv, vec4 offsetScale, vec2 rotationSinCos) {
  #|  vec2 scaled = uv * offsetScale.zw;
  #|  return vec2(
  #|    scaled.x * rotationSinCos.y - scaled.y * rotationSinCos.x + offsetScale.x,
  #|    scaled.x * rotationSinCos.x + scaled.y * rotationSinCos.y + offsetScale.y
  #|  );
  #|}
  #|float spotAttenuation(vec3 spotDirection, vec3 lightToFragment, vec3 params) {
  #|  float inner = max(params.y, params.z);
  #|  float outer = min(params.y, params.z);
  #|  float cosTheta = dot(normalize(spotDirection), normalize(lightToFragment));
  #|  float delta = inner - outer;
  #|  if (cosTheta <= outer) return 0.0;
  #|  if (cosTheta >= inner || abs(delta) <= 0.000001) return 1.0;
  #|  return (cosTheta - outer) / delta;
  #|}
  #|mat3 cotangentFrame(vec3 normal, vec3 worldPosition, vec2 uv) {
  #|  vec3 dp1 = dFdx(worldPosition);
  #|  vec3 dp2 = dFdy(worldPosition);
  #|  vec2 duv1 = dFdx(uv);
  #|  vec2 duv2 = dFdy(uv);
  #|  vec3 dp2perp = cross(dp2, normal);
  #|  vec3 dp1perp = cross(normal, dp1);
  #|  vec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x;
  #|  vec3 bitangent = dp2perp * duv1.y + dp1perp * duv2.y;
  #|  float scale = max(dot(tangent, tangent), dot(bitangent, bitangent));
  #|  if (scale <= 0.000001) return mat3(1.0);
  #|  float invScale = inversesqrt(scale);
  #|  return mat3(tangent * invScale, bitangent * invScale, normal);
  #|}
  #|vec3 applyNormalMap(vec3 normal, vec3 worldPosition, vec2 uv) {
  #|  vec3 mapNormal = texture(texture2, uv).xyz * 2.0 - 1.0;
  #|  mapNormal.xy *= materialNormalScale;
  #|  mapNormal = normalize(mapNormal);
  #|  mat3 tbn = cotangentFrame(normal, worldPosition, uv);
  #|  if (materialHasTangents != 0) {
  #|    vec3 tangent = normalize(fragTangent.xyz);
  #|    vec3 bitangent = normalize(cross(normal, tangent) * fragTangent.w);
  #|    if (dot(tangent, tangent) > 0.000001 && dot(bitangent, bitangent) > 0.000001) {
  #|      tbn = mat3(tangent, bitangent, normal);
  #|    }
  #|  }
  #|  return normalize(tbn * mapNormal);
  #|}
  #|void addLight(
  #|  vec3 lightDir,
  #|  vec3 lightColor,
  #|  float attenuation,
  #|  vec3 normal,
  #|  vec3 viewDir,
  #|  float shininess,
  #|  float specularStrength,
  #|  inout vec3 diffuseLight,
  #|  inout vec3 specularLight
  #|) {
  #|  if (attenuation <= 0.0) return;
  #|  float lambert = max(dot(normal, lightDir), 0.0);
  #|  if (lambert <= 0.0) return;
  #|  diffuseLight += lightColor * (lambert * attenuation);
  #|  vec3 halfway = normalize(lightDir + viewDir);
  #|  float spec = pow(max(dot(normal, halfway), 0.0), shininess) * specularStrength;
  #|  specularLight += lightColor * (spec * attenuation);
  #|}
  #|float sampleShadowMapDepth(int index, vec2 uv) {
  #|  if (index == 0) return texture(shadowMap0, uv).r;
  #|  if (index == 1) return texture(shadowMap1, uv).r;
  #|  if (index == 2) return texture(shadowMap2, uv).r;
  #|  if (index == 3) return texture(shadowMap3, uv).r;
  #|  return 1.0;
  #|}
  #|float sampleShadowAtlasDepth(sampler2D atlasTexture, vec2 uv, vec4 atlasRect) {
  #|  return texture(atlasTexture, atlasRect.xy + uv * atlasRect.zw).r;
  #|}
  #|float sampleDirectionalShadowCascade(int lightIndex, int cascadeIndex, vec3 normal, vec3 lightDir) {
  #|  int matrixIndex = lightIndex * 4 + cascadeIndex;
  #|  vec4 atlasRect = shadowRects[matrixIndex];
  #|  if (atlasRect.z <= 0.0 || atlasRect.w <= 0.0) return 1.0;
  #|  vec4 shadowClip = shadowMatrices[matrixIndex] * vec4(fragPosition, 1.0);
  #|  if (abs(shadowClip.w) <= 0.00001) return 1.0;
  #|  vec3 shadowNdc = shadowClip.xyz / shadowClip.w;
  #|  vec2 shadowUv = shadowNdc.xy * 0.5 + 0.5;
  #|  float shadowDepth = shadowNdc.z * 0.5 + 0.5;
  #|  if (shadowDepth <= 0.0 || shadowDepth >= 1.0) return 1.0;
  #|  if (shadowUv.x <= 0.0 || shadowUv.x >= 1.0 || shadowUv.y <= 0.0 || shadowUv.y >= 1.0) return 1.0;
  #|  float texel = max(directionalShadowTexelSize.x, directionalShadowTexelSize.y);
  #|  float ndotl = max(dot(normal, lightDir), 0.0);
  #|  float bias = max(0.0, shadowDepthBiases[lightIndex]) + max(0.0, shadowNormalBiases[lightIndex]) * texel * (1.0 - ndotl);
  #|  float filterRadius = 1.25;
  #|  float lit = 0.0;
  #|  float totalWeight = 0.0;
  #|  for (int x = -1; x <= 1; x++) {
  #|    for (int y = -1; y <= 1; y++) {
  #|      float weight = 1.0 / (1.0 + abs(float(x)) + abs(float(y)));
  #|      vec2 sampleUv = shadowUv + vec2(float(x), float(y)) * directionalShadowTexelSize * filterRadius;
  #|      float closest = sampleShadowMapDepth(lightIndex, clamp(atlasRect.xy + sampleUv * atlasRect.zw, vec2(0.0), vec2(1.0)));
  #|      lit += (shadowDepth - bias <= closest ? 1.0 : 0.0) * weight;
  #|      totalWeight += weight;
  #|    }
  #|  }
  #|  return totalWeight <= 0.00001 ? 1.0 : lit / totalWeight;
  #|}
  #|float sampleDirectionalShadow(int index, vec3 normal, vec3 lightDir) {
  #|  if (materialReceiveShadows == 0 || index < 0 || index >= 4 || shadowEnabled[index] == 0) return 1.0;
  #|  int cascadeCount = shadowCascadeCounts[index];
  #|  if (cascadeCount <= 0) return 1.0;
  #|  float viewDepth = dot(fragPosition - viewPosition, viewForward);
  #|  int baseIndex = index * 4;
  #|  float firstNear = shadowBounds[baseIndex].x;
  #|  float lastFar = shadowBounds[baseIndex + cascadeCount - 1].y;
  #|  if (viewDepth < firstNear || viewDepth > lastFar) return 1.0;
  #|  int selected = cascadeCount - 1;
  #|  for (int i = 0; i < 4; i++) {
  #|    if (i >= cascadeCount) break;
  #|    if (viewDepth <= shadowBounds[baseIndex + i].y) {
  #|      selected = i;
  #|      break;
  #|    }
  #|  }
  #|  float shadow = sampleDirectionalShadowCascade(index, selected, normal, lightDir);
  #|  if (selected > 0) {
  #|    int previous = selected - 1;
  #|    float overlapNear = shadowBounds[baseIndex + selected].x;
  #|    float overlapFar = shadowBounds[baseIndex + previous].y;
  #|    if (overlapNear < overlapFar && viewDepth >= overlapNear && viewDepth <= overlapFar) {
  #|      float blend = clamp((viewDepth - overlapNear) / (overlapFar - overlapNear), 0.0, 1.0);
  #|      shadow = mix(sampleDirectionalShadowCascade(index, previous, normal, lightDir), shadow, blend);
  #|    }
  #|  }
  #|  return shadow;
  #|}
  #|float sampleSpotShadow(int index, vec3 normal, vec3 lightDir) {
  #|  if (materialReceiveShadows == 0 || index < 0 || index >= 4 || spotShadowEnabled[index] == 0) return 1.0;
  #|  vec4 shadowClip = spotShadowMatrices[index] * vec4(fragPosition, 1.0);
  #|  if (abs(shadowClip.w) <= 0.00001) return 1.0;
  #|  vec3 shadowNdc = shadowClip.xyz / shadowClip.w;
  #|  vec2 shadowUv = shadowNdc.xy * 0.5 + 0.5;
  #|  float shadowDepth = shadowNdc.z * 0.5 + 0.5;
  #|  if (shadowDepth <= 0.0 || shadowDepth >= 1.0) return 1.0;
  #|  if (shadowUv.x <= 0.0 || shadowUv.x >= 1.0 || shadowUv.y <= 0.0 || shadowUv.y >= 1.0) return 1.0;
  #|  float texel = max(spotShadowTexelSize.x, spotShadowTexelSize.y);
  #|  float ndotl = max(dot(normal, lightDir), 0.0);
  #|  float bias = max(0.0, spotShadowDepthBiases[index]) + max(0.0, spotShadowNormalBiases[index]) * texel * (1.0 - ndotl);
  #|  float filterRadius = 1.25;
  #|  float lit = 0.0;
  #|  float totalWeight = 0.0;
  #|  for (int x = -1; x <= 1; x++) {
  #|    for (int y = -1; y <= 1; y++) {
  #|      float weight = 1.0 / (1.0 + abs(float(x)) + abs(float(y)));
  #|      vec2 sampleUv = shadowUv + vec2(float(x), float(y)) * spotShadowTexelSize * filterRadius;
  #|      float closest = sampleShadowAtlasDepth(spotShadowAtlas, clamp(sampleUv, vec2(0.0), vec2(1.0)), spotShadowRects[index]);
  #|      lit += (shadowDepth - bias <= closest ? 1.0 : 0.0) * weight;
  #|      totalWeight += weight;
  #|    }
  #|  }
  #|  return totalWeight <= 0.00001 ? 1.0 : lit / totalWeight;
  #|}
  #|int selectPointShadowFace(vec3 dir) {
  #|  vec3 absDir = abs(dir);
  #|  if (absDir.x >= absDir.y && absDir.x >= absDir.z) return dir.x >= 0.0 ? 0 : 1;
  #|  if (absDir.y >= absDir.x && absDir.y >= absDir.z) return dir.y >= 0.0 ? 2 : 3;
  #|  return dir.z >= 0.0 ? 4 : 5;
  #|}
  #|float samplePointShadow(int index, vec3 normal, vec3 lightDir, vec3 lightToFragment) {
  #|  if (materialReceiveShadows == 0 || index < 0 || index >= 8 || pointShadowEnabled[index] == 0) return 1.0;
  #|  int face = selectPointShadowFace(lightToFragment);
  #|  int matrixIndex = index * 6 + face;
  #|  vec4 shadowClip = pointShadowMatrices[matrixIndex] * vec4(fragPosition, 1.0);
  #|  if (abs(shadowClip.w) <= 0.00001) return 1.0;
  #|  vec3 shadowNdc = shadowClip.xyz / shadowClip.w;
  #|  vec2 shadowUv = shadowNdc.xy * 0.5 + 0.5;
  #|  float shadowDepth = shadowNdc.z * 0.5 + 0.5;
  #|  if (shadowDepth <= 0.0 || shadowDepth >= 1.0) return 1.0;
  #|  if (shadowUv.x <= 0.0 || shadowUv.x >= 1.0 || shadowUv.y <= 0.0 || shadowUv.y >= 1.0) return 1.0;
  #|  float texel = max(pointShadowTexelSize.x, pointShadowTexelSize.y);
  #|  float ndotl = max(dot(normal, lightDir), 0.0);
  #|  float bias = max(0.0, pointShadowDepthBiases[index]) + max(0.0, pointShadowNormalBiases[index]) * texel * (1.0 - ndotl);
  #|  float filterRadius = 1.0;
  #|  float lit = 0.0;
  #|  float totalWeight = 0.0;
  #|  for (int x = -1; x <= 1; x++) {
  #|    for (int y = -1; y <= 1; y++) {
  #|      float weight = 1.0 / (1.0 + abs(float(x)) + abs(float(y)));
  #|      vec2 sampleUv = shadowUv + vec2(float(x), float(y)) * pointShadowTexelSize * filterRadius;
  #|      float closest = sampleShadowAtlasDepth(pointShadowAtlas, clamp(sampleUv, vec2(0.0), vec2(1.0)), pointShadowRects[matrixIndex]);
  #|      lit += (shadowDepth - bias <= closest ? 1.0 : 0.0) * weight;
  #|      totalWeight += weight;
  #|    }
  #|  }
  #|  return totalWeight <= 0.00001 ? 1.0 : lit / totalWeight;
  #|}
  #|void main() {
  #|  vec2 baseUv = applyTextureTransform(selectUv(baseUvSet), baseUvOffsetScale, baseUvRotation);
  #|  vec2 metallicRoughnessUv = applyTextureTransform(selectUv(metallicRoughnessUvSet), metallicRoughnessUvOffsetScale, metallicRoughnessUvRotation);
  #|  vec2 normalUv = applyTextureTransform(selectUv(normalUvSet), normalUvOffsetScale, normalUvRotation);
  #|  vec2 occlusionUv = applyTextureTransform(selectUv(occlusionUvSet), occlusionUvOffsetScale, occlusionUvRotation);
  #|  vec2 emissiveUv = applyTextureTransform(selectUv(emissiveUvSet), emissiveUvOffsetScale, emissiveUvRotation);
  #|  vec4 albedoSample = vec4(1.0);
  #|  if (materialHasBaseTexture != 0) albedoSample = texture(texture0, baseUv);
  #|  vec4 albedo = albedoSample * fragVertexColor * materialBaseColor;
  #|  if (materialAlphaMode == 0) {
  #|    albedo.a = 1.0;
  #|  } else if (materialAlphaMode == 1) {
  #|    if (albedo.a < materialAlphaCutoff) discard;
  #|    albedo.a = 1.0;
  #|  } else if (albedo.a <= 0.0001) discard;
  #|  if (materialUnlit != 0) {
  #|    finalColor = vec4(albedo.rgb, albedo.a);
  #|    return;
  #|  }
  #|  vec4 metallicRoughnessSample = vec4(1.0);
  #|  if (materialHasMetallicRoughnessTexture != 0) {
  #|    metallicRoughnessSample = texture(texture1, metallicRoughnessUv);
  #|  }
  #|  vec3 normal = normalize(fragNormal);
  #|  if (length(normal) <= 0.00001) normal = vec3(0.0, 1.0, 0.0);
  #|  vec3 geometricNormal = normal;
  #|  if (materialHasNormalTexture != 0) normal = applyNormalMap(normal, fragPosition, normalUv);
  #|  vec3 viewDir = normalize(viewPosition - fragPosition);
  #|  if (length(viewDir) <= 0.00001) viewDir = vec3(0.0, 0.0, 1.0);
  #|  float roughness = clamp(materialRoughness * metallicRoughnessSample.g, 0.089, 1.0);
  #|  float metallic = clamp(materialMetallic * metallicRoughnessSample.b, 0.0, 1.0);
  #|  float shininess = mix(96.0, 8.0, roughness);
  #|  float specularStrength = mix(0.04, 1.0, metallic);
  #|  vec3 diffuseLight = ambientLight;
  #|  vec3 specularLight = vec3(0.0);
  #|  for (int i = 0; i < directionalCount; i++) {
  #|    vec3 lightDir = normalize(-directionalDirections[i]);
  #|    float attenuation = sampleDirectionalShadow(i, geometricNormal, lightDir);
  #|    addLight(lightDir, directionalColors[i], attenuation, normal, viewDir, shininess, specularStrength, diffuseLight, specularLight);
  #|  }
  #|  for (int i = 0; i < pointCount; i++) {
  #|    vec3 toLight = pointPositions[i] - fragPosition;
  #|    float distanceToLight = length(toLight);
  #|    if (distanceToLight <= 0.00001) continue;
  #|    vec3 lightDir = toLight / distanceToLight;
  #|    float attenuation = rangeAttenuation(distanceToLight, pointRanges[i]) * samplePointShadow(i, geometricNormal, lightDir, fragPosition - pointPositions[i]);
  #|    addLight(lightDir, pointColors[i], attenuation, normal, viewDir, shininess, specularStrength, diffuseLight, specularLight);
  #|  }
  #|  for (int i = 0; i < spotCount; i++) {
  #|    vec3 toLight = spotPositions[i] - fragPosition;
  #|    float distanceToLight = length(toLight);
  #|    if (distanceToLight <= 0.00001) continue;
  #|    vec3 lightDir = toLight / distanceToLight;
  #|    float attenuation = rangeAttenuation(distanceToLight, spotParams[i].x);
  #|    float cone = spotAttenuation(spotDirections[i], -lightDir, spotParams[i]);
  #|    addLight(lightDir, spotColors[i], attenuation * cone * sampleSpotShadow(i, geometricNormal, lightDir), normal, viewDir, shininess, specularStrength, diffuseLight, specularLight);
  #|  }
  #|  if (directionalCount == 0 && pointCount == 0 && spotCount == 0 && dot(ambientLight, ambientLight) <= 0.000001) {
  #|    diffuseLight = vec3(1.0);
  #|  }
  #|  float occlusion = 1.0;
  #|  if (materialHasOcclusionTexture != 0) {
  #|    occlusion = mix(1.0, texture(texture4, occlusionUv).r, clamp(materialOcclusionStrength, 0.0, 1.0));
  #|  }
  #|  vec3 emissive = materialEmissiveColor;
  #|  if (materialHasEmissiveTexture != 0) {
  #|    emissive = texture(texture5, emissiveUv).rgb * materialEmissiveColor;
  #|  }
  #|  vec3 litColor = albedo.rgb * diffuseLight * occlusion + specularLight * occlusion + emissive;
  #|  finalColor = vec4(litColor, albedo.a);
  #|}
  #|

///|
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
}