Skip to content

Commit

Permalink
Scene Tags (#1030)
Browse files Browse the repository at this point in the history
  • Loading branch information
HunterBarclay authored Jul 19, 2024
2 parents 94839ef + 31ea4f7 commit 55a2dde
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 0 deletions.
2 changes: 2 additions & 0 deletions fission/src/Synthesis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { ProgressHandle } from "./ui/components/ProgressNotificationData.ts"
import ConfigureRobotModal from "./ui/modals/configuring/ConfigureRobotModal.tsx"
import ResetAllInputsModal from "./ui/modals/configuring/ResetAllInputsModal.tsx"
import ZoneConfigPanel from "./ui/panels/configuring/scoring/ZoneConfigPanel.tsx"
import SceneOverlay from "./ui/components/SceneOverlay.tsx"

import WPILibWSWorker from "@/systems/simulation/wpilib_brain/WPILibWSWorker.ts?worker"
import WSViewPanel from "./ui/panels/WSViewPanel.tsx"
Expand Down Expand Up @@ -191,6 +192,7 @@ function Synthesis() {
>
<ToastProvider key="toast-provider">
<Scene useStats={true} key="scene-in-toast-provider" />
<SceneOverlay />
<MainHUD key={"main-hud"} />
{panelElements.length > 0 && panelElements}
{modalElement && (
Expand Down
34 changes: 34 additions & 0 deletions fission/src/mirabuf/MirabufSceneObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import PreferencesSystem from "@/systems/preferences/PreferencesSystem"
import { MiraType } from "./MirabufLoader"
import IntakeSensorSceneObject from "./IntakeSensorSceneObject"
import EjectableSceneObject from "./EjectableSceneObject"
import { SceneOverlayTag } from "@/ui/components/SceneOverlayEvents"
import { ProgressHandle } from "@/ui/components/ProgressNotificationData"

const DEBUG_BODIES = false
Expand Down Expand Up @@ -45,6 +46,8 @@ class MirabufSceneObject extends SceneObject {
private _intakeSensor?: IntakeSensorSceneObject
private _ejectable?: EjectableSceneObject

private _nameTag: SceneOverlayTag | undefined

get mirabufInstance() {
return this._mirabufInstance
}
Expand Down Expand Up @@ -99,6 +102,11 @@ class MirabufSceneObject extends SceneObject {
this.EnableTransformControls() // adding transform gizmo to mirabuf object on its creation

this.getPreferences()

// creating nametag for robots
if (this.miraType === MiraType.ROBOT) {
this._nameTag = new SceneOverlayTag("Ernie")
}
}

public Setup(): void {
Expand Down Expand Up @@ -208,6 +216,18 @@ class MirabufSceneObject extends SceneObject {
x.computeBoundingBox()
x.computeBoundingSphere()
})

/* Updating the position of the name tag according to the robots position on screen */
if (this._nameTag && PreferencesSystem.getGlobalPreference<boolean>("RenderSceneTags")) {
const boundingBox = this.ComputeBoundingBox()
this._nameTag.position = World.SceneRenderer.WorldToPixelSpace(
new THREE.Vector3(
(boundingBox.max.x + boundingBox.min.x) / 2,
boundingBox.max.y + 0.1,
(boundingBox.max.z + boundingBox.min.z) / 2
)
)
}
}

public Dispose(): void {
Expand All @@ -225,6 +245,7 @@ class MirabufSceneObject extends SceneObject {
World.PhysicsSystem.RemoveBodyAssocation(bodyId)
})

this._nameTag?.Dispose()
this.DisableTransformControls()
World.SimulationSystem.UnregisterMechanism(this._mechanism)
World.PhysicsSystem.DestroyMechanism(this._mechanism)
Expand Down Expand Up @@ -345,6 +366,19 @@ class MirabufSceneObject extends SceneObject {
this.EnablePhysics()
}

/**
*
* @returns The bounding box of the mirabuf object.
*/
private ComputeBoundingBox(): THREE.Box3 {
const box = new THREE.Box3()
this._mirabufInstance.batches.forEach(batch => {
if (batch.boundingBox) box.union(batch.boundingBox)
})

return box
}

private getPreferences(): void {
this._intakePreferences = PreferencesSystem.getRobotPreferences(this.assemblyName)?.intake
this._ejectorPreferences = PreferencesSystem.getRobotPreferences(this.assemblyName)?.ejector
Expand Down
2 changes: 2 additions & 0 deletions fission/src/systems/preferences/PreferenceTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type GlobalPreference =
| "ReportAnalytics"
| "UseMetric"
| "RenderScoringZones"
| "RenderSceneTags"

export const RobotPreferencesKey: string = "Robots"
export const FieldPreferencesKey: string = "Fields"
Expand All @@ -23,6 +24,7 @@ export const DefaultGlobalPreferences: { [key: string]: unknown } = {
ReportAnalytics: false,
UseMetric: false,
RenderScoringZones: true,
RenderSceneTags: true,
}

export type IntakePreferences = {
Expand Down
20 changes: 20 additions & 0 deletions fission/src/systems/scene/SceneRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import fragmentShader from "@/shaders/fragment.glsl"
import { Theme } from "@/ui/ThemeContext"
import InputSystem from "../input/InputSystem"

import { PixelSpaceCoord, SceneOverlayEvent, SceneOverlayEventKey } from "@/ui/components/SceneOverlayEvents"
import {} from "@/ui/components/SceneOverlayEvents"
import PreferencesSystem from "../preferences/PreferencesSystem"

const CLEAR_COLOR = 0x121212
const GROUND_COLOR = 0x4066c7

Expand Down Expand Up @@ -151,6 +155,10 @@ class SceneRenderer extends WorldSystem {
)
})

// Update the tags each frame if they are enabled in preferences
if (PreferencesSystem.getGlobalPreference<boolean>("RenderSceneTags"))
new SceneOverlayEvent(SceneOverlayEventKey.UPDATE)

this._composer.render(deltaT)
}

Expand Down Expand Up @@ -219,6 +227,18 @@ class SceneRenderer extends WorldSystem {
return screenSpace.unproject(this.mainCamera)
}

/**
* Convert world space coordinates to screen space coordinates
*
* @param world World space coordinates
* @returns Pixel space coordinates
*/
public WorldToPixelSpace(world: THREE.Vector3): PixelSpaceCoord {
this._mainCamera.updateMatrixWorld()
const screenSpace = world.project(this._mainCamera)
return [(window.innerWidth * (screenSpace.x + 1.0)) / 2.0, (window.innerHeight * (1.0 - screenSpace.y)) / 2.0]
}

/**
* Updates the skybox colors based on the current theme
Expand Down
107 changes: 107 additions & 0 deletions fission/src/ui/components/SceneOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Box } from "@mui/material"
import { useEffect, useReducer, useState } from "react"
import {
SceneOverlayTag,
SceneOverlayEvent,
SceneOverlayEventKey,
SceneOverlayTagEvent,
SceneOverlayTagEventKey,
} from "./SceneOverlayEvents"
import Label, { LabelSize } from "./Label"

const tagMap = new Map<number, SceneOverlayTag>()

function SceneOverlay() {
/* State to determine if the overlay is disabled */
const [isDisabled, setIsDisabled] = useState<boolean>(false)

/* h1 text for each tagMap tag */
const [components, updateComponents] = useReducer(() => {
if (isDisabled) return <></> // if the overlay is disabled, return nothing

return [...tagMap.values()].map(x => (
<div
key={x.id}
style={{
position: "absolute",
left: x.position[0],
top: x.position[1],
backgroundColor: "rgba(0, 0, 0, 0.5)",
borderRadius: "8px",
padding: "8px",
whiteSpace: "nowrap",
transform: "translate(-50%, -100%)",
}}
>
<Label size={LabelSize.Large}>{x.text}</Label>
</div>
))
}, [])

/* Creating listener for tag events to update tagMap and rerender overlay */
useEffect(() => {
const onTagAdd = (e: Event) => {
tagMap.set((e as SceneOverlayTagEvent).tag.id, (e as SceneOverlayTagEvent).tag)
}

const onTagRemove = (e: Event) => {
tagMap.delete((e as SceneOverlayTagEvent).tag.id)
}

const onUpdate = (_: Event) => {
updateComponents()
}

const onDisable = () => {
setIsDisabled(true)
updateComponents()
}

const onEnable = () => {
setIsDisabled(false)
updateComponents()
}

// listening for tags being added and removed
SceneOverlayTagEvent.Listen(SceneOverlayTagEventKey.ADD, onTagAdd)
SceneOverlayTagEvent.Listen(SceneOverlayTagEventKey.REMOVE, onTagRemove)

// listening for updates to the overlay every frame
SceneOverlayEvent.Listen(SceneOverlayEventKey.UPDATE, onUpdate)

// listening for disabling and enabling scene tags
SceneOverlayEvent.Listen(SceneOverlayEventKey.DISABLE, onDisable)
SceneOverlayEvent.Listen(SceneOverlayEventKey.ENABLE, onEnable)

// disposing all the tags and listeners when the scene is destroyed
return () => {
SceneOverlayTagEvent.RemoveListener(SceneOverlayTagEventKey.ADD, onTagAdd)
SceneOverlayTagEvent.RemoveListener(SceneOverlayTagEventKey.REMOVE, onTagRemove)
SceneOverlayEvent.RemoveListener(SceneOverlayEventKey.UPDATE, onUpdate)
SceneOverlayEvent.RemoveListener(SceneOverlayEventKey.DISABLE, onDisable)
SceneOverlayEvent.RemoveListener(SceneOverlayEventKey.ENABLE, onEnable)
tagMap.clear()
}
}, [])

/* Render the overlay as a box that spans the entire screen and does not intercept any user interaction */
return (
<Box
component="div"
display="flex"
sx={{
position: "fixed",
left: "0pt",
top: "0pt",
width: "100vw",
height: "100vh",
overflow: "hidden",
pointerEvents: "none",
}}
>
{components ?? <></>}
</Box>
)
}

export default SceneOverlay
85 changes: 85 additions & 0 deletions fission/src/ui/components/SceneOverlayEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
let nextTagId = 0

/* Coordinates for tags in world space */
export type PixelSpaceCoord = [number, number]

/** Contains the event keys for events that require a SceneOverlayTag as a parameter */
export const enum SceneOverlayTagEventKey {
ADD = "SceneOverlayTagAddEvent",
REMOVE = "SceneOverlayTagRemoveEvent",
}

/** Contains the event keys for other Scene Overlay Events */
export const enum SceneOverlayEventKey {
UPDATE = "SceneOverlayUpdateEvent",
DISABLE = "SceneOverlayDisableEvent",
ENABLE = "SceneOverlayEnableEvent",
}

/**
* Represents a tag that can be displayed on the screen
*
* @param text The text to display
* @param position The position of the tag in screen space (default: [0,0])
*/
export class SceneOverlayTag {
private _id: number
public text: string
public position: PixelSpaceCoord // Screen Space

public get id() {
return this._id
}

/** Create a new tag */
public constructor(text: string, position?: PixelSpaceCoord) {
this._id = nextTagId++

this.text = text
this.position = position ?? [0, 0]
new SceneOverlayTagEvent(SceneOverlayTagEventKey.ADD, this)
}

/** Removing the tag */
public Dispose() {
new SceneOverlayTagEvent(SceneOverlayTagEventKey.REMOVE, this)
}
}

/** Event handler for events that use a SceneOverlayTag as a parameter */
export class SceneOverlayTagEvent extends Event {
public tag: SceneOverlayTag

public constructor(eventKey: SceneOverlayTagEventKey, tag: SceneOverlayTag) {
super(eventKey)

this.tag = tag

window.dispatchEvent(this)
}

public static Listen(eventKey: SceneOverlayTagEventKey, func: (e: Event) => void) {
window.addEventListener(eventKey, func)
}

public static RemoveListener(eventKey: SceneOverlayTagEventKey, func: (e: Event) => void) {
window.removeEventListener(eventKey, func)
}
}

/** Event handler for other SceneOverlay events */
export class SceneOverlayEvent extends Event {
public constructor(eventKey: SceneOverlayEventKey) {
super(eventKey)

window.dispatchEvent(this)
}

public static Listen(eventKey: SceneOverlayEventKey, func: (e: Event) => void) {
window.addEventListener(eventKey, func)
}

public static RemoveListener(eventKey: SceneOverlayEventKey, func: (e: Event) => void) {
window.removeEventListener(eventKey, func)
}
}
14 changes: 14 additions & 0 deletions fission/src/ui/modals/configuring/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Button from "@/components/Button"
import Slider from "@/components/Slider"
import Checkbox from "@/components/Checkbox"
import PreferencesSystem from "@/systems/preferences/PreferencesSystem"
import { SceneOverlayEvent, SceneOverlayEventKey } from "@/ui/components/SceneOverlayEvents"

const moveElementToTop = (arr: string[], element: string | undefined) => {
if (element == undefined) {
Expand Down Expand Up @@ -44,6 +45,9 @@ const SettingsModal: React.FC<ModalPropsImpl> = ({ modalId }) => {
const [renderScoringZones, setRenderScoringZones] = useState<boolean>(
PreferencesSystem.getGlobalPreference<boolean>("RenderScoringZones")
)
const [renderSceneTags, setRenderSceneTags] = useState<boolean>(
PreferencesSystem.getGlobalPreference<boolean>("RenderSceneTags")
)

const saveSettings = () => {
PreferencesSystem.setGlobalPreference<string>("ScreenMode", screenMode)
Expand All @@ -54,6 +58,7 @@ const SettingsModal: React.FC<ModalPropsImpl> = ({ modalId }) => {
PreferencesSystem.setGlobalPreference<boolean>("ReportAnalytics", reportAnalytics)
PreferencesSystem.setGlobalPreference<boolean>("UseMetric", useMetric)
PreferencesSystem.setGlobalPreference<boolean>("RenderScoringZones", renderScoringZones)
PreferencesSystem.setGlobalPreference<boolean>("RenderSceneTags", renderSceneTags)

PreferencesSystem.savePreferences()
}
Expand Down Expand Up @@ -136,6 +141,15 @@ const SettingsModal: React.FC<ModalPropsImpl> = ({ modalId }) => {
setRenderScoringZones(checked)
}}
/>
<Checkbox
label="Render Scene Tags"
defaultState={PreferencesSystem.getGlobalPreference<boolean>("RenderSceneTags")}
onClick={checked => {
setRenderSceneTags(checked)
if (!checked) new SceneOverlayEvent(SceneOverlayEventKey.DISABLE)
else new SceneOverlayEvent(SceneOverlayEventKey.ENABLE)
}}
/>
</Modal>
)
}
Expand Down

0 comments on commit 55a2dde

Please sign in to comment.