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}