diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index badb33a28..d18d7c7e5 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -60,6 +60,7 @@ 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 GraphicsSettings from "./ui/panels/GraphicsSettingsPanel.tsx" const worker = new Lazy(() => new WPILibWSWorker()) @@ -228,6 +229,7 @@ const initialPanels: ReactElement[] = [ , , , + , ] export default Synthesis diff --git a/fission/src/systems/preferences/PreferenceTypes.ts b/fission/src/systems/preferences/PreferenceTypes.ts index 8c99e60d1..9d9b3adac 100644 --- a/fission/src/systems/preferences/PreferenceTypes.ts +++ b/fission/src/systems/preferences/PreferenceTypes.ts @@ -3,7 +3,6 @@ import { Vector3Tuple } from "three" /** Names of all global preferences. */ export type GlobalPreference = - | "QualitySettings" | "ZoomSensitivity" | "PitchSensitivity" | "YawSensitivity" @@ -17,13 +16,13 @@ export type GlobalPreference = export const RobotPreferencesKey: string = "Robots" export const FieldPreferencesKey: string = "Fields" +export const GraphicsPreferenceKey: string = "Quality" /** * Default values for GlobalPreferences as a fallback if they are not configured by the user. * Every global preference should have a default value. */ export const DefaultGlobalPreferences: { [key: string]: unknown } = { - QualitySettings: "High" as QualitySetting, ZoomSensitivity: 15, PitchSensitivity: 10, YawSensitivity: 3, @@ -36,7 +35,25 @@ export const DefaultGlobalPreferences: { [key: string]: unknown } = { SubsystemGravity: false, } -export type QualitySetting = "Low" | "Medium" | "High" +export type GraphicsPreferences = { + lightIntensity: number + fancyShadows: boolean + maxFar: number + cascades: number + shadowMapSize: number + antiAliasing: boolean +} + +export function DefaultGraphicsPreferences(): GraphicsPreferences { + return { + lightIntensity: 5, + fancyShadows: false, + maxFar: 30, + cascades: 4, + shadowMapSize: 4096, + antiAliasing: false, + } +} export type IntakePreferences = { deltaTransformation: number[] diff --git a/fission/src/systems/preferences/PreferencesSystem.ts b/fission/src/systems/preferences/PreferencesSystem.ts index 74a7d2460..af4b7fa5f 100644 --- a/fission/src/systems/preferences/PreferencesSystem.ts +++ b/fission/src/systems/preferences/PreferencesSystem.ts @@ -2,11 +2,14 @@ import { DefaultFieldPreferences, DefaultGlobalPreferences, DefaultRobotPreferences, + DefaultGraphicsPreferences, FieldPreferences, FieldPreferencesKey, GlobalPreference, RobotPreferences, RobotPreferencesKey, + GraphicsPreferences, + GraphicsPreferenceKey, } from "./PreferenceTypes" /** An event that's triggered when a preference is changed. */ @@ -133,6 +136,18 @@ class PreferencesSystem { return allFieldPrefs } + /** Gets simulation quality preferences */ + public static getGraphicsPreferences(): GraphicsPreferences { + let graphicsPrefs = this.getPreference(GraphicsPreferenceKey) + + if (graphicsPrefs == undefined) { + graphicsPrefs = DefaultGraphicsPreferences() + this._preferences[GraphicsPreferenceKey] = graphicsPrefs + } + + return graphicsPrefs + } + /** Loads all preferences from local storage. */ public static loadPreferences() { const loadedPrefs = window.localStorage.getItem(this._localStorageKey) diff --git a/fission/src/systems/scene/SceneRenderer.ts b/fission/src/systems/scene/SceneRenderer.ts index 205aa4434..4442e8384 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -15,6 +15,7 @@ import Jolt from "@barclah/jolt-physics" import { PixelSpaceCoord, SceneOverlayEvent, SceneOverlayEventKey } from "@/ui/components/SceneOverlayEvents" import PreferencesSystem from "../preferences/PreferencesSystem" import { CSM } from "three/examples/jsm/csm/CSM.js" +import { GraphicsPreferences } from "../preferences/PreferenceTypes" const CLEAR_COLOR = 0x121212 const GROUND_COLOR = 0x4066c7 @@ -28,8 +29,6 @@ class SceneRenderer extends WorldSystem { private _skybox: THREE.Mesh private _composer: EffectComposer - private _antiAliasPass: EffectPass - private _sceneObjects: Map private _orbitControls: OrbitControls @@ -65,11 +64,11 @@ class SceneRenderer extends WorldSystem { this._scene = new THREE.Scene() this._renderer = new THREE.WebGLRenderer({ - // Following parameters are used to optimize post-processing powerPreference: "high-performance", antialias: false, stencil: false, - depth: false, + depth: !PreferencesSystem.getGraphicsPreferences().antiAliasing, + // logarithmicDepthBuffer: true, }) this._renderer.setClearColor(CLEAR_COLOR) this._renderer.setPixelRatio(window.devicePixelRatio) @@ -78,7 +77,7 @@ class SceneRenderer extends WorldSystem { this._renderer.setSize(window.innerWidth, window.innerHeight) // Adding the lighting uisng quality preferences - this.ChangeLighting(PreferencesSystem.getGlobalPreference("QualitySettings")) + this.ChangeLighting(PreferencesSystem.getGraphicsPreferences().fancyShadows) const ambientLight = new THREE.AmbientLight(0xffffff, 0.3) this._scene.add(ambientLight) @@ -111,9 +110,11 @@ class SceneRenderer extends WorldSystem { this._composer = new EffectComposer(this._renderer) this._composer.addPass(new RenderPass(this._scene, this._mainCamera)) - const antiAliasEffect = new SMAAEffect({ edgeDetectionMode: EdgeDetectionMode.COLOR }) - this._antiAliasPass = new EffectPass(this._mainCamera, antiAliasEffect) - this._composer.addPass(this._antiAliasPass) + if (PreferencesSystem.getGraphicsPreferences().antiAliasing) { + const antiAliasEffect = new SMAAEffect({ edgeDetectionMode: EdgeDetectionMode.COLOR }) + const antiAliasPass = new EffectPass(this._mainCamera, antiAliasEffect) + this._composer.addPass(antiAliasPass) + } // Orbit controls this._orbitControls = new OrbitControls(this._mainCamera, this._renderer.domElement) @@ -127,6 +128,7 @@ class SceneRenderer extends WorldSystem { this._mainCamera.updateProjectionMatrix() } + /** Function to disable or enable the antiAliasingPass */ public Update(deltaT: number): void { this._sceneObjects.forEach(obj => { obj.Update() @@ -164,7 +166,7 @@ class SceneRenderer extends WorldSystem { * * @param quality: string representing the quality of lighting - "Low", "Medium", "High" */ - public ChangeLighting(quality: string): void { + public ChangeLighting(fancyShadows: boolean): void { // removing the previous lighting method if (this._light instanceof THREE.DirectionalLight) { this._scene.remove(this._light) @@ -174,13 +176,14 @@ class SceneRenderer extends WorldSystem { } // setting the shadow map size - const shadowMapSize = Math.min(4096, this._renderer.capabilities.maxTextureSize) + const graphicsSettings = PreferencesSystem.getGraphicsPreferences() + const shadowMapSize = Math.min(graphicsSettings.shadowMapSize, this._renderer.capabilities.maxTextureSize) // setting the light to a basic directional light - if (quality === "Low" || quality === "Medium") { + if (!fancyShadows) { const shadowCamSize = 15 - this._light = new THREE.DirectionalLight(0xffffff, 5.0) + this._light = new THREE.DirectionalLight(0xffffff, graphicsSettings.lightIntensity) this._light.position.set(-1.0, 3.0, 2.0) this._light.castShadow = true this._light.shadow.camera.top = shadowCamSize @@ -192,42 +195,79 @@ class SceneRenderer extends WorldSystem { this._light.shadow.bias = 0.0 this._light.shadow.normalBias = 0.01 this._scene.add(this._light) - } else if (quality === "High") { - // setting light to cascading shadows - this._light = new CSM({ - parent: this._scene, - camera: this._mainCamera, - cascades: 4, - lightDirection: new THREE.Vector3(1.0, -3.0, -2.0).normalize(), - lightIntensity: 5, - shadowMapSize: shadowMapSize, - mode: "custom", - maxFar: 30, - shadowBias: -0.00001, - customSplitsCallback: (cascades: number, near: number, far: number, breaks: number[]) => { - const blend = 0.7 - for (let i = 1; i < cascades; i++) { - const uniformFactor = (near + ((far - near) * i) / cascades) / far - const logarithmicFactor = (near * (far / near) ** (i / cascades)) / far - const combinedFactor = uniformFactor * (1 - blend) + logarithmicFactor * blend - - breaks.push(combinedFactor) - } - - breaks.push(1) - }, - }) - - // setting up the materials for all objects in the scene - this._light.fade = true - this._scene.children.forEach(child => { - if (child instanceof THREE.Mesh) { - if (this._light instanceof CSM) this._light.setupMaterial(child.material) + } else { + // setting the light to a cascading shadow map + this.CreateCSM(graphicsSettings) + + // setting up all the materials + this.SetupCSMMaterials() + } + } + + public CreateCSM(settings: GraphicsPreferences) { + this._light = new CSM({ + parent: this._scene, + camera: this._mainCamera, + cascades: settings.cascades, + lightDirection: new THREE.Vector3(1.0, -3.0, -2.0).normalize(), + lightIntensity: settings.lightIntensity, + shadowMapSize: settings.shadowMapSize, + mode: "custom", + maxFar: settings.maxFar, + shadowBias: -0.00001, + customSplitsCallback: (cascades: number, near: number, far: number, breaks: number[]) => { + const blend = 0.7 + for (let i = 1; i < cascades; i++) { + const uniformFactor = (near + ((far - near) * i) / cascades) / far + const logarithmicFactor = (near * (far / near) ** (i / cascades)) / far + const combinedFactor = uniformFactor * (1 - blend) + logarithmicFactor * blend + + breaks.push(combinedFactor) } - }) + + breaks.push(1) + }, + }) + this._light.fade = true + } + + private SetupCSMMaterials() { + this._scene.children.forEach(child => { + if (child instanceof THREE.Mesh) { + if (this._light instanceof CSM) this._light.setupMaterial(child.material) + } + }) + } + + /** Sets the light intensity for both directional light and csm */ + public setLightIntensity(intensity: number) { + if (this._light instanceof THREE.DirectionalLight) { + this._light.intensity = intensity + } else if (this._light instanceof CSM) { + this._light.lightIntensity = intensity + + this._light.createLights() + this._light.injectInclude() + this._light.updateFrustums() + this._light.update() + console.log(this._light.lightIntensity) } } + /** Changes the settings of the cascading shadows from the Quality Settings Panel */ + public changeCSMSettings(settings: GraphicsPreferences) { + if (!(this._light instanceof CSM)) return + + this._light.maxFar = settings.maxFar + this._light.shadowMapSize = Math.min(settings.shadowMapSize, this._renderer.capabilities.maxTextureSize) + this._light.cascades = settings.cascades + + this._light.createLights() + this._light.injectInclude() + this._light.updateFrustums() + this._light.update() + } + public RegisterSceneObject(obj: T): number { const id = nextSceneObjectId++ obj.id = id diff --git a/fission/src/ui/components/StyledComponents.tsx b/fission/src/ui/components/StyledComponents.tsx index 66e404d71..bb8f2a939 100644 --- a/fission/src/ui/components/StyledComponents.tsx +++ b/fission/src/ui/components/StyledComponents.tsx @@ -3,7 +3,7 @@ import Label, { LabelSize } from "./Label" import Button, { ButtonProps, ButtonSize } from "./Button" import { IoCheckmark, IoPencil, IoPeople, IoTrashBin } from "react-icons/io5" import { HiDownload } from "react-icons/hi" -import { AiOutlinePlus } from "react-icons/ai" +import { AiOutlineInfoCircle, AiOutlinePlus } from "react-icons/ai" import { BiRefresh } from "react-icons/bi" import { AiFillWarning } from "react-icons/ai" import { BsCodeSquare } from "react-icons/bs" @@ -53,6 +53,7 @@ export class SynthesisIcons { public static SteeringWheel = () public static OutlineDoubleRight = () public static Connect = () + public static Info = () public static Bug = () /** Large icons: used for icon buttons */ diff --git a/fission/src/ui/modals/configuring/SettingsModal.tsx b/fission/src/ui/modals/configuring/SettingsModal.tsx index 006666d7b..35e9f56ed 100644 --- a/fission/src/ui/modals/configuring/SettingsModal.tsx +++ b/fission/src/ui/modals/configuring/SettingsModal.tsx @@ -1,21 +1,21 @@ import React, { useState } from "react" +import { useModalControlContext } from "@/ui/ModalContext" +import { usePanelControlContext } from "@/ui/PanelContext" import Modal, { ModalPropsImpl } from "@/components/Modal" import { FaGear } from "react-icons/fa6" import Label, { LabelSize } from "@/components/Label" -import Dropdown from "@/components/Dropdown" +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" -import { QualitySetting } from "@/systems/preferences/PreferenceTypes" import { Box } from "@mui/material" import { Spacer } from "@/ui/components/StyledComponents" -import World from "@/systems/World" const SettingsModal: React.FC = ({ modalId }) => { - const [qualitySettings, setQualitySettings] = useState( - PreferencesSystem.getGlobalPreference("QualitySettings") - ) + const { openModal, closeModal } = useModalControlContext() + const { openPanel } = usePanelControlContext() + const [zoomSensitivity, setZoomSensitivity] = useState( PreferencesSystem.getGlobalPreference("ZoomSensitivity") ) @@ -43,7 +43,6 @@ const SettingsModal: React.FC = ({ modalId }) => { ) const saveSettings = () => { - PreferencesSystem.setGlobalPreference("QualitySettings", qualitySettings) PreferencesSystem.setGlobalPreference("ZoomSensitivity", zoomSensitivity) PreferencesSystem.setGlobalPreference("PitchSensitivity", pitchSensitivity) PreferencesSystem.setGlobalPreference("YawSensitivity", yawSensitivity) @@ -68,15 +67,15 @@ const SettingsModal: React.FC = ({ modalId }) => { >
- ("QualitySettings")} - onSelect={selected => { - setQualitySettings(selected) - World.SceneRenderer.ChangeLighting(selected) - }} - /> + +