ambient_decals/
lib.rs

1use std::sync::Arc;
2
3use ambient_asset_cache::{AssetCache, AsyncAssetKeyExt, SyncAssetKey, SyncAssetKeyExt};
4use ambient_core::{
5    asset_cache,
6    async_ecs::async_run,
7    bounding::{local_bounding_aabb, world_bounding_aabb, world_bounding_sphere},
8    gpu_ecs::ENTITIES_BIND_GROUP,
9    main_scene, mesh, runtime,
10    transform::{local_to_world, mesh_to_world},
11};
12use ambient_ecs::generated::components::core::rendering::decal_from_url;
13use ambient_ecs::{components, query, Entity, MakeDefault, Networked, Store, SystemGroup};
14use ambient_gpu::shader_module::{Shader, ShaderModule};
15use ambient_meshes::CubeMeshKey;
16use ambient_renderer::{
17    color, get_forward_modules, gpu_primitives_lod, gpu_primitives_mesh, material,
18    pbr_material::{PbrMaterialFromUrl, PbrMaterialShaderKey},
19    primitives, renderer_shader, MaterialShader, RendererShader, GLOBALS_BIND_GROUP, MATERIAL_BIND_GROUP, PRIMITIVES_BIND_GROUP,
20};
21use ambient_std::{
22    asset_url::{MaterialAssetType, TypedAssetUrl, AbsAssetUrl},
23    cb, include_file,
24    shapes::AABB,
25    unwrap_log_warn,
26};
27use glam::{Vec3, Vec4};
28
29components!("decals", {
30    @[MakeDefault,  Networked, Store]
31    decal: TypedAssetUrl<MaterialAssetType>,
32});
33
34pub struct DecalShaderKey {
35    pub material_shader: Arc<MaterialShader>,
36    pub lit: bool,
37    pub shadow_cascades: u32,
38}
39
40impl std::fmt::Debug for DecalShaderKey {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        f.debug_struct("DecalShaderKey").field("material_shader", &self.material_shader.id).field("lit", &self.lit).finish()
43    }
44}
45
46impl SyncAssetKey<Arc<RendererShader>> for DecalShaderKey {
47    fn load(&self, assets: AssetCache) -> Arc<RendererShader> {
48        let id = format!("decal_shader_{}_{}", self.material_shader.id, self.lit);
49        let shader = Shader::new(
50            &assets,
51            id.clone(),
52            &[GLOBALS_BIND_GROUP, ENTITIES_BIND_GROUP, PRIMITIVES_BIND_GROUP, MATERIAL_BIND_GROUP],
53            &ShaderModule::new("decal_material", include_file!("decal.wgsl"))
54                .with_dependencies(get_forward_modules(&assets, self.shadow_cascades))
55                .with_dependency(self.material_shader.shader.clone()),
56        )
57        .unwrap();
58
59        Arc::new(RendererShader {
60            shader,
61            id,
62            vs_main: "vs_main".to_string(),
63            fs_shadow_main: "fs_shadow_main".to_string(),
64            fs_forward_main: if self.lit { "fs_forward_lit_main".to_string() } else { "fs_forward_unlit_main".to_string() },
65            fs_outline_main: "fs_outlines_main".to_string(),
66            transparent: true,
67            double_sided: true,
68            depth_write_enabled: false,
69            transparency_group: -100,
70        })
71    }
72}
73
74pub fn client_systems() -> SystemGroup {
75    SystemGroup::new(
76        "decals_client",
77        vec![
78            query(decal().changed()).to_system(|q, world, qs, _| {
79            for (id, decal) in q.collect_cloned(world, qs) {
80                let decal = if let Some(url) = decal.abs() {
81                    url
82                } else {
83                    log::error!("Decal was not an absolute url: {}", decal);
84                    continue;
85                };
86                let assets = world.resource(asset_cache()).clone();
87                let async_run = world.resource(async_run()).clone();
88                world.resource(runtime()).spawn(async move {
89                    let mat = unwrap_log_warn!(PbrMaterialFromUrl(decal).get(&assets).await);
90                    async_run.run(move |world| {
91                        let aabb = AABB { min: -Vec3::ONE, max: Vec3::ONE };
92                        let mut data = Entity::new()
93                            .with(material(), mat.into())
94                            .with(
95                                renderer_shader(),
96                                cb(move |assets, config| {
97                                    DecalShaderKey {
98                                        material_shader: PbrMaterialShaderKey.get(assets),
99                                        lit: true,
100                                        shadow_cascades: config.shadow_cascades,
101                                    }
102                                    .get(assets)
103                                }),
104                            )
105                            .with(mesh(), CubeMeshKey.get(&assets))
106                            .with(primitives(), vec![])
107                            .with_default(gpu_primitives_mesh())
108                            .with_default(gpu_primitives_lod())
109                            .with(main_scene(), ())
110                            .with(local_bounding_aabb(), aabb)
111                            .with(world_bounding_sphere(), aabb.to_sphere())
112                            .with(world_bounding_aabb(), aabb);
113
114                        if !world.has_component(id, local_to_world()) {
115                            data.set(local_to_world(), Default::default());
116                        }
117                        if !world.has_component(id, mesh_to_world()) {
118                            data.set(mesh_to_world(), Default::default());
119                        }
120                        if !world.has_component(id, color()) {
121                            data.set(color(), Vec4::ONE);
122                        }
123                        world.add_components(id, data).ok();
124                    })
125                });
126            }
127            }),
128
129            query(decal_from_url().changed()).to_system(|q, world, qs, _| {
130            for (id, url) in q.collect_cloned(world, qs) {
131                let url = match AbsAssetUrl::parse(url) {
132                    Ok(value) => value,
133                    Err(err) => {
134                        log::warn!("Failed to parse pbr_material_from_url url: {:?}", err);
135                        continue;
136                    }
137                };
138                let assets = world.resource(asset_cache()).clone();
139                let async_run = world.resource(async_run()).clone();
140                world.resource(runtime()).spawn(async move {
141                    let mat = unwrap_log_warn!(PbrMaterialFromUrl(url).get(&assets).await);
142                    async_run.run(move |world| {
143                        let aabb = AABB { min: -Vec3::ONE, max: Vec3::ONE };
144                        let mut data = Entity::new()
145                            .with(material(), mat.into())
146                            .with(
147                                renderer_shader(),
148                                cb(move |assets, config| {
149                                    DecalShaderKey {
150                                        material_shader: PbrMaterialShaderKey.get(assets),
151                                        lit: true,
152                                        shadow_cascades: config.shadow_cascades,
153                                    }
154                                    .get(assets)
155                                }),
156                            )
157                            .with(mesh(), CubeMeshKey.get(&assets))
158                            .with(primitives(), vec![])
159                            .with_default(gpu_primitives_mesh())
160                            .with_default(gpu_primitives_lod())
161                            .with(main_scene(), ())
162                            .with(local_bounding_aabb(), aabb)
163                            .with(world_bounding_sphere(), aabb.to_sphere())
164                            .with(world_bounding_aabb(), aabb);
165
166                        if !world.has_component(id, local_to_world()) {
167                            data.set(local_to_world(), Default::default());
168                        }
169                        if !world.has_component(id, mesh_to_world()) {
170                            data.set(mesh_to_world(), Default::default());
171                        }
172                        if !world.has_component(id, color()) {
173                            data.set(color(), Vec4::ONE);
174                        }
175                        world.add_components(id, data).ok();
176                    })
177                });
178            }
179            })
180        ],
181    )
182}