// 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.
///|
fn repeat_mode_to_code(repeat : @smath.RepeatMode) -> Int {
match repeat {
RepeatX => 0
RepeatY => 1
NoRepeat => 2
Repeat => 3
}
}
///|
fn h_align_to_code(align : @smath.HAlign) -> Int {
match align {
Left => 0
Center => 1
Right => 2
}
}
///|
fn v_align_to_code(align : @smath.VAlign) -> Int {
match align {
Top => 0
Center => 1
Bottom => 2
}
}
///|
fn color_rgba(color : @render.Color) -> (Double, Double, Double, Double) {
(
color.r.reinterpret_as_int().to_double(),
color.g.reinterpret_as_int().to_double(),
color.b.reinterpret_as_int().to_double(),
color.a,
)
}
///|
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 synced_mesh_assets : Map[
@render3d_types.MeshHandle,
@render3d_types.MeshAsset,
] = Map::new()
///|
let synced_material_assets : Map[
@render3d_types.MaterialHandle,
@render3d_types.StandardMaterial3D,
] = Map::new()
///|
let synced_image_assets : Map[
@render3d_types.ImageHandle,
@render3d_types.ImageAsset,
] = Map::new()
///|
priv struct MaterialTextureSource3D {
path : String
texcoord_set : Int
sampler_code : Int
}
///|
priv struct MaterialTextureBundle3D {
base_source : MaterialTextureSource3D?
emissive_source : MaterialTextureSource3D?
metallic_roughness_source : MaterialTextureSource3D?
occlusion_source : MaterialTextureSource3D?
normal_source : MaterialTextureSource3D?
primary_texcoord_set : Int
}
///|
priv struct ResolvedMaterialTextureInput3D {
path : String
uvs : Array[@smath.Vec2D]
sampler_code : Int
}
///|
pub fn preload_img(path : String) -> Unit {
webgpu_preload_img(resolve_asset_path(path))
}
///|
pub fn begin_frame(clear : @render.Color) -> Unit {
webgpu_begin_frame(
clear.r.reinterpret_as_int().to_double() / 255.0,
clear.g.reinterpret_as_int().to_double() / 255.0,
clear.b.reinterpret_as_int().to_double() / 255.0,
clear.a,
)
}
///|
pub fn end_frame() -> Unit {
webgpu_end_frame()
}
///|
pub fn push_clip_rect(_rect : @smath.Rect) -> Unit {
()
}
///|
pub fn pop_clip_rect() -> Unit {
()
}
///|
pub fn reset_clip_stack() -> Unit {
()
}
///|
pub fn draw_image(
command : @render2d_types.ImageDrawCommand2D,
image_path : String,
) -> Unit {
let transform = command.transform
let mut source_x = 0.0
let mut source_y = 0.0
let mut source_width = 0.0
let mut source_height = 0.0
let mut has_source = false
match command.source {
Some(source) => {
source_x = source.position[X]
source_y = source.position[Y]
source_width = source.size[X]
source_height = source.size[Y]
has_source = true
}
None => ()
}
let (tr, tg, tb, ta) = color_rgba(command.color)
webgpu_draw_image(
resolve_asset_path(image_path),
command.destination.position[X],
command.destination.position[Y],
command.destination.size[X],
command.destination.size[Y],
has_source,
source_x,
source_y,
source_width,
source_height,
transform.a,
transform.b,
transform.c,
transform.d,
transform.tx,
transform.ty,
repeat_mode_to_code(command.repeat),
tr,
tg,
tb,
ta,
)
}
///|
pub fn draw_text(command : @render2d_types.TextDrawCommand2D) -> Unit {
if draw_text_with_cosmic(command) {
return
}
let (r, g, b, a) = color_rgba(command.style.color)
let transform = command.transform
webgpu_draw_text(
command.text,
command.position[X],
command.position[Y],
transform.a,
transform.b,
transform.c,
transform.d,
transform.tx,
transform.ty,
command.style.family,
command.style.size,
h_align_to_code(command.style.align),
v_align_to_code(command.style.baseline),
r,
g,
b,
a,
)
}
///|
pub fn measure_text(
style : @render2d_types.TextStyle2D,
text : String,
) -> @smath.Vec2D {
let measured = webgpu_measure_text(text, style.family, style.size)
if measured.length() >= 2 {
@smath.Vec2D(measured[0], measured[1])
} else {
let width = text.length().to_double() * style.size * 0.6
let height = if style.size <= 0.0 { 1.0 } else { style.size }
@smath.Vec2D(width, height)
}
}
///|
pub fn draw_rect(command : @render2d_types.RectDrawCommand2D) -> Unit {
let (fr, fg, fb, fa) = color_rgba(command.fill_color)
let mut has_stroke = false
let mut sr = 0.0
let mut sg = 0.0
let mut sb = 0.0
let mut sa = 1.0
match command.stroke_color {
Some(stroke_color) => {
has_stroke = true
let (tr, tg, tb, ta) = color_rgba(stroke_color)
sr = tr
sg = tg
sb = tb
sa = ta
}
None => ()
}
webgpu_draw_rect(
command.rect.position[X],
command.rect.position[Y],
command.rect.size[X],
command.rect.size[Y],
command.transform.a,
command.transform.b,
command.transform.c,
command.transform.d,
command.transform.tx,
command.transform.ty,
fr,
fg,
fb,
fa,
has_stroke,
sr,
sg,
sb,
sa,
)
}
///|
pub fn draw_circle(command : @render2d_types.CircleDrawCommand2D) -> Unit {
let (fr, fg, fb, fa) = color_rgba(command.fill_color)
let mut has_stroke = false
let mut sr = 0.0
let mut sg = 0.0
let mut sb = 0.0
let mut sa = 1.0
match command.stroke_color {
Some(stroke_color) => {
has_stroke = true
let (tr, tg, tb, ta) = color_rgba(stroke_color)
sr = tr
sg = tg
sb = tb
sa = ta
}
None => ()
}
webgpu_draw_circle(
command.center[X],
command.center[Y],
command.radius,
command.transform.a,
command.transform.b,
command.transform.c,
command.transform.d,
command.transform.tx,
command.transform.ty,
fr,
fg,
fb,
fa,
has_stroke,
sr,
sg,
sb,
sa,
)
}
///|
pub fn draw_gradient_rect(
command : @render2d_types.GradientRectDrawCommand2D,
) -> Unit {
let (sr, sg, sb, sa) = color_rgba(command.color_start)
let (er, eg, eb, ea) = color_rgba(command.color_end)
webgpu_draw_gradient_rect(
command.rect.position[X],
command.rect.position[Y],
command.rect.size[X],
command.rect.size[Y],
command.transform.a,
command.transform.b,
command.transform.c,
command.transform.d,
command.transform.tx,
command.transform.ty,
sr,
sg,
sb,
sa,
er,
eg,
eb,
ea,
)
}
///|
fn begin_3d(camera : @render3d_types.FrameCamera3D) -> Unit {
webgpu_begin_3d(
camera.position.x,
camera.position.y,
camera.position.z,
camera.target.x,
camera.target.y,
camera.target.z,
camera.up.x,
camera.up.y,
camera.up.z,
camera.fov_y,
camera.near,
camera.far,
if camera.projection == Orthographic {
1.0
} else {
0.0
},
camera.orthographic_size.map_or(0.0, fn(size) { size[X] }),
camera.orthographic_size.map_or(0.0, fn(size) { size[Y] }),
)
}
///|
fn end_3d() -> Unit {
webgpu_end_3d()
}
///|
pub fn sync_mesh_asset(
handle : @render3d_types.MeshHandle,
mesh : @render3d_types.MeshAsset,
) -> Unit {
synced_mesh_assets.set(handle, mesh)
}
///|
pub fn sync_material_asset(
handle : @render3d_types.MaterialHandle,
material : @render3d_types.StandardMaterial3D,
) -> Unit {
synced_material_assets.set(handle, material)
}
///|
pub fn sync_image_asset(
handle : @render3d_types.ImageHandle,
image : @render3d_types.ImageAsset,
) -> Unit {
synced_image_assets.set(handle, image)
}
///|
pub fn release_mesh_asset(handle : @render3d_types.MeshHandle) -> Unit {
synced_mesh_assets.remove(handle)
}
///|
pub fn release_material_asset(handle : @render3d_types.MaterialHandle) -> Unit {
synced_material_assets.remove(handle)
}
///|
pub fn release_image_asset(handle : @render3d_types.ImageHandle) -> Unit {
synced_image_assets.remove(handle)
}
///|
pub fn render_3d_frame(frame : @render3d_types.RenderFrame3D) -> Unit {
guard frame.camera is Some(active_camera) else { return }
configure_scene_lighting(frame)
begin_3d(active_camera)
for item in frame.items {
guard synced_mesh_assets.get(item.mesh) is Some(mesh_asset) else {
continue
}
let material = synced_material_assets
.get(item.material)
.unwrap_or(@render3d_types.default_standard_material3d())
let center = item.transform.translation
let rotation = item.transform.rotation
let color = effective_material_color(material)
let (emissive_r, emissive_g, emissive_b) = color_rgb01(
material.emissive_color,
)
let alpha_mode = alpha_mode_code(material.alpha_mode)
let alpha_cutoff = material.alpha_cutoff
let unlit = material.unlit
match mesh_asset.primitive {
Cube(size) =>
webgpu_draw_cube_3d(
center.x,
center.y,
center.z,
size.x * item.transform.scale.x,
size.y * item.transform.scale.y,
size.z * item.transform.scale.z,
rotation.x,
rotation.y,
rotation.z,
rotation.w,
color.r.reinterpret_as_int().to_double(),
color.g.reinterpret_as_int().to_double(),
color.b.reinterpret_as_int().to_double(),
color.a,
emissive_r,
emissive_g,
emissive_b,
alpha_mode,
alpha_cutoff,
unlit,
item.cast_shadows,
item.receive_shadows,
)
Sphere(radius) =>
webgpu_draw_sphere_3d(
center.x,
center.y,
center.z,
scaled_radius(radius, item.transform.scale),
rotation.x,
rotation.y,
rotation.z,
rotation.w,
color.r.reinterpret_as_int().to_double(),
color.g.reinterpret_as_int().to_double(),
color.b.reinterpret_as_int().to_double(),
color.a,
emissive_r,
emissive_g,
emissive_b,
alpha_mode,
alpha_cutoff,
unlit,
item.cast_shadows,
item.receive_shadows,
)
Cylinder(radius_top, radius_bottom, height, slices) =>
webgpu_draw_cylinder_3d(
center.x,
center.y,
center.z,
scaled_radius(radius_top, item.transform.scale),
scaled_radius(radius_bottom, item.transform.scale),
height * item.transform.scale.y.abs(),
slices,
rotation.x,
rotation.y,
rotation.z,
rotation.w,
color.r.reinterpret_as_int().to_double(),
color.g.reinterpret_as_int().to_double(),
color.b.reinterpret_as_int().to_double(),
color.a,
emissive_r,
emissive_g,
emissive_b,
alpha_mode,
alpha_cutoff,
unlit,
item.cast_shadows,
item.receive_shadows,
)
Plane(size) => {
let scaled = scaled_vec3(size, item.transform.scale)
let y = if scaled.y.abs() < 0.0001 { 0.02 } else { scaled.y }
webgpu_draw_cube_3d(
center.x,
center.y,
center.z,
scaled.x,
y,
scaled.z,
rotation.x,
rotation.y,
rotation.z,
rotation.w,
color.r.reinterpret_as_int().to_double(),
color.g.reinterpret_as_int().to_double(),
color.b.reinterpret_as_int().to_double(),
color.a,
emissive_r,
emissive_g,
emissive_b,
alpha_mode,
alpha_cutoff,
unlit,
item.cast_shadows,
item.receive_shadows,
)
}
Triangles(mesh) =>
if resolve_triangle_indices(mesh) is Some(triangle_indices) &&
triangle_indices.length() >= 3 {
let normals = resolve_mesh_normals(mesh)
let tangents = resolve_mesh_tangents(mesh)
match resolve_material_texture_bundle(material) {
Some(bundle) =>
match mesh.uv_sets.get(bundle.primary_texcoord_set) {
Some(primary_uvs) =>
if primary_uvs.length() == mesh.positions.length() &&
normals.length() == mesh.positions.length() {
let base_input = resolve_material_texture_input(
mesh,
bundle.base_source,
primary_uvs,
transform_binding=material.base_color_texture,
)
let emissive_input = resolve_material_texture_input(
mesh,
bundle.emissive_source,
primary_uvs,
transform_binding=material.emissive_texture,
)
let metallic_roughness_input = resolve_material_texture_input(
mesh,
bundle.metallic_roughness_source,
primary_uvs,
transform_binding=material.metallic_roughness_texture,
)
let occlusion_input = resolve_material_texture_input(
mesh,
bundle.occlusion_source,
primary_uvs,
transform_binding=material.occlusion_texture,
)
let normal_input = resolve_material_texture_input(
mesh,
bundle.normal_source,
primary_uvs,
transform_binding=material.normal_texture,
)
webgpu_draw_textured_triangles_3d(
base_input.path,
emissive_input.path,
metallic_roughness_input.path,
occlusion_input.path,
if tangents.length() == mesh.positions.length() {
normal_input.path
} else {
""
},
base_input.sampler_code,
emissive_input.sampler_code,
metallic_roughness_input.sampler_code,
occlusion_input.sampler_code,
normal_input.sampler_code,
flatten_textured_vertices3d(
mesh.positions,
triangle_indices,
base_input.uvs,
emissive_input.uvs,
metallic_roughness_input.uvs,
occlusion_input.uvs,
normal_input.uvs,
normals,
tangents,
mesh.colors,
item.transform.scale,
material,
color,
item.receive_shadows,
),
center.x,
center.y,
center.z,
rotation.x,
rotation.y,
rotation.z,
rotation.w,
material.double_sided,
item.cast_shadows,
item.receive_shadows,
)
} else {
webgpu_draw_colored_triangles_3d(
flatten_colored_vertices3d(
mesh.positions,
triangle_indices,
normals,
mesh.colors,
item.transform.scale,
material,
color,
item.receive_shadows,
),
center.x,
center.y,
center.z,
rotation.x,
rotation.y,
rotation.z,
rotation.w,
material.double_sided,
item.cast_shadows,
item.receive_shadows,
)
}
None =>
webgpu_draw_colored_triangles_3d(
flatten_colored_vertices3d(
mesh.positions,
triangle_indices,
normals,
mesh.colors,
item.transform.scale,
material,
color,
item.receive_shadows,
),
center.x,
center.y,
center.z,
rotation.x,
rotation.y,
rotation.z,
rotation.w,
material.double_sided,
item.cast_shadows,
item.receive_shadows,
)
}
None =>
webgpu_draw_colored_triangles_3d(
flatten_colored_vertices3d(
mesh.positions,
triangle_indices,
normals,
mesh.colors,
item.transform.scale,
material,
color,
item.receive_shadows,
),
center.x,
center.y,
center.z,
rotation.x,
rotation.y,
rotation.z,
rotation.w,
material.double_sided,
item.cast_shadows,
item.receive_shadows,
)
}
}
}
}
end_3d()
}
///|
fn scaled_vec3(size : @smath.Vec3, scale : @smath.Vec3) -> @smath.Vec3 {
{
x: size.x * scale.x.abs(),
y: size.y * scale.y.abs(),
z: size.z * scale.z.abs(),
}
}
///|
fn scaled_radius(radius : Double, scale : @smath.Vec3) -> Double {
let sx = scale.x.abs()
let sz = scale.z.abs()
let max_xz = if sx > sz { sx } else { sz }
radius * max_xz
}
///|
fn configure_scene_lighting(frame : @render3d_types.RenderFrame3D) -> Unit {
let has_lighting = frame.ambient_light is Some(_) ||
frame.directional_lights.length() > 0 ||
frame.point_lights.length() > 0 ||
frame.spot_lights.length() > 0
let ambient = if has_lighting {
frame.ambient_light.map_or(@smath.Vec3::zero(), fn(light) {
let (r, g, b) = color_rgb01(light.color)
@smath.Vec3::new(r, g, b).scalar_mul(light.intensity)
})
} else {
@smath.Vec3::one()
}
let directional_data = pack_directional_light_data(frame)
let point_data = pack_point_light_data(frame)
let spot_data = pack_spot_light_data(frame)
webgpu_set_lighting_3d(
directional_data,
point_data,
spot_data,
ambient.x,
ambient.y,
ambient.z,
frame.directional_shadow_map_size.to_double(),
frame.point_shadow_map_size.to_double(),
)
}
///|
fn pack_directional_light_data(
frame : @render3d_types.RenderFrame3D,
) -> Array[Double] {
let directional_data : Array[Double] = []
let directional_count = if frame.directional_lights.length() <
MAX_DIRECTIONAL_LIGHTS {
frame.directional_lights.length()
} else {
MAX_DIRECTIONAL_LIGHTS
}
for idx in 0.. MAX_DIRECTIONAL_CASCADES {
MAX_DIRECTIONAL_CASCADES
} else {
authored_bounds.length()
}
directional_data.push(direction.x)
directional_data.push(direction.y)
directional_data.push(direction.z)
directional_data.push(light.intensity)
directional_data.push(r)
directional_data.push(g)
directional_data.push(b)
directional_data.push(if light.shadows { 1.0 } else { 0.0 })
directional_data.push(light.shadow_depth_bias)
directional_data.push(light.shadow_normal_bias)
directional_data.push(cascade_count.to_double())
directional_data.push(light.cascade_shadow_config.minimum_distance)
directional_data.push(light.cascade_shadow_config.overlap_proportion)
let fallback_bound = match authored_bounds.last() {
Some(bound) => bound
None => 0.0
}
for bound_index in 0.. Array[Double] {
let point_data : Array[Double] = []
let point_count = if frame.point_lights.length() < MAX_POINT_LIGHTS {
frame.point_lights.length()
} else {
MAX_POINT_LIGHTS
}
for idx in 0.. Array[Double] {
let spot_data : Array[Double] = []
let spot_count = if frame.spot_lights.length() < MAX_SPOT_LIGHTS {
frame.spot_lights.length()
} else {
MAX_SPOT_LIGHTS
}
for idx in 0.. Array[@smath.Vec3] {
let triangle_indices = resolve_triangle_indices(mesh).unwrap_or([])
mesh.normals
.filter(fn(candidates) { candidates.length() == mesh.positions.length() })
.unwrap_or(compute_vertex_normals(mesh.positions, triangle_indices))
}
///|
fn resolve_triangle_indices(
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() {
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)
}
///|
fn default_mesh_indices(vertex_count : Int) -> 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 compute_vertex_normals(
positions : Array[@smath.Vec3],
triangle_indices : Array[Int],
) -> Array[@smath.Vec3] {
let normals : Array[@smath.Vec3] = []
for _ in 0..= positions.length() ||
ib < 0 ||
ib >= positions.length() ||
ic < 0 ||
ic >= positions.length() {
continue
}
let a = positions[ia]
let b = positions[ib]
let c = positions[ic]
let mut normal = (b - a).cross(c - a)
if normal.length_squared() <= 0.0000001 {
normal = @smath.Vec3::new(0.0, 1.0, 0.0)
}
normals[ia] = normals[ia] + normal
normals[ib] = normals[ib] + normal
normals[ic] = normals[ic] + normal
}
for index in 0.. Array[@render3d_types.Tangent3D] {
match mesh.tangents {
Some(candidates) if candidates.length() == mesh.positions.length() =>
candidates
_ => {
let tangents : Array[@render3d_types.Tangent3D] = []
for _ in 0.. Array[Double] {
let values : Array[Double] = []
let (emissive_r, emissive_g, emissive_b) = color_rgb01(
material.emissive_color,
)
let alpha_mode = alpha_mode_code(material.alpha_mode)
let alpha_cutoff = material.alpha_cutoff
let unlit = material_unlit_code(material)
for index in triangle_indices {
let position = positions[index]
let normal = normals[index]
let color = resolve_vertex_color(vertex_colors, index, base_color)
let (r, g, b) = color_rgb01(color)
let a = color.a
values.push(position.x * scale.x)
values.push(position.y * scale.y)
values.push(position.z * scale.z)
values.push(normal.x)
values.push(normal.y)
values.push(normal.z)
values.push(r)
values.push(g)
values.push(b)
values.push(a)
values.push(emissive_r)
values.push(emissive_g)
values.push(emissive_b)
values.push(alpha_mode)
values.push(alpha_cutoff)
values.push(unlit)
values.push(if receive_shadows { 1.0 } else { 0.0 })
}
values
}
///|
fn flatten_textured_vertices3d(
positions : Array[@smath.Vec3],
triangle_indices : Array[Int],
base_uvs : Array[@smath.Vec2D],
emissive_uvs : Array[@smath.Vec2D],
metallic_roughness_uvs : Array[@smath.Vec2D],
occlusion_uvs : Array[@smath.Vec2D],
normal_uvs : Array[@smath.Vec2D],
normals : Array[@smath.Vec3],
tangents : Array[@render3d_types.Tangent3D],
vertex_colors : Array[@render.Color]?,
scale : @smath.Vec3,
material : @render3d_types.StandardMaterial3D,
base_color : @render.Color,
receive_shadows : Bool,
) -> Array[Double] {
let values : Array[Double] = []
let (emissive_r, emissive_g, emissive_b) = color_rgb01(
material.emissive_color,
)
let alpha_mode = alpha_mode_code(material.alpha_mode)
let alpha_cutoff = material.alpha_cutoff
let metallic = material.metallic
let roughness = material.roughness
let occlusion_strength = material.occlusion_strength
let normal_scale = material.normal_scale
let unlit = material_unlit_code(material)
for index in triangle_indices {
let position = positions[index]
let base_uv = base_uvs[index]
let emissive_uv = emissive_uvs[index]
let metallic_roughness_uv = metallic_roughness_uvs[index]
let occlusion_uv = occlusion_uvs[index]
let normal_uv = normal_uvs[index]
let normal = normals[index]
let tangent = tangents[index]
let color = resolve_vertex_color(vertex_colors, index, base_color)
let (r, g, b) = color_rgb01(color)
let a = color.a
values.push(position.x * scale.x)
values.push(position.y * scale.y)
values.push(position.z * scale.z)
values.push(normal.x)
values.push(normal.y)
values.push(normal.z)
values.push(base_uv[X])
values.push(base_uv[Y])
values.push(emissive_uv[X])
values.push(emissive_uv[Y])
values.push(metallic_roughness_uv[X])
values.push(metallic_roughness_uv[Y])
values.push(occlusion_uv[X])
values.push(occlusion_uv[Y])
values.push(normal_uv[X])
values.push(normal_uv[Y])
values.push(r)
values.push(g)
values.push(b)
values.push(a)
values.push(emissive_r)
values.push(emissive_g)
values.push(emissive_b)
values.push(tangent.x)
values.push(tangent.y)
values.push(tangent.z)
values.push(tangent.w)
values.push(alpha_mode)
values.push(alpha_cutoff)
values.push(metallic)
values.push(roughness)
values.push(occlusion_strength)
values.push(normal_scale)
values.push(unlit)
values.push(if receive_shadows { 1.0 } else { 0.0 })
}
values
}
///|
fn resolve_material_texture_input(
mesh : @render3d_types.TriangleMesh3D,
source : MaterialTextureSource3D?,
fallback_uvs : Array[@smath.Vec2D],
transform_binding? : @render3d_types.TextureBinding3D? = None,
) -> ResolvedMaterialTextureInput3D {
match source {
Some(source) =>
match mesh.uv_sets.get(source.texcoord_set) {
Some(uvs) if uvs.length() == mesh.positions.length() =>
{
path: source.path,
uvs: resolve_material_texture_uvs(
transform_binding,
source.texcoord_set,
uvs,
),
sampler_code: source.sampler_code,
}
_ => { path: "", uvs: fallback_uvs, sampler_code: 0 }
}
None => { path: "", uvs: fallback_uvs, sampler_code: 0 }
}
}
///|
fn resolve_material_texture_uvs(
binding : @render3d_types.TextureBinding3D?,
texcoord_set : Int,
uvs : Array[@smath.Vec2D],
) -> Array[@smath.Vec2D] {
match binding {
Some(binding) =>
if binding.texcoord_set == texcoord_set &&
binding.transform != @render3d_types.default_texture_transform3d() {
uvs.map(fn(uv) {
@render3d_types.apply_texture_transform3d(binding.transform, uv)
})
} else {
uvs
}
_ => uvs
}
}
///|
fn color_rgb01(color : @render.Color) -> (Double, Double, Double) {
(
color.r.reinterpret_as_int().to_double() / 255.0,
color.g.reinterpret_as_int().to_double() / 255.0,
color.b.reinterpret_as_int().to_double() / 255.0,
)
}
///|
fn alpha_mode_code(mode : @render3d_types.AlphaMode3D) -> Double {
match mode {
Opaque => 0.0
Mask => 1.0
Blend => 2.0
}
}
///|
fn material_unlit_code(material : @render3d_types.StandardMaterial3D) -> Double {
if material.unlit {
1.0
} else {
0.0
}
}
///|
fn effective_material_color(
material : @render3d_types.StandardMaterial3D,
) -> @render.Color {
match material.alpha_mode {
Opaque => { ..material.base_color, a: 1.0 }
_ => material.base_color
}
}
///|
fn resolve_vertex_color(
vertex_colors : Array[@render.Color]?,
index : Int,
base_color : @render.Color,
) -> @render.Color {
match vertex_colors.bind(fn(values) { values.get(index) }) {
Some(vertex_color) => multiply_colors(base_color, vertex_color)
None => base_color
}
}
///|
fn multiply_colors(lhs : @render.Color, rhs : @render.Color) -> @render.Color {
{
r: ((lhs.r.reinterpret_as_int() * rhs.r.reinterpret_as_int() + 127) / 255).reinterpret_as_uint(),
g: ((lhs.g.reinterpret_as_int() * rhs.g.reinterpret_as_int() + 127) / 255).reinterpret_as_uint(),
b: ((lhs.b.reinterpret_as_int() * rhs.b.reinterpret_as_int() + 127) / 255).reinterpret_as_uint(),
a: lhs.a * rhs.a,
}
}
///|
fn texture_sampler_code(sampler : @render3d_types.TextureSampler3D) -> Int {
let repeat_u = sampler.wrap_u != ClampToEdge
let repeat_v = sampler.wrap_v != ClampToEdge
if repeat_u && repeat_v {
3
} else if repeat_u {
1
} else if repeat_v {
2
} else {
0
}
}
///|
fn resolve_texture_source(
binding : @render3d_types.TextureBinding3D,
) -> MaterialTextureSource3D? {
match synced_image_assets.get(binding.image) {
Some(image_asset) =>
Some({
path: resolve_asset_path(image_asset.path),
texcoord_set: binding.texcoord_set,
sampler_code: texture_sampler_code(binding.sampler),
})
None => None
}
}
///|
fn resolve_material_texture_bundle(
material : @render3d_types.StandardMaterial3D,
) -> MaterialTextureBundle3D? {
let base_source = material.base_color_texture.bind(resolve_texture_source)
let emissive_source = material.emissive_texture.bind(resolve_texture_source)
let metallic_roughness_source = material.metallic_roughness_texture.bind(
resolve_texture_source,
)
let occlusion_source = material.occlusion_texture.bind(resolve_texture_source)
let normal_source = material.normal_texture.bind(resolve_texture_source)
let primary = match base_source {
Some(base) => Some(base)
None =>
match emissive_source {
Some(emissive) => Some(emissive)
None =>
match metallic_roughness_source {
Some(metallic_roughness) => Some(metallic_roughness)
None =>
match occlusion_source {
Some(occlusion) => Some(occlusion)
None => normal_source
}
}
}
}
match primary {
Some(primary_source) =>
Some({
base_source,
emissive_source,
metallic_roughness_source,
occlusion_source,
normal_source,
primary_texcoord_set: primary_source.texcoord_set,
})
None => None
}
}