-
Notifications
You must be signed in to change notification settings - Fork 2.3k
#000. #446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#000. #446
Changes from all commits
ff34d3c
da1d7cb
fd9b736
e4f603a
6d82345
64de9de
fee5845
4b370ae
32787f6
7e6526d
37e0e3e
58fa0a3
8b4cfc8
0c5c16b
a8a5f14
d88115b
658baf9
6deb8ff
217712a
344d60a
af1beac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| 'use client' | ||
|
|
||
| import useViewer from '@pascal-app/viewer/store/use-viewer' | ||
|
|
||
| /** | ||
| * A pure-SVG north-arrow compass widget. | ||
| * The `bearingDeg` prop is the angle (clockwise degrees from screen-up) that | ||
| * the north arrow should point — 0 means north faces straight up, 90 means | ||
| * north faces right, etc. | ||
| */ | ||
| function CompassSVG({ bearingDeg }: { bearingDeg: number }) { | ||
| return ( | ||
| <div | ||
| aria-label={`North arrow, ${Math.round(bearingDeg)}° clockwise from screen top`} | ||
| className="pointer-events-none select-none" | ||
| role="img" | ||
| style={{ width: 44, height: 44 }} | ||
| > | ||
| <svg | ||
| fill="none" | ||
| height="44" | ||
| viewBox="0 0 44 44" | ||
| width="44" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| {/* Outer ring */} | ||
| <circle cx="22" cy="22" r="20" stroke="currentColor" strokeOpacity="0.15" strokeWidth="1" /> | ||
|
|
||
| {/* Rotating group — north arrow */} | ||
| <g | ||
| style={{ | ||
| transformOrigin: '22px 22px', | ||
| transform: `rotate(${bearingDeg}deg)`, | ||
| }} | ||
| > | ||
| {/* North half of needle — red */} | ||
| <path d="M22 6 L25.5 22 L22 20 L18.5 22 Z" fill="#ef4444" opacity="0.95" /> | ||
| {/* South half of needle — muted */} | ||
| <path d="M22 38 L18.5 22 L22 24 L25.5 22 Z" fill="currentColor" opacity="0.30" /> | ||
| {/* Centre dot */} | ||
| <circle cx="22" cy="22" fill="currentColor" opacity="0.5" r="1.5" /> | ||
| </g> | ||
|
|
||
| {/* "N" label — always screen-up, outside the rotating group */} | ||
| <text | ||
| dominantBaseline="middle" | ||
| fill="currentColor" | ||
| fontSize="7" | ||
| fontWeight="600" | ||
| opacity="0.55" | ||
| textAnchor="middle" | ||
| x="22" | ||
| y="5" | ||
| > | ||
| N | ||
| </text> | ||
| </svg> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * DOM overlay — renders the compass in the bottom-right of the nearest | ||
| * `relative` positioned ancestor. Must be placed inside the viewport wrapper, | ||
| * not inside the toolbar strip. | ||
| * Reads the bearing from useViewer, written each frame by NorthCompassR3F | ||
| * (which lives inside the Canvas in packages/viewer). | ||
| */ | ||
| export function NorthCompassWidget() { | ||
| const bearingDeg = useViewer((s) => s.northBearingDeg) | ||
| return ( | ||
| <div className="pointer-events-none absolute bottom-3 right-3 z-10 text-foreground/70"> | ||
| <CompassSVG bearingDeg={bearingDeg} /> | ||
| </div> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| 'use client' | ||
|
|
||
| import { NORTH_DIRECTION_DEFAULT } from '@pascal-app/core/schema' | ||
| import useScene from '@pascal-app/core/store/use-scene' | ||
| import { useFrame, useThree } from '@react-three/fiber' | ||
| import { useRef } from 'react' | ||
| import * as THREE from 'three' | ||
| import useViewer from '../../store/use-viewer' | ||
|
|
||
| function useNorthDirection(): number { | ||
| const nodes = useScene((state) => state.nodes) | ||
| for (const node of Object.values(nodes)) { | ||
| if (node.type === 'site') { | ||
| const dir = (node as { northDirection?: unknown }).northDirection | ||
| return typeof dir === 'number' ? dir : NORTH_DIRECTION_DEFAULT | ||
| } | ||
| } | ||
| return NORTH_DIRECTION_DEFAULT | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Site north from wrong nodeMedium Severity
Reviewed by Cursor Bugbot for commit af1beac. Configure here. |
||
| } | ||
|
|
||
| /** | ||
| * Mounts inside the R3F <Canvas>. Reads the camera azimuth every frame, | ||
| * combines it with the scene's northDirection, and pushes the result to | ||
| * useViewer.northBearingDeg so NorthCompassWidget (outside the canvas) can render it. | ||
| */ | ||
| export function NorthCompassR3F() { | ||
| const { camera } = useThree() | ||
| const northDirection = useNorthDirection() | ||
| const setNorthBearingDeg = useViewer((s) => s.setNorthBearingDeg) | ||
|
|
||
| const _euler = useRef(new THREE.Euler()) | ||
| const _quat = useRef(new THREE.Quaternion()) | ||
| const _prevDeg = useRef(0) | ||
|
|
||
| useFrame(() => { | ||
| camera.getWorldQuaternion(_quat.current) | ||
| _euler.current.setFromQuaternion(_quat.current, 'YXZ') | ||
| const cameraYawRad = _euler.current.y | ||
|
|
||
| const northFromScreen = -(northDirection - Math.PI / 2 - cameraYawRad) | ||
| const deg = ((northFromScreen * 180) / Math.PI + 360) % 360 | ||
|
|
||
| if (Math.abs(_prevDeg.current - deg) > 0.5) { | ||
| _prevDeg.current = deg | ||
| setNorthBearingDeg(deg) | ||
| } | ||
| }) | ||
|
|
||
| return null | ||
| } | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default northDirection contradicts −Z north
High Severity
The
northDirectionproperty's defaultπ/2value has conflicting interpretations for "True North." Despite being defined as radians CCW from world +X, the documentation, templates, and surveyor conversion inconsistently describeπ/2as pointing to +Z north, +Z south, or -Z north.Reviewed by Cursor Bugbot for commit 0c5c16b. Configure here.