Skip to content
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

Camera Options & Context Menu #1079

Merged
merged 22 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8942914
Adding alternative camera modes
HunterBarclay Jul 19, 2024
f042878
JS Events are driving me insane
HunterBarclay Jul 20, 2024
6041371
Recreated orbit camera controls
HunterBarclay Jul 20, 2024
23ed227
Tinkering with the panning and the zooming
HunterBarclay Jul 20, 2024
2e2b49c
Added right-click selection for focus, panning cancels focus, and loc…
HunterBarclay Jul 21, 2024
b3e3a49
Add touch support, pen support, and added optional hosting and ssl op…
HunterBarclay Jul 24, 2024
fce8b4b
Untested context menu and toggle
HunterBarclay Jul 24, 2024
457f4f7
Context menu fixes
HunterBarclay Jul 24, 2024
647a25a
Fixed fov scaling issue
HunterBarclay Jul 25, 2024
4f62968
Slowly adding a context menu. JS Events are annoying me again
HunterBarclay Jul 24, 2024
4187041
Finally fixed issue with context menu click events passing through
HunterBarclay Jul 25, 2024
ccd3b4f
Context menu
HunterBarclay Aug 3, 2024
4729d85
Formatting
HunterBarclay Aug 3, 2024
2225821
Disabling SSL for unit tests
HunterBarclay Aug 3, 2024
13d31aa
Adding move option
HunterBarclay Aug 3, 2024
2f6fc41
Added scrolling fix
HunterBarclay Aug 13, 2024
1c70994
fixed scrolling disabled
HunterBarclay Aug 21, 2024
654389f
Added pan/pinch features for touch controls, fixed context menu controls
HunterBarclay Aug 26, 2024
fc474d9
Formatted.
HunterBarclay Aug 26, 2024
ad6cb23
Merge branch 'dev' into barclah/context-menu
HunterBarclay Nov 9, 2024
9f72176
fix: Repair broken changes from the merge
HunterBarclay Nov 10, 2024
11ee094
tidy: formatter
HunterBarclay Nov 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading