///|
fn upload_texture_transform_uniforms(
shader : @raylib.Shader,
offset_scale_loc : Int,
rotation_loc : Int,
transform : @render3d_types.TextureTransform3D,
) -> Unit {
set_shader_uniform(
shader,
offset_scale_loc,
uniform_texture_transform_offset_scale(transform),
)
set_shader_uniform(
shader,
rotation_loc,
uniform_texture_transform_rotation(transform),
)
}
///|
fn upload_lighting_uniforms(
frame : @render3d_types.RenderFrame3D,
camera_position : @smath.Vec3,
material : @render3d_types.StandardMaterial3D,
texture_bundle : MaterialTextureBundle3D?,
mesh_has_tangents : Bool,
receive_shadows : Bool,
shadow_state : FrameShadowState?,
) -> LightingShaderState {
let state = get_lighting_shader_state()
let shader = state.shader
let default_shadow_rect : ShadowAtlasRect = {
offset: Vec2(0.0, 0.0),
scale: Vec2(0.0, 0.0),
}
let ambient = frame.ambient_light
.map(fn(light) { light_rgb(light.color, light.intensity) })
.unwrap_or(@smath.Vec3::zero())
set_shader_uniform(shader, state.ambient_light_loc, uniform_vec3(ambient))
set_shader_uniform(
shader,
state.view_position_loc,
uniform_vec3(camera_position),
)
let camera_forward = frame.camera.map_or(@smath.Vec3(0.0, 0.0, -1.0), fn(
active_camera,
) {
normalize_or(
active_camera.target - active_camera.position,
Vec3(0.0, 0.0, -1.0),
)
})
set_shader_uniform(
shader,
state.view_forward_loc,
uniform_vec3(camera_forward),
)
let directional_count = @cmp.minimum(
frame.directional_lights.length(),
MAX_DIRECTIONAL_LIGHTS,
)
let directional_directions : Array[@smath.Vec3] = []
let directional_colors : Array[@smath.Vec3] = []
for index in 0.. frame_shadow.directional_shadows.get(index)
None => None
}
set_shader_uniform(
shader,
state.shadow_enabled_locs[index],
uniform_int(if shadow is Some(_) { 1 } else { 0 }),
)
set_shader_uniform(
shader,
state.shadow_cascade_count_locs[index],
uniform_int(shadow.map_or(0, fn(value) { value.cascades.length() })),
)
set_shader_uniform(
shader,
state.shadow_depth_bias_locs[index],
uniform_float(shadow.map_or(0.0, fn(value) { value.depth_bias })),
)
set_shader_uniform(
shader,
state.shadow_normal_bias_locs[index],
uniform_float(shadow.map_or(0.0, fn(value) { value.normal_bias })),
)
let base_index = index * MAX_DIRECTIONAL_CASCADES
for cascade_index in 0.. frame_shadow.spot_shadows.get(index)
None => None
}
set_shader_uniform(
shader,
state.spot_shadow_enabled_locs[index],
uniform_int(if shadow is Some(_) { 1 } else { 0 }),
)
@raylib.set_shader_value_matrix(
shader,
state.spot_shadow_matrix_locs[index],
shadow.map_or(@raylib.Matrix::identity(), fn(value) {
value.light_view_projection
}),
)
set_shader_uniform(
shader,
state.spot_shadow_rect_locs[index],
uniform_shadow_atlas_rect(
shadow.map_or(default_shadow_rect, fn(value) { value.atlas_rect }),
),
)
set_shader_uniform(
shader,
state.spot_shadow_depth_bias_locs[index],
uniform_float(shadow.map_or(0.0, fn(value) { value.depth_bias })),
)
set_shader_uniform(
shader,
state.spot_shadow_normal_bias_locs[index],
uniform_float(shadow.map_or(0.0, fn(value) { value.normal_bias })),
)
}
set_shader_uniform(
shader,
state.spot_shadow_texel_size_loc,
uniform_vec2(
shadow_state.map_or(Vec2(0.0, 0.0), fn(value) { value.spot_texel_size }),
),
)
for index in 0.. frame_shadow.point_shadows.get(index)
None => None
}
set_shader_uniform(
shader,
state.point_shadow_enabled_locs[index],
uniform_int(if shadow is Some(_) { 1 } else { 0 }),
)
set_shader_uniform(
shader,
state.point_shadow_depth_bias_locs[index],
uniform_float(shadow.map_or(0.0, fn(value) { value.depth_bias })),
)
set_shader_uniform(
shader,
state.point_shadow_normal_bias_locs[index],
uniform_float(shadow.map_or(0.0, fn(value) { value.normal_bias })),
)
for face_index in 0.. Int {
match mode {
Opaque => 0
Mask => 1
Blend => 2
}
}
///|
fn to_ray_camera3d(camera : @render3d_types.FrameCamera3D) -> @raylib.Camera3D {
@raylib.Camera3D::new(
to_ray_vector3_smath(camera.position),
to_ray_vector3_smath(camera.target),
to_ray_vector3_smath(camera.up),
to_float(
match camera.projection {
Perspective => camera.fov_y
Orthographic =>
camera.orthographic_size.map_or(10.0, fn(size) { size[Y] })
},
),
match camera.projection {
Perspective => @raylib.CameraPerspective
Orthographic => @raylib.CameraOrthographic
},
)
}
///|
fn configured_filter() -> Int {
if backend.image_smooth {
@raylib.TextureFilterBilinear
} else {
@raylib.TextureFilterPoint
}
}
///|
fn file_extension(path : String) -> String {
if path.to_lower().has_prefix("data:") {
return data_uri_extension(path)
}
let slash_idx = if path.rev_find("/") is Some(i) { i } else { -1 }
let backslash_idx = if path.rev_find("\\") is Some(i) { i } else { -1 }
let last_separator = if slash_idx > backslash_idx {
slash_idx
} else {
backslash_idx
}
if path.rev_find(".") is Some(dot_idx) && dot_idx > last_separator {
path.unsafe_substring(start=dot_idx, end=path.length())
} else {
".bin"
}
}
///|
fn data_uri_extension(uri : String) -> String {
guard uri.find(",") is Some(comma_idx) else { return ".bin" }
let meta = uri.unsafe_substring(start=0, end=comma_idx).to_lower()
if meta.contains("image/png") {
".png"
} else if meta.contains("image/jpeg") {
".jpg"
} else if meta.contains("image/webp") {
".webp"
} else if meta.contains("image/bmp") {
".bmp"
} else if meta.contains("image/tga") {
".tga"
} else {
".bin"
}
}
///|
fn decode_data_uri_base64(uri : String) -> Bytes? {
guard uri.find(",") is Some(comma_idx) else { return None }
let meta = uri.unsafe_substring(start=0, end=comma_idx).to_lower()
if !meta.has_suffix(";base64") {
return None
}
let payload = uri.unsafe_substring(start=comma_idx + 1, end=uri.length())
Some(@base64.decode_lossy(payload.view()))
}
///|
fn get_embedded_asset(path : String) -> Bytes? {
if backend.embedded_asset_cache.get(path) is Some(cached) {
return Some(cached)
}
if (backend.embedded_asset_lookup)(path) is Some(bytes) {
backend.embedded_asset_cache.set(path, bytes)
Some(bytes)
} else {
None
}
}
///|
fn warn_texture_issue(key : String, message : String) -> Unit {
if backend.texture_warning_keys.contains(key) {
return
}
backend.texture_warning_keys.add(key)
println("[selene-raylib][texture-warning] \{message}")
}
///|
fn get_or_create_primitive_mesh(
key : String,
create : () -> @raylib.Mesh,
) -> @raylib.Mesh {
if backend.primitive_meshes.get(key) is Some(mesh) {
return mesh
}
let mesh = create()
backend.primitive_meshes.set(key, mesh)
mesh
}
///|
fn mesh_handle_text(mesh_handle : @render3d_types.MeshHandle) -> String {
mesh_handle.hash().to_string()
}
///|
fn image_handle_text(image_handle : @render3d_types.ImageHandle) -> String {
image_handle.hash().to_string()
}
///|
fn triangle_mesh_cache_prefix(
mesh_handle : @render3d_types.MeshHandle,
) -> String {
"triangle_mesh:\{mesh_handle_text(mesh_handle)}:"
}
///|
fn triangle_mesh_cache_key(mesh_handle : @render3d_types.MeshHandle) -> String {
"\{triangle_mesh_cache_prefix(mesh_handle)}mesh"
}
///|
fn clear_triangle_mesh_cache_for_handle(
mesh_handle : @render3d_types.MeshHandle,
) -> Unit {
let prefix = triangle_mesh_cache_prefix(mesh_handle)
for pair in backend.triangle_mesh_cache.to_array() {
if pair.0.has_prefix(prefix) {
@raylib.unload_mesh(pair.1)
backend.triangle_mesh_cache.remove(pair.0)
}
}
}
///|
fn to_fixed_vec3_array(values : Array[@smath.Vec3]) -> FixedArray[Float] {
let out = FixedArray::make(values.length() * 3, to_float(0.0))
for index in 0.. FixedArray[Float] {
let out = FixedArray::make(values.length() * 2, to_float(0.0))
for index in 0.. FixedArray[Byte] {
let out = FixedArray::make(values.length() * 4, b'\x00')
for index in 0.. FixedArray[Float] {
let out = FixedArray::make(values.length() * 4, to_float(0.0))
for index in 0.. Array[Int] {
let indices : Array[Int] = []
for index in 0.. Array[Int] {
let triangle_indices : Array[Int] = []
let push_index = fn(index : Int) { triangle_indices.push(index) }
match topology {
TriangleList => {
let tri_count = indices.length() / 3
for tri in 0..
if indices.length() >= 3 {
for i in 0..<(indices.length() - 2) {
if i % 2 == 0 {
push_index(indices[i])
push_index(indices[i + 1])
push_index(indices[i + 2])
} else {
push_index(indices[i + 1])
push_index(indices[i])
push_index(indices[i + 2])
}
}
}
TriangleFan =>
if indices.length() >= 3 {
let head = indices[0]
for i in 1..<(indices.length() - 1) {
push_index(head)
push_index(indices[i])
push_index(indices[i + 1])
}
}
}
triangle_indices
}
///|
fn resolve_triangle_indices(
mesh_handle : @render3d_types.MeshHandle,
mesh : @render3d_types.TriangleMesh3D,
) -> Array[Int]? {
let base_indices = mesh.indices.unwrap_or(
default_mesh_indices(mesh.positions.length()),
)
if base_indices.length() == 0 {
return None
}
for index in base_indices {
if index < 0 || index >= mesh.positions.length() {
warn_texture_issue(
"triangles_invalid_index:\{mesh_handle_text(mesh_handle)}:\{index}",
"triangle mesh \{mesh_handle_text(mesh_handle)} contains out-of-range index \{index}; skipping draw",
)
return None
}
}
let triangle_indices = triangulate_triangle_indices(
base_indices,
mesh.topology,
)
if triangle_indices.length() < 3 || triangle_indices.length() % 3 != 0 {
return None
}
Some(triangle_indices)
}