@@ -3,6 +3,7 @@ use processing::prelude::*;
33use pyo3:: types:: PyDict ;
44use pyo3:: { exceptions:: PyRuntimeError , prelude:: * } ;
55
6+ use crate :: color:: PyColor ;
67use crate :: compute:: Buffer ;
78use crate :: math:: { PyVec2 , PyVec3 , PyVec4 } ;
89use crate :: shader:: Shader ;
@@ -52,8 +53,51 @@ pub(crate) fn py_to_shader_value(value: &Bound<'_, PyAny>) -> PyResult<shader_va
5253 ) ) )
5354}
5455
56+ /// Apply an `albedo` value to a material, dispatching by Python type. The
57+ /// material's backing asset is swapped between plain-PBR and field-buffer
58+ /// variants as needed; all other `StandardMaterial` state survives the swap.
59+ fn apply_albedo ( entity : Entity , value : & Bound < ' _ , PyAny > ) -> PyResult < ( ) > {
60+ if let Ok ( buf) = value. extract :: < PyRef < Buffer > > ( ) {
61+ return material_set_albedo_buffer ( entity, buf. entity )
62+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ;
63+ }
64+ if let Ok ( c) = value. extract :: < PyRef < PyColor > > ( ) {
65+ let srgba: bevy:: color:: Srgba = c. 0 . into ( ) ;
66+ return material_set_albedo_color ( entity, [ srgba. red , srgba. green , srgba. blue , srgba. alpha ] )
67+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ;
68+ }
69+ if let Ok ( rgba) = value. extract :: < [ f32 ; 4 ] > ( ) {
70+ return material_set_albedo_color ( entity, rgba)
71+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ;
72+ }
73+ if let Ok ( rgb) = value. extract :: < [ f32 ; 3 ] > ( ) {
74+ return material_set_albedo_color ( entity, [ rgb[ 0 ] , rgb[ 1 ] , rgb[ 2 ] , 1.0 ] )
75+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ;
76+ }
77+ Err ( PyRuntimeError :: new_err ( format ! (
78+ "unsupported albedo type: {} (expected Color, Buffer, or [r,g,b,(a)])" ,
79+ value. get_type( ) . name( ) ?
80+ ) ) )
81+ }
82+
83+ fn apply_kwargs ( entity : Entity , kwargs : & Bound < ' _ , PyDict > ) -> PyResult < ( ) > {
84+ for ( key, value) in kwargs. iter ( ) {
85+ let name: String = key. extract ( ) ?;
86+ if name == "albedo" {
87+ apply_albedo ( entity, & value) ?;
88+ continue ;
89+ }
90+ let v = py_to_shader_value ( & value) ?;
91+ material_set ( entity, & name, v) . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?;
92+ }
93+ Ok ( ( ) )
94+ }
95+
5596#[ pymethods]
5697impl Material {
98+ /// Construct a material. With no args, returns a default PBR. With a
99+ /// `shader` arg, returns a custom material. Any kwargs (`albedo=...`,
100+ /// `roughness=...`, etc.) are applied after construction.
57101 #[ new]
58102 #[ pyo3( signature = ( shader=None , * * kwargs) ) ]
59103 pub fn new ( shader : Option < & Shader > , kwargs : Option < & Bound < ' _ , PyDict > > ) -> PyResult < Self > {
@@ -64,48 +108,48 @@ impl Material {
64108 material_create_pbr ( ) . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?
65109 } ;
66110
67- let mat = Self { entity } ;
68111 if let Some ( kwargs) = kwargs {
69- for ( key, value) in kwargs. iter ( ) {
70- let name: String = key. extract ( ) ?;
71- let value = py_to_shader_value ( & value) ?;
72- material_set ( mat. entity , & name, value)
73- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?;
74- }
112+ apply_kwargs ( entity, kwargs) ?;
75113 }
76- Ok ( mat )
114+ Ok ( Self { entity } )
77115 }
78116
117+ /// PBR-lit material. `albedo` accepts a `Color` (solid) or a `Buffer`
118+ /// (per-particle, indexed by per-instance tag — used with `Field`s).
119+ #[ staticmethod]
79120 #[ pyo3( signature = ( * * kwargs) ) ]
80- pub fn set ( & self , kwargs : Option < & Bound < ' _ , PyDict > > ) -> PyResult < ( ) > {
81- let Some ( kwargs) = kwargs else {
82- return Ok ( ( ) ) ;
83- } ;
84- for ( key, value) in kwargs. iter ( ) {
85- let name: String = key. extract ( ) ?;
86- let value = py_to_shader_value ( & value) ?;
87- material_set ( self . entity , & name, value)
88- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?;
121+ pub fn pbr ( kwargs : Option < & Bound < ' _ , PyDict > > ) -> PyResult < Self > {
122+ let entity = material_create_pbr ( ) . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?;
123+ if let Some ( kwargs) = kwargs {
124+ apply_kwargs ( entity, kwargs) ?;
89125 }
90- Ok ( ( ) )
126+ Ok ( Self { entity } )
91127 }
92128
93- /// Unlit per-particle color material. Each particle samples its color from
94- /// the given buffer indexed by per-instance tag .
129+ /// Unlit material — same shape as `pbr` but skips lighting calculations
130+ /// ( the per-particle / solid color is the final output) .
95131 #[ staticmethod]
96- pub fn field_color ( buffer : & Buffer ) -> PyResult < Self > {
97- let entity = material_create_field_color ( buffer. entity )
132+ #[ pyo3( signature = ( * * kwargs) ) ]
133+ pub fn unlit ( kwargs : Option < & Bound < ' _ , PyDict > > ) -> PyResult < Self > {
134+ let entity = material_create_pbr ( ) . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?;
135+ material_set ( entity, "unlit" , shader_value:: ShaderValue :: Float ( 1.0 ) )
98136 . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?;
137+ if let Some ( kwargs) = kwargs {
138+ apply_kwargs ( entity, kwargs) ?;
139+ }
99140 Ok ( Self { entity } )
100141 }
101142
102- /// PBR-lit per-particle color material. Same tag-indexed lookup as
103- /// `field_color`, but composed with `StandardMaterial` for proper lighting.
104- #[ staticmethod]
105- pub fn field_pbr ( buffer : & Buffer ) -> PyResult < Self > {
106- let entity = material_create_field_pbr ( buffer. entity )
107- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?;
108- Ok ( Self { entity } )
143+ /// Patch one or more material properties. `albedo` is special-cased and
144+ /// may swap the backing asset type between solid-color and buffer-color
145+ /// variants — all other `StandardMaterial` state (roughness, metallic,
146+ /// emissive, alpha_mode, unlit, etc.) is preserved across the swap.
147+ #[ pyo3( signature = ( * * kwargs) ) ]
148+ pub fn set ( & self , kwargs : Option < & Bound < ' _ , PyDict > > ) -> PyResult < ( ) > {
149+ let Some ( kwargs) = kwargs else {
150+ return Ok ( ( ) ) ;
151+ } ;
152+ apply_kwargs ( self . entity , kwargs)
109153 }
110154}
111155
0 commit comments