1use super::interference::{displays_pierce, overlaps, posed_bbox};
40use super::pmi_section::{clip_display_all, side_of_all, Side};
41use super::{component_move::now_ms, EngineState};
42use crate::camera::Aabb;
43use crate::scene::SolidDisplay;
44use brep_kernel::{PmiCamera, PmiProjection};
45use brep_simulation::model::Study;
46use brep_simulation::sample::SectionSample;
47use brep_simulation::FrameSample;
48use serde_json::{json, Map, Value};
49use std::collections::{BTreeMap, HashMap};
50
51const BAKE_BUDGET_MS: f64 = 12.0;
54
55#[derive(Debug, Clone, PartialEq)]
58pub struct BakedFrame {
59 pub poses: BTreeMap<String, [f64; 16]>,
60 pub failed: bool,
62 pub clashes: Vec<(String, String)>,
66}
67
68pub(super) const IDENTITY: [f64; 16] = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0];
70
71const CLASH_TINT: [f32; 3] = [0.92, 0.25, 0.2];
73
74#[derive(Debug)]
77pub struct SimulationPlayback {
78 pub study: String,
79 baked_study: Study,
82 revision: u64,
83 base: HashMap<String, f64>,
85 pub frames: Vec<BakedFrame>,
86 total: usize,
87 reference: Map<String, Value>,
89 pub time: f64,
91 pub playing: bool,
92 pub looping: bool,
93 shown: Option<usize>,
95 wanted: Option<usize>,
97 originals: HashMap<String, SolidDisplay>,
99 applied: HashMap<String, [f64; 16]>,
101 pub error: Option<String>,
103 participants: Vec<(String, Vec<String>)>,
106 tinted: Vec<String>,
108 hidden: Vec<String>,
110 translucent: Vec<String>,
112 sectioned: Vec<String>,
115 camera_before: Option<PmiCamera>,
118 shown_sample: Option<FrameSample>,
120}
121
122impl SimulationPlayback {
123 pub(crate) fn baked_study(&self) -> &Study {
125 &self.baked_study
126 }
127
128 pub(crate) fn revision(&self) -> u64 {
130 self.revision
131 }
132
133 pub(crate) fn base(&self) -> &HashMap<String, f64> {
135 &self.base
136 }
137
138 pub(crate) fn original(&self, name: &str) -> Option<&SolidDisplay> {
140 self.originals.get(name)
141 }
142}
143
144impl EngineState {
145 pub(crate) fn simulation_playback(&self) -> Option<&SimulationPlayback> {
147 self.simulation_play.as_ref()
148 }
149
150 pub fn simulation_bake_all(&mut self, study: Option<&str>) -> Result<String, String> {
154 let id = self.simulation_prepare(study)?;
155 if let Some(play) = self.simulation_play.as_mut() {
156 play.playing = false;
157 }
158 self.ensure_assembly_session();
159 let mut guard = 0usize;
160 while self.simulation_play.as_ref().is_some_and(|play| play.frames.len() < play.total) {
161 guard += 1;
162 if guard > 1_000_000 {
163 return Err("the bake did not finish".into());
164 }
165 self.simulation_bake_next();
166 }
167 Ok(id)
168 }
169
170 pub(super) fn simulation_keep_bake_through_publish(&mut self, study: &str, motion: Value) {
174 let revision = self.history.revision();
175 if let Some(play) = self.simulation_play.as_mut().filter(|play| play.study == study) {
176 play.baked_study.motion = Some(motion);
177 play.revision = revision;
178 }
179 }
180
181 pub fn simulation_play(&mut self, study: Option<&str>, looping: Option<bool>) -> Result<String, String> {
185 let id = self.simulation_prepare(study)?;
186 let play = self.simulation_play.as_mut().expect("prepared");
187 if let Some(looping) = looping {
188 play.looping = looping;
189 }
190 if play.time >= play.baked_study.duration - 1e-9 {
191 play.time = 0.0;
192 }
193 play.playing = true;
194 play.wanted = Some(play.baked_study.frame_at(play.time));
195 Ok(id)
196 }
197
198 pub fn simulation_pause(&mut self) {
200 if let Some(play) = self.simulation_play.as_mut() {
201 play.playing = false;
202 }
203 }
204
205 pub fn simulation_stop(&mut self) {
208 self.simulation_restore_displays();
209 if let Some(play) = self.simulation_play.as_mut() {
210 play.playing = false;
211 play.time = 0.0;
212 play.wanted = None;
213 }
214 }
215
216 pub fn simulation_seek(&mut self, study: Option<&str>, time: f64) -> Result<(), String> {
219 if !time.is_finite() {
220 return Err("seek: the time is not a number".into());
221 }
222 self.simulation_prepare(study)?;
223 let play = self.simulation_play.as_mut().expect("prepared");
224 play.playing = false;
225 play.time = time.clamp(0.0, play.baked_study.duration);
226 play.wanted = Some(play.baked_study.frame_at(play.time));
227 self.simulation_show_wanted();
228 Ok(())
229 }
230
231 pub fn simulation_set_looping(&mut self, study: Option<&str>, looping: bool) -> Result<(), String> {
233 self.simulation_prepare(study)?;
234 self.simulation_play.as_mut().expect("prepared").looping = looping;
235 Ok(())
236 }
237
238 pub fn simulation_playing_or_shown(&self) -> bool {
240 self.simulation_play.as_ref().is_some_and(|play| play.playing || play.shown.is_some())
241 }
242
243 fn simulation_prepare(&mut self, study: Option<&str>) -> Result<String, String> {
246 let block = self.simulation()?.ok_or("no study yet: New study starts one")?;
247 let study = match study {
248 Some(id) => block.study(id).ok_or_else(|| format!("no study '{id}'"))?,
249 None => block.active_study().ok_or("no study yet: New study starts one")?,
250 }
251 .clone();
252 let revision = self.history.revision();
253 let current = self
254 .simulation_play
255 .as_ref()
256 .is_some_and(|play| play.study == study.id && play.revision == revision && play.baked_study == study);
257 if current {
258 return Ok(study.id);
259 }
260 self.simulation_restore_displays();
264 if self.pmi_active_view.is_some() {
265 self.pmi_deactivate_view();
266 }
267 let previous = self.simulation_play.take();
268 let (time, looping) = previous
269 .filter(|play| play.study == study.id)
270 .map_or((0.0, false), |play| (play.time.min(study.duration), play.looping));
271 let base = self.simulation_motor_mates().into_iter().map(|mate| (mate.id, mate.value)).collect();
272 let participants = if study.interference.enabled { self.interference_participants() } else { Vec::new() };
273 let total = study.frame_count();
274 let id = study.id.clone();
275 self.simulation_play = Some(SimulationPlayback {
276 study: id.clone(),
277 baked_study: study,
278 revision,
279 base,
280 frames: Vec::with_capacity(total),
281 total,
282 reference: Map::new(),
283 time,
284 playing: false,
285 looping,
286 shown: None,
287 wanted: None,
288 originals: HashMap::new(),
289 applied: HashMap::new(),
290 error: None,
291 participants,
292 tinted: Vec::new(),
293 hidden: Vec::new(),
294 translucent: Vec::new(),
295 sectioned: Vec::new(),
296 camera_before: None,
297 shown_sample: None,
298 });
299 Ok(id)
300 }
301
302 pub fn simulation_tick(&mut self, dt: f64) -> bool {
306 let Some(play) = self.simulation_play.as_ref() else { return false };
307 if play.revision != self.history.revision() {
309 let (study, time, playing, looping) = (play.study.clone(), play.time, play.playing, play.looping);
310 self.simulation_restore_displays();
311 self.simulation_play = None;
312 if self.simulation_prepare(Some(&study)).is_err() {
313 return false;
314 }
315 let play = self.simulation_play.as_mut().expect("prepared");
316 play.time = time.min(play.baked_study.duration);
317 play.playing = playing;
318 play.looping = looping;
319 play.wanted = Some(play.baked_study.frame_at(play.time));
320 }
321 let start = now_ms();
322 if self.simulation_play.as_ref().is_some_and(|play| play.frames.len() < play.total) {
323 self.ensure_assembly_session();
327 }
328 while self.simulation_play.as_ref().is_some_and(|play| play.frames.len() < play.total) && now_ms() - start < BAKE_BUDGET_MS {
329 self.simulation_bake_next();
330 }
331 let play = self.simulation_play.as_mut().expect("checked above");
332 if play.playing {
333 let duration = play.baked_study.duration;
334 play.time += dt.max(0.0);
335 if play.time >= duration - 1e-9 {
336 if play.looping {
337 play.time = (play.time % duration).max(0.0);
338 } else {
339 play.time = duration;
340 play.playing = false;
341 }
342 }
343 let baked = play.frames.len();
345 if play.baked_study.frame_at(play.time) >= baked {
346 play.time = play.baked_study.frame_time(baked.saturating_sub(1));
347 if baked == 0 {
348 play.time = 0.0;
349 }
350 }
351 play.wanted = Some(play.baked_study.frame_at(play.time));
352 }
353 self.simulation_show_wanted();
354 let play = self.simulation_play.as_ref().expect("checked above");
355 play.playing || play.frames.len() < play.total
356 }
357
358 pub(super) fn simulation_bake_next(&mut self) {
361 let Some(play) = self.simulation_play.as_mut() else { return };
362 let index = play.frames.len();
363 if index >= play.total {
364 return;
365 }
366 let time = play.baked_study.frame_time(index);
367 let base = &play.base;
368 let sample = play.baked_study.sample(time, &|id| base.get(id).copied());
369 let mut overrides = Map::new();
370 for (id, mate) in &sample.mates {
371 let mut change = Map::new();
372 if let Some(value) = mate.value {
373 change.insert("value".into(), json!(value));
374 }
375 if let Some(enabled) = mate.enabled {
376 change.insert("enabled".into(), json!(enabled));
377 }
378 overrides.insert(id.clone(), Value::Object(change));
379 }
380 let request = json!({ "overrides": overrides, "reference": play.reference });
381 let (poses, failed): (BTreeMap<String, [f64; 16]>, bool) = match brep_kernel::assembly_frame_solve_impl(&request.to_string()) {
382 Ok(reply) => {
383 let reply: Value = serde_json::from_str(&reply).unwrap_or(Value::Null);
384 let poses_json = reply.get("poses").and_then(Value::as_object).cloned().unwrap_or_default();
385 let poses = poses_json.iter().map(|(id, pose)| (id.clone(), crate::pipeline::pose_params_to_transform(pose))).collect();
386 play.reference = poses_json;
387 (poses, false)
388 }
389 Err(error) => {
390 let poses = play.frames.last().map(|frame| frame.poses.clone()).unwrap_or_default();
391 play.error = Some(format!("frame {index} ({time:.3} s): {error}"));
392 (poses, true)
393 }
394 };
395 let participants = std::mem::take(&mut play.participants);
398 let previous = play.frames.last().cloned();
399 let clashes = if participants.is_empty() { Vec::new() } else { self.simulation_frame_clashes(&participants, &poses, previous.as_ref()) };
400 let Some(play) = self.simulation_play.as_mut() else { return };
401 play.participants = participants;
402 play.frames.push(BakedFrame { poses, failed, clashes });
403 }
404
405 fn simulation_frame_clashes(&self, participants: &[(String, Vec<String>)], poses: &BTreeMap<String, [f64; 16]>, previous: Option<&BakedFrame>) -> Vec<(String, String)> {
414 let originals = self.simulation_play.as_ref().map(|play| &play.originals);
415 let display_of = |name: &String| -> Option<&SolidDisplay> { originals.and_then(|originals| originals.get(name)).or_else(|| self.scene.solid(name)) };
416 let delta_of = |occ: &str| -> [f64; 16] {
417 let top = occ.split(':').next().unwrap_or(occ);
418 let (key, world) = match poses.get(occ) {
419 Some(world) => (occ, world),
420 None => match poses.get(top) {
421 Some(world) => (top, world),
422 None => return IDENTITY,
423 },
424 };
425 match self.occurrence_document_matrix(key) {
426 Some(document) => mat_mul(world, &rigid_inverse(&document)),
427 None => IDENTITY,
428 }
429 };
430 let mut posed: Vec<(&str, [f64; 16], Aabb, &[String])> = Vec::new();
431 for (occ, solids) in participants {
432 let delta = delta_of(occ);
433 let mut bbox = Aabb::empty();
434 for name in solids {
435 if let Some(solid) = display_of(name) {
436 bbox.union(&posed_bbox(&solid.bbox, &delta));
437 }
438 }
439 if !bbox.is_empty() {
440 posed.push((occ.as_str(), delta, bbox, solids.as_slice()));
441 }
442 }
443 let same_as_previous = |occ: &str, prev: &BakedFrame| {
444 let top = occ.split(':').next().unwrap_or(occ);
445 poses.get(occ) == prev.poses.get(occ) && poses.get(top) == prev.poses.get(top)
446 };
447 let mut clashes = Vec::new();
448 for i in 0..posed.len() {
449 for j in i + 1..posed.len() {
450 if !overlaps(&posed[i].2, &posed[j].2) {
451 continue;
452 }
453 let pair = (posed[i].0.to_string(), posed[j].0.to_string());
454 if let Some(prev) = previous {
455 if same_as_previous(&pair.0, prev) && same_as_previous(&pair.1, prev) {
456 if prev.clashes.contains(&pair) {
457 clashes.push(pair);
458 }
459 continue;
460 }
461 }
462 let hit = posed[i].3.iter().filter_map(display_of).any(|a| posed[j].3.iter().filter_map(display_of).any(|b| displays_pierce(a, &posed[i].1, b, &posed[j].1)));
463 if hit {
464 clashes.push(pair);
465 }
466 }
467 }
468 clashes
469 }
470
471 fn simulation_show_wanted(&mut self) {
473 let Some(play) = self.simulation_play.as_ref() else { return };
474 let Some(wanted) = play.wanted else { return };
475 if wanted >= play.frames.len() || play.shown == Some(wanted) {
476 return;
477 }
478 self.simulation_pose_frame(wanted);
479 }
480
481 pub(crate) fn simulation_frame_worlds(&self, index: usize) -> Option<BTreeMap<String, [f64; 16]>> {
488 let play = self.simulation_play.as_ref()?;
489 let frame = play.frames.get(index)?;
490 let mut worlds = frame.poses.clone();
491 let time = play.baked_study.frame_time(index);
492 let base = &play.base;
493 let sample = play.baked_study.sample(time, &|id| base.get(id).copied());
494 for explode in &sample.explode {
495 let Some(view) = self.exploded_views().into_iter().find(|view| view.id == explode.view) else { continue };
496 let steps: Vec<_> = view.steps.iter().filter(|step| step.enabled).collect();
497 let count = steps.len() as f64;
498 for (k, step) in steps.iter().enumerate() {
499 let fraction = (explode.progress * count - k as f64).clamp(0.0, 1.0);
500 if fraction <= 0.0 {
501 continue;
502 }
503 for target in &step.targets {
504 let Some(world) = worlds.get(target).copied().or_else(|| self.occurrence_document_matrix(target)) else { continue };
505 let mut shift = IDENTITY;
506 for axis in 0..3 {
507 shift[axis * 4 + 3] = step.translate[axis] * fraction;
508 }
509 worlds.insert(target.clone(), mat_mul(&shift, &world));
510 }
511 }
512 }
513 Some(worlds)
514 }
515
516 fn simulation_pose_frame(&mut self, index: usize) {
520 let Some(play) = self.simulation_play.as_ref() else { return };
521 if index >= play.frames.len() {
522 return;
523 }
524 let time = play.baked_study.frame_time(index);
525 let base = play.base.clone();
526 let sample = play.baked_study.sample(time, &|id| base.get(id).copied());
527 let Some(worlds) = self.simulation_frame_worlds(index) else { return };
528 let Some(play) = self.simulation_play.as_ref() else { return };
529 let frame = worlds;
530 let gone: Vec<String> = play.applied.keys().filter(|id| !frame.contains_key(*id)).cloned().collect();
532 self.simulation_unsection();
533 let Some(play) = self.simulation_play.as_ref() else { return };
534 let mut changed = false;
535 for id in gone {
536 self.simulation_pose_occurrence(&id, None);
537 changed = true;
538 }
539 for (id, world) in &frame {
540 let Some(document) = self.occurrence_document_matrix(id) else { continue };
541 let shown = self.simulation_play.as_ref().and_then(|play| play.applied.get(id).copied()).unwrap_or(document);
542 if same_matrix(&shown, world) {
543 continue;
544 }
545 if same_matrix(world, &document) {
546 self.simulation_pose_occurrence(id, None);
548 } else {
549 let delta = mat_mul(world, &rigid_inverse(&document));
550 self.simulation_pose_occurrence(id, Some((delta, *world)));
551 }
552 changed = true;
553 }
554 if let Some(play) = self.simulation_play.as_mut() {
555 play.shown = Some(index);
556 }
557 if changed {
558 self.dirty = true;
559 }
560 self.simulation_apply_section(sample.section.as_ref());
561 self.simulation_tint_clashes(index);
562 self.simulation_apply_hidden(&sample.hidden);
563 self.simulation_apply_opacity(&sample.opacity);
564 if let Some(camera) = &sample.camera {
565 self.simulation_apply_camera(&camera.from, &camera.to, camera.t);
566 }
567 if let Some(play) = self.simulation_play.as_mut() {
568 play.shown_sample = Some(sample);
569 }
570 }
571
572 fn simulation_unsection(&mut self) {
576 let Some(play) = self.simulation_play.as_mut() else { return };
577 let sectioned = std::mem::take(&mut play.sectioned);
578 if sectioned.is_empty() {
579 return;
580 }
581 let applied: Vec<(String, [f64; 16])> = play.applied.iter().map(|(id, world)| (id.clone(), *world)).collect();
582 let mut reposed: Vec<String> = Vec::new();
583 for name in §ioned {
584 if let (Some(original), Some(display)) = (play.originals.get(name), self.scene.solid_mut(name)) {
585 *display = original.clone();
586 }
587 if let Some((id, _)) = applied.iter().filter(|(id, _)| name.starts_with(&format!("{id}:"))).max_by_key(|(id, _)| id.len()) {
589 if !reposed.contains(id) {
590 reposed.push(id.clone());
591 }
592 }
593 }
594 for id in reposed {
595 let Some(world) = applied.iter().find(|(key, _)| *key == id).map(|(_, world)| *world) else { continue };
596 let Some(document) = self.occurrence_document_matrix(&id) else { continue };
597 self.simulation_pose_occurrence(&id, Some((mat_mul(&world, &rigid_inverse(&document)), world)));
598 }
599 self.dirty = true;
600 }
601
602 fn simulation_apply_section(&mut self, section: Option<&SectionSample>) {
607 let Some(section) = section else { return };
608 let state = self.pmi_state_shared();
609 let Some(view) = state.find_view(§ion.view) else { return };
610 let report = self.pmi_report.as_ref().and_then(|report| report.view(§ion.view));
611 let planes: Vec<([f64; 3], [f64; 3])> = view
612 .section_planes_in(report)
613 .into_iter()
614 .map(|(point, normal)| (std::array::from_fn(|i| point[i] + normal[i] * section.offset), normal))
615 .collect();
616 if planes.is_empty() {
617 return;
618 }
619 let exclude = view.display.section.clone();
620 let names: Vec<(String, Side)> = self
621 .scene
622 .solids()
623 .iter()
624 .filter(|solid| solid.visible)
625 .map(|solid| (solid.name.clone(), if exclude.as_ref().is_some_and(|cut| cut.excludes(&solid.name)) { Side::Kept } else { side_of_all(solid, &planes) }))
626 .collect();
627 let mut sectioned = Vec::new();
628 for (name, side) in names {
629 if side == Side::Kept {
630 continue;
631 }
632 let Some(display) = self.scene.solid(&name).cloned() else { continue };
633 if let Some(play) = self.simulation_play.as_mut() {
634 play.originals.entry(name.clone()).or_insert(display);
635 }
636 if let Some(display) = self.scene.solid_mut(&name) {
637 match side {
638 Side::Removed => display.visible = false,
639 Side::Cut => clip_display_all(display, &planes),
640 Side::Kept => {}
641 }
642 display.revision = crate::scene::next_revision();
643 }
644 sectioned.push(name);
645 }
646 if let Some(play) = self.simulation_play.as_mut() {
647 play.sectioned = sectioned;
648 }
649 self.dirty = true;
650 }
651
652 fn simulation_apply_opacity(&mut self, opacity: &BTreeMap<String, f64>) {
655 let mut want: Vec<(String, f32)> = Vec::new();
656 for (id, value) in opacity {
657 for name in self.simulation_occurrence_solids(id) {
658 want.push((name, value.clamp(0.0, 1.0) as f32));
659 }
660 }
661 want.sort_by(|a, b| a.0.cmp(&b.0));
662 want.dedup_by(|a, b| a.0 == b.0);
663 let Some(play) = self.simulation_play.as_ref() else { return };
664 let previous = play.translucent.clone();
665 let mut changed = false;
666 for name in previous.iter().filter(|name| !want.iter().any(|(n, _)| n == *name)) {
667 let opacity = self.simulation_play.as_ref().and_then(|play| play.originals.get(name)).map(|original| original.opacity);
668 if let (Some(opacity), Some(display)) = (opacity, self.scene.solid_mut(name)) {
669 if display.opacity != opacity {
670 display.opacity = opacity;
671 display.revision = crate::scene::next_revision();
672 changed = true;
673 }
674 }
675 }
676 for (name, value) in &want {
677 let Some(display) = self.scene.solid(name).cloned() else { continue };
678 if let Some(play) = self.simulation_play.as_mut() {
679 play.originals.entry(name.clone()).or_insert(display);
680 }
681 if let Some(display) = self.scene.solid_mut(name) {
682 if display.opacity != Some(*value) {
683 display.opacity = Some(*value);
684 display.revision = crate::scene::next_revision();
685 changed = true;
686 }
687 }
688 }
689 if let Some(play) = self.simulation_play.as_mut() {
690 play.translucent = want.into_iter().map(|(name, _)| name).collect();
691 }
692 if changed {
693 self.dirty = true;
694 }
695 }
696
697 fn simulation_occurrence_solids(&self, id: &str) -> Vec<String> {
700 if id.contains(':') {
701 let prefix = format!("{id}:");
702 self.scene.solids().iter().filter(|solid| solid.name.starts_with(&prefix)).map(|solid| solid.name.clone()).collect()
703 } else {
704 self.member_solid_names(id)
705 }
706 }
707
708 fn simulation_apply_hidden(&mut self, occurrences: &[String]) {
712 let mut want: Vec<String> = occurrences.iter().flat_map(|id| self.simulation_occurrence_solids(id)).collect();
713 want.sort();
714 want.dedup();
715 let Some(play) = self.simulation_play.as_ref() else { return };
716 let previous = play.hidden.clone();
717 let mut changed = false;
718 for name in previous.iter().filter(|name| !want.contains(name)) {
719 let visible = self.simulation_play.as_ref().and_then(|play| play.originals.get(name)).map(|original| original.visible);
720 if let (Some(visible), Some(display)) = (visible, self.scene.solid_mut(name)) {
721 if display.visible != visible {
722 display.visible = visible;
723 changed = true;
724 }
725 }
726 }
727 for name in &want {
728 let Some(display) = self.scene.solid(name).cloned() else { continue };
729 if let Some(play) = self.simulation_play.as_mut() {
730 play.originals.entry(name.clone()).or_insert(display);
731 }
732 if let Some(display) = self.scene.solid_mut(name) {
733 if display.visible {
734 display.visible = false;
735 changed = true;
736 }
737 }
738 }
739 if let Some(play) = self.simulation_play.as_mut() {
740 play.hidden = want;
741 }
742 if changed {
743 self.dirty = true;
744 }
745 }
746
747 fn simulation_apply_camera(&mut self, from: &str, to: &str, t: f64) {
752 let Some(camera) = self.simulation_camera_between(from, to, t) else { return };
753 let standing = self.snapshot_camera();
754 if let Some(play) = self.simulation_play.as_mut() {
755 if play.camera_before.is_none() {
756 play.camera_before = Some(standing);
757 }
758 }
759 self.simulation_apply_pmi_camera(&camera);
760 }
761
762 pub(crate) fn simulation_camera_between(&self, from: &str, to: &str, t: f64) -> Option<PmiCamera> {
766 let state = self.pmi_state_shared();
767 let a = state.find_view(from).and_then(|v| v.camera.clone())?;
768 let b = state.find_view(to).and_then(|v| v.camera.clone())?;
769 let t = t.clamp(0.0, 1.0);
770 let lerp = |p: [f64; 3], q: [f64; 3]| -> [f64; 3] { std::array::from_fn(|i| p[i] + (q[i] - p[i]) * t) };
771 let scale_of = |camera: &PmiCamera| match camera.projection {
772 PmiProjection::Orthographic { half_height } => half_height,
773 PmiProjection::Perspective { fov_y_deg } => fov_y_deg,
774 };
775 let orthographic = |camera: &PmiCamera| matches!(camera.projection, PmiProjection::Orthographic { .. });
776 let same_kind = orthographic(&a) == orthographic(&b);
777 let (ortho, scale) = if same_kind {
778 (orthographic(&a), scale_of(&a) + (scale_of(&b) - scale_of(&a)) * t)
779 } else if t < 0.5 {
780 (orthographic(&a), scale_of(&a))
781 } else {
782 (orthographic(&b), scale_of(&b))
783 };
784 Some(PmiCamera {
785 eye: lerp(a.eye, b.eye),
786 target: lerp(a.target, b.target),
787 up: lerp(a.up, b.up),
788 projection: if ortho { PmiProjection::Orthographic { half_height: scale } } else { PmiProjection::Perspective { fov_y_deg: scale } },
789 viewport: a.viewport,
790 })
791 }
792
793 fn simulation_apply_pmi_camera(&mut self, camera: &PmiCamera) {
795 let (kind, scale) = match camera.projection {
796 PmiProjection::Orthographic { half_height } => ("orthographic", half_height),
797 PmiProjection::Perspective { fov_y_deg } => ("perspective", fov_y_deg),
798 };
799 let json = json!({ "kind": kind, "eye": camera.eye, "target": camera.target, "up": camera.up, "scale": scale }).to_string();
800 if self.apply_camera_state_json(&json).is_ok() {
801 self.controls_sync_after_camera_apply();
802 }
803 }
804
805 fn simulation_tint_clashes(&mut self, index: usize) {
810 let Some(play) = self.simulation_play.as_ref() else { return };
811 let Some(frame) = play.frames.get(index) else { return };
812 let mut want: Vec<String> = Vec::new();
813 for (a, b) in &frame.clashes {
814 for occ in [a, b] {
815 if let Some((_, solids)) = play.participants.iter().find(|(id, _)| id == occ) {
816 want.extend(solids.iter().cloned());
817 }
818 }
819 }
820 want.sort();
821 want.dedup();
822 let previous = play.tinted.clone();
823 let mut changed = false;
824 for name in previous.iter().filter(|name| !want.contains(name)) {
825 let color = self.simulation_play.as_ref().and_then(|play| play.originals.get(name)).map(|original| original.color_override);
826 if let (Some(color), Some(display)) = (color, self.scene.solid_mut(name)) {
827 if display.color_override != color {
828 display.color_override = color;
829 display.revision = crate::scene::next_revision();
830 changed = true;
831 }
832 }
833 }
834 for name in &want {
835 let Some(display) = self.scene.solid(name).cloned() else { continue };
838 if let Some(play) = self.simulation_play.as_mut() {
839 play.originals.entry(name.clone()).or_insert(display);
840 }
841 if let Some(display) = self.scene.solid_mut(name) {
842 if display.color_override != Some(CLASH_TINT) {
843 display.color_override = Some(CLASH_TINT);
844 display.revision = crate::scene::next_revision();
846 changed = true;
847 }
848 }
849 }
850 if let Some(play) = self.simulation_play.as_mut() {
851 play.tinted = want;
852 }
853 if changed {
854 self.dirty = true;
855 }
856 }
857
858 fn simulation_pose_occurrence(&mut self, id: &str, pose: Option<([f64; 16], [f64; 16])>) {
864 let names: Vec<String> = if id.contains(':') {
865 let prefix = format!("{id}:");
866 self.scene.solids().iter().filter(|solid| solid.name.starts_with(&prefix)).map(|solid| solid.name.clone()).collect()
867 } else {
868 self.member_solid_names(id)
869 };
870 let Some(play) = self.simulation_play.as_mut() else { return };
871 for name in names {
872 let Some(display) = self.scene.solid(&name) else { continue };
873 let original = play.originals.entry(name.clone()).or_insert_with(|| display.clone()).clone();
874 if let Some(display) = self.scene.solid_mut(&name) {
875 *display = original;
876 if let Some((delta, _)) = &pose {
877 pose_display_by_matrix(display, delta);
878 }
879 }
880 }
881 let nested = format!("{id}:");
882 play.applied.retain(|key, _| !key.starts_with(&nested));
883 match pose {
884 Some((_, world)) => {
885 play.applied.insert(id.to_string(), world);
886 }
887 None => {
888 play.applied.remove(id);
889 }
890 }
891 }
892
893 pub(super) fn occurrence_document_matrix(&self, id: &str) -> Option<[f64; 16]> {
897 let (top, rest) = match id.split_once(':') {
898 Some((top, rest)) => (top, Some(rest)),
899 None => (id, None),
900 };
901 let record = self.assembly_components().iter().find(|record| record.id == top)?;
902 match rest {
903 None => Some(record.transform),
904 Some(rest) => Some(mat_mul(&record.transform, record.nested_poses.get(rest)?)),
905 }
906 }
907
908 pub(crate) fn simulation_restore_displays(&mut self) {
912 let Some(play) = self.simulation_play.as_mut() else { return };
913 play.applied.clear();
914 play.tinted.clear();
915 play.hidden.clear();
916 play.translucent.clear();
917 play.sectioned.clear();
918 play.shown_sample = None;
919 let camera_before = play.camera_before.take();
920 let originals = std::mem::take(&mut play.originals);
921 if let Some(camera) = camera_before {
922 self.simulation_apply_pmi_camera(&camera);
923 }
924 let Some(play) = self.simulation_play.as_mut() else { return };
925 if let Some(shown) = play.shown.take() {
926 play.wanted.get_or_insert(shown);
927 }
928 if originals.is_empty() {
929 return;
930 }
931 for (name, original) in originals {
932 if let Some(display) = self.scene.solid_mut(&name) {
933 *display = original;
934 }
935 }
936 self.dirty = true;
937 }
938
939 pub(crate) fn simulation_after_apply(&mut self) {
943 let Some(play) = self.simulation_play.as_ref() else { return };
944 if play.revision == self.history.revision() {
945 self.simulation_show_wanted();
946 }
947 }
948
949 pub fn simulation_forget_playback(&mut self) {
952 self.simulation_restore_displays();
953 self.simulation_play = None;
954 }
955
956 pub fn simulation_playback_json(&self) -> Value {
969 let Some(play) = self.simulation_play.as_ref() else { return Value::Null };
970 let failed: Vec<usize> = play.frames.iter().enumerate().filter(|(_, frame)| frame.failed).map(|(index, _)| index).collect();
971 let poses: BTreeMap<&str, Value> = play
972 .applied
973 .iter()
974 .map(|(id, world)| (id.as_str(), brep_kernel::transform_to_pose_params(&brep_kernel::AffineTransform { elements: *world })))
975 .collect();
976 json!({
977 "study": play.study,
978 "time": play.time,
979 "playheadFrame": play.baked_study.frame_at(play.time),
980 "shown": play.shown,
981 "baked": play.frames.len(),
982 "total": play.total,
983 "playing": play.playing,
984 "looping": play.looping,
985 "failed": failed,
986 "error": play.error,
987 "poses": poses,
988 "interference": play.baked_study.interference.enabled,
989 "clashes": play.frames.iter().enumerate().filter(|(_, frame)| !frame.clashes.is_empty()).map(|(index, _)| index).collect::<Vec<usize>>(),
990 "shownClashes": play.shown.and_then(|index| play.frames.get(index)).map(|frame| frame.clashes.clone()).unwrap_or_default(),
991 "hidden": play.shown_sample.as_ref().map(|sample| sample.hidden.clone()).unwrap_or_default(),
992 "explode": play.shown_sample.as_ref().map(|sample| sample.explode.iter().map(|e| json!({ "view": e.view, "progress": e.progress })).collect::<Vec<Value>>()).unwrap_or_default(),
993 "camera": play.shown_sample.as_ref().and_then(|sample| sample.camera.as_ref()).map(|c| json!({ "from": c.from, "to": c.to, "t": c.t })),
994 "cameraHeld": play.camera_before.is_some(),
995 "opacity": play.shown_sample.as_ref().map(|sample| sample.opacity.clone()).unwrap_or_default(),
996 "section": play.shown_sample.as_ref().and_then(|sample| sample.section.as_ref()).map(|s| json!({ "view": s.view, "offset": s.offset })),
997 })
998 }
999}
1000
1001pub(super) fn mat_mul(a: &[f64; 16], b: &[f64; 16]) -> [f64; 16] {
1003 std::array::from_fn(|k| {
1004 let (row, col) = (k / 4, k % 4);
1005 (0..4).map(|n| a[row * 4 + n] * b[n * 4 + col]).sum()
1006 })
1007}
1008
1009pub(super) fn rigid_inverse(m: &[f64; 16]) -> [f64; 16] {
1011 let mut out = [0.0; 16];
1012 for row in 0..3 {
1013 for col in 0..3 {
1014 out[row * 4 + col] = m[col * 4 + row];
1015 }
1016 out[row * 4 + 3] = -(0..3).map(|k| m[k * 4 + row] * m[k * 4 + 3]).sum::<f64>();
1017 }
1018 out[15] = 1.0;
1019 out
1020}
1021
1022pub(super) fn same_matrix(a: &[f64; 16], b: &[f64; 16]) -> bool {
1024 a.iter().zip(b).all(|(x, y)| (x - y).abs() <= 1e-9)
1025}
1026
1027fn mat4_mul_column_major(a: &[f32; 16], b: &[f32; 16]) -> [f32; 16] {
1029 std::array::from_fn(|k| {
1030 let (col, row) = (k / 4, k % 4);
1031 (0..4).map(|n| a[n * 4 + row] * b[col * 4 + n]).sum()
1032 })
1033}
1034
1035fn pose_display_by_matrix(display: &mut SolidDisplay, m: &[f64; 16]) {
1040 let column_major: [f32; 16] = std::array::from_fn(|k| {
1041 let (col, row) = (k / 4, k % 4);
1042 m[row * 4 + col] as f32
1043 });
1044 display.pose = if display.shape_revision == 0 {
1045 Some(column_major)
1046 } else {
1047 display.pose.map(|before| mat4_mul_column_major(&column_major, &before))
1048 };
1049 if display.shape_revision == 0 {
1050 display.shape_revision = display.revision;
1051 }
1052 display.revision = crate::scene::next_revision();
1053 let point = |p: [f64; 3]| -> [f64; 3] {
1054 std::array::from_fn(|i| m[i * 4] * p[0] + m[i * 4 + 1] * p[1] + m[i * 4 + 2] * p[2] + m[i * 4 + 3])
1055 };
1056 let vector = |v: [f64; 3]| -> [f64; 3] { std::array::from_fn(|i| m[i * 4] * v[0] + m[i * 4 + 1] * v[1] + m[i * 4 + 2] * v[2]) };
1057 let mut bbox = crate::camera::Aabb::empty();
1058 for position in &mut display.mesh.positions {
1059 let posed = point(position.map(f64::from));
1060 *position = posed.map(|v| v as f32);
1061 bbox.expand(posed);
1062 }
1063 for normal in &mut display.mesh.normals {
1064 let rotated = vector(normal.map(f64::from));
1065 let length = rotated.iter().map(|v| v * v).sum::<f64>().sqrt().max(1e-20);
1066 *normal = rotated.map(|v| (v / length) as f32);
1067 }
1068 for edge in &mut display.edges {
1069 for p in &mut edge.polyline {
1070 let posed = point(p.map(f64::from));
1071 *p = posed.map(|v| v as f32);
1072 bbox.expand(posed);
1073 }
1074 }
1075 for vertex in &mut display.vertices {
1076 vertex.position = point(vertex.position);
1077 bbox.expand(vertex.position);
1078 }
1079 if !bbox.is_empty() {
1080 display.bbox = bbox;
1081 }
1082}