1use std::collections::{BTreeMap, BTreeSet};
13
14use serde::Serialize;
15use serde_json::Value;
16
17use crate::convert::{PartitionGroup, PartitionPlan};
18use crate::pass::{Given, OptionSpec, Options, Pass, PassLog, PassSpec};
19use crate::{
20 Component, Entity, Instance, JsonError, Mesh, MeshFile, Name, Resource, RigInstance, Skeleton, Skin,
21 SkinInstance, Source, Transform, World,
22};
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
26pub enum Role {
27 Part,
30 Rig,
32 Clip,
36 Alternative,
38}
39
40#[derive(Clone, Debug, Default, PartialEq, Serialize)]
42pub struct Asset {
43 pub members: Vec<(Entity, Role)>,
44}
45
46impl Component for Asset {
47 const NAME: &'static str = "core.asset";
48 const GEOMETRY: crate::Geometry = crate::Geometry::None;
49 fn to_json(&self) -> Result<Option<Value>, JsonError> {
50 crate::json::to_value(self).map(Some)
51 }
52 fn references(&self, out: &mut Vec<Entity>) {
53 out.extend(self.members.iter().map(|(e, _)| *e));
54 }
55 fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
61 let names = w.component_names();
62 let in_source_form = |m: Entity| {
63 names
64 .iter()
65 .any(|n| !n.starts_with("core.") && w.entity_has_named(m, n))
66 };
67 let mut seen = BTreeSet::new();
68 for &(m, role) in &self.members {
69 if m == e || !seen.insert(m) {
70 return Err(format!("member {m:?} is the root or listed twice"));
71 }
72 let rig = w.has::<Skeleton>(m);
74 let clip = w.has::<crate::Clip>(m);
75 let part = w.has::<Mesh>(m) || w.has::<MeshFile>(m) || w.has::<Instance>(m);
76 let (own, other) = match role {
77 Role::Rig => (rig, clip || part),
78 Role::Clip => (clip, rig || part),
79 Role::Part | Role::Alternative => (part, rig || clip),
80 };
81 let fits = own || (!other && in_source_form(m));
82 if !fits {
83 return Err(format!("member {m:?} is not what its role {role:?} says"));
84 }
85 }
86 Ok(())
87 }
88}
89
90#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
101pub struct PlaceAssets;
102
103pub const PLACE_ASSETS: PassSpec = PassSpec {
105 name: "place-assets",
106 options: &[],
107 make: |_| Ok(Box::new(PlaceAssets)),
108};
109
110impl Pass for PlaceAssets {
111 fn name(&self) -> &'static str {
112 "place-assets"
113 }
114 fn may_lack_inputs(&self) -> bool {
116 true
117 }
118 fn requires(&self) -> &[&'static str] {
119 &[Asset::NAME]
120 }
121 fn provides(&self) -> &[&'static str] {
122 &[
123 Instance::NAME,
124 Transform::NAME,
125 RigInstance::NAME,
126 SkinInstance::NAME,
127 ]
128 }
129 fn run(&self, w: &mut World, log: &mut PassLog) -> Result<(), String> {
130 let mut placed: BTreeSet<Entity> = w.iter::<Instance>().map(|(_, i)| i.prototype).collect();
132 let mut rigged: BTreeMap<Entity, Entity> = BTreeMap::new();
133 for (rig, r) in w.iter::<RigInstance>() {
134 rigged.entry(r.skeleton).or_insert(rig);
135 }
136 let assets: Vec<(Entity, Asset)> = w.iter::<Asset>().map(|(e, a)| (e, a.clone())).collect();
137 for (root, asset) in assets {
138 let mut rigs: Vec<(Entity, Entity)> = Vec::new();
139 for &(m, role) in &asset.members {
140 if role != Role::Rig {
141 continue;
142 }
143 if let Some(&rig) = rigged.get(&m) {
144 log.count("rigs already placed, left as they are", 1);
145 rigs.push((m, rig));
146 continue;
147 }
148 let rig = w.spawn();
149 w.insert(rig, RigInstance { skeleton: m });
150 w.insert(rig, Transform::IDENTITY);
151 log.place_from(rig, root);
152 rigged.insert(m, rig);
153 rigs.push((m, rig));
154 }
155 for &(m, role) in &asset.members {
156 match role {
157 Role::Alternative => log.count("alternative parts not placed (a selection chooses)", 1),
158 Role::Part if w.has::<Instance>(m) || placed.contains(&m) => {
159 log.count("parts the source places, left as they are", 1);
160 }
161 Role::Part => {
162 let p = w.spawn();
163 w.insert(
164 p,
165 Instance {
166 prototype: m,
167 variant: 0,
168 },
169 );
170 w.insert(p, Transform::IDENTITY);
171 let binding = w.get::<Skin>(m).and_then(|s| {
172 s.bindings.iter().find_map(|b| {
173 rigs.iter()
174 .find(|(skel, _)| *skel == b.skeleton)
175 .map(|&(_, rig)| (rig, b.id))
176 })
177 });
178 if let Some((rig, binding)) = binding {
179 w.insert(p, SkinInstance { rig, binding });
180 log.count("skinned parts placed with their rig", 1);
181 }
182 log.place_from(p, root);
183 log.count("parts placed", 1);
184 placed.insert(m);
185 }
186 Role::Rig | Role::Clip => {}
187 }
188 }
189 }
190 Ok(())
191 }
192}
193
194#[derive(Clone, Copy, Debug, PartialEq, Eq)]
196pub enum AssetField {
197 Key,
199 Name,
201}
202
203impl AssetField {
204 fn option(self) -> &'static str {
205 match self {
206 AssetField::Key => "keys",
207 AssetField::Name => "names",
208 }
209 }
210
211 fn of(self, w: &World, root: Entity) -> Option<&str> {
213 match self {
214 AssetField::Key => w.get::<Source>(root).map(|s| s.0.as_str()),
215 AssetField::Name => w.get::<Name>(root).map(|n| n.0.as_str()),
216 }
217 }
218}
219
220#[derive(Clone, Debug, PartialEq, Eq)]
228pub struct SelectAssets {
229 pub field: AssetField,
230 pub values: Vec<String>,
232}
233
234pub const SELECT_ASSETS: PassSpec = PassSpec {
236 name: "select-assets",
237 options: &[
238 OptionSpec::one_of(
239 "assets",
240 "keys",
241 "example",
242 "the assets' keys, `;` between, each once",
243 ),
244 OptionSpec::one_of(
245 "assets",
246 "names",
247 "example",
248 "the assets' names, `;` between, each once",
249 ),
250 ],
251 make: select_assets,
252};
253
254fn select_assets(g: &Given) -> Result<Box<dyn Pass>, String> {
255 let (k, field, v) = match (g.optional("keys"), g.optional("names")) {
256 (Some(v), _) => ("keys", AssetField::Key, v),
257 (None, Some(v)) => ("names", AssetField::Name, v),
258 (None, None) => unreachable!("the declaration's check gives one of keys and names"),
259 };
260 let mut values: Vec<String> = Vec::new();
261 for value in v.split(';').map(str::trim) {
262 if value.is_empty() {
263 return Err(format!("{k}={v}: an empty value"));
264 }
265 if values.iter().any(|x| x == value) {
266 return Err(format!("{k}={v}: {value} twice"));
267 }
268 values.push(value.to_owned());
269 }
270 Ok(Box::new(SelectAssets { field, values }))
271}
272
273impl Pass for SelectAssets {
274 fn name(&self) -> &'static str {
275 "select-assets"
276 }
277 fn options(&self) -> Options {
278 Options::from([(self.field.option().to_owned(), self.values.join(";"))])
279 }
280 fn requires(&self) -> &[&'static str] {
281 &[Asset::NAME]
282 }
283 fn despawns_entities(&self) -> bool {
284 true
285 }
286 fn run(&self, w: &mut World, log: &mut PassLog) -> Result<(), String> {
287 keep_assets(w, self.field, &self.values, log)
288 }
289}
290
291pub fn keep_assets(
295 w: &mut World,
296 field: AssetField,
297 values: &[String],
298 log: &mut PassLog,
299) -> Result<(), String> {
300 let roots: Vec<Entity> = w.iter::<Asset>().map(|(e, _)| e).collect();
301 let mut selected = BTreeSet::new();
302 for value in values {
303 let matched: Vec<Entity> = roots
304 .iter()
305 .copied()
306 .filter(|&r| field.of(w, r) == Some(value.as_str()))
307 .collect();
308 if matched.is_empty() {
309 let mut there: Vec<&str> = roots.iter().filter_map(|&r| field.of(w, r)).collect();
310 there.sort_unstable();
311 let shown = there.iter().take(20).copied().collect::<Vec<_>>().join(", ");
312 let more = there.len().saturating_sub(20);
313 return Err(format!(
314 "no asset whose {} is {value} (the world's: {shown}{})",
315 match field {
316 AssetField::Key => "core.source",
317 AssetField::Name => "core.name",
318 },
319 if more > 0 {
320 format!(" and {more} more")
321 } else {
322 String::new()
323 }
324 ));
325 }
326 selected.extend(matched);
327 }
328 let keep = w.closure(selected.iter().copied());
329 let gone: Vec<Entity> = w.entities().filter(|e| !keep.contains(e)).collect();
330 log.count("assets selected", selected.len() as u64);
331 log.count("assets left out", (roots.len() - selected.len()) as u64);
332 for e in gone {
333 crate::ledger::despawn(w, e, log, "not in a selected asset");
334 }
335 Ok(())
336}
337
338#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
347pub struct PartitionByAsset;
348
349pub const PARTITION_BY_ASSET: PassSpec = PassSpec {
351 name: "partition-by-asset",
352 options: &[],
353 make: |_| Ok(Box::new(PartitionByAsset)),
354};
355
356impl Pass for PartitionByAsset {
357 fn name(&self) -> &'static str {
358 "partition-by-asset"
359 }
360 fn requires(&self) -> &[&'static str] {
361 &[Asset::NAME]
362 }
363 fn provides(&self) -> &[&'static str] {
364 &[PartitionPlan::NAME]
365 }
366 fn modifies(&self) -> &[&'static str] {
367 &[PartitionPlan::NAME]
368 }
369 fn run(&self, w: &mut World, log: &mut PassLog) -> Result<(), String> {
370 let names = w.component_names();
372 let independent: Vec<Entity> = w
373 .unreferenced()
374 .into_iter()
375 .filter(|&e| names.iter().any(|n| w.entity_has_named(e, n)))
376 .collect();
377 let (assets, rest): (Vec<Entity>, Vec<Entity>) =
378 independent.into_iter().partition(|&e| w.has::<Asset>(e));
379 let groups: Vec<PartitionGroup> = assets
380 .iter()
381 .map(|&root| PartitionGroup {
382 key: AssetField::Key
383 .of(w, root)
384 .or_else(|| AssetField::Name.of(w, root))
385 .map_or_else(|| format!("asset {}", root.index()), str::to_owned),
386 roots: BTreeSet::from([root]),
387 context: BTreeSet::new(),
388 area: None,
389 })
390 .collect();
391 let mut groups = groups;
392 log.count("assets, each a part", assets.len() as u64);
393 if !rest.is_empty() {
394 log.count("content outside any asset, in its own part", rest.len() as u64);
395 groups.push(PartitionGroup {
396 key: "outside assets".into(),
397 roots: rest.into_iter().collect(),
398 context: BTreeSet::new(),
399 area: None,
400 });
401 }
402 let mut keys = BTreeSet::new();
404 if let Some(g) = groups.iter().find(|g| !keys.insert(g.key.as_str())) {
405 return Err(format!(
406 "two parts would be keyed {:?}: assets share a source or a name (or one is named as the content outside \
407 assets); give them distinct ones",
408 g.key
409 ));
410 }
411 w.insert_resource(PartitionPlan {
412 by: "partition-by-asset".into(),
413 groups,
414 });
415 Ok(())
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422 use crate::pass::{PassManager, PassRegistry, RunReport};
423 use crate::{
424 Binding, BindingId, Deformation, Influences, Joint, JointId, Primitive, Rest, Semantic, Slot, Stream,
425 };
426 use crate::{Topology, WeightRule};
427
428 fn world() -> (World, Entity, [Entity; 3], Entity) {
431 let mut w = World::new();
432 let skeleton = w.spawn();
433 w.insert(
434 skeleton,
435 Skeleton {
436 joints: vec![Joint {
437 id: JointId(0),
438 name: "root".into(),
439 parent: None,
440 rest: Rest::Affine(Transform::IDENTITY),
441 }],
442 },
443 );
444 let mesh = || {
445 Mesh::new(vec![Primitive {
446 topology: Topology::Points,
447 streams: [(Semantic::Position, Stream::F32x3(vec![[0.0; 3]]))].into(),
448 indices: None,
449 material: None,
450 }])
451 };
452 let parts = [w.spawn(), w.spawn(), w.spawn()];
453 for &p in &parts {
454 w.insert(p, mesh());
455 }
456 w.insert(
457 parts[0],
458 Skin {
459 influences: vec![Some(Influences::Whole { slot: 0 })],
460 weights: WeightRule::SumsToOne,
461 bindings: vec![Binding {
462 id: BindingId(2),
463 skeleton,
464 palettes: vec![Some(vec![Slot {
465 joint: JointId(0),
466 inverse_bind: Transform::IDENTITY,
467 }])],
468 deformation: Deformation::Linear,
469 }],
470 },
471 );
472 let root = w.spawn();
473 w.insert(
474 root,
475 Asset {
476 members: vec![
477 (skeleton, Role::Rig),
478 (parts[0], Role::Part),
479 (parts[1], Role::Part),
480 (parts[2], Role::Alternative),
481 ],
482 },
483 );
484 (w, root, parts, skeleton)
485 }
486
487 #[test]
488 fn an_asset_reaches_its_members_and_verifies() {
489 let (mut w, root, parts, skeleton) = world();
490 assert_eq!(w.verify_components(), vec![]);
491 assert!(
492 w.closure([root])
493 .is_superset(&[skeleton, parts[0], parts[1], parts[2]].into())
494 );
495 w.get_mut::<Asset>(root)
496 .unwrap()
497 .members
498 .push((parts[0], Role::Part));
499 assert!(
500 w.verify_components()
501 .iter()
502 .any(|(_, _, m)| m.contains("listed twice"))
503 );
504 w.get_mut::<Asset>(root).unwrap().members.pop();
505 w.get_mut::<Asset>(root).unwrap().members[0].1 = Role::Clip;
506 assert!(
507 w.verify_components()
508 .iter()
509 .any(|(_, _, m)| m.contains("not what its role"))
510 );
511 }
512
513 #[test]
516 fn a_member_in_its_sources_form_fits_its_role() {
517 let (mut w, root, parts, _) = world();
518 w.remove::<Mesh>(parts[1]);
519 w.insert(parts[1], Name("model".into()));
520 assert!(
521 w.verify_components()
522 .iter()
523 .any(|(_, _, m)| m.contains("not what its role Part says"))
524 );
525 w.insert_opaque(
526 parts[1],
527 "source.model",
528 crate::Opaque {
529 version: 1,
530 data: crate::OpaqueData::Bytes(vec![1, 2, 3]),
531 },
532 )
533 .unwrap();
534 assert_eq!(w.verify_components(), vec![]);
535 assert!(w.closure([root]).contains(&parts[1]));
536 let clip_member = parts[0];
538 w.get_mut::<Asset>(root)
539 .unwrap()
540 .members
541 .iter_mut()
542 .find(|(m, _)| *m == clip_member)
543 .unwrap()
544 .1 = Role::Clip;
545 w.insert_opaque(
546 clip_member,
547 "source.clip",
548 crate::Opaque {
549 version: 1,
550 data: crate::OpaqueData::Bytes(vec![4]),
551 },
552 )
553 .unwrap();
554 assert!(
555 w.verify_components()
556 .iter()
557 .any(|(_, _, m)| m.contains("not what its role Clip says")),
558 "{:?}",
559 w.verify_components()
560 );
561 }
562
563 fn registry() -> PassManager {
564 let mut r = PassRegistry::new();
565 crate::register(&mut r);
566 PassManager::new(r).with_pass_checks(true)
567 }
568
569 fn two_assets() -> (World, [Entity; 6]) {
572 let mut w = World::new();
573 let material = w.spawn();
574 w.insert(material, Name("m".into()));
575 let mut part = || {
576 let p = w.spawn();
577 w.insert(
578 p,
579 Mesh::new(vec![Primitive {
580 topology: Topology::Points,
581 streams: [(Semantic::Position, Stream::F32x3(vec![[0.0; 3]]))].into(),
582 indices: None,
583 material: Some(material),
584 }]),
585 );
586 p
587 };
588 let (pa, pb) = (part(), part());
589 let mut root = |key: &str, name: &str, p: Entity| {
590 let r = w.spawn();
591 w.insert(r, Source(key.into()));
592 w.insert(r, Name(name.into()));
593 w.insert(
594 r,
595 Asset {
596 members: vec![(p, Role::Part)],
597 },
598 );
599 r
600 };
601 let (ra, rb) = (root("a", "A", pa), root("b", "B", pb));
602 let placement = w.spawn();
603 w.insert(
604 placement,
605 Instance {
606 prototype: pb,
607 variant: 0,
608 },
609 );
610 w.insert(placement, Transform::IDENTITY);
611 (w, [ra, pa, rb, pb, material, placement])
612 }
613
614 #[test]
617 fn select_assets_keeps_what_the_chosen_assets_reach() {
618 let m = registry();
619 for (pipeline, kept) in [
620 ("select-assets{keys=a}", [0, 1, 4]),
621 ("select-assets{names=B}", [2, 3, 4]),
622 ] {
623 let (mut w, e) = two_assets();
624 let mut report = RunReport::default();
625 m.run(&mut w, &m.registry.parse(pipeline).unwrap(), &mut report)
626 .unwrap();
627 let alive: BTreeSet<Entity> = w.entities().collect();
628 assert_eq!(alive, kept.map(|i| e[i]).into(), "{pipeline}");
629 assert_eq!(w.verify_components(), vec![], "{pipeline}");
630 let log = &report.steps[0].log;
631 assert_eq!(
632 (log.counts["assets selected"], log.counts["assets left out"]),
633 (1, 1)
634 );
635 }
636 let (mut w, e) = two_assets();
638 let mut report = RunReport::default();
639 m.run(
640 &mut w,
641 &m.registry.parse("select-assets{keys=b;a}").unwrap(),
642 &mut report,
643 )
644 .unwrap();
645 assert_eq!(
646 w.entities().collect::<BTreeSet<_>>(),
647 e[..5].iter().copied().collect()
648 );
649 }
650
651 #[test]
652 fn select_assets_refuses_a_value_no_asset_has_and_bad_options() {
653 let m = registry();
654 let (mut w, _) = two_assets();
655 let err = m
656 .run(
657 &mut w,
658 &m.registry.parse("select-assets{keys=a;c}").unwrap(),
659 &mut RunReport::default(),
660 )
661 .unwrap_err()
662 .to_string();
663 assert!(
664 err.contains("no asset whose core.source is c (the world's: a, b)"),
665 "{err}"
666 );
667 for (bad, why) in [
668 ("select-assets", "one of keys or names is required"),
669 (
670 "select-assets{keys=a,names=A}",
671 "only one of keys or names may be given",
672 ),
673 ("select-assets{keys=a;;b}", "an empty value"),
674 ("select-assets{keys=a;a}", "a twice"),
675 (
676 "select-assets{key=a}",
677 "unknown option key (select-assets takes keys, names)",
678 ),
679 ] {
680 let err = m.registry.parse(bad).unwrap_err().to_string();
681 assert!(err.contains(why), "{bad}: {err}");
682 }
683 let p = m.registry.parse("select-assets{names=B;A}").unwrap();
685 assert_eq!(p.to_string(), "select-assets{names=B;A}");
686 }
687
688 #[test]
690 fn partition_by_asset_plans_a_part_per_asset() {
691 let m = registry();
692 let (mut w, e) = two_assets();
693 w.remove::<Source>(e[2]);
694 let mut report = RunReport::default();
695 m.run(
696 &mut w,
697 &m.registry.parse("partition-by-asset").unwrap(),
698 &mut report,
699 )
700 .unwrap();
701 let plan = w.resource::<PartitionPlan>().unwrap();
702 assert_eq!(plan.by, "partition-by-asset");
703 let groups: Vec<(&str, Vec<Entity>)> = plan
704 .groups
705 .iter()
706 .map(|g| (g.key.as_str(), g.roots.iter().copied().collect()))
707 .collect();
708 assert_eq!(
709 groups,
710 [
711 ("a", vec![e[0]]),
712 ("B", vec![e[2]]),
713 ("outside assets", vec![e[5]])
714 ]
715 );
716 assert!(
717 plan.groups
718 .iter()
719 .all(|g| g.context.is_empty() && g.area.is_none())
720 );
721 let log = &report.steps[0].log;
722 assert_eq!(log.counts["assets, each a part"], 2);
723 assert_eq!(log.counts["content outside any asset, in its own part"], 1);
724 }
725
726 #[test]
728 fn partition_by_asset_refuses_two_parts_of_one_key() {
729 let m = registry();
730 let (mut w, e) = two_assets();
731 w.insert(e[2], Source("a".into()));
732 let err = m
733 .run(
734 &mut w,
735 &m.registry.parse("partition-by-asset").unwrap(),
736 &mut RunReport::default(),
737 )
738 .unwrap_err();
739 assert!(err.to_string().contains("keyed \"a\""), "{err}");
740 assert!(w.resource::<PartitionPlan>().is_none());
741 }
742
743 #[test]
746 fn place_assets_places_rigs_and_parts() {
747 let (mut w, _, parts, skeleton) = world();
748 let mut r = PassRegistry::new();
749 r.add(&PLACE_ASSETS);
750 let m = PassManager::new(r).with_pass_checks(true);
751 let mut report = RunReport::default();
752 m.run(&mut w, &m.registry.parse("place-assets").unwrap(), &mut report)
753 .unwrap();
754 let rigs: Vec<_> = w.iter::<RigInstance>().collect();
755 assert_eq!(rigs.len(), 1);
756 assert_eq!(rigs[0].1.skeleton, skeleton);
757 let placed: Vec<Entity> = w.iter::<Instance>().map(|(_, i)| i.prototype).collect();
758 assert_eq!(placed, [parts[0], parts[1]]);
759 let skinned: Vec<_> = w.iter::<SkinInstance>().collect();
760 assert_eq!(skinned.len(), 1);
761 assert_eq!(
762 (skinned[0].1.rig, skinned[0].1.binding),
763 (rigs[0].0, BindingId(2))
764 );
765 assert_eq!(w.verify_components(), vec![]);
766 let log = &report.steps[0].log;
767 assert_eq!(
768 log.counts["alternative parts not placed (a selection chooses)"],
769 1
770 );
771 assert_eq!(log.placed.len(), 3);
772 let mut again = RunReport::default();
774 m.run(&mut w, &m.registry.parse("place-assets").unwrap(), &mut again)
775 .unwrap();
776 assert_eq!(
777 again.steps[0].log.counts["parts the source places, left as they are"],
778 2
779 );
780 assert_eq!(w.iter::<RigInstance>().count(), 1);
782 assert_eq!(
783 again.steps[0].log.counts["rigs already placed, left as they are"],
784 1
785 );
786 assert!(again.steps[0].log.placed.is_empty());
787 }
788}