1use crate::player::{ObjRef, Player};
6use crate::shader::{
7 PassKind, PassState, ShaderPass, ShaderStencilOp, ShaderTargetBlend, ShaderValue, SubShader,
8};
9use crate::value::{Array, Value};
10use crate::{Result, invalid};
11
12#[derive(Clone, Debug, PartialEq)]
14pub struct TexEnv {
15 pub texture: Option<ObjRef>,
17 pub scale: [f32; 2],
19 pub offset: [f32; 2],
21}
22
23#[derive(Clone, Debug, PartialEq)]
25pub struct UnityMaterialData {
26 pub name: String,
28 pub shader: Option<ObjRef>,
30 pub textures: Vec<(String, TexEnv)>,
32 pub floats: Vec<(String, f32)>,
34 pub ints: Vec<(String, i64)>,
36 pub colors: Vec<(String, [f32; 4])>,
38 pub keywords: Vec<String>,
40 pub tags: Vec<(String, String)>,
42 pub render_queue: i64,
44}
45
46fn list<'a>(v: &'a Value, k: &str) -> Result<&'a [Value]> {
47 match v.get(k) {
48 Some(Value::Array(Array::Values(items))) => Ok(items),
49 Some(Value::Array(a)) if a.is_empty() => Ok(&[]),
50 None => Ok(&[]),
51 _ => Err(invalid(format!("material: {k} is not a list"))),
52 }
53}
54
55fn pairs<'a, T>(v: &'a Value, k: &str, f: impl Fn(&'a Value) -> Option<T>) -> Result<Vec<(String, T)>> {
57 list(v, k)?
58 .iter()
59 .map(|p| match p {
60 Value::Pair(kv) => {
61 let name =
62 kv.0.as_str()
63 .ok_or_else(|| invalid(format!("material: {k} key")))?;
64 let value = f(&kv.1).ok_or_else(|| invalid(format!("material: {k} {name}")))?;
65 Ok((name.to_owned(), value))
66 }
67 _ => Err(invalid(format!("material: {k} entry is not a pair"))),
68 })
69 .collect()
70}
71
72fn f2(v: &Value) -> Option<[f32; 2]> {
73 Some([v.get("x")?.as_f32()?, v.get("y")?.as_f32()?])
74}
75
76fn strings(v: &Value, k: &str) -> Result<Vec<String>> {
77 list(v, k)?
78 .iter()
79 .map(|s| {
80 s.as_str()
81 .map(str::to_owned)
82 .ok_or_else(|| invalid(format!("material: {k} entry")))
83 })
84 .collect()
85}
86
87fn keywords(m: &Value) -> Result<Vec<String>> {
90 if m.get("m_ValidKeywords").is_some() {
91 return strings(m, "m_ValidKeywords");
92 }
93 match m.get("m_ShaderKeywords") {
94 None => Ok(Vec::new()),
95 Some(v) => Ok(v
96 .as_str()
97 .ok_or_else(|| invalid("material: m_ShaderKeywords is not a string"))?
98 .split_whitespace()
99 .map(str::to_owned)
100 .collect()),
101 }
102}
103
104impl UnityMaterialData {
105 pub fn read(player: &Player, r: &ObjRef) -> Result<Self> {
107 let m = player.decode(r)?;
108 let props = m
109 .get("m_SavedProperties")
110 .ok_or_else(|| invalid("material: no m_SavedProperties"))?;
111 let shader = match m.get("m_Shader") {
112 Some(p) => player.resolve(&r.file, p)?,
113 None => None,
114 };
115 let textures = list(props, "m_TexEnvs")?
116 .iter()
117 .map(|p| {
118 let Value::Pair(kv) = p else {
119 return Err(invalid("material: m_TexEnvs entry is not a pair"));
120 };
121 let name = kv.0.as_str().ok_or_else(|| invalid("material: texture name"))?;
122 let env = &kv.1;
123 let texture = match env.get("m_Texture") {
124 Some(t) => player.resolve(&r.file, t)?,
125 None => None,
126 };
127 let ts = |k: &str| {
128 env.get(k)
129 .and_then(f2)
130 .ok_or_else(|| invalid(format!("material: {name} {k}")))
131 };
132 Ok((
133 name.to_owned(),
134 TexEnv {
135 texture,
136 scale: ts("m_Scale")?,
137 offset: ts("m_Offset")?,
138 },
139 ))
140 })
141 .collect::<Result<_>>()?;
142 Ok(UnityMaterialData {
143 name: m.get("m_Name").and_then(Value::as_str).unwrap_or("").to_owned(),
144 shader,
145 textures,
146 floats: pairs(props, "m_Floats", Value::as_f32)?,
147 ints: pairs(props, "m_Ints", Value::as_i64)?,
148 colors: pairs(props, "m_Colors", |c| {
149 Some([
150 c.get("r")?.as_f32()?,
151 c.get("g")?.as_f32()?,
152 c.get("b")?.as_f32()?,
153 c.get("a")?.as_f32()?,
154 ])
155 })?,
156 keywords: keywords(&m)?,
157 tags: pairs(&m, "stringTagMap", |s| s.as_str().map(str::to_owned))?,
158 render_queue: m.get("m_CustomRenderQueue").and_then(Value::as_i64).unwrap_or(-1),
159 })
160 }
161}
162
163#[derive(Clone, Debug, PartialEq)]
165pub struct ShaderData {
166 pub name: String,
168 pub subshaders: Vec<SubShader>,
170}
171
172fn tag_list(v: Option<&Value>) -> Result<Vec<(String, String)>> {
173 let Some(v) = v else { return Ok(Vec::new()) };
174 let inner = v.get("tags").or_else(|| v.get("m_Tags")).unwrap_or(v);
175 pairs_of(inner, "tag")
176}
177
178fn pairs_of(v: &Value, what: &str) -> Result<Vec<(String, String)>> {
179 let items: &[Value] = match v {
180 Value::Array(Array::Values(items)) => items,
181 Value::Array(a) if a.is_empty() => &[],
182 _ => return Err(invalid(format!("shader: {what}s are not a list"))),
183 };
184 items
185 .iter()
186 .map(|p| match p {
187 Value::Pair(kv) => Ok((
188 kv.0.as_str()
189 .ok_or_else(|| invalid(format!("shader: {what} key")))?
190 .to_owned(),
191 kv.1.as_str()
192 .ok_or_else(|| invalid(format!("shader: {what} value")))?
193 .to_owned(),
194 )),
195 _ => Err(invalid(format!("shader: {what} entry is not a pair"))),
196 })
197 .collect()
198}
199
200fn shader_value(v: Option<&Value>, what: &str) -> Result<ShaderValue> {
201 let v = v.ok_or_else(|| invalid(format!("shader pass: no {what}")))?;
202 let value = v
203 .get("val")
204 .and_then(Value::as_f32)
205 .ok_or_else(|| invalid(format!("shader pass: {what} without a value")))?;
206 let property = match v.get("name").and_then(Value::as_str) {
207 None | Some("") | Some("<noninit>") => None,
208 Some(n) => Some(n.to_owned()),
209 };
210 Ok(ShaderValue { value, property })
211}
212
213fn target_blend(rt: &Value) -> Result<ShaderTargetBlend> {
215 Ok(ShaderTargetBlend {
216 src: shader_value(rt.get("srcBlend"), "srcBlend")?,
217 dst: shader_value(rt.get("destBlend"), "destBlend")?,
218 src_alpha: shader_value(rt.get("srcBlendAlpha"), "srcBlendAlpha")?,
219 dst_alpha: shader_value(rt.get("destBlendAlpha"), "destBlendAlpha")?,
220 op: shader_value(rt.get("blendOp"), "blendOp")?,
221 op_alpha: shader_value(rt.get("blendOpAlpha"), "blendOpAlpha")?,
222 mask: shader_value(rt.get("colMask"), "colMask")?,
223 })
224}
225
226fn stencil_op(st: &Value, what: &str) -> Result<ShaderStencilOp> {
228 let op = st
229 .get(what)
230 .ok_or_else(|| invalid(format!("shader pass: no {what}")))?;
231 Ok(ShaderStencilOp {
232 pass: shader_value(op.get("pass"), "stencil pass")?,
233 fail: shader_value(op.get("fail"), "stencil fail")?,
234 z_fail: shader_value(op.get("zFail"), "stencil zFail")?,
235 comp: shader_value(op.get("comp"), "stencil comp")?,
236 })
237}
238
239fn pass_state(st: &Value, name: &str) -> Result<PassState> {
241 let mut targets = Vec::with_capacity(8);
242 for n in 0..8 {
243 let rt = st
244 .get(&format!("rtBlend{n}"))
245 .ok_or_else(|| invalid(format!("shader {name}: a pass without rtBlend{n}")))?;
246 targets.push(target_blend(rt)?);
247 }
248 Ok(PassState {
249 targets,
250 separate_blend: st
251 .get("rtSeparateBlend")
252 .and_then(Value::as_bool)
253 .ok_or_else(|| invalid(format!("shader {name}: a pass without rtSeparateBlend")))?,
254 z_test: shader_value(st.get("zTest"), "zTest")?,
255 offset_factor: shader_value(st.get("offsetFactor"), "offsetFactor")?,
256 offset_units: shader_value(st.get("offsetUnits"), "offsetUnits")?,
257 stencil_ref: shader_value(st.get("stencilRef"), "stencilRef")?,
258 stencil_read_mask: shader_value(st.get("stencilReadMask"), "stencilReadMask")?,
259 stencil_write_mask: shader_value(st.get("stencilWriteMask"), "stencilWriteMask")?,
260 stencil_op: stencil_op(st, "stencilOp")?,
261 stencil_op_front: stencil_op(st, "stencilOpFront")?,
262 stencil_op_back: stencil_op(st, "stencilOpBack")?,
263 })
264}
265
266impl ShaderData {
267 pub fn read(player: &Player, r: &ObjRef) -> Result<Self> {
269 let s = player.decode(r)?;
270 let name =
271 shader_name_of(&s).ok_or_else(|| invalid(format!("shader {}/{}: no name", r.file, r.path_id)))?;
272 let mut subshaders = Vec::new();
273 if let Some(form) = s.get("m_ParsedForm") {
274 for sub in list(form, "m_SubShaders")? {
275 let mut passes = Vec::new();
276 for p in list(sub, "m_Passes")? {
277 let st = p
278 .get("m_State")
279 .ok_or_else(|| invalid(format!("shader {name}: a pass without m_State")))?;
280 let rt = st
281 .get("rtBlend0")
282 .ok_or_else(|| invalid(format!("shader {name}: a pass without rtBlend0")))?;
283 let kind = match p.get("m_Type").and_then(Value::as_i64) {
284 Some(0) => PassKind::Normal,
285 Some(1) => PassKind::Use(
286 p.get("m_UseName")
287 .and_then(Value::as_str)
288 .ok_or_else(|| {
289 invalid(format!("shader {name}: a UsePass without m_UseName"))
290 })?
291 .to_owned(),
292 ),
293 Some(2) => PassKind::Grab,
294 t => return Err(invalid(format!("shader {name}: pass type {t:?}"))),
295 };
296 passes.push(ShaderPass {
297 kind,
298 light_mode: tag_list(st.get("m_Tags"))?
299 .into_iter()
300 .find(|(k, _)| k.eq_ignore_ascii_case("LightMode"))
301 .map(|(_, v)| v),
302 src_blend: shader_value(rt.get("srcBlend"), "srcBlend")?,
303 dst_blend: shader_value(rt.get("destBlend"), "destBlend")?,
304 zwrite: shader_value(st.get("zWrite"), "zWrite")?,
305 cull: shader_value(st.get("culling"), "culling")?,
306 state: pass_state(st, &name)?,
307 });
308 }
309 subshaders.push(SubShader {
310 tags: tag_list(sub.get("m_Tags"))?,
311 passes,
312 });
313 }
314 }
315 Ok(ShaderData { name, subshaders })
316 }
317}
318
319fn shader_name_of(s: &Value) -> Option<String> {
320 s.at(&["m_ParsedForm", "m_Name"])
321 .or_else(|| s.get("m_Name"))
322 .and_then(Value::as_str)
323 .map(str::to_owned)
324}
325
326pub fn shader_name(player: &Player, r: &ObjRef) -> Result<String> {
328 shader_name_of(&player.decode(r)?)
329 .ok_or_else(|| invalid(format!("shader {}/{}: no name", r.file, r.path_id)))
330}