khora_core/asset/materials/mod.rs
1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Defines the core traits and material types for the rendering system.
16
17mod alpha_mode;
18mod emissive;
19mod standard;
20mod unlit;
21mod wireframe;
22
23pub use alpha_mode::*;
24pub use emissive::*;
25pub use standard::*;
26pub use unlit::*;
27pub use wireframe::*;
28
29use std::any::Any;
30
31use super::Asset;
32
33/// Helper trait to allow downcasting `dyn Material` trait objects to their concrete types.
34pub trait AsAny {
35 /// Returns a reference to the inner value as `&dyn Any`.
36 fn as_any(&self) -> &dyn Any;
37}
38
39impl<T: Any> AsAny for T {
40 fn as_any(&self) -> &dyn Any {
41 self
42 }
43}
44
45/// Supertrait that gives every material a `clone_box` without making `Material`
46/// itself depend on the non-object-safe `Clone`. The blanket impl below covers
47/// every `T: Material + Clone`, so concrete materials implement nothing extra.
48pub trait MaterialClone {
49 /// Clones `self` into a new boxed `dyn Material`.
50 fn clone_box(&self) -> Box<dyn Material>;
51}
52
53impl<T: Material + Clone + 'static> MaterialClone for T {
54 fn clone_box(&self) -> Box<dyn Material> {
55 Box::new(self.clone())
56 }
57}
58
59/// A trait for types that can be used as a material.
60///
61/// A material defines the surface properties of an object being rendered,
62/// influencing how it interacts with light and determining which shader
63/// (`RenderPipeline`) is used to draw it.
64pub trait Material: Asset + AsAny + MaterialClone {
65 /// Returns the base color (albedo or diffuse) of the material.
66 /// Default implementation is White.
67 fn base_color(&self) -> crate::math::LinearRgba {
68 crate::math::LinearRgba::WHITE
69 }
70
71 /// Returns the emissive color of the material.
72 /// Default implementation is Black.
73 fn emissive_color(&self) -> crate::math::LinearRgba {
74 crate::math::LinearRgba::BLACK
75 }
76
77 /// Returns the specular power or roughness conversion for the material.
78 /// Default implementation is 32.0.
79 fn specular_power(&self) -> f32 {
80 32.0
81 }
82
83 /// Returns the ambient color modifier for the material.
84 /// Default implementation is (0.1, 0.1, 0.1, 0.0).
85 fn ambient_color(&self) -> crate::math::LinearRgba {
86 crate::math::LinearRgba::new(0.1, 0.1, 0.1, 0.0)
87 }
88
89 /// Returns the metallic factor (0.0 = dielectric, 1.0 = metal).
90 /// Default implementation is 0.0.
91 fn metallic(&self) -> f32 {
92 0.0
93 }
94
95 /// Returns the roughness factor (0.0 = smooth, 1.0 = rough).
96 /// Default implementation is 1.0.
97 fn roughness(&self) -> f32 {
98 1.0
99 }
100
101 /// Returns the UUID of the base-color (albedo) texture, if any.
102 /// Default implementation is `None` (untextured).
103 fn base_color_texture(&self) -> Option<crate::asset::AssetUUID> {
104 None
105 }
106
107 /// Returns the UUID of the metallic-roughness texture, if any
108 /// (glTF convention: B=metallic, G=roughness).
109 /// Default implementation is `None`.
110 fn metallic_roughness_texture(&self) -> Option<crate::asset::AssetUUID> {
111 None
112 }
113
114 /// Returns the UUID of the tangent-space normal map, if any.
115 /// Default implementation is `None`.
116 fn normal_map(&self) -> Option<crate::asset::AssetUUID> {
117 None
118 }
119
120 /// Returns the UUID of the emissive texture, if any.
121 /// Default implementation is `None`.
122 fn emissive_texture(&self) -> Option<crate::asset::AssetUUID> {
123 None
124 }
125
126 /// Returns the UUID of the ambient-occlusion map, if any (red channel).
127 /// Attenuates the indirect (ambient/IBL) term only, never direct light.
128 /// Default implementation is `None`.
129 fn occlusion_map(&self) -> Option<crate::asset::AssetUUID> {
130 None
131 }
132
133 /// Returns the material's alpha (transparency) mode. For [`AlphaMode::Mask`]
134 /// the contained value is the alpha-cutoff threshold: fragments below it are
135 /// discarded. Default implementation is [`AlphaMode::Opaque`].
136 fn alpha_mode(&self) -> AlphaMode {
137 AlphaMode::Opaque
138 }
139
140 /// Whether the material is rendered double-sided (back faces not culled).
141 /// `false` (the default) culls back faces for correctly-wound meshes.
142 fn double_sided(&self) -> bool {
143 false
144 }
145}
146
147/// This is the key to our type-erased material handle system.
148/// We explicitly tell the compiler that a boxed, dynamic Material trait
149/// object can itself be treated as a valid Asset. This allows it to be
150/// stored inside an AssetHandle.
151impl Asset for Box<dyn Material> {}
152
153impl Clone for Box<dyn Material> {
154 fn clone(&self) -> Self {
155 self.clone_box()
156 }
157}