Compare commits
12 Commits
439f9c1dad
...
28f7db172c
| Author | SHA1 | Date | |
|---|---|---|---|
| 28f7db172c | |||
| 2063656f29 | |||
| 592cfa405f | |||
| 0a32cd1d21 | |||
| 4516cf4ec6 | |||
| c6f60d1ca7 | |||
| aa35e97cbb | |||
| 57c142c8ef | |||
| 4843bf1d75 | |||
| d02cf29a1d | |||
| 3b4c9c2529 | |||
| fdf03349cf |
@@ -2,7 +2,7 @@ name: 🔁 Branch Promotions
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 6 * * 1,4" # Lundi et Jeudi à 6h UTC (design → develop)
|
||||
- cron: "0 6 * * 1,4" # Lundi et Jeudi à 6h UTC (design → develop)
|
||||
- cron: "0 6 * * 1" # Lundi à 6h UTC (develop → main)
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import tseslint from "typescript-eslint";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(["dist"]),
|
||||
globalIgnores(["dist", "POC-grass"]),
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
export const GAME_SCENE_SKY_MODEL_PATH = "/models/skybox/skybox.gltf";
|
||||
export const GAME_SCENE_SKY_MODEL_PATH = "/models/skybox/model.gltf";
|
||||
export const GAME_SCENE_FALLBACK_SKY_MODEL_PATH = "/models/sky/model.glb";
|
||||
export const GAME_SCENE_SKY_MODEL_SCALE = 100;
|
||||
export const GAME_SCENE_FALLBACK_SKY_MODEL_SCALE = 1;
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { RefObject } from "react";
|
||||
import type { Object3D } from "three";
|
||||
import { Octree } from "three/addons/math/Octree.js";
|
||||
import type { OctreeReadyHandler } from "@/types/three/three";
|
||||
import { logger } from "@/utils/core/Logger";
|
||||
|
||||
export function useOctreeGraphNode(
|
||||
graphNodeRef: RefObject<Object3D | null>,
|
||||
@@ -17,16 +18,25 @@ export function useOctreeGraphNode(
|
||||
}, [rebuildKey]);
|
||||
|
||||
useEffect(() => {
|
||||
logger.debug("useOctreeGraphNode", "Check", {
|
||||
enabled,
|
||||
octreeBuilt: octreeBuilt.current,
|
||||
hasGraphNode: !!graphNodeRef.current,
|
||||
rebuildKey,
|
||||
});
|
||||
|
||||
if (!enabled) return;
|
||||
|
||||
const graphNode = graphNodeRef.current;
|
||||
if (!enabled || octreeBuilt.current || !graphNode) return;
|
||||
octreeBuilt.current = true;
|
||||
|
||||
logger.info("useOctreeGraphNode", "Building octree from graph node");
|
||||
graphNode.updateMatrixWorld(true);
|
||||
|
||||
const octree = new Octree();
|
||||
octree.fromGraphNode(graphNode);
|
||||
logger.info("useOctreeGraphNode", "Octree built, calling onOctreeReady");
|
||||
onOctreeReady(octree);
|
||||
}, [enabled, graphNodeRef, onOctreeReady, rebuildKey]);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ export function useGraphicsSettings(): GraphicsState {
|
||||
}
|
||||
|
||||
export function useSetGraphicsSettings(): (
|
||||
graphics: Partial<GraphicsState>
|
||||
graphics: Partial<GraphicsState>,
|
||||
) => void {
|
||||
return useWorldSettingsStore((state) => state.setGraphics);
|
||||
}
|
||||
@@ -33,19 +33,19 @@ export function useGrassDensity(): number {
|
||||
|
||||
export function useGraphicsSetters() {
|
||||
const setDynamicGrass = useWorldSettingsStore(
|
||||
(state) => state.setDynamicGrass
|
||||
(state) => state.setDynamicGrass,
|
||||
);
|
||||
const setDynamicTrees = useWorldSettingsStore(
|
||||
(state) => state.setDynamicTrees
|
||||
(state) => state.setDynamicTrees,
|
||||
);
|
||||
const setDynamicClouds = useWorldSettingsStore(
|
||||
(state) => state.setDynamicClouds
|
||||
(state) => state.setDynamicClouds,
|
||||
);
|
||||
const setShadowsEnabled = useWorldSettingsStore(
|
||||
(state) => state.setShadowsEnabled
|
||||
(state) => state.setShadowsEnabled,
|
||||
);
|
||||
const setGrassDensity = useWorldSettingsStore(
|
||||
(state) => state.setGrassDensity
|
||||
(state) => state.setGrassDensity,
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from "react";
|
||||
import type { Octree } from "three/addons/math/Octree.js";
|
||||
import type { SceneMode } from "@/types/debug/debug";
|
||||
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
||||
import { logger } from "@/utils/core/Logger";
|
||||
|
||||
interface UseWorldSceneLoadingOptions {
|
||||
onLoadingStateChange?: SceneLoadingChangeHandler | undefined;
|
||||
@@ -31,10 +32,12 @@ export function useWorldSceneLoading({
|
||||
(sceneMode === "physics" && octree !== null);
|
||||
|
||||
const handleGameMapLoaded = useCallback(() => {
|
||||
logger.info("WorldSceneLoading", "GameMap loaded");
|
||||
setGameMapLoaded(true);
|
||||
}, []);
|
||||
|
||||
const handleGameStageLoaded = useCallback(() => {
|
||||
logger.info("WorldSceneLoading", "GameStage loaded");
|
||||
setGameStageLoaded(true);
|
||||
onLoadingStateChange?.({
|
||||
currentStep: "Initialisation gameplay",
|
||||
@@ -45,6 +48,7 @@ export function useWorldSceneLoading({
|
||||
|
||||
const handleOctreeReady = useCallback(
|
||||
(nextOctree: Octree) => {
|
||||
logger.info("WorldSceneLoading", "Octree ready");
|
||||
setOctree(nextOctree);
|
||||
onLoadingStateChange?.({
|
||||
currentStep: "Collision prête",
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
type SceneLoadingChangeHandler,
|
||||
type SceneLoadingState,
|
||||
} from "@/types/world/sceneLoading";
|
||||
import { logger } from "@/utils/core/Logger";
|
||||
|
||||
const SAVE_ERROR_MESSAGE = "Erreur lors de l'enregistrement";
|
||||
|
||||
@@ -243,8 +244,27 @@ export function EditorPage(): React.JSX.Element {
|
||||
<Canvas
|
||||
camera={{ position: [0, 50, 100], fov: 50 }}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
gl={{
|
||||
powerPreference: "high-performance",
|
||||
antialias: true,
|
||||
stencil: false,
|
||||
}}
|
||||
onCreated={({ gl }) => {
|
||||
gl.setClearColor("#050505");
|
||||
|
||||
const canvas = gl.domElement;
|
||||
const handleContextLost = (event: Event) => {
|
||||
event.preventDefault();
|
||||
logger.error("WebGL", "Context lost - GPU resources exhausted");
|
||||
};
|
||||
const handleContextRestored = () => {
|
||||
logger.info("WebGL", "Context restored");
|
||||
};
|
||||
canvas.addEventListener("webglcontextlost", handleContextLost);
|
||||
canvas.addEventListener(
|
||||
"webglcontextrestored",
|
||||
handleContextRestored,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<EditorSceneLoadingTracker
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
INITIAL_SCENE_LOADING_STATE,
|
||||
type SceneLoadingState,
|
||||
} from "@/types/world/sceneLoading";
|
||||
import { logger } from "@/utils/core/Logger";
|
||||
import { World } from "@/world/World";
|
||||
|
||||
export function HomePage(): React.JSX.Element {
|
||||
@@ -51,11 +52,36 @@ export function HomePage(): React.JSX.Element {
|
||||
[],
|
||||
);
|
||||
|
||||
const handleCanvasCreated = useCallback(
|
||||
({ gl }: { gl: THREE.WebGLRenderer }) => {
|
||||
const canvas = gl.domElement;
|
||||
|
||||
const handleContextLost = (event: Event) => {
|
||||
event.preventDefault();
|
||||
logger.error("WebGL", "Context lost - GPU resources exhausted");
|
||||
};
|
||||
|
||||
const handleContextRestored = () => {
|
||||
logger.info("WebGL", "Context restored");
|
||||
};
|
||||
|
||||
canvas.addEventListener("webglcontextlost", handleContextLost);
|
||||
canvas.addEventListener("webglcontextrestored", handleContextRestored);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<HandTrackingProvider>
|
||||
<Canvas
|
||||
camera={{ position: [85, 60, 85], fov: 42 }}
|
||||
shadows={{ type: THREE.PCFShadowMap }}
|
||||
gl={{
|
||||
powerPreference: "high-performance",
|
||||
antialias: true,
|
||||
stencil: false,
|
||||
}}
|
||||
onCreated={handleCanvasCreated}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
<World onLoadingStateChange={handleSceneLoadingStateChange} />
|
||||
|
||||
@@ -7,7 +7,36 @@ const HTML_CONTENT_TYPE = "text/html";
|
||||
const MAP_STRUCTURE_NODE_NAMES = new Set(["Scene", "blocking"]);
|
||||
type ModelEntry = [modelName: string, modelUrl: string];
|
||||
|
||||
let cachedSceneData: SceneData | null = null;
|
||||
let loadingPromise: Promise<SceneData | null> | null = null;
|
||||
|
||||
export async function loadMapSceneData(): Promise<SceneData | null> {
|
||||
if (cachedSceneData) {
|
||||
return cachedSceneData;
|
||||
}
|
||||
|
||||
if (loadingPromise) {
|
||||
return loadingPromise;
|
||||
}
|
||||
|
||||
loadingPromise = loadMapSceneDataInternal();
|
||||
cachedSceneData = await loadingPromise;
|
||||
loadingPromise = null;
|
||||
|
||||
return cachedSceneData;
|
||||
}
|
||||
|
||||
export function getMapNodes(): MapNode[] | null {
|
||||
return cachedSceneData?.mapNodes ?? null;
|
||||
}
|
||||
|
||||
export function getMapNodesByName(name: string): MapNode[] {
|
||||
const nodes = cachedSceneData?.mapNodes;
|
||||
if (!nodes) return [];
|
||||
return nodes.filter((node) => node.name === name);
|
||||
}
|
||||
|
||||
async function loadMapSceneDataInternal(): Promise<SceneData | null> {
|
||||
const response = await fetch(MAP_JSON_PATH);
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
+12
-1
@@ -7,9 +7,12 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import * as THREE from "three";
|
||||
import { useClonedObject } from "@/hooks/three/useClonedObject";
|
||||
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
|
||||
import { TerrainModel } from "@/components/three/world/TerrainModel";
|
||||
import { GameMapCollision } from "@/world/GameMapCollision";
|
||||
import { VegetationSystem } from "@/world/vegetation/VegetationSystem";
|
||||
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
||||
import { logger } from "@/utils/core/Logger";
|
||||
import { loadMapSceneData } from "@/utils/map/loadMapSceneData";
|
||||
@@ -222,6 +225,8 @@ export function GameMap({
|
||||
</ModelErrorBoundary>
|
||||
))}
|
||||
</group>
|
||||
<VegetationSystem />
|
||||
<TerrainModel />
|
||||
<GameMapCollision
|
||||
buildOctree={buildOctree}
|
||||
mapReady={mapReady}
|
||||
@@ -299,8 +304,14 @@ function ModelInstance({
|
||||
const sceneInstance = useClonedObject(scene);
|
||||
|
||||
useEffect(() => {
|
||||
sceneInstance.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
});
|
||||
onLoaded();
|
||||
}, [onLoaded]);
|
||||
}, [onLoaded, sceneInstance]);
|
||||
|
||||
return (
|
||||
<primitive
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useOctreeGraphNode } from "@/hooks/three/useOctreeGraphNode";
|
||||
import type { MapNode } from "@/types/editor/editor";
|
||||
import type { OctreeReadyHandler } from "@/types/three/three";
|
||||
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
||||
import { logger } from "@/utils/core/Logger";
|
||||
import { logModelLoadError } from "@/utils/three/modelLoadLogger";
|
||||
|
||||
export interface GameMapCollisionNode {
|
||||
@@ -108,6 +109,14 @@ export function GameMapCollision({
|
||||
const collisionReady =
|
||||
mapReady && settledCollisionNodeCount >= collisionNodes.length;
|
||||
|
||||
logger.debug("GameMapCollision", "State", {
|
||||
mapReady,
|
||||
collisionNodesCount: collisionNodes.length,
|
||||
settledCollisionNodeCount,
|
||||
collisionReady,
|
||||
buildOctree,
|
||||
});
|
||||
|
||||
const notifyLoaded = useCallback(() => {
|
||||
if (loadedNotifiedRef.current) return;
|
||||
|
||||
@@ -124,6 +133,7 @@ export function GameMapCollision({
|
||||
|
||||
const handleOctreeReady = useCallback<OctreeReadyHandler>(
|
||||
(octree) => {
|
||||
logger.info("GameMapCollision", "Octree built, calling onOctreeReady");
|
||||
onLoadingStateChange?.({
|
||||
currentStep: "Collision prête",
|
||||
progress: 0.92,
|
||||
|
||||
+20
-1
@@ -1,4 +1,4 @@
|
||||
import { useRef } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import type { AmbientLight, DirectionalLight } from "three";
|
||||
import {
|
||||
@@ -23,6 +23,11 @@ import {
|
||||
} from "@/data/world/lightingConfig";
|
||||
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
|
||||
|
||||
const SHADOW_MAP_SIZE = 2048;
|
||||
const SHADOW_CAMERA_SIZE = 100;
|
||||
const SHADOW_CAMERA_NEAR = 0.5;
|
||||
const SHADOW_CAMERA_FAR = 200;
|
||||
|
||||
type LightingState = {
|
||||
ambientIntensity: number;
|
||||
sunIntensity: number;
|
||||
@@ -37,6 +42,20 @@ export function Lighting(): React.JSX.Element {
|
||||
const ambient = useRef<AmbientLight>(null);
|
||||
const sun = useRef<DirectionalLight>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sun.current) return;
|
||||
|
||||
sun.current.shadow.mapSize.width = SHADOW_MAP_SIZE;
|
||||
sun.current.shadow.mapSize.height = SHADOW_MAP_SIZE;
|
||||
sun.current.shadow.camera.left = -SHADOW_CAMERA_SIZE;
|
||||
sun.current.shadow.camera.right = SHADOW_CAMERA_SIZE;
|
||||
sun.current.shadow.camera.top = SHADOW_CAMERA_SIZE;
|
||||
sun.current.shadow.camera.bottom = -SHADOW_CAMERA_SIZE;
|
||||
sun.current.shadow.camera.near = SHADOW_CAMERA_NEAR;
|
||||
sun.current.shadow.camera.far = SHADOW_CAMERA_FAR;
|
||||
sun.current.shadow.camera.updateProjectionMatrix();
|
||||
}, []);
|
||||
|
||||
useDebugFolder("Lighting", (folder) => {
|
||||
folder
|
||||
.add(
|
||||
|
||||
Reference in New Issue
Block a user