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

Feat/graphics settings #2534

Merged
merged 18 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
105 changes: 58 additions & 47 deletions client/src/three/GameRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SetupResult } from "@/dojo/setup";
import useUIStore, { AppStore } from "@/hooks/store/useUIStore";
import { SceneName } from "@/types";
import { IS_LOW_GRAPHICS_ENABLED } from "@/ui/config";
import { GRAPHICS_SETTING, GraphicsSettings } from "@/ui/config";
import throttle from "lodash/throttle";
import {
BloomEffect,
Expand Down Expand Up @@ -69,10 +69,10 @@ export default class GameRenderer {
private sceneManager!: SceneManager;
private systemManager!: SystemManager;

private isLowGraphicsMode: boolean;
private graphicsSetting: GraphicsSettings;

constructor(dojoContext: SetupResult) {
this.isLowGraphicsMode = IS_LOW_GRAPHICS_ENABLED;
this.graphicsSetting = GRAPHICS_SETTING;
this.initializeRenderer();
this.dojo = dojoContext;
this.locationManager = new LocationManager();
Expand Down Expand Up @@ -165,16 +165,13 @@ export default class GameRenderer {
this.renderer = new THREE.WebGLRenderer({
powerPreference: "high-performance",
antialias: false,
stencil: false,
depth: false,
stencil: this.graphicsSetting === GraphicsSettings.LOW,
depth: this.graphicsSetting === GraphicsSettings.LOW,
});
this.renderer.setPixelRatio(this.isLowGraphicsMode ? 0.75 : window.devicePixelRatio);
this.renderer.shadowMap.enabled = !this.isLowGraphicsMode;
this.renderer.setPixelRatio(this.graphicsSetting !== GraphicsSettings.HIGH ? 0.75 : window.devicePixelRatio);
this.renderer.shadowMap.enabled = this.graphicsSetting !== GraphicsSettings.LOW;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.renderer.setSize(
this.isLowGraphicsMode ? window.innerWidth : window.innerWidth,
this.isLowGraphicsMode ? window.innerHeight : window.innerHeight,
);
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.toneMapping = THREE.NoToneMapping;
this.renderer.toneMappingExposure = 1;
this.renderer.autoClear = false;
Expand Down Expand Up @@ -207,7 +204,7 @@ export default class GameRenderer {
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.1;
this.controls.target.set(0, 0, 0);
if (this.isLowGraphicsMode) {
if (this.graphicsSetting !== GraphicsSettings.HIGH) {
this.controls.enableDamping = false;
}
this.controls.addEventListener(
Expand Down Expand Up @@ -296,41 +293,45 @@ export default class GameRenderer {

const obj = { brightness: -0.1, contrast: 0 };
const folder = GUIManager.addFolder("BrightnesContrastt");
const BCEffect = new BrightnessContrastEffect({
brightness: obj.brightness,
contrast: obj.contrast,
});
folder
.add(obj, "brightness")
.name("Brightness")
.min(-1)
.max(1)
.step(0.01)
.onChange((value: number) => {
BCEffect.brightness = value;
});
folder
.add(obj, "contrast")
.name("Contrast")
.min(-1)
.max(1)
.step(0.01)
.onChange((value: number) => {
BCEffect.contrast = value;
if (GRAPHICS_SETTING !== GraphicsSettings.LOW) {
const BCEffect = new BrightnessContrastEffect({
brightness: obj.brightness,
contrast: obj.contrast,
});
this.composer.addPass(
new EffectPass(
this.camera,

new FXAAEffect(),
new BloomEffect({
luminanceThreshold: 1.1,
mipmapBlur: true,
intensity: 0.25,
}),
BCEffect,
),
);

folder
.add(obj, "brightness")
.name("Brightness")
.min(-1)
.max(1)
.step(0.01)
.onChange((value: number) => {
BCEffect.brightness = value;
});
folder
.add(obj, "contrast")
.name("Contrast")
.min(-1)
.max(1)
.step(0.01)
.onChange((value: number) => {
BCEffect.contrast = value;
});

this.composer.addPass(
new EffectPass(
this.camera,

new FXAAEffect(),
new BloomEffect({
luminanceThreshold: 1.1,
mipmapBlur: true,
intensity: 0.25,
}),
BCEffect,
),
);
}

this.sceneManager.moveCameraForScene();
}
Expand Down Expand Up @@ -391,8 +392,19 @@ export default class GameRenderer {
});
return;
}

const currentTime = performance.now();
const deltaTime = (currentTime - this.lastTime) / 1000; // Convert to seconds

// Skip frame if not enough time has passed (for 30 FPS)
if (this.graphicsSetting !== GraphicsSettings.HIGH) {
const frameTime = 1000 / 30; // 33.33ms for 30 FPS
if (currentTime - this.lastTime < frameTime) {
Comment on lines +401 to +402
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider extracting the frame rate limiting logic into a reusable utility function since it's used in multiple places

Suggested change
const frameTime = 1000 / 30; // 33.33ms for 30 FPS
if (currentTime - this.lastTime < frameTime) {
if (isFrameSkipped(currentTime, this.lastTime, 30)) {

requestAnimationFrame(() => this.animate());
return;
}
}

this.lastTime = currentTime;

if (this.stats) this.stats.update();
Expand Down Expand Up @@ -421,7 +433,6 @@ export default class GameRenderer {
this.renderer.render(this.hudScene.getScene(), this.hudScene.getCamera());
this.labelRenderer.render(this.hudScene.getScene(), this.hudScene.getCamera());

// Update the minimap
requestAnimationFrame(() => {
this.animate();
});
Expand Down
8 changes: 6 additions & 2 deletions client/src/three/components/ArmyModel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IS_LOW_GRAPHICS_ENABLED } from "@/ui/config";
import { GRAPHICS_SETTING, GraphicsSettings } from "@/ui/config";
import * as THREE from "three";
import { AnimationClip, AnimationMixer } from "three";
import { gltfLoader } from "../helpers/utils";
Expand Down Expand Up @@ -159,6 +159,10 @@ export class ArmyModel {
updateAnimations(deltaTime: number) {
const time = performance.now() * 0.001;

if (GRAPHICS_SETTING === GraphicsSettings.LOW) {
return;
}

this.models.forEach((modelData) => {
let needsMatrixUpdate = false;

Expand All @@ -184,7 +188,7 @@ export class ArmyModel {

for (let i = 0; i < modelData.mesh.count; i++) {
const animationState = this.animationStates[i];
if (IS_LOW_GRAPHICS_ENABLED && animationState === ANIMATION_STATE_IDLE) {
if (GRAPHICS_SETTING === GraphicsSettings.MID && animationState === ANIMATION_STATE_IDLE) {
continue;
}

Expand Down
5 changes: 5 additions & 0 deletions client/src/three/components/BattleModel.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GRAPHICS_SETTING, GraphicsSettings } from "@/ui/config";
import * as THREE from "three";
import { AnimationClip, AnimationMixer } from "three";
import { gltfLoader } from "../helpers/utils";
Expand Down Expand Up @@ -74,6 +75,10 @@ export class BattleModel {
}

updateAnimations(deltaTime: number) {
if (GRAPHICS_SETTING === GraphicsSettings.LOW) {
return;
}

if (this.mixer && this.mesh && this.animation) {
const time = performance.now() * 0.001;
for (let i = 0; i < this.mesh.count; i++) {
Expand Down
7 changes: 6 additions & 1 deletion client/src/three/components/InstancedBiome.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GRAPHICS_SETTING, GraphicsSettings } from "@/ui/config";
import * as THREE from "three";
import { AnimationClip, AnimationMixer } from "three";
import { PREVIEW_BUILD_COLOR_INVALID } from "../scenes/constants";
Expand Down Expand Up @@ -43,7 +44,7 @@ export default class InstancedModel {
tmp.userData.isInstanceModel = true;

if (!enableRaycast) {
tmp.raycast = () => {};
tmp.raycast = () => { };
}

this.mixer = new AnimationMixer(gltf.scene);
Expand Down Expand Up @@ -137,6 +138,10 @@ export default class InstancedModel {
}

updateAnimations(deltaTime: number) {
if (GRAPHICS_SETTING === GraphicsSettings.LOW) {
return;
}

if (this.mixer && this.animation) {
const time = performance.now() * 0.001;
this.instancedMeshes.forEach((mesh, meshIndex) => {
Expand Down
7 changes: 6 additions & 1 deletion client/src/three/components/InstancedModel.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GRAPHICS_SETTING, GraphicsSettings } from "@/ui/config";
import { ResourcesIds, StructureType } from "@bibliothecadao/eternum";
import * as THREE from "three";
import { AnimationClip, AnimationMixer } from "three";
Expand Down Expand Up @@ -73,7 +74,7 @@ export default class InstancedModel {
tmp.userData.isInstanceModel = true;

if (!enableRaycast) {
tmp.raycast = () => {};
tmp.raycast = () => { };
}

this.mixer = new AnimationMixer(gltf.scene);
Expand Down Expand Up @@ -168,6 +169,10 @@ export default class InstancedModel {
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove console.log statement in removeInstance method

Suggested change
}


updateAnimations(deltaTime: number) {
if (GRAPHICS_SETTING === GraphicsSettings.LOW) {
return;
}

if (this.mixer && this.animation) {
const time = performance.now() * 0.001;
this.instancedMeshes.forEach((mesh, meshIndex) => {
Expand Down
32 changes: 25 additions & 7 deletions client/src/ui/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,47 @@ import { BuildingType, FELT_CENTER } from "@bibliothecadao/eternum";

export { FELT_CENTER };

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding JSDoc comments to document what features/performance characteristics each graphics setting level provides

Suggested change
/**
* Graphics quality settings
* LOW - Reduced shadows, animations, and effects. 30 FPS cap. Best for low-end devices
* MID - Balanced settings with some effects enabled. 30 FPS cap
* HIGH - Full graphics quality with all effects enabled. Uncapped FPS
*/

const checkIfGameIsRunningOnLaptop = async () => {
export enum GraphicsSettings {
LOW = "LOW",
MID = "MID",
HIGH = "HIGH"
}

const checkGraphicsSettings = async () => {
// Handle migration from old LOW_GRAPHICS_FLAG
const oldLowGraphicsFlag = localStorage.getItem("LOW_GRAPHICS_FLAG");
if (oldLowGraphicsFlag !== null) {
// Migrate old setting to new format
const newSetting = oldLowGraphicsFlag === "true" ? GraphicsSettings.LOW : GraphicsSettings.HIGH;
localStorage.setItem("GRAPHICS_SETTING", newSetting);
Comment on lines +12 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using TypeScript const assertions for the localStorage keys to prevent typos and improve type safety

Suggested change
const checkGraphicsSettings = async () => {
// Handle migration from old LOW_GRAPHICS_FLAG
const oldLowGraphicsFlag = localStorage.getItem("LOW_GRAPHICS_FLAG");
if (oldLowGraphicsFlag !== null) {
// Migrate old setting to new format
const newSetting = oldLowGraphicsFlag === "true" ? GraphicsSettings.LOW : GraphicsSettings.HIGH;
localStorage.setItem("GRAPHICS_SETTING", newSetting);
const STORAGE_KEYS = {
GRAPHICS_SETTING: 'GRAPHICS_SETTING',
LOW_GRAPHICS_FLAG: 'LOW_GRAPHICS_FLAG',
INITIAL_LAPTOP_CHECK: 'INITIAL_LAPTOP_CHECK'
} as const;
const checkGraphicsSettings = async () => {
// Handle migration from old LOW_GRAPHICS_FLAG
const oldLowGraphicsFlag = localStorage.getItem(STORAGE_KEYS.LOW_GRAPHICS_FLAG);
if (oldLowGraphicsFlag !== null) {
// Migrate old setting to new format
const newSetting = oldLowGraphicsFlag === "true" ? GraphicsSettings.LOW : GraphicsSettings.HIGH;
localStorage.setItem(STORAGE_KEYS.GRAPHICS_SETTING, newSetting);

localStorage.removeItem("LOW_GRAPHICS_FLAG"); // Clean up old setting
return newSetting;
}

// Check if initial laptop check has been done
if (!localStorage.getItem("INITIAL_LAPTOP_CHECK")) {
try {
const battery = await (navigator as any).getBattery();
if (battery.charging && battery.chargingTime === 0) {
// It's likely a desktop
localStorage.setItem("LOW_GRAPHICS_FLAG", "false");
localStorage.setItem("GRAPHICS_SETTING", GraphicsSettings.HIGH);
} else {
// It's likely a laptop or mobile device.
localStorage.setItem("LOW_GRAPHICS_FLAG", "true");
// It's likely a laptop or mobile device
localStorage.setItem("GRAPHICS_SETTING", GraphicsSettings.LOW);
}
} catch (error) {
console.error("Error calling getBattery():", error);
// Set default values if getBattery() is not supported
localStorage.setItem("LOW_GRAPHICS_FLAG", "true");
localStorage.setItem("GRAPHICS_SETTING", GraphicsSettings.LOW);
} finally {
localStorage.setItem("INITIAL_LAPTOP_CHECK", "true");
}
}
return localStorage.getItem("LOW_GRAPHICS_FLAG") === "true";

return localStorage.getItem("GRAPHICS_SETTING") as GraphicsSettings || GraphicsSettings.HIGH;
};

export const IS_LOW_GRAPHICS_ENABLED = await checkIfGameIsRunningOnLaptop();
export const GRAPHICS_SETTING = await checkGraphicsSettings();

export const IS_MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);

Expand Down
41 changes: 24 additions & 17 deletions client/src/ui/modules/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useMusicPlayer } from "@/hooks/useMusic";
import useScreenOrientation from "@/hooks/useScreenOrientation";
import { settings } from "@/ui/components/navigation/Config";
import { OSWindow } from "@/ui/components/navigation/OSWindow";
import { IS_LOW_GRAPHICS_ENABLED } from "@/ui/config";
import { GraphicsSettings } from "@/ui/config";
import Avatar from "@/ui/elements/Avatar";
import Button from "@/ui/elements/Button";
import { Checkbox } from "@/ui/elements/Checkbox";
Expand Down Expand Up @@ -61,10 +61,11 @@ export const SettingsWindow = () => {

const isOpen = useUIStore((state) => state.isPopupOpen(settings));

const isLowGraphics = IS_LOW_GRAPHICS_ENABLED;
const GRAPHICS_SETTING = localStorage.getItem("GRAPHICS_SETTING") as GraphicsSettings || GraphicsSettings.HIGH;

return (
<OSWindow onClick={() => togglePopup(settings)} show={isOpen} title={settings}>
<div className="flex p-4 justify-between">
<div className="flex justify-between p-4">
<div className="relative">
<Avatar
onClick={() => setShowSettings(!showSettings)}
Expand Down Expand Up @@ -93,25 +94,31 @@ export const SettingsWindow = () => {
<Headline>Graphics</Headline>
<div className="flex space-x-2">
<Button
disabled={!!isLowGraphics}
variant={isLowGraphics ? "success" : "outline"}
disabled={GRAPHICS_SETTING === GraphicsSettings.LOW}
variant={GRAPHICS_SETTING === GraphicsSettings.LOW ? "success" : "outline"}
onClick={() => {
if (!isLowGraphics) {
localStorage.setItem("LOW_GRAPHICS_FLAG", "true");
window.location.reload();
}
localStorage.setItem("GRAPHICS_SETTING", GraphicsSettings.LOW);
window.location.reload();
}}
>
Low
</Button>
<Button
disabled={!isLowGraphics}
variant={isLowGraphics ? "outline" : "success"}
disabled={GRAPHICS_SETTING === GraphicsSettings.MID}
variant={GRAPHICS_SETTING === GraphicsSettings.MID ? "success" : "outline"}
onClick={() => {
localStorage.setItem("GRAPHICS_SETTING", GraphicsSettings.MID);
window.location.reload();
}}
>
Medium
</Button>
<Button
disabled={GRAPHICS_SETTING === GraphicsSettings.HIGH}
variant={GRAPHICS_SETTING === GraphicsSettings.HIGH ? "success" : "outline"}
onClick={() => {
if (isLowGraphics) {
localStorage.removeItem("LOW_GRAPHICS_FLAG");
window.location.reload();
}
localStorage.setItem("GRAPHICS_SETTING", GraphicsSettings.HIGH);
window.location.reload();
}}
>
High
Expand All @@ -122,11 +129,11 @@ export const SettingsWindow = () => {
<div className="flex space-x-2">
{isSoundOn ? (
<Button variant="outline" onClick={() => toggleSound()}>
<Unmuted className="w-4 cursor-pointer fill-gold" />
<Unmuted className="w-4 cursor-pointer fill-gold" />
</Button>
) : (
<Button variant="outline" onClick={() => toggleSound()}>
<Muted className="w-4 cursor-pointer fill-gold" />
<Muted className="w-4 cursor-pointer fill-gold" />
</Button>
)}

Expand Down
Loading