Skip to content

Commit

Permalink
[AARD-1760] Camera Options & Context Menu (#1079)
Browse files Browse the repository at this point in the history
  • Loading branch information
HunterBarclay authored Nov 10, 2024
1 parent 9a27ecf commit 392b698
Show file tree
Hide file tree
Showing 19 changed files with 1,148 additions and 102 deletions.
2 changes: 2 additions & 0 deletions fission/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"type": "module",
"scripts": {
"init": "(bun run assetpack && bun run playwright:install) || (npm run assetpack && npm run playwright:install)",
"host": "vite --open --host",
"dev": "vite --open",
"build": "tsc && vite build",
"build:prod": "tsc && vite build --base=/fission/ --outDir dist/prod",
Expand Down Expand Up @@ -56,6 +57,7 @@
"@types/three": "^0.160.0",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"@vitejs/plugin-basic-ssl": "^1.1.0",
"@vitejs/plugin-react": "^4.0.3",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.14",
Expand Down
4 changes: 4 additions & 0 deletions fission/src/Synthesis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import AnalyticsConsent from "./ui/components/AnalyticsConsent.tsx"
import PreferencesSystem from "./systems/preferences/PreferencesSystem.ts"
import APSManagementModal from "./ui/modals/APSManagementModal.tsx"
import ConfigurePanel from "./ui/panels/configuring/assembly-config/ConfigurePanel.tsx"
import CameraSelectionPanel from "./ui/panels/configuring/CameraSelectionPanel.tsx"
import ContextMenu from "./ui/components/ContextMenu.tsx"
import GlobalUIComponent from "./ui/components/GlobalUIComponent.tsx"
import InitialConfigPanel from "./ui/panels/configuring/initial-config/InitialConfigPanel.tsx"

Expand Down Expand Up @@ -161,6 +163,7 @@ function Synthesis() {
<GlobalUIComponent />
<Scene useStats={import.meta.env.DEV} key="scene-in-toast-provider" />
<SceneOverlay />
<ContextMenu />
<MainHUD key={"main-hud"} />
{panelElements.length > 0 && panelElements}
{modalElement && (
Expand Down Expand Up @@ -225,6 +228,7 @@ const initialPanels: ReactElement[] = [
<WSViewPanel key="ws-view" panelId="ws-view" />,
<DebugPanel key="debug" panelId="debug" />,
<ConfigurePanel key="configure" panelId="configure" />,
<CameraSelectionPanel key="camera-select" panelId="camera-select" />,
<InitialConfigPanel key="initial-config" panelId="initial-config" />,
]

Expand Down
95 changes: 94 additions & 1 deletion fission/src/mirabuf/MirabufSceneObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,18 @@ import ScoringZoneSceneObject from "./ScoringZoneSceneObject"
import { SceneOverlayTag } from "@/ui/components/SceneOverlayEvents"
import { ProgressHandle } from "@/ui/components/ProgressNotificationData"
import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain"
import { ContextData, ContextSupplier } from "@/ui/components/ContextMenuData"
import { CustomOrbitControls } from "@/systems/scene/CameraControls"
import GizmoSceneObject from "@/systems/scene/GizmoSceneObject"
import {
ConfigMode,
setNextConfigurePanelSettings,
} from "@/ui/panels/configuring/assembly-config/ConfigurePanelControls"
import { Global_OpenPanel } from "@/ui/components/GlobalUIControls"
import {
ConfigurationType,
setSelectedConfigurationType,
} from "@/ui/panels/configuring/assembly-config/ConfigurationType"

const DEBUG_BODIES = false

Expand All @@ -46,7 +57,7 @@ export function getSpotlightAssembly(): MirabufSceneObject | undefined {
return World.SceneRenderer.sceneObjects.get(spotlightAssembly ?? 0) as MirabufSceneObject
}

class MirabufSceneObject extends SceneObject {
class MirabufSceneObject extends SceneObject implements ContextSupplier {
private _assemblyName: string
private _mirabufInstance: MirabufInstance
private _mechanism: Mechanism
Expand Down Expand Up @@ -466,6 +477,88 @@ class MirabufSceneObject extends SceneObject {
public GetRootNodeId(): Jolt.BodyID | undefined {
return this._mechanism.GetBodyByNodeId(this._mechanism.rootBody)
}

public LoadFocusTransform(mat: THREE.Matrix4) {
const com = World.PhysicsSystem.GetBody(
this._mechanism.nodeToBody.get(this.rootNodeId)!
).GetCenterOfMassTransform()
mat.copy(JoltMat44_ThreeMatrix4(com))
}

public getSupplierData(): ContextData {
const data: ContextData = { title: this.miraType == MiraType.ROBOT ? "A Robot" : "A Field", items: [] }

data.items.push({
name: "Move",
func: () => {
setSelectedConfigurationType(
this.miraType == MiraType.ROBOT ? ConfigurationType.ROBOT : ConfigurationType.FIELD
)
setNextConfigurePanelSettings({
configMode: ConfigMode.MOVE,
selectedAssembly: this,
})
Global_OpenPanel?.("configure")
},
})

if (this.miraType == MiraType.ROBOT) {
const brainIndex = (this.brain as SynthesisBrain)?.brainIndex
if (brainIndex != undefined) {
data.items.push({
name: "Set Scheme",
func: () => {
setSpotlightAssembly(this)
Global_OpenPanel?.("choose-scheme")
},
})
}
}

if (World.SceneRenderer.currentCameraControls.controlsType == "Orbit") {
const cameraControls = World.SceneRenderer.currentCameraControls as CustomOrbitControls
if (cameraControls.focusProvider == this) {
data.items.push({
name: "Camera: Unfocus",
func: () => {
cameraControls.focusProvider = undefined
},
})

if (cameraControls.locked) {
data.items.push({
name: "Camera: Unlock",
func: () => {
cameraControls.locked = false
},
})
} else {
data.items.push({
name: "Camera: Lock",
func: () => {
cameraControls.locked = true
},
})
}
} else {
data.items.push({
name: "Camera: Focus",
func: () => {
cameraControls.focusProvider = this
},
})
}
}

data.items.push({
name: "Remove",
func: () => {
World.SceneRenderer.RemoveSceneObject(this.id)
},
})

return data
}
}

export async function CreateMirabuf(
Expand Down
234 changes: 234 additions & 0 deletions fission/src/systems/scene/CameraControls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import MirabufSceneObject from "@/mirabuf/MirabufSceneObject"
import * as THREE from "three"
import ScreenInteractionHandler, {
InteractionEnd,
InteractionMove,
InteractionStart,
PRIMARY_MOUSE_INTERACTION,
SECONDARY_MOUSE_INTERACTION,
} from "./ScreenInteractionHandler"

export type CameraControlsType = "Orbit"

export abstract class CameraControls {
private _controlsType: CameraControlsType

public abstract set enabled(val: boolean)
public abstract get enabled(): boolean

public get controlsType() {
return this._controlsType
}

public constructor(controlsType: CameraControlsType) {
this._controlsType = controlsType
}

public abstract update(deltaT: number): void

public abstract dispose(): void
}

interface SphericalCoords {
theta: number
phi: number
r: number
}

type PointerType = -1 | 0 | 1 | 2

const CO_MAX_ZOOM = 40.0
const CO_MIN_ZOOM = 0.1
const CO_MAX_PHI = Math.PI / 2.1
const CO_MIN_PHI = -Math.PI / 2.1

const CO_SENSITIVITY_ZOOM = 4.0
const CO_SENSITIVITY_PHI = 0.5
const CO_SENSITIVITY_THETA = 0.5

const CO_DEFAULT_ZOOM = 3.5
const CO_DEFAULT_PHI = -Math.PI / 6.0
const CO_DEFAULT_THETA = -Math.PI / 4.0

const DEG2RAD = Math.PI / 180.0

/**
* Creates a pseudo frustum of the perspective camera to scale the mouse movement to something relative to the scenes dimensions and scale
*
* @param camera Main Camera
* @param distanceFromFocus Distance from the focus point
* @param originalMovement Original movement of the mouse across the screen
* @returns Augmented movement to scale to the scenes relative dimensions
*/
function augmentMovement(
camera: THREE.Camera,
distanceFromFocus: number,
originalMovement: [number, number]
): [number, number] {
const aspect = (camera as THREE.PerspectiveCamera)?.aspect ?? 1.0
// const aspect = 1.0
const fov: number | undefined = (camera as THREE.PerspectiveCamera)?.getEffectiveFOV()
if (fov) {
const res: [number, number] = [
(2 *
distanceFromFocus *
Math.tan(Math.min((Math.PI * 0.9) / 2, (DEG2RAD * fov * aspect) / 2)) *
originalMovement[0]) /
window.innerWidth,
(2 * distanceFromFocus * Math.tan((DEG2RAD * fov) / 2) * originalMovement[1]) / window.innerHeight,
]
return res
} else {
return originalMovement
}
}

export class CustomOrbitControls extends CameraControls {
private _enabled = true

private _mainCamera: THREE.Camera

private _activePointerType: PointerType
private _nextCoords: SphericalCoords
private _coords: SphericalCoords
private _focus: THREE.Matrix4

private _focusProvider: MirabufSceneObject | undefined
public locked: boolean

private _interactionHandler: ScreenInteractionHandler

public set enabled(val: boolean) {
this._enabled = val
}
public get enabled(): boolean {
return this._enabled
}

public set focusProvider(provider: MirabufSceneObject | undefined) {
this._focusProvider = provider
}
public get focusProvider() {
return this._focusProvider
}

public constructor(mainCamera: THREE.Camera, interactionHandler: ScreenInteractionHandler) {
super("Orbit")

this._mainCamera = mainCamera
this._interactionHandler = interactionHandler

this.locked = false

this._nextCoords = { theta: CO_DEFAULT_THETA, phi: CO_DEFAULT_PHI, r: CO_DEFAULT_ZOOM }
this._coords = { theta: CO_DEFAULT_THETA, phi: CO_DEFAULT_PHI, r: CO_DEFAULT_ZOOM }
this._activePointerType = -1

// Identity
this._focus = new THREE.Matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)

this._interactionHandler.interactionStart = e => this.interactionStart(e)
this._interactionHandler.interactionEnd = e => this.interactionEnd(e)
this._interactionHandler.interactionMove = e => this.interactionMove(e)
}

public interactionEnd(end: InteractionEnd) {
/**
* If Pointer is already down, and the button that is being
* released is the primary button, make Pointer not be down
*/
if (end.interactionType == this._activePointerType) {
this._activePointerType = -1
}
}

public interactionStart(start: InteractionStart) {
// If primary button, make Pointer be down
if (this._activePointerType < start.interactionType) {
switch (start.interactionType) {
case PRIMARY_MOUSE_INTERACTION:
this._activePointerType = PRIMARY_MOUSE_INTERACTION
break
case SECONDARY_MOUSE_INTERACTION:
this._activePointerType = SECONDARY_MOUSE_INTERACTION
break
default:
break
}
}
}

public interactionMove(move: InteractionMove) {
if (move.movement) {
if (this._activePointerType == PRIMARY_MOUSE_INTERACTION) {
// Add the movement of the mouse to the _currentPos
this._nextCoords.theta -= move.movement[0]
this._nextCoords.phi -= move.movement[1]
} else if (this._activePointerType == SECONDARY_MOUSE_INTERACTION && !this.locked) {
this._focusProvider = undefined

const orientation = new THREE.Quaternion().setFromEuler(this._mainCamera.rotation)

const augmentedMovement = augmentMovement(this._mainCamera, this._coords.r, [
move.movement[0],
move.movement[1],
])

const pan = new THREE.Vector3(-augmentedMovement[0], augmentedMovement[1], 0).applyQuaternion(
orientation
)
const newPos = new THREE.Vector3().setFromMatrixPosition(this._focus)
newPos.add(pan)
this._focus.setPosition(newPos)
}
}

if (move.scale) {
this._nextCoords.r += move.scale
}
}

public update(deltaT: number): void {
deltaT = Math.max(1.0 / 60.0, Math.min(1 / 144.0, deltaT))

if (this.enabled) this._focusProvider?.LoadFocusTransform(this._focus)

// Generate delta of spherical coordinates
const omega: SphericalCoords = this.enabled
? {
theta: this._nextCoords.theta - this._coords.theta,
phi: this._nextCoords.phi - this._coords.phi,
r: this._nextCoords.r - this._coords.r,
}
: { theta: 0, phi: 0, r: 0 }

this._coords.theta += omega.theta * deltaT * CO_SENSITIVITY_THETA
this._coords.phi += omega.phi * deltaT * CO_SENSITIVITY_PHI
this._coords.r += omega.r * deltaT * CO_SENSITIVITY_ZOOM * Math.pow(this._coords.r, 1.4)

this._coords.phi = Math.min(CO_MAX_PHI, Math.max(CO_MIN_PHI, this._coords.phi))
this._coords.r = Math.min(CO_MAX_ZOOM, Math.max(CO_MIN_ZOOM, this._coords.r))

const deltaTransform = new THREE.Matrix4()
.makeTranslation(0, 0, this._coords.r)
.premultiply(
new THREE.Matrix4().makeRotationFromEuler(
new THREE.Euler(this._coords.phi, this._coords.theta, 0, "YXZ")
)
)

if (this.locked && this._focusProvider) {
deltaTransform.premultiply(this._focus)
} else {
const focusPosition = new THREE.Matrix4().copyPosition(this._focus)
deltaTransform.premultiply(focusPosition)
}

this._mainCamera.position.setFromMatrixPosition(deltaTransform)
this._mainCamera.rotation.setFromRotationMatrix(deltaTransform)

this._nextCoords = { theta: this._coords.theta, phi: this._coords.phi, r: this._coords.r }
}

public dispose(): void {}
}
Loading

0 comments on commit 392b698

Please sign in to comment.