diff --git a/client/public/models/biomes-flat-opt/deciduousForest.glb b/client/public/models/biomes-flat-opt/deciduousForest.glb new file mode 100644 index 000000000..df46296f5 Binary files /dev/null and b/client/public/models/biomes-flat-opt/deciduousForest.glb differ diff --git a/client/public/models/biomes-flat-opt/grassland.glb b/client/public/models/biomes-flat-opt/grassland.glb new file mode 100644 index 000000000..a9c1da6b1 Binary files /dev/null and b/client/public/models/biomes-flat-opt/grassland.glb differ diff --git a/client/public/models/biomes-flat/deciduousForest.glb b/client/public/models/biomes-flat/deciduousForest.glb new file mode 100644 index 000000000..90a7789da Binary files /dev/null and b/client/public/models/biomes-flat/deciduousForest.glb differ diff --git a/client/public/models/biomes-flat/grassland.glb b/client/public/models/biomes-flat/grassland.glb new file mode 100644 index 000000000..cd9ed12aa Binary files /dev/null and b/client/public/models/biomes-flat/grassland.glb differ diff --git a/client/src/three/GameRenderer.ts b/client/src/three/GameRenderer.ts index 4c6fbd618..8c5dd1fc8 100644 --- a/client/src/three/GameRenderer.ts +++ b/client/src/three/GameRenderer.ts @@ -1,7 +1,7 @@ import { SetupResult } from "@/dojo/setup"; import useUIStore, { AppStore } from "@/hooks/store/useUIStore"; import { SceneName } from "@/types"; -import { GRAPHICS_SETTING, GraphicsSettings } from "@/ui/config"; +import { GRAPHICS_SETTING, GraphicsSettings, IS_FLAT_MODE } from "@/ui/config"; import throttle from "lodash/throttle"; import { BloomEffect, @@ -87,7 +87,7 @@ export default class GameRenderer { this.state = state; }); - this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 30); + this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, IS_FLAT_MODE ? 50 : 30); const cameraHeight = Math.sin(this.cameraAngle) * this.cameraDistance; const cameraDepth = Math.cos(this.cameraAngle) * this.cameraDistance; this.camera.position.set(0, cameraHeight, cameraDepth); diff --git a/client/src/three/helpers/GUIManager.ts b/client/src/three/helpers/GUIManager.ts index adfc4dd5d..d9156ff5c 100644 --- a/client/src/three/helpers/GUIManager.ts +++ b/client/src/three/helpers/GUIManager.ts @@ -3,7 +3,7 @@ import GUI from "lil-gui"; import { env } from "../../../env"; export const GUIManager = new GUI({ - autoPlace: env.VITE_PUBLIC_DEV == true && !IS_MOBILE, + autoPlace: env.VITE_PUBLIC_GRAPHICS_DEV == true && !IS_MOBILE, }); GUIManager.close(); diff --git a/client/src/three/scenes/HexagonScene.ts b/client/src/three/scenes/HexagonScene.ts index 61c616ce1..4ef880b65 100644 --- a/client/src/three/scenes/HexagonScene.ts +++ b/client/src/three/scenes/HexagonScene.ts @@ -1,6 +1,7 @@ import { type SetupResult } from "@/dojo/setup"; import useUIStore, { type AppStore } from "@/hooks/store/useUIStore"; import { type HexPosition, type SceneName } from "@/types"; +import { GRAPHICS_SETTING, GraphicsSettings, IS_FLAT_MODE } from "@/ui/config"; import { LeftView } from "@/ui/modules/navigation/LeftNavigationModule"; import { RightView } from "@/ui/modules/navigation/RightNavigationModule"; import { getWorldPositionForHex } from "@/ui/utils/utils"; @@ -66,7 +67,9 @@ export abstract class HexagonScene { this.scene.background = new THREE.Color(0x8790a1); this.state = useUIStore.getState(); this.fog = new THREE.Fog(0xffffff, 21, 30); - this.scene.fog = this.fog; + if (!IS_FLAT_MODE && GRAPHICS_SETTING === GraphicsSettings.HIGH) { + this.scene.fog = this.fog; + } // subscribe to state changes useUIStore.subscribe( @@ -311,19 +314,22 @@ export abstract class HexagonScene { public moveCameraToXYZ(x: number, y: number, z: number, duration: number = 2) { const newTarget = new THREE.Vector3(x, y, z); - const target = this.controls.target; const pos = this.controls.object.position; - - // go to new target but keep same view angle const deltaX = newTarget.x - target.x; const deltaZ = newTarget.z - target.z; + + const newPosition = IS_FLAT_MODE + ? new THREE.Vector3(newTarget.x, pos.y, newTarget.z) + : new THREE.Vector3(pos.x + deltaX, pos.y, pos.z + deltaZ); + if (duration) { - this.cameraAnimate(new THREE.Vector3(pos.x + deltaX, pos.y, pos.z + deltaZ), newTarget, duration); + this.cameraAnimate(newPosition, newTarget, duration); } else { - target.set(newTarget.x, newTarget.y, newTarget.z); - pos.set(pos.x + deltaX, pos.y, pos.z + deltaZ); + target.copy(newTarget); + pos.copy(newPosition); } + this.controls.update(); } @@ -338,11 +344,16 @@ export abstract class HexagonScene { // go to new target with but keep same view angle const deltaX = newTarget.x - target.x; const deltaZ = newTarget.z - target.z; + + const newPosition = IS_FLAT_MODE + ? new THREE.Vector3(newTarget.x, pos.y, newTarget.z) + : new THREE.Vector3(pos.x + deltaX, pos.y, pos.z + deltaZ); + if (duration) { - this.cameraAnimate(new THREE.Vector3(pos.x + deltaX, pos.y, pos.z + deltaZ), newTarget, duration); + this.cameraAnimate(newPosition, newTarget, duration); } else { - target.set(newTarget.x, newTarget.y, newTarget.z); - pos.set(pos.x + deltaX, pos.y, pos.z + deltaZ); + target.copy(newTarget); + pos.copy(newPosition); } this.controls.update(); } diff --git a/client/src/three/scenes/Hexception.tsx b/client/src/three/scenes/Hexception.tsx index b5e252e0e..702c2bb82 100644 --- a/client/src/three/scenes/Hexception.tsx +++ b/client/src/three/scenes/Hexception.tsx @@ -5,6 +5,7 @@ import { SetupResult } from "@/dojo/setup"; import useUIStore from "@/hooks/store/useUIStore"; import { HexPosition, ResourceMiningTypes, SceneName } from "@/types"; import { Position } from "@/types/Position"; +import { IS_FLAT_MODE } from "@/ui/config"; import { ResourceIcon } from "@/ui/elements/ResourceIcon"; import { LeftView } from "@/ui/modules/navigation/LeftNavigationModule"; import { @@ -271,7 +272,7 @@ export default class HexceptionScene extends HexagonScene { this.removeCastleFromScene(); this.updateHexceptionGrid(this.hexceptionRadius); - this.controls.maxDistance = 18; + this.controls.maxDistance = IS_FLAT_MODE ? 36 : 18; this.controls.enablePan = false; this.controls.zoomToCursor = false; @@ -540,6 +541,7 @@ export default class HexceptionScene extends HexagonScene { this.pillars!.setColorAt(index + pillarOffset, BIOME_COLORS[biome as BiomeType]); }); pillarOffset += matrices.length; + this.pillars!.position.y = -0.01; this.pillars!.count = pillarOffset; this.pillars!.computeBoundingSphere(); hexMesh.setCount(matrices.length); @@ -670,12 +672,16 @@ export default class HexceptionScene extends HexagonScene { positions.forEach((position) => { dummy.position.x = position.x; dummy.position.z = position.z; - dummy.position.y = isMainHex || isFlat || position.isBorder ? 0 : position.y / 2; + dummy.position.y = isMainHex || isFlat || position.isBorder || IS_FLAT_MODE ? 0 : position.y / 2; dummy.scale.set(HEX_SIZE, HEX_SIZE, HEX_SIZE); const rotationSeed = this.hashCoordinates(position.col, position.row); const rotationIndex = Math.floor(rotationSeed * 6); const randomRotation = (rotationIndex * Math.PI) / 3; - dummy.rotation.y = randomRotation; + if (!IS_FLAT_MODE) { + dummy.rotation.y = randomRotation; + } else { + dummy.rotation.y = 0; + } dummy.updateMatrix(); biomeHexes[biome].push(dummy.matrix.clone()); }); diff --git a/client/src/three/scenes/Worldmap.ts b/client/src/three/scenes/Worldmap.ts index 734aa3eec..19b4cd289 100644 --- a/client/src/three/scenes/Worldmap.ts +++ b/client/src/three/scenes/Worldmap.ts @@ -6,7 +6,7 @@ import { LoadingStateKey } from "@/hooks/store/useWorldLoading"; import { soundSelector } from "@/hooks/useUISound"; import { HexPosition, SceneName } from "@/types"; import { Position } from "@/types/Position"; -import { FELT_CENTER, IS_MOBILE } from "@/ui/config"; +import { FELT_CENTER, IS_FLAT_MODE, IS_MOBILE } from "@/ui/config"; import { UNDEFINED_STRUCTURE_ENTITY_ID } from "@/ui/constants"; import { LeftView } from "@/ui/modules/navigation/LeftNavigationModule"; import { getWorldPositionForHex } from "@/ui/utils/utils"; @@ -369,7 +369,7 @@ export default class WorldmapScene extends HexagonScene { } setup() { - this.controls.maxDistance = 20; + this.controls.maxDistance = IS_FLAT_MODE ? 40 : 20; this.controls.enablePan = true; this.controls.zoomToCursor = true; this.moveCameraToURLLocation(); @@ -428,10 +428,14 @@ export default class WorldmapScene extends HexagonScene { dummy.scale.set(HEX_SIZE, HEX_SIZE, HEX_SIZE); } - const rotationSeed = this.hashCoordinates(col, row); - const rotationIndex = Math.floor(rotationSeed * 6); - const randomRotation = (rotationIndex * Math.PI) / 3; - dummy.rotation.y = randomRotation; + if (!IS_FLAT_MODE) { + const rotationSeed = this.hashCoordinates(col, row); + const rotationIndex = Math.floor(rotationSeed * 6); + const randomRotation = (rotationIndex * Math.PI) / 3; + dummy.rotation.y = randomRotation; + } else { + dummy.rotation.y = 0; + } const biomePosition = new Position({ x: col, y: row }).getContract(); const biome = this.biome.getBiome(biomePosition.x, biomePosition.y); @@ -483,6 +487,23 @@ export default class WorldmapScene extends HexagonScene { this.removeCachedMatricesAroundColRow(renderedChunkCenterCol, renderedChunkCenterRow); } + getChunksAround(chunkKey: string) { + const startRow = parseInt(chunkKey.split(",")[0]); + const startCol = parseInt(chunkKey.split(",")[1]); + const chunks: string[] = []; + for (let i = -this.renderChunkSize.width / 2; i <= this.renderChunkSize.width / 2; i += this.chunkSize) { + for (let j = -this.renderChunkSize.width / 2; j <= this.renderChunkSize.height / 2; j += this.chunkSize) { + const { x, y, z } = getWorldPositionForHex({ row: startRow + i, col: startCol + j }); + const { chunkX, chunkZ } = this.worldToChunkCoordinates(x, z); + const _chunkKey = `${chunkZ * this.chunkSize},${chunkX * this.chunkSize}`; + if (!chunks.includes(_chunkKey)) { + chunks.push(_chunkKey); + } + } + } + return chunks; + } + removeCachedMatricesAroundColRow(col: number, row: number) { for (let i = -this.renderChunkSize.width / 2; i <= this.renderChunkSize.width / 2; i += 10) { for (let j = -this.renderChunkSize.width / 2; j <= this.renderChunkSize.height / 2; j += 10) { @@ -547,6 +568,7 @@ export default class WorldmapScene extends HexagonScene { async updateHexagonGrid(startRow: number, startCol: number, rows: number, cols: number) { await Promise.all(this.modelLoadPromises); if (this.applyCachedMatricesForChunk(startRow, startCol)) { + console.log("cache applied"); this.computeInteractiveHexes(startRow, startCol, rows, cols); return; } @@ -577,7 +599,11 @@ export default class WorldmapScene extends HexagonScene { const batchSize = 25; // Adjust batch size as needed let currentIndex = 0; let hashedTiles: string[] = []; - + this.computeTileEntities(this.currentChunk); + this.getChunksAround(this.currentChunk).forEach((chunkKey) => { + console.log("chunkKey", chunkKey); + this.computeTileEntities(chunkKey); + }); const processBatch = async () => { const endIndex = Math.min(currentIndex + batchSize, rows * cols); @@ -620,7 +646,11 @@ export default class WorldmapScene extends HexagonScene { const rotationSeed = this.hashCoordinates(startCol + col, startRow + row); const rotationIndex = Math.floor(rotationSeed * 6); const randomRotation = (rotationIndex * Math.PI) / 3; - dummy.rotation.y = randomRotation; + if (!IS_FLAT_MODE) { + dummy.rotation.y = randomRotation; + } else { + dummy.rotation.y = 0; + } const biome = this.biome.getBiome(startCol + col + FELT_CENTER, startRow + row + FELT_CENTER); @@ -648,8 +678,6 @@ export default class WorldmapScene extends HexagonScene { } this.cacheMatricesForChunk(startRow, startCol); this.interactiveHexManager.renderHexes(); - - await this.computeTileEntities(); } }; @@ -658,25 +686,11 @@ export default class WorldmapScene extends HexagonScene { }); } - private async computeTileEntities() { - const cameraPosition = new THREE.Vector3(); - cameraPosition.copy(this.controls.target); - - const adjustedX = cameraPosition.x + (this.chunkSize * HEX_SIZE * Math.sqrt(3)) / 2; - const adjustedZ = cameraPosition.z + (this.chunkSize * HEX_SIZE * 1.5) / 3; - - // Parse current chunk coordinates - const { chunkX, chunkZ } = this.worldToChunkCoordinates(adjustedX, adjustedZ); - - const startCol = chunkX * this.chunkSize + FELT_CENTER; - const startRow = chunkZ * this.chunkSize + FELT_CENTER; - - const { width } = this.renderChunkSize; - const range = width / 2; + private async computeTileEntities(chunkKey: string) { + const startCol = parseInt(chunkKey.split(",")[1]) + FELT_CENTER; + const startRow = parseInt(chunkKey.split(",")[0]) + FELT_CENTER; - // Create a unique key for this chunk range - const chunkKey = `${startCol - range},${startCol + range},${startRow - range},${startRow + range}`; - console.log({ chunkKey }); + const range = this.chunkSize / 2; // Skip if we've already fetched this chunk if (this.fetchedChunks.has(chunkKey)) { diff --git a/client/src/three/scenes/constants.ts b/client/src/three/scenes/constants.ts index a1dc21b81..672bd5a46 100644 --- a/client/src/three/scenes/constants.ts +++ b/client/src/three/scenes/constants.ts @@ -1,4 +1,5 @@ import { HyperstructureTypesNames, ResourceMiningTypes } from "@/types"; +import { IS_FLAT_MODE } from "@/ui/config"; import { BuildingType, RealmLevelNames, RealmLevels, ResourcesIds, StructureType } from "@bibliothecadao/eternum"; import * as THREE from "three"; import { BiomeType } from "../components/Biome"; @@ -76,24 +77,47 @@ export const buildingModelPaths: Record< }; const BASE_PATH = "/models/biomes-opt/"; +const FLAT_PATH = "/models/biomes-flat-opt/"; +const MODELS_PATH = IS_FLAT_MODE ? FLAT_PATH : BASE_PATH; + +export enum BiomeFilenames { + Bare = "bare.glb", + Beach = "beach.glb", + TemperateDeciduousForest = "deciduousForest.glb", + DeepOcean = "deepOcean.glb", + Grassland = "grassland.glb", + Ocean = "ocean.glb", + Outline = "outline.glb", + Scorched = "scorched.glb", + Tundra = "tundra.glb", + TemperateDesert = "temperateDesert.glb", + Shrubland = "shrubland.glb", + Snow = "snow.glb", + Taiga = "taiga.glb", + TemperateRainForest = "temperateRainforest.glb", + SubtropicalDesert = "subtropicalDesert.glb", + TropicalRainForest = "tropicalRainforest.glb", + TropicalSeasonalForest = "tropicalSeasonalForest.glb", +} + export const biomeModelPaths: Record = { - Bare: BASE_PATH + "bare.glb", - Beach: BASE_PATH + "beach.glb", - TemperateDeciduousForest: BASE_PATH + "deciduousForest.glb", - DeepOcean: BASE_PATH + "deepOcean.glb", - Grassland: BASE_PATH + "grassland.glb", - Ocean: BASE_PATH + "ocean.glb", - Outline: BASE_PATH + "outline.glb", - Scorched: BASE_PATH + "scorched.glb", - Tundra: BASE_PATH + "tundra.glb", - TemperateDesert: BASE_PATH + "temperateDesert.glb", - Shrubland: BASE_PATH + "shrubland.glb", - Snow: BASE_PATH + "snow.glb", - Taiga: BASE_PATH + "taiga.glb", - TemperateRainForest: BASE_PATH + "temperateRainforest.glb", - SubtropicalDesert: BASE_PATH + "subtropicalDesert.glb", - TropicalRainForest: BASE_PATH + "tropicalRainforest.glb", - TropicalSeasonalForest: BASE_PATH + "tropicalSeasonalForest.glb", + Bare: MODELS_PATH + BiomeFilenames.Grassland, + Beach: MODELS_PATH + BiomeFilenames.Grassland, + TemperateDeciduousForest: MODELS_PATH + BiomeFilenames.Grassland, + DeepOcean: MODELS_PATH + BiomeFilenames.Grassland, + Grassland: MODELS_PATH + BiomeFilenames.Grassland, + Ocean: MODELS_PATH + BiomeFilenames.Grassland, + Outline: BASE_PATH + BiomeFilenames.Outline, + Scorched: MODELS_PATH + BiomeFilenames.Grassland, + Tundra: MODELS_PATH + BiomeFilenames.TemperateDeciduousForest, + TemperateDesert: MODELS_PATH + BiomeFilenames.TemperateDeciduousForest, + Shrubland: MODELS_PATH + BiomeFilenames.TemperateDeciduousForest, + Snow: MODELS_PATH + BiomeFilenames.TemperateDeciduousForest, + Taiga: MODELS_PATH + BiomeFilenames.TemperateDeciduousForest, + TemperateRainForest: MODELS_PATH + BiomeFilenames.TemperateDeciduousForest, + SubtropicalDesert: MODELS_PATH + BiomeFilenames.TemperateDeciduousForest, + TropicalRainForest: MODELS_PATH + BiomeFilenames.TemperateDeciduousForest, + TropicalSeasonalForest: MODELS_PATH + BiomeFilenames.TemperateDeciduousForest, }; export const PROGRESS_HALF_THRESHOLD = 50; diff --git a/client/src/ui/config.tsx b/client/src/ui/config.tsx index f51a3d559..a22f0d924 100644 --- a/client/src/ui/config.tsx +++ b/client/src/ui/config.tsx @@ -6,7 +6,7 @@ export { FELT_CENTER }; export enum GraphicsSettings { LOW = "LOW", MID = "MID", - HIGH = "HIGH" + HIGH = "HIGH", } const checkGraphicsSettings = async () => { @@ -40,10 +40,16 @@ const checkGraphicsSettings = async () => { } } - return localStorage.getItem("GRAPHICS_SETTING") as GraphicsSettings || GraphicsSettings.HIGH; + return (localStorage.getItem("GRAPHICS_SETTING") as GraphicsSettings) || GraphicsSettings.HIGH; +}; + +const getFlatMode = () => { + const flatMode = localStorage.getItem("FLAT_MODE"); + return flatMode === null ? false : flatMode === "true"; }; export const GRAPHICS_SETTING = await checkGraphicsSettings(); +export const IS_FLAT_MODE = getFlatMode(); export const IS_MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); diff --git a/client/src/ui/modules/settings/Settings.tsx b/client/src/ui/modules/settings/Settings.tsx index 60b1719d3..e7849a37d 100644 --- a/client/src/ui/modules/settings/Settings.tsx +++ b/client/src/ui/modules/settings/Settings.tsx @@ -13,7 +13,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 { GraphicsSettings } from "@/ui/config"; +import { GraphicsSettings, IS_FLAT_MODE } from "@/ui/config"; import Avatar from "@/ui/elements/Avatar"; import Button from "@/ui/elements/Button"; import { Checkbox } from "@/ui/elements/Checkbox"; @@ -86,6 +86,17 @@ export const SettingsWindow = () => { toast("Guild whitelist cleared!"); }; + const [isFlatMode, setIsFlatMode] = useState(() => IS_FLAT_MODE); + + const toggleFlatMode = () => { + setIsFlatMode((prev) => { + const newFlatMode = !prev; + localStorage.setItem("FLAT_MODE", newFlatMode.toString()); + window.location.reload(); // Reload the page to apply changes + return newFlatMode; + }); + }; + return ( togglePopup(settings)} show={isOpen} title={settings}>
@@ -147,7 +158,10 @@ export const SettingsWindow = () => { High
- Sound +
+ +
Flat Mode
+
Whitelist guilds
@@ -169,7 +183,7 @@ export const SettingsWindow = () => { )}
- + Sound
{isSoundOn ? (