-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathmaterial.rs
More file actions
438 lines (396 loc) · 18.4 KB
/
Copy pathmaterial.rs
File metadata and controls
438 lines (396 loc) · 18.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
use std::collections::HashSet;
use crate::{
content::{content_mapping::DclContentMappingAndUrl, content_provider::ContentProvider},
dcl::{
components::{
material::{DclMaterial, DclSourceTex, DclTexture},
SceneComponentId,
},
crdt::{
last_write_wins::LastWriteWinsComponentOperation, SceneCrdtState,
SceneCrdtStateProtoComponents,
},
},
godot_classes::dcl_global::DclGlobal,
scene_runner::scene::{MaterialItem, Scene},
};
use godot::{
classes::{
base_material_3d::{EmissionOperator, Feature, Flags, ShadingMode, Transparency},
Material, MeshInstance3D, StandardMaterial3D, Texture2D,
},
global::weakref,
prelude::*,
};
use crate::dcl::components::proto_components::sdk::components::MaterialTransparencyMode;
pub fn update_material(scene: &mut Scene, crdt_state: &mut SceneCrdtState) {
let godot_dcl_scene = &mut scene.godot_dcl_scene;
let dirty_lww_components = &scene.current_dirty.lww_components;
let material_component = SceneCrdtStateProtoComponents::get_material(crdt_state);
let mut content_provider = DclGlobal::singleton().bind().get_content_provider();
if let Some(material_dirty) = dirty_lww_components.get(&SceneComponentId::MATERIAL) {
for entity in material_dirty {
let new_value = material_component.get(entity);
if new_value.is_none() {
continue;
}
let new_value = new_value.unwrap();
let dcl_material = if let Some(material) = new_value.value.as_ref() {
material
.material
.as_ref()
.map(|material| DclMaterial::from_proto(material, &scene.content_mapping))
} else {
None
};
let (godot_entity_node, node_3d) = godot_dcl_scene.ensure_node_3d(entity);
if let Some(dcl_material) = dcl_material {
let previous_dcl_material = godot_entity_node.material.as_ref();
if let Some(previous_dcl_material) = previous_dcl_material {
if previous_dcl_material.eq(&dcl_material) {
continue;
}
}
let existing_material = if let Some(material_item) = scene.materials.get(entity) {
let material_item = material_item.weak_ref.call("get_ref", &[]);
if material_item.is_nil() {
None
} else {
Some(material_item)
}
} else {
None
};
if existing_material.is_none() {
for tex in dcl_material.get_textures().into_iter().flatten() {
if let DclSourceTex::Texture(hash) = &tex.source {
content_provider.call_deferred(
"fetch_texture_by_hash",
&[
hash.to_godot().to_variant(),
DclContentMappingAndUrl::from_ref(
scene.content_mapping.clone(),
)
.to_variant(),
],
);
}
}
}
let mut godot_material = if let Some(material) = existing_material {
material.to::<Gd<StandardMaterial3D>>()
} else {
let godot_material = StandardMaterial3D::new_gd();
let waiting_textures = {
match &dcl_material {
DclMaterial::Unlit(unlit) => unlit.texture.is_some(),
DclMaterial::Pbr(pbr) => {
pbr.texture.is_some()
|| pbr.bump_texture.is_some()
|| pbr.alpha_texture.is_some()
|| pbr.emissive_texture.is_some()
}
}
};
scene.materials.insert(
*entity,
MaterialItem {
dcl_mat: dcl_material.clone(),
weak_ref: weakref(&godot_material.to_variant()),
waiting_textures,
alive: true,
},
);
godot_material
};
match &dcl_material {
DclMaterial::Unlit(unlit) => {
godot_material.set_metallic(0.0);
godot_material.set_roughness(0.0);
godot_material.set_specular(0.0);
godot_material.set_shading_mode(ShadingMode::UNSHADED);
godot_material.set_flag(Flags::ALBEDO_TEXTURE_FORCE_SRGB, true);
godot_material
.set_albedo(unlit.diffuse_color.0.to_godot().linear_to_srgb());
// Apply UV offset/tiling from main texture (only main texture supports this)
if let Some(texture) = &unlit.texture {
godot_material.set_uv1_offset(godot::builtin::Vector3::new(
texture.offset.0.x,
texture.offset.0.y,
0.0,
));
godot_material.set_uv1_scale(godot::builtin::Vector3::new(
texture.tiling.0.x,
texture.tiling.0.y,
1.0,
));
}
// Handle transparency for unlit materials (auto-detect)
if unlit.diffuse_color.0.a < 1.0 || unlit.texture.is_some() {
godot_material.set_transparency(Transparency::ALPHA_DEPTH_PRE_PASS);
} else {
godot_material.set_transparency(Transparency::DISABLED);
}
}
DclMaterial::Pbr(pbr) => {
godot_material.set_metallic(pbr.metallic.0);
godot_material.set_roughness(pbr.roughness.0);
godot_material.set_specular(pbr.specular_intensity.0);
godot_material.set_emission(pbr.emissive_color.0.to_godot());
godot_material.set_emission_energy_multiplier(pbr.emissive_intensity.0);
godot_material.set_feature(Feature::EMISSION, true);
// Use MULTIPLY operator when there's an emissive texture
if pbr.emissive_texture.is_some() {
godot_material.set_emission_operator(EmissionOperator::MULTIPLY);
}
godot_material.set_flag(Flags::ALBEDO_TEXTURE_FORCE_SRGB, true);
godot_material.set_albedo(pbr.albedo_color.0.to_godot());
// Apply UV offset/tiling from main texture (only main texture supports this)
if let Some(texture) = &pbr.texture {
godot_material.set_uv1_offset(godot::builtin::Vector3::new(
texture.offset.0.x,
texture.offset.0.y,
0.0,
));
godot_material.set_uv1_scale(godot::builtin::Vector3::new(
texture.tiling.0.x,
texture.tiling.0.y,
1.0,
));
}
// Handle transparency mode
match pbr.transparency_mode {
MaterialTransparencyMode::MtmOpaque => {
godot_material.set_transparency(Transparency::DISABLED);
}
MaterialTransparencyMode::MtmAlphaTest => {
godot_material.set_transparency(Transparency::ALPHA_SCISSOR);
godot_material.set_alpha_scissor_threshold(pbr.alpha_test.0);
}
MaterialTransparencyMode::MtmAlphaBlend => {
godot_material.set_transparency(Transparency::ALPHA_DEPTH_PRE_PASS);
}
MaterialTransparencyMode::MtmAlphaTestAndAlphaBlend => {
godot_material.set_transparency(Transparency::ALPHA_DEPTH_PRE_PASS);
godot_material.set_alpha_scissor_threshold(pbr.alpha_test.0);
}
MaterialTransparencyMode::MtmAuto => {
// Auto-detect: use alpha blend if albedo has transparency
if pbr.albedo_color.0.a < 1.0 || pbr.texture.is_some() {
godot_material
.set_transparency(Transparency::ALPHA_DEPTH_PRE_PASS);
} else {
godot_material.set_transparency(Transparency::DISABLED);
}
}
}
}
}
let mesh_renderer = node_3d.try_get_node_as::<MeshInstance3D>("MeshRenderer");
if let Some(mut mesh_renderer) = mesh_renderer {
mesh_renderer
.set_surface_override_material(0, &godot_material.upcast::<Material>());
}
} else {
let mesh_renderer = node_3d.try_get_node_as::<MeshInstance3D>("MeshRenderer");
if let Some(mut mesh_renderer) = mesh_renderer {
mesh_renderer.call(
"set_surface_override_material",
&[0.to_variant(), Variant::nil()],
);
godot_entity_node.material.take();
}
}
}
scene.dirty_materials = true;
}
if scene.dirty_materials {
let mut keep_dirty = false;
let mut dead_materials = HashSet::with_capacity(scene.materials.capacity());
let mut no_more_waiting_materials = HashSet::new();
for (entity, item) in scene.materials.iter() {
let dcl_material = item.dcl_mat.clone();
if item.waiting_textures {
let material_item = item.weak_ref.call("get_ref", &[]);
if material_item.is_nil() {
// item.alive = false;
dead_materials.insert(*entity);
continue;
}
let mut material = material_item.to::<Gd<StandardMaterial3D>>();
let mut ready = true;
match dcl_material {
DclMaterial::Unlit(unlit_material) => {
ready &= check_texture(
godot::classes::base_material_3d::TextureParam::ALBEDO,
&unlit_material.texture,
&mut material,
content_provider.bind_mut(),
scene,
);
}
DclMaterial::Pbr(pbr) => {
ready &= check_texture(
godot::classes::base_material_3d::TextureParam::ALBEDO,
&pbr.texture,
&mut material,
content_provider.bind_mut(),
scene,
);
// check_texture(
// godot::classes::base_material_3d::TextureParam::,
// &pbr.alpha_texture,
// item,
// &mut content_provider,
// );
ready &= check_texture(
godot::classes::base_material_3d::TextureParam::NORMAL,
&pbr.bump_texture,
&mut material,
content_provider.bind_mut(),
scene,
);
ready &= check_texture(
godot::classes::base_material_3d::TextureParam::EMISSION,
&pbr.emissive_texture,
&mut material,
content_provider.bind_mut(),
scene,
);
}
}
if !ready {
keep_dirty = true;
} else {
// item.waiting_textures = false;
no_more_waiting_materials.insert(*entity);
}
}
}
for materials in no_more_waiting_materials {
scene
.materials
.get_mut(&materials)
.unwrap()
.waiting_textures = false;
}
scene.materials.retain(|k, _| !dead_materials.contains(k));
scene.dirty_materials = keep_dirty;
}
}
fn check_texture(
param: godot::classes::base_material_3d::TextureParam,
dcl_texture: &Option<DclTexture>,
material: &mut Gd<StandardMaterial3D>,
mut content_provider: GdMut<ContentProvider>,
_scene: &Scene,
) -> bool {
if dcl_texture.is_none() {
return true;
}
let dcl_texture = dcl_texture.as_ref().unwrap();
match &dcl_texture.source {
DclSourceTex::Texture(content_hash) => {
if content_provider.is_resource_from_hash_loaded(content_hash.to_godot()) {
if let Some(resource) =
content_provider.get_texture_from_hash(content_hash.to_godot())
{
material.set_texture(param, &resource.upcast::<Texture2D>());
}
return true;
} else {
return false;
}
}
DclSourceTex::AvatarTexture(_user_id) => {
// TODO: implement load avatar texture
}
DclSourceTex::VideoTexture(_video_entity_id) => {
// Video textures need special handling:
// - LiveKit: uses dcl_texture (ImageTexture) which is updated from Rust
// - ExoPlayer: uses ExternalTexture from GDScript, accessed via get_backend_texture()
//
// We return false here to keep the material "dirty" so video textures
// are re-applied each frame. This ensures texture changes (video frames,
// ExoPlayer texture resize) are reflected in the material.
//
// The actual texture binding happens in update_video_material_textures()
// which is called after the main material loop.
return false;
}
}
true
}
/// Update video textures on materials.
/// This is called separately from check_texture because video textures need mutable access
/// to video_players to call get_backend_texture(), which would conflict with the material
/// iteration loop.
pub fn update_video_material_textures(scene: &mut Scene) {
// Collect video texture bindings we need to update
// Format: (material weak_ref, texture_param, video_entity_id)
let mut video_texture_updates: Vec<(
Variant,
godot::classes::base_material_3d::TextureParam,
crate::dcl::components::SceneEntityId,
)> = Vec::new();
for (_entity, item) in scene.materials.iter() {
if !item.waiting_textures {
continue;
}
let material_ref = item.weak_ref.call("get_ref", &[]);
if material_ref.is_nil() {
continue;
}
// Check each texture in the material for video textures
let textures_to_check: Vec<(
godot::classes::base_material_3d::TextureParam,
&Option<DclTexture>,
)> = match &item.dcl_mat {
DclMaterial::Unlit(unlit) => {
vec![(
godot::classes::base_material_3d::TextureParam::ALBEDO,
&unlit.texture,
)]
}
DclMaterial::Pbr(pbr) => {
vec![
(
godot::classes::base_material_3d::TextureParam::ALBEDO,
&pbr.texture,
),
(
godot::classes::base_material_3d::TextureParam::NORMAL,
&pbr.bump_texture,
),
(
godot::classes::base_material_3d::TextureParam::EMISSION,
&pbr.emissive_texture,
),
]
}
};
for (param, dcl_texture) in textures_to_check {
if let Some(tex) = dcl_texture {
if let DclSourceTex::VideoTexture(video_entity_id) = &tex.source {
video_texture_updates.push((material_ref.clone(), param, *video_entity_id));
}
}
}
}
// Now apply the video textures (we can mutably borrow video_players here)
for (material_ref, param, video_entity_id) in video_texture_updates {
if let Some(video_player) = scene.video_players.get_mut(&video_entity_id) {
let mut material = material_ref.to::<Gd<StandardMaterial3D>>();
// Try get_backend_texture first (works for ExoPlayer's ExternalTexture)
let backend_texture = video_player.bind_mut().get_backend_texture();
if let Some(texture) = backend_texture {
material.set_texture(param, &texture.upcast::<Texture2D>());
} else {
// Fallback to dcl_texture (works for LiveKit's ImageTexture)
if let Some(texture) = video_player.bind().get_dcl_texture() {
material.set_texture(param, &texture.upcast::<Texture2D>());
}
}
}
}
}