(ctx: &mut ScriptContext<'_, API>)
| 569 | pub fn stream_pointer_ray_3d<API: ScriptAPI + ?Sized>( |
| 570 | ctx: &mut ScriptContext<'_, API>, |
| 571 | pointer: ViewportPointer, |
| 572 | ) -> Option<ViewportRay3D> { |
| 573 | let camera = with_state!(ctx.run, EditorState, ctx.id, |state| { |
| 574 | (state.preview_camera_3d != 0).then(|| NodeID::from_u64(state.preview_camera_3d)) |
| 575 | }).unwrap_or_default() |
| 576 | .or_else(|| find_named(ctx, "editor_camera_3d"))?; |
| 577 | let global = ctx.run.Nodes().get_global_transform_3d(camera)?; |
| 578 | let projection = with_node!(ctx.run, Camera3D, camera, |node| node.projection.clone()).unwrap_or_default(); |
| 579 | let aspect = 16.0 / 9.0; |
| 580 | let local_dir = match projection { |
| 581 | CameraProjection::Perspective { fov_y_degrees, .. } => { |
| 582 | let tan_y = (fov_y_degrees.to_radians() * 0.5).tan(); |
| 583 | Vector3::new(pointer.ndc.x * aspect * tan_y, pointer.ndc.y * tan_y, -1.0).normalized() |
| 584 | } |
| 585 | CameraProjection::Orthographic { .. } => Vector3::new(0.0, 0.0, -1.0), |
| 586 | CameraProjection::Frustum { |
| 587 | left, |
| 588 | right, |
| 589 | bottom, |
| 590 | top, |
| 591 | near, |
| 592 | .. |
| 593 | } => { |
| 594 | let x = left + (pointer.uv.x * (right - left)); |
| 595 | let y = bottom + ((1.0 - pointer.uv.y) * (top - bottom)); |
| 596 | Vector3::new(x, y, -near.max(0.001)).normalized() |
| 597 | } |
| 598 | }; |
| 599 | let local_origin = match projection { |
| 600 | CameraProjection::Orthographic { size, .. } => Vector3::new( |
| 601 | pointer.ndc.x * size * aspect * 0.5, |
| 602 | pointer.ndc.y * size * 0.5, |
| 603 | 0.0, |
| 604 | ), |
| 605 | _ => Vector3::ZERO, |
| 606 | }; |
| 607 | let origin_offset = global.rotation.rotate_vector3(local_origin); |
| 608 | Some(ViewportRay3D { |
| 609 | origin: global.position + origin_offset, |
| 610 | direction: global.rotation.rotate_vector3(local_dir).normalized(), |
| 611 | }) |
| 612 | } |
| 613 | |
| 614 | pub fn poll_project_diffs<API: ScriptAPI + ?Sized>(ctx: &mut ScriptContext<'_, API>) { |
| 615 | let action = with_state_mut!(ctx.run, EditorState, ctx.id, |state| { |
| 616 | if state.project_root.is_empty() { |
| 617 | return None; |
| 618 | } |
| 619 | state.file_watch_frame = state.file_watch_frame.wrapping_add(1); |
| 620 | let root = PathBuf::from(&state.project_root); |
| 621 | let Some(scan) = editor_file_watch::take_project_scan(root.as_path()) else { |
| 622 | if state.file_watch_frame % FILE_WATCH_INTERVAL_FRAMES == 0 { |
| 623 | editor_file_watch::request_project_scan(root, state.project_file_sigs.clone()); |
| 624 | } |
| 625 | return None; |
| 626 | }; |
| 627 | |
| 628 | let editor_file_watch::ProjectScan { |
nothing calls this directly
no test coverage detected