1use super::controllers::MdlControllerType;
19use super::types::{
20 MdlAabb, MdlAnimMesh, MdlDangly, MdlEmitter, MdlLight, MdlNodeData, MdlReference, MdlSaber,
21 MdlSkin,
22};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum NodeTypeContext {
32 Base,
34 Mesh,
36 Light,
38 Emitter,
40}
41
42const BASE_CONTROLLERS: &[(u32, &str)] = &[(8, "position"), (20, "orientation"), (36, "scale")];
47
48const MESH_CONTROLLERS: &[(u32, &str)] = &[(100, "selfillumcolor"), (132, "alpha")];
54
55const LIGHT_CONTROLLERS: &[(u32, &str)] = &[
59 (76, "color"),
60 (88, "radius"),
61 (96, "shadowradius"),
62 (100, "verticaldisplacement"),
63 (140, "multiplier"),
64];
65
66const EMITTER_CONTROLLERS: &[(u32, &str)] = &[
71 (80, "alphaEnd"),
72 (84, "alphaStart"),
73 (88, "birthrate"),
74 (92, "bounce_co"),
75 (96, "combinetime"),
76 (100, "drag"),
77 (104, "fps"),
78 (108, "frameEnd"),
79 (112, "frameStart"),
80 (116, "grav"),
81 (120, "lifeExp"),
82 (124, "mass"),
83 (128, "p2p_bezier2"),
84 (132, "p2p_bezier3"),
85 (136, "particleRot"),
86 (140, "randvel"),
87 (144, "sizeStart"),
88 (148, "sizeEnd"),
89 (152, "sizeStart_y"),
90 (156, "sizeEnd_y"),
91 (160, "spread"),
92 (164, "threshold"),
93 (168, "velocity"),
94 (172, "xsize"),
95 (176, "ysize"),
96 (180, "blurlength"),
97 (184, "lightningDelay"),
98 (188, "lightningRadius"),
99 (192, "lightningScale"),
100 (196, "lightningSubDiv"),
101 (200, "lightningZigzag"),
102 (216, "alphaMid"),
103 (220, "percentStart"),
104 (224, "percentMid"),
105 (228, "percentEnd"),
106 (232, "sizeMid"),
107 (236, "sizeMid_y"),
108 (240, "m_fRandomBirthRate"),
109 (252, "targetsize"),
110 (256, "numcontrolpts"),
111 (260, "controlptradius"),
112 (264, "controlptdelay"),
113 (268, "tangentspread"),
114 (272, "tangentlength"),
115 (284, "colorMid"),
116 (380, "colorEnd"),
117 (392, "colorStart"),
118 (502, "detonate"),
119];
120
121pub fn controller_ascii_name(
127 code: MdlControllerType,
128 ctx: NodeTypeContext,
129) -> Option<&'static str> {
130 let raw = code.raw();
131
132 for &(c, name) in BASE_CONTROLLERS {
134 if c == raw {
135 return Some(name);
136 }
137 }
138
139 let table = match ctx {
141 NodeTypeContext::Base => return None,
142 NodeTypeContext::Mesh => MESH_CONTROLLERS,
143 NodeTypeContext::Light => LIGHT_CONTROLLERS,
144 NodeTypeContext::Emitter => EMITTER_CONTROLLERS,
145 };
146
147 for &(c, name) in table {
148 if c == raw {
149 return Some(name);
150 }
151 }
152
153 None
154}
155
156pub fn controller_from_ascii_name(name: &str, ctx: NodeTypeContext) -> Option<MdlControllerType> {
161 for &(code, ascii) in BASE_CONTROLLERS {
163 if ascii.eq_ignore_ascii_case(name) {
164 return Some(MdlControllerType::from_raw(code));
165 }
166 }
167
168 let table = match ctx {
170 NodeTypeContext::Base => return None,
171 NodeTypeContext::Mesh => MESH_CONTROLLERS,
172 NodeTypeContext::Light => LIGHT_CONTROLLERS,
173 NodeTypeContext::Emitter => EMITTER_CONTROLLERS,
174 };
175
176 for &(code, ascii) in table {
177 if ascii.eq_ignore_ascii_case(name) {
178 return Some(MdlControllerType::from_raw(code));
179 }
180 }
181
182 None
183}
184
185const CLASSIFICATIONS: &[(u8, &str)] = &[
199 (0, "other"),
200 (1, "effect"),
201 (2, "tile"),
202 (4, "character"),
203 (8, "door"),
204 (16, "lightsaber"),
205 (32, "placeable"),
206 (64, "flyer"),
207];
208
209pub fn classification_to_ascii(code: u8) -> &'static str {
213 for &(c, name) in CLASSIFICATIONS {
214 if c == code {
215 return name;
216 }
217 }
218 "Other"
219}
220
221pub fn classification_from_ascii(name: &str) -> Option<u8> {
225 for &(code, ascii) in CLASSIFICATIONS {
226 if ascii.eq_ignore_ascii_case(name) {
227 return Some(code);
228 }
229 }
230 None
231}
232
233pub fn node_type_ascii_name(data: &MdlNodeData) -> &'static str {
239 match data {
240 MdlNodeData::Base => "dummy",
241 MdlNodeData::Light(_) => "light",
242 MdlNodeData::Emitter(_) => "emitter",
243 MdlNodeData::Camera(_) => "dummy",
244 MdlNodeData::Reference(_) => "reference",
245 MdlNodeData::Mesh(_) => "trimesh",
246 MdlNodeData::Skin(_) => "skin",
247 MdlNodeData::AnimMesh(_) => "animmesh",
248 MdlNodeData::Dangly(_) => "danglymesh",
249 MdlNodeData::Aabb(_) => "aabb",
250 MdlNodeData::Saber(_) => "lightsaber",
251 }
252}
253
254pub fn node_type_context(data: &MdlNodeData) -> NodeTypeContext {
258 match data {
259 MdlNodeData::Base | MdlNodeData::Camera(_) => NodeTypeContext::Base,
260 MdlNodeData::Light(_) => NodeTypeContext::Light,
261 MdlNodeData::Emitter(_) => NodeTypeContext::Emitter,
262 MdlNodeData::Mesh(_)
263 | MdlNodeData::Skin(_)
264 | MdlNodeData::AnimMesh(_)
265 | MdlNodeData::Dangly(_)
266 | MdlNodeData::Aabb(_)
267 | MdlNodeData::Saber(_)
268 | MdlNodeData::Reference(_) => NodeTypeContext::Mesh,
269 }
270}
271
272pub fn node_data_from_ascii_name(name: &str) -> MdlNodeData {
277 if name.eq_ignore_ascii_case("trimesh") {
278 MdlNodeData::Mesh(Default::default())
279 } else if name.eq_ignore_ascii_case("skin") {
280 MdlNodeData::Skin(MdlSkin::default())
281 } else if name.eq_ignore_ascii_case("danglymesh") {
282 MdlNodeData::Dangly(MdlDangly::default())
283 } else if name.eq_ignore_ascii_case("aabb") {
284 MdlNodeData::Aabb(MdlAabb::default())
285 } else if name.eq_ignore_ascii_case("lightsaber") {
286 MdlNodeData::Saber(MdlSaber::default())
287 } else if name.eq_ignore_ascii_case("light") {
288 MdlNodeData::Light(MdlLight::default())
289 } else if name.eq_ignore_ascii_case("emitter") {
290 MdlNodeData::Emitter(MdlEmitter::default())
291 } else if name.eq_ignore_ascii_case("reference") {
292 MdlNodeData::Reference(MdlReference::default())
293 } else if name.eq_ignore_ascii_case("animmesh") {
294 MdlNodeData::AnimMesh(MdlAnimMesh::default())
295 } else {
296 MdlNodeData::Base
297 }
298}
299
300pub fn is_non_controller_keyword(name: &str) -> bool {
307 const KEYWORDS: &[&str] = &[
308 "parent",
309 "bitmap",
310 "bitmap2",
311 "texture0",
312 "texture1",
313 "diffuse",
314 "ambient",
315 "transparencyhint",
316 "animateuv",
317 "uvdirectionx",
318 "uvdirectiony",
319 "uvjitter",
320 "uvjitterspeed",
321 "lightmapped",
322 "rotatetexture",
323 "m_bisbackgroundgeometry",
324 "shadow",
325 "beaming",
326 "render",
327 "verts",
328 "faces",
329 "tverts",
330 "tverts0",
331 "tverts1",
332 "tverts2",
333 "tverts3",
334 "colors",
335 "tangentspace",
336 "dirt_enabled",
337 "dirt_texture",
338 "dirt_worldspace",
339 "hologram_donotdraw",
340 "inv_count",
341 "weights",
342 "skinweights",
343 "constraints",
344 "displacement",
345 "tightness",
346 "period",
347 "aabb",
348 "lightpriority",
349 "ambientonly",
350 "ndynamictype",
351 "affectdynamic",
352 "generateflare",
353 "fadinglight",
354 "flareradius",
355 "lensflares",
356 "texturenames",
357 "flarepositions",
358 "flaresizes",
359 "flarecolorshifts",
360 "deadspace",
361 "blastradius",
362 "blastlength",
363 "numbranches",
364 "controlptsmoothing",
365 "xgrid",
366 "ygrid",
367 "spawntype",
368 "update",
369 "blend",
370 "texture",
371 "chunkname",
372 "twosidedtex",
373 "loop",
374 "renderorder",
375 "m_bframeblending",
376 "m_sdepthtexturename",
377 "p2p",
378 "p2p_sel",
379 "affectedbywind",
380 "m_istinted",
381 "bounce",
382 "random",
383 "inherit",
384 "inheritvel",
385 "inherit_local",
386 "splat",
387 "inherit_part",
388 "depth_texture",
389 "refmodel",
390 "reattachable",
391 "sampleperiod",
392 "animverts",
393 "animtverts",
394 "bmin",
395 "bmax",
396 "endnode",
397 "endmodelgeom",
398 "donemodel",
399 "doneanim",
400 "beginmodelgeom",
401 "newmodel",
402 "newanim",
403 "node",
404 "endlist",
405 "compress_quaternions",
406 "headlink",
407 "setanimationscale",
408 "ignorefog",
409 "classification",
410 "classification_unk1",
411 "setsupermodel",
412 "length",
413 "transtime",
414 "animroot",
415 "event",
416 ];
417 let lower = name.to_ascii_lowercase();
418 KEYWORDS.contains(&lower.as_str())
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
426 fn base_controllers_all_contexts() {
427 for ctx in [
429 NodeTypeContext::Base,
430 NodeTypeContext::Mesh,
431 NodeTypeContext::Light,
432 NodeTypeContext::Emitter,
433 ] {
434 assert_eq!(
435 controller_ascii_name(MdlControllerType::POSITION, ctx),
436 Some("position")
437 );
438 assert_eq!(
439 controller_ascii_name(MdlControllerType::ORIENTATION, ctx),
440 Some("orientation")
441 );
442 assert_eq!(
443 controller_ascii_name(MdlControllerType::SCALE, ctx),
444 Some("scale")
445 );
446 }
447 }
448
449 #[test]
450 fn code_100_disambiguation() {
451 assert_eq!(
453 controller_ascii_name(MdlControllerType::SELFILLUMCOLOR, NodeTypeContext::Mesh),
454 Some("selfillumcolor")
455 );
456 assert_eq!(
457 controller_ascii_name(
458 MdlControllerType::VERTICAL_DISPLACEMENT,
459 NodeTypeContext::Light
460 ),
461 Some("verticaldisplacement")
462 );
463 assert_eq!(
464 controller_ascii_name(MdlControllerType::DRAG, NodeTypeContext::Emitter),
465 Some("drag")
466 );
467 }
468
469 #[test]
470 fn reverse_lookup_case_insensitive() {
471 assert_eq!(
472 controller_from_ascii_name("Position", NodeTypeContext::Base),
473 Some(MdlControllerType::POSITION)
474 );
475 assert_eq!(
476 controller_from_ascii_name("SELFILLUMCOLOR", NodeTypeContext::Mesh),
477 Some(MdlControllerType::SELFILLUMCOLOR)
478 );
479 assert_eq!(
480 controller_from_ascii_name("birthrate", NodeTypeContext::Emitter),
481 Some(MdlControllerType::BIRTHRATE)
482 );
483 }
484
485 #[test]
486 fn unknown_controller_returns_none() {
487 assert_eq!(
488 controller_ascii_name(MdlControllerType::from_raw(9999), NodeTypeContext::Mesh),
489 None
490 );
491 }
492
493 #[test]
494 fn classification_roundtrip() {
495 for &(code, name) in CLASSIFICATIONS {
496 assert_eq!(classification_to_ascii(code), name);
497 assert_eq!(classification_from_ascii(name), Some(code));
498 }
499 }
500
501 #[test]
502 fn classification_case_insensitive() {
503 assert_eq!(classification_from_ascii("character"), Some(4));
504 assert_eq!(classification_from_ascii("CHARACTER"), Some(4));
505 }
506
507 #[test]
508 fn node_type_names() {
509 assert_eq!(node_type_ascii_name(&MdlNodeData::Base), "dummy");
510 assert_eq!(
511 node_type_ascii_name(&MdlNodeData::Mesh(Default::default())),
512 "trimesh"
513 );
514 }
515}