diff --git a/.agent/AGENT.md b/.agent/AGENT.md index 0f732ba..aa8477b 100644 --- a/.agent/AGENT.md +++ b/.agent/AGENT.md @@ -24,7 +24,8 @@ You are working on **La Fabrik**, an interactive 3D web experience built with Re ## Current Architecture Rules -- Scene objects live in `src/world/` and `src/components/3d/`. +- Scene objects live in `src/world/` and `src/components/three/`. +- Shared 3D components are grouped by domain under `src/components/three/models/`, `src/components/three/interaction/`, `src/components/three/gameplay/`, and `src/components/three/world/`. - HTML overlays live in `src/components/ui/`. - Shared static config lives in `src/data/`. - Debug tooling lives in `src/utils/debug/` and `src/hooks/debug/`. diff --git a/.agent/skills/debug.md b/.agent/skills/debug.md index 94ae992..7a0cd78 100644 --- a/.agent/skills/debug.md +++ b/.agent/skills/debug.md @@ -58,19 +58,18 @@ if (debug.active) { r3f-perf is loaded only in debug mode to avoid dependency issues in production: ```tsx -// src/utils/debug/DebugPerf.tsx import { Suspense, lazy } from "react"; -import { Debug } from "@/utils/debug/Debug"; +import { useShowDebugPerf } from "@/hooks/debug/useShowDebugPerf"; const Perf = lazy(() => import("r3f-perf").then((m) => ({ default: m.Perf }))); export function DebugPerf() { - const debug = Debug.getInstance(); - if (!debug.active) return null; + const showDebugPerf = useShowDebugPerf(); + if (!showDebugPerf) return null; return ( - + ); } @@ -89,6 +88,9 @@ Usage in Canvas: - All debug UI goes through `Debug.getInstance()` — never inline `if (isDev)` checks - r3f-perf is always lazy-imported, never a hard dependency in scene components -- Debug folders should be organized by domain (Lighting, PostFX, Player, Zone) +- Debug folders should be organized by domain (Lighting, Player, Zone, Interaction) +- Global debug controls include camera mode, scene mode, `R3F Perf`, and `Debug Overlay` +- Interaction-specific controls such as interaction spheres belong in the `Interaction` folder +- HTML debug panels should be grouped under `src/components/ui/debug/DebugOverlayLayout.tsx` - Debug panel must not affect production builds — it simply doesn't mount when `?debug` is absent - Clean up debug folders in `destroy()` when relevant diff --git a/.agent/skills/managers.md b/.agent/skills/managers.md index 3fea605..37c004c 100644 --- a/.agent/skills/managers.md +++ b/.agent/skills/managers.md @@ -28,14 +28,17 @@ export class SomeManager { ## Managers in this project -| Manager | File | Role | -| ------------------ | -------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| `GameManager` | `src/stateManager/GameManager.ts` | Single source of truth. Owns phase, zone, mission, input lock, dialogue. Has `subscribe()` + `getState()`. | -| `CinematicManager` | `src/stateManager/CinematicManager.ts` | GSAP timelines. Locks/unlocks input via GameManager. | -| `AudioManager` | `src/stateManager/AudioManager.ts` | Music, SFX, spatial audio. Reads phase from GameManager. | -| `ZoneManager` | `src/stateManager/ZoneManager.ts` | Zone entry/exit detection, LOD triggers. Notifies GameManager of zone changes. | +| Manager | File | Role | +| -------------------- | ------------------------------------ | ----------------------------------------------------------------------------- | +| `AudioManager` | `src/managers/AudioManager.ts` | Music and SFX playback. | +| `InteractionManager` | `src/managers/InteractionManager.ts` | Focus, nearby, trigger, grab, and hand-grab interaction state. | +| `GameManager` | target-state only | Future single source of truth for phase, zone, mission, input lock, dialogue. | +| `CinematicManager` | target-state only | Future GSAP timeline orchestrator. | +| `ZoneManager` | target-state only | Future zone entry/exit detection and LOD triggers. | -## GameManager is the orchestrator +## Target-State GameManager + +`GameManager` does not exist in the current implementation. The following pattern is target-state guidance only and should not be applied until the manager exists in code. ```ts export class GameManager { @@ -51,7 +54,7 @@ export class GameManager { } ``` -Components and hooks access other managers **through GameManager only**: +When a `GameManager` exists, components and hooks should access other managers through it: ```ts // Correct @@ -61,7 +64,7 @@ GameManager.getInstance().cinematic.play("intro"); CinematicManager.getInstance().play("intro"); ``` -## Subscribe pattern (GameManager only) +## Target-State Subscribe Pattern ```ts private listeners = new Set<() => void>() @@ -76,9 +79,9 @@ private emit(): void { } ``` -Every `set*()` method calls `this.emit()` to notify subscribers. +In that target-state manager, every `set*()` method calls `this.emit()` to notify subscribers. -## React bridge hook +## Target-State React Bridge Hook ```ts // hooks/useGameState.ts @@ -96,8 +99,8 @@ export function useGameState() { ## Rules -- Max 4 managers total -- Only `GameManager` holds durable state with `subscribe()` -- Other managers are side-effect handlers — they do not store persistent state -- Always call `destroy()` on cleanup (App unmount) -- Never create manager instances with `new` — always use `.getInstance()` +- Do not add a `GameManager` unless the feature requires a real shared gameplay state owner. +- Current managers may be imported directly until the target-state orchestrator exists. +- Keep singleton managers limited to side-effect services or shared interaction state. +- Always call `destroy()` on cleanup when a manager owns external resources. +- Never create manager instances with `new` — always use `.getInstance()`. diff --git a/.agent/skills/r3f.md b/.agent/skills/r3f.md index 50352a8..e278670 100644 --- a/.agent/skills/r3f.md +++ b/.agent/skills/r3f.md @@ -66,21 +66,6 @@ import { RigidBody, CuboidCollider } from "@react-three/rapier"; - `type="dynamic"` for movable objects - Player uses `type="dynamic"` with `lockRotations` -## Postprocessing - -```tsx -import { EffectComposer, Bloom, Vignette } from "@react-three/postprocessing"; - - - - -; -``` - -- Always wrap in `` -- Keep effects minimal for performance -- Disable heavy effects on low-end devices via Debug panel - ## What NOT to do - Do not use `new THREE.Scene()` or `new THREE.WebGLRenderer()` — R3F handles this diff --git a/.gitattributes b/.gitattributes index 5c48165..3ae4da4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,7 @@ *.glb filter=lfs diff=lfs merge=lfs -text *.gltf filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text # Textures *.png filter=lfs diff=lfs merge=lfs -text @@ -21,4 +22,4 @@ # Video (cinematics) *.mp4 filter=lfs diff=lfs merge=lfs -text -*.webm filter=lfs diff=lfs merge=lfs -text \ No newline at end of file +*.webm filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 7657786..52dcec6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ # Dependencies node_modules/ +.venv/ +backend/.venv/ +__pycache__/ +*.pyc # Build dist/ @@ -38,8 +42,3 @@ Thumbs.db # 3D Assets Cache (drei, GLTFJSX) .drei/ .glitchdrei-cache/ - -# Temporaire -.backend/ -backend/ -temp/ diff --git a/README.md b/README.md index b7acd20..d36be11 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ Built with React, Three.js, and Vite. Runs in the browser, no installation requi | [@react-three/fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction) | | [@react-three/drei](https://pmndrs.github.io/drei) | | [@react-three/rapier](https://rapier.rs/docs/) | -| [@react-three/postprocessing](https://github.com/pmndrs/postprocessing) | | [GSAP](https://gsap.com/docs/v3/Installation/) | ### Performance & Effects @@ -48,74 +47,60 @@ la-fabrik/ │ └── sounds/ │ └── src/ - ├── world/ # Single persistent 3D world - │ ├── World.tsx # Main scene composition - │ ├── Map.tsx # Base map, always mounted + ├── world/ # Persistent 3D world composition + │ ├── World.tsx # Active scene composition + │ ├── GameMap.tsx # Map loading and octree collision │ ├── Lighting.tsx # Ambient, directional, point lights - │ ├── Environment.tsx # HDRI, fog, sky - │ ├── PostFX.tsx # Bloom, SSAO, chromatic aberration - │ ├── zones/ # Spatial zones — LOD per zone - │ │ ├── WorkshopZone.tsx - │ │ ├── PowerGridZone.tsx - │ │ ├── FarmZone.tsx - │ │ ├── SchoolZone.tsx - │ │ └── ResidentialZone.tsx + │ ├── Environment.tsx # Scene background / sky model + │ ├── GameMusic.tsx # Game scene music lifecycle + │ ├── debug/ # Debug-only test scene + │ │ └── TestMap.tsx │ └── player/ - │ ├── FPSController.tsx # PointerLockControls + Rapier movement - │ └── Crosshair.tsx + │ ├── Player.tsx # Player rig composition + │ ├── PlayerCamera.tsx # Player camera mount + │ └── PlayerController.tsx # Pointer lock movement and inputs │ ├── components/ - │ ├── 3d/ # Shared reusable 3D elements - │ │ └── InteractiveObject.tsx # Raycasting + outline wrapper + │ ├── three/ # Shared R3F components by domain + │ │ ├── gameplay/ # Core repair gameplay prototype + │ │ ├── handTracking/ # R3F hand tracking debug models + │ │ ├── interaction/ # Trigger, grab, focus wrappers + │ │ ├── models/ # GLTF model components + │ │ └── world/ # Environment-specific 3D objects │ └── ui/ # HTML overlays — outside Canvas - │ ├── NarrativeOverlay.tsx # Floating dialogues - │ ├── MissionHUD.tsx # Current objective - │ ├── MapHUD.tsx # Minimap - │ ├── CinematicBars.tsx # GSAP black bars - │ └── LoadingScreen.tsx # Asset progress + │ ├── Crosshair.tsx + │ ├── debug/ # Debug-only HTML overlay panels + │ │ ├── DebugOverlayLayout.tsx + │ │ ├── GameStateDebugPanel.tsx + │ │ └── HandTrackingDebugPanel.tsx + │ ├── HandTrackingVisualizer.tsx + │ └── InteractPrompt.tsx │ - ├── stateManager/ # All logic, state, orchestration - │ ├── GameManager.ts # Single source of truth: phase, zone, mission - │ ├── CinematicManager.ts # GSAP timelines, camera lock/unlock - │ ├── AudioManager.ts # Music, SFX, spatial audio - │ └── ZoneManager.ts # Zone detection, LOD triggers + ├── managers/ # Current singleton-style services + │ ├── AudioManager.ts # Music and SFX playback + │ └── InteractionManager.ts # Focus, nearby, grab state │ - ├── hooks/ # React hooks — thin wrappers on managers - │ ├── useGameState.ts # Subscribes to GameManager - │ ├── useZoneDetection.ts - │ ├── useInteraction.ts - │ ├── useCinematic.ts - │ ├── useAudio.ts - │ └── useLOD.ts + ├── hooks/ # React hooks by domain + │ ├── debug/ # Debug state and GUI folders + │ ├── docs/ # Docs language context access + │ ├── editor/ # Editor loading and history + │ ├── gameplay/ # Repair gameplay helpers + │ ├── handTracking/ # Webcam/WebSocket hand tracking + │ ├── interaction/ # Interaction manager subscriptions + │ └── three/ # Three.js/R3F helpers │ ├── data/ - │ ├── zones.ts # { id, position, radius, missionId } - │ ├── dialogues.ts # Narrative scripts, PNJ states - │ └── missions.ts # Mission definitions, steps - │ - ├── shaders/ - │ └── hologram/ - │ ├── vertex.glsl - │ └── fragment.glsl + │ ├── interaction/ # Interaction tuning + │ ├── player/ # Player tuning + │ ├── gameplay/ # Repair gameplay static config + │ └── world/ # Environment and lighting config │ ├── utils/ - │ ├── EventEmitter.ts # Simple typed pub/sub utility - │ ├── Sizes.ts # Viewport size tracking - │ ├── Time.ts # Animation frame timing utility - │ └── debug/ # Dev-only tools and scene inspection - │ ├── Debug.ts # Global lil-gui manager - │ ├── DebugPerf.tsx # r3f-perf overlay mounted in Canvas - │ ├── isDebugEnabled.ts # Debug query-string helper - │ └── scene/ - │ ├── DebugHelpers.tsx # Grid + axes helpers shown in debug mode - │ └── DebugCameraControls.tsx # Free debug camera for map inspection - ├── hooks/ - │ └── debug/ - │ ├── useCameraMode.ts - │ ├── useDebugFolder.ts - │ ├── useDebugStore.ts - │ └── useSceneMode.ts - │ + │ ├── core/ # Logger and generic utilities + │ ├── debug/ # Dev-only tools and scene inspection + │ ├── editor/ # Editor-only parsing utilities + │ ├── map/ # Map loading and validation + │ └── three/ # Three.js helpers ├── App.tsx # Canvas bootstrap └── main.tsx ``` diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..18607aa --- /dev/null +++ b/backend/README.md @@ -0,0 +1,91 @@ +# Hand Tracking Backend + +Remote-compatible Python backend for La-Fabrik hand tracking. + +The browser captures webcam frames, downsizes them, sends JPEG frames to this backend over WebSocket, and receives hand landmarks plus closed-fist state. + +## Setup + +```bash +python3.11 -m venv backend/.venv +source backend/.venv/bin/activate +python -m pip install --upgrade pip +python -m pip install -r backend/requirements.txt +python backend/download_model.py +``` + +## Run + +Run the Vite frontend and the Python backend in two separate terminals. + +Terminal 1: + +```bash +npm run dev +``` + +Terminal 2: + +```bash +source backend/.venv/bin/activate +python -m backend.main +``` + +The WebSocket endpoint is: + +```txt +ws://localhost:8000/ws +``` + +## Health Check + +```txt +http://localhost:8000/health +``` + +## Message Flow + +Client sends a compressed frame: + +```json +{ + "type": "frame", + "timestamp": 1234567890, + "width": 320, + "height": 240, + "image": "base64-jpeg" +} +``` + +Server responds with detected hands: + +```json +{ + "type": "hands", + "timestamp": 1234567890, + "hands": [ + { + "x": 0.5, + "y": 0.3, + "z": 0.1, + "landmarks": [ + { + "x": 0.48, + "y": 0.32, + "z": 0.02 + } + ], + "handedness": "Right", + "isFist": true, + "score": 0.92 + } + ] +} +``` + +## Notes + +- The backend does not read `cv2.VideoCapture(0)`. +- This keeps local development and production behavior aligned. +- Each browser connection sends its own webcam frames. +- The backend rate-limits frames per connection and drops work when a client is already being processed. diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/connection_manager.py b/backend/connection_manager.py new file mode 100644 index 0000000..db62611 --- /dev/null +++ b/backend/connection_manager.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any +from uuid import uuid4 + +from fastapi import WebSocket + + +@dataclass +class ClientConnection: + id: str + websocket: WebSocket + is_processing: bool = False + last_frame_at: float = 0.0 + metadata: dict[str, Any] = field(default_factory=dict) + + +class ConnectionManager: + def __init__(self) -> None: + self._connections: dict[str, ClientConnection] = {} + + @property + def count(self) -> int: + return len(self._connections) + + async def connect(self, websocket: WebSocket) -> ClientConnection: + await websocket.accept() + connection = ClientConnection(id=str(uuid4()), websocket=websocket) + self._connections[connection.id] = connection + return connection + + def disconnect(self, connection: ClientConnection) -> None: + self._connections.pop(connection.id, None) + + async def send(self, connection: ClientConnection, payload: dict[str, Any]) -> None: + await connection.websocket.send_json(payload) diff --git a/backend/download_model.py b/backend/download_model.py new file mode 100644 index 0000000..75d7353 --- /dev/null +++ b/backend/download_model.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from pathlib import Path +from urllib.request import urlretrieve + + +MODEL_URL = "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task" +MODEL_PATH = Path(__file__).with_name("hand_landmarker.task") + + +def download_model() -> None: + if MODEL_PATH.exists(): + print(f"Model already exists at {MODEL_PATH}") + return + + print("Downloading MediaPipe Hand Landmarker model...") + urlretrieve(MODEL_URL, MODEL_PATH) + print(f"Model downloaded to {MODEL_PATH}") + + +if __name__ == "__main__": + download_model() diff --git a/backend/hand_landmarker.task b/backend/hand_landmarker.task new file mode 100644 index 0000000..0d53faf Binary files /dev/null and b/backend/hand_landmarker.task differ diff --git a/backend/hand_tracker.py b/backend/hand_tracker.py new file mode 100644 index 0000000..71fac5a --- /dev/null +++ b/backend/hand_tracker.py @@ -0,0 +1,142 @@ +from __future__ import annotations + +import base64 +import math +import time +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +import cv2 +import mediapipe as mp +import numpy as np +from mediapipe.tasks import python +from mediapipe.tasks.python import vision + + +@dataclass(frozen=True) +class HandData: + x: float + y: float + z: float + landmarks: list[dict[str, float]] + handedness: str + is_fist: bool + score: float + + def to_payload(self) -> dict[str, float | str | bool | list[dict[str, float]]]: + return { + "x": self.x, + "y": self.y, + "z": self.z, + "landmarks": self.landmarks, + "handedness": self.handedness, + "isFist": self.is_fist, + "score": self.score, + } + + +class HandTracker: + def __init__(self, max_hands: int = 2) -> None: + model_path = Path(__file__).with_name("hand_landmarker.task") + if not model_path.exists(): + raise FileNotFoundError( + "Missing hand_landmarker.task. Run `python backend/download_model.py`.", + ) + + base_options = python.BaseOptions(model_asset_path=str(model_path)) + options = vision.HandLandmarkerOptions( + base_options=base_options, + running_mode=vision.RunningMode.IMAGE, + num_hands=max_hands, + ) + self._detector = vision.HandLandmarker.create_from_options(options) + + def detect_from_base64_jpeg(self, image_base64: str) -> list[HandData]: + image_data = base64.b64decode(image_base64, validate=True) + image_buffer = np.frombuffer(image_data, dtype=np.uint8) + frame = cv2.imdecode(image_buffer, cv2.IMREAD_COLOR) + if frame is None: + raise ValueError("Invalid JPEG frame") + + rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb_frame) + result = self._detector.detect(mp_image) + return self._to_hands(result) + + def close(self) -> None: + self._detector.close() + + def _to_hands(self, result: vision.HandLandmarkerResult) -> list[HandData]: + hands: list[HandData] = [] + if not result.hand_landmarks or not result.handedness: + return hands + + for landmarks, handedness_categories in zip( + result.hand_landmarks, + result.handedness, + ): + palm_center = self._average_points( + [landmarks[0], landmarks[5], landmarks[9], landmarks[13], landmarks[17]], + ) + is_fist = self._is_fist(landmarks) + handedness = handedness_categories[0] + + hands.append( + HandData( + x=palm_center["x"], + y=palm_center["y"], + z=palm_center["z"], + landmarks=[ + {"x": point.x, "y": point.y, "z": point.z} + for point in landmarks + ], + handedness=handedness.category_name, + is_fist=is_fist, + score=handedness.score, + ), + ) + + return hands + + def _is_fist(self, landmarks: list[Any]) -> bool: + palm_center = self._average_points( + [landmarks[0], landmarks[5], landmarks[9], landmarks[13], landmarks[17]], + ) + palm_size = self._calculate_distance(landmarks[0], landmarks[9]) + if palm_size <= 0: + return False + + folded_finger_count = sum( + self._calculate_distance(landmarks[index], palm_center) / palm_size < 1.05 + for index in (8, 12, 16, 20) + ) + + return folded_finger_count >= 4 + + def _average_points(self, points: list[Any]) -> dict[str, float]: + return { + "x": sum(point.x for point in points) / len(points), + "y": sum(point.y for point in points) / len(points), + "z": sum(point.z for point in points) / len(points), + } + + def _calculate_distance(self, point_a: Any, point_b: Any) -> float: + return math.sqrt( + (self._get_coordinate(point_a, "x") - self._get_coordinate(point_b, "x")) + ** 2 + + (self._get_coordinate(point_a, "y") - self._get_coordinate(point_b, "y")) + ** 2 + + (self._get_coordinate(point_a, "z") - self._get_coordinate(point_b, "z")) + ** 2, + ) + + def _get_coordinate(self, point: Any, axis: str) -> float: + if isinstance(point, dict): + return point[axis] + + return getattr(point, axis) + + +def now_ms() -> int: + return time.monotonic_ns() // 1_000_000 diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..e5f26f3 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,122 @@ +from __future__ import annotations + +import asyncio +from contextlib import asynccontextmanager +from typing import Any + +from fastapi import FastAPI, WebSocket, WebSocketDisconnect +from fastapi.responses import JSONResponse + +from backend.connection_manager import ClientConnection, ConnectionManager +from backend.hand_tracker import HandTracker, now_ms + + +MAX_FRAME_BYTES = 220_000 +MIN_FRAME_INTERVAL_SECONDS = 0.08 + +manager = ConnectionManager() +tracker: HandTracker | None = None +detection_lock = asyncio.Lock() + + +@asynccontextmanager +async def lifespan(app: FastAPI): + global tracker + tracker = HandTracker(max_hands=2) + yield + if tracker: + tracker.close() + + +app = FastAPI(title="La-Fabrik Hand Tracking", lifespan=lifespan) + + +@app.get("/health") +async def health() -> JSONResponse: + return JSONResponse( + { + "status": "ok", + "connections": manager.count, + }, + ) + + +@app.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket) -> None: + connection = await manager.connect(websocket) + await manager.send(connection, status_payload("connected")) + + try: + while True: + message = await websocket.receive_json() + response = await handle_message(connection, message) + await manager.send(connection, response) + except WebSocketDisconnect: + manager.disconnect(connection) + except Exception as error: + await manager.send(connection, error_payload(str(error))) + manager.disconnect(connection) + + +async def handle_message( + connection: ClientConnection, + message: dict[str, Any], +) -> dict[str, Any]: + if message.get("type") != "frame": + return error_payload("Unsupported message type") + + current_time = asyncio.get_running_loop().time() + if current_time - connection.last_frame_at < MIN_FRAME_INTERVAL_SECONDS: + return status_payload("rate_limited") + + if connection.is_processing: + return status_payload("busy") + + image = message.get("image") + if not isinstance(image, str): + return error_payload("Missing image payload") + + if len(image) > MAX_FRAME_BYTES: + return error_payload("Frame payload too large") + + if tracker is None: + return error_payload("Hand tracker is not ready") + + if detection_lock.locked(): + return status_payload("busy") + + connection.last_frame_at = current_time + connection.is_processing = True + try: + async with detection_lock: + hands = await asyncio.to_thread(tracker.detect_from_base64_jpeg, image) + return { + "type": "hands", + "timestamp": now_ms(), + "hands": [hand.to_payload() for hand in hands], + } + finally: + connection.is_processing = False + + +def status_payload(status: str) -> dict[str, str | int]: + return { + "type": "status", + "timestamp": now_ms(), + "status": status, + } + + +def error_payload(message: str) -> dict[str, str | int | list[Any]]: + return { + "type": "error", + "timestamp": now_ms(), + "hands": [], + "message": message, + } + + +if __name__ == "__main__": + import uvicorn + + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..347181a --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.115.0 +uvicorn[standard]==0.30.6 +opencv-python-headless==4.10.0.84 +mediapipe==0.10.20 +numpy==1.26.4 diff --git a/docs/technical/animation.md b/docs/technical/animation.md index 3bbd2b2..ce22beb 100644 --- a/docs/technical/animation.md +++ b/docs/technical/animation.md @@ -1,338 +1,51 @@ -# Animation & 3D Model System +# Animation & 3D Components -This document describes how to use the 3D model components and animation system in La-Fabrik. +This document describes the 3D components that are currently used in the runtime. -## Table of Contents +## Runtime Components -1. [Model Types Overview](#model-types-overview) -2. [SimpleModel - Static Models](#simplemodel---static-models) -3. [AnimatedModel - Animated Models](#animatedmodel---animated-models) -4. [Animation Control](#animation-control) -5. [Other 3D Components](#other-3d-components) -6. [Technical Notes](#technical-notes) +| Domain | Component | Role | +| ----------- | -------------------- | --------------------------------------------------------------------- | +| Interaction | `InteractableObject` | Focus detection through distance and raycasting | +| Interaction | `TriggerObject` | Press-to-trigger interactions, optional sound, optional spawned model | +| Interaction | `GrabbableObject` | Physics grab and hand-tracking grab behavior | +| Model | `ExplodableModel` | Split/reassemble a GLTF model into separated parts | +| Gameplay | `RepairCaseModel` | Repair case lid animation, proximity float, and wobble | ---- +## Continuous Animation -## Model Types Overview +Use `useFrame` for per-frame 3D behavior. Current examples: -The project provides three main types of model instantiation: +- `GrabbableObject` updates held object velocity every frame. +- `ExplodableModel` updates split part positions every frame. +- `RepairCaseModel` updates proximity float and rotation wobble every frame. +- `SkyModel` follows the camera position every frame. -| Type | Component | Use Case | -| ----------- | -------------------------------------------------------- | -------------------------------------------- | -| Static | `SimpleModel` | Props, decoration, objects without animation | -| Animated | `AnimatedModel` | Characters, animated objects with skeleton | -| Interactive | `GrabbableObject`, `TriggerObject`, `InteractableObject` | Objects player can interact with | +## Timeline Animation ---- +Use GSAP only for discrete timeline-style transitions. Current example: -## SimpleModel - Static Models +- `RepairCaseModel` animates the case lid between open and closed rotations. -Use for GLTF models **without** skeleton/armature and no animations. +## GLTF Reuse -```tsx -import { SimpleModel } from "@/components/3d"; - -; -``` - -### Props - -| Prop | Type | Default | Description | -| --------------- | ------------------------ | ----------- | --------------------------------- | -| `modelPath` | `string` | required | Path to GLTF file in `/public` | -| `position` | `Vector3Tuple` | `[0, 0, 0]` | World position [x, y, z] | -| `rotation` | `Vector3Tuple` | `[0, 0, 0]` | Rotation in degrees [x, y, z] | -| `scale` | `number \| Vector3Tuple` | `1` | Scale factor or [x, y, z] | -| `castShadow` | `boolean` | `true` | Enable shadow casting | -| `receiveShadow` | `boolean` | `true` | Enable shadow receiving | -| `children` | `ReactNode` | - | Child components to render inside | - ---- - -## AnimatedModel - Animated Models - -Use for GLTF models **with** skeleton/armature and animations (like Mixamo characters). - -```tsx -import { AnimatedModel, useAnimatedModel } from "@/components/3d"; - -// Basic usage -; -``` - -### Props - -| Prop | Type | Default | Description | -| ------------------ | ------------------------ | ----------- | --------------------------------------------- | -| `modelPath` | `string` | required | Path to GLTF file in `/public` | -| `defaultAnimation` | `string` | `"Idle"` | Animation name to play by default | -| `animations` | `string[]` | `[]` | List of animation names (optional) | -| `position` | `Vector3Tuple` | `[0, 0, 0]` | World position [x, y, z] | -| `rotation` | `Vector3Tuple` | `[0, 0, 0]` | Rotation in degrees [x, y, z] | -| `scale` | `number \| Vector3Tuple` | `1` | Scale factor | -| `autoPlay` | `boolean` | `true` | Auto-play default animation | -| `speed` | `number` | `1` | Animation playback speed | -| `fadeDuration` | `number` | `0.3` | Transition duration in seconds | -| `onLoaded` | `() => void` | - | Callback when model loads | -| `onAnimationEnd` | `(name: string) => void` | - | Callback when animation ends | -| `children` | `ReactNode` | - | Child components (can use `useAnimatedModel`) | - -### Important: Scale - -Animated models (like Mixamo exports) often need a small scale (e.g., `0.01`) because they are exported in meters while Three.js uses different units. Adjust until the model appears at the right size. - ---- - -## Animation Control - -To control animations from inside or outside the `AnimatedModel`, use the `useAnimatedModel` hook. - -### Basic Control - -```tsx -import { AnimatedModel, useAnimatedModel } from "@/components/3d"; - -// Create a controller component to use inside AnimatedModel -function AnimationController() { - const { play, stop, fadeTo, currentAnimation, names, setSpeed, isReady } = - useAnimatedModel(); - - // names contains all available animation names - // currentAnimation is the name of the currently playing animation - // isReady is true when model and animations are loaded - - return ( - play("Run", 0.5)}> - - - ); -} - -// Usage - - -; -``` - -### Available Methods - -| Method | Signature | Description | -| ------------------ | --------------------------------------- | ------------------------------------ | -| `play` | `(name: string, fade?: number) => void` | Play animation with optional fade | -| `fadeTo` | `(name: string, fade?: number) => void` | Fade to another animation | -| `stop` | `(fade?: number) => void` | Stop and return to default animation | -| `setSpeed` | `(speed: number) => void` | Set animation speed | -| `currentAnimation` | `string` | Current animation name (getter) | -| `names` | `string[]` | Available animation names | -| `isReady` | `boolean` | Whether model is loaded | - -### Transition Example - -```tsx -function Character() { - const { play, fadeTo, currentAnimation } = useAnimatedModel(); - - const handleWalk = () => fadeTo("Walk", 0.5); // 0.5s fade - const handleRun = () => play("Run", 0.3); // 0.3s fade - const handleIdle = () => play("Idle", 0.5); // return to idle - - return ( - - - - - - - - - - - - ); -} -``` - -### Combined: GrabbableObject with Animation - -You can combine `AnimatedModel` inside `GrabbableObject` to create animated objects that can be picked up: - -```tsx -import { AnimatedModel, GrabbableObject } from "@/components/3d"; - -// Animated weapon/tool that player can pick up - - -; -``` - -Or create an animated character that can be grabbed: - -```tsx -import { - AnimatedModel, - GrabbableObject, - useAnimatedModel, -} from "@/components/3d"; - -// Controller that triggers animations when grabbed -function AnimatedGrabber() { - const { play, fadeTo } = useAnimatedModel(); - - return ( - - ); -} - -// When grabbed, play "Grab" animation - { - // This would require a context or store to trigger - console.log("Object grabbed!"); - }} -> - -; -``` - -**Note:** For complex interactions (like playing specific animations when grabbing), you'll need to connect the grab events to animation controls via a state manager or context. - ---- - -## Other 3D Components - -### GrabbableObject - -Objects that can be picked up by the player. - -```tsx -import { GrabbableObject } from "@/components/3d"; - - - - - - -; -``` - -### TriggerObject - -Objects that trigger events when interacted with. - -```tsx -import { TriggerObject } from "@/components/3d"; - - console.log("Triggered!")} -> - - - - -; -``` - -### InteractableObject - -Base object for interactions. - -```tsx -import { InteractableObject } from "@/components/3d"; - - console.log("Interacted!")} -> - - - - -; -``` - ---- - -## Technical Notes - -### GLTF Models - -- Models should be placed in `/public/models/` -- Supported formats: `.gltf`, `.glb` -- Animated models must have an Armature/skeleton for animations to work - -### Model Scale Issue - -If animated models don't appear, they may be too small or too large. Try: - -- Scale `0.01` for Mixamo-exported models -- Scale `1` for models in correct units - -### Cloning - -- `SimpleModel` uses `scene.clone()` for proper React lifecycle -- `AnimatedModel` uses the original scene directly to preserve SkinnedMesh + Armature structure - -### Animation System - -The animation system uses: - -- `@react-three/drei`: `useGLTF` for loading, `useAnimations` for animation control -- Three.js: `AnimationMixer` for playback - -### No State Machine - -This system intentionally avoids complex state machines (like Unity's Animator). For simple animation transitions, use the `play`, `fadeTo`, and `stop` methods directly. - ---- +Use `useClonedObject` when a GLTF scene is reused by a component instance. It memoizes `scene.clone(true)` and keeps clone creation out of render churn. ## File Structure -``` -src/ -├── components/3d/ -│ ├── AnimatedModel.tsx # Animated model component + context -│ ├── SimpleModel.tsx # Static model component -│ ├── GrabbableObject.tsx # Pickable object -│ ├── TriggerObject.tsx # Trigger event object +```txt +src/components/three/ +├── gameplay/ +│ ├── RepairCaseModel.tsx +│ ├── RepairCaseObject.tsx +│ ├── RepairGameZone.tsx +│ └── RepairModuleSlot.tsx +├── interaction/ +│ ├── GrabbableObject.tsx │ ├── InteractableObject.tsx -│ └── index.ts # Central exports -└── hooks/ - └── useCharacterAnimation.ts # Animation hook (legacy) +│ └── TriggerObject.tsx +├── models/ +│ └── ExplodableModel.tsx +└── world/ + └── SkyModel.tsx ``` diff --git a/docs/technical/architecture.md b/docs/technical/architecture.md index 61ec7b2..f8fdbda 100644 --- a/docs/technical/architecture.md +++ b/docs/technical/architecture.md @@ -4,8 +4,9 @@ This document describes the code that exists today in the repository. ## Runtime Structure -- `src/main.tsx` mounts React and wraps the app in `BrowserRouter`. -- `src/App.tsx` declares the top-level routes: +- `src/main.tsx` mounts React. +- `src/App.tsx` mounts the TanStack `RouterProvider`. +- `src/router.tsx` declares the top-level routes: - `/` mounts the playable 3D scene, debug perf overlay, and HTML overlays. - `/editor` mounts the map editor page. - `src/world/World.tsx` composes the active scene, including: @@ -14,22 +15,22 @@ This document describes the code that exists today in the repository. - either the map scene or the debug physics test scene - the player rig when the active camera mode is `player` - `src/world/GameMap.tsx` loads map nodes from `public/map.json`, resolves available models, and builds the collision octree. -- `src/world/debug/TestScene.tsx` provides a debug-oriented interaction and physics scene. -- `src/world/player/PlayerComponent.tsx` mounts the camera and controller. +- `src/world/debug/TestMap.tsx` provides a debug-oriented interaction and physics map. +- `src/world/player/Player.tsx` mounts the camera and controller. - `src/world/player/PlayerController.tsx` owns pointer lock movement, jump handling, and interaction input. ## Interaction Model -- `src/stateManager/InteractionManager.ts` is the current interaction state source. -- `src/components/3d/InteractableObject.tsx` handles focus detection through distance and raycasting. -- `src/components/3d/TriggerObject.tsx` implements trigger-style interactions. -- `src/components/3d/GrabbableObject.tsx` implements hold-and-release interactions. -- `src/hooks/useInteraction.ts` exposes the interaction snapshot to React UI. +- `src/managers/InteractionManager.ts` is the current interaction state source. +- `src/components/three/interaction/InteractableObject.tsx` handles focus detection through distance and raycasting. +- `src/components/three/interaction/TriggerObject.tsx` implements trigger-style interactions. +- `src/components/three/interaction/GrabbableObject.tsx` implements hold-and-release interactions. +- `src/hooks/interaction/useInteraction.ts` exposes the interaction snapshot to React UI. - `src/components/ui/InteractPrompt.tsx` shows the `E` prompt for trigger interactions. ## Audio -- `src/stateManager/AudioManager.ts` currently provides pooled one-shot sound playback. +- `src/managers/AudioManager.ts` currently provides pooled one-shot sound playback and looped music playback. - Trigger interactions may play audio directly through `AudioManager`. ## Debug System @@ -37,13 +38,26 @@ This document describes the code that exists today in the repository. - Debug mode is enabled with `?debug`. - `src/utils/debug/Debug.ts` owns the `lil-gui` instance and debug controls. - `src/hooks/debug/useCameraMode.ts` and `src/hooks/debug/useSceneMode.ts` subscribe to debug state. -- `src/utils/debug/DebugPerf.tsx` lazily mounts `r3f-perf` in debug mode. -- `src/utils/debug/scene/DebugHelpers.tsx` mounts debug helpers. -- `src/utils/debug/scene/DebugCameraControls.tsx` mounts the free debug camera. +- `src/components/debug/DebugPerf.tsx` lazily mounts `r3f-perf` in debug mode. +- `src/components/ui/debug/DebugOverlayLayout.tsx` mounts the compact HTML debug overlay when enabled from `lil-gui`. +- `src/components/ui/debug/GameStateDebugPanel.tsx` exposes current game state, main/sub-state switching, previous/next step controls, and reset. +- `src/components/ui/debug/HandTrackingDebugPanel.tsx` shows hand tracking status, usage, loaded glove model, hand count, and fist state while hand tracking is active. +- `src/components/three/handTracking/HandTrackingGlove.tsx` places the rigged `gant_l` and `gant_r` models on detected hands in the debug physics scene. +- `src/components/debug/scene/DebugHelpers.tsx` mounts debug helpers. +- `src/components/debug/scene/DebugCameraControls.tsx` mounts the free debug camera. +- `lil-gui` global debug controls include camera mode, scene mode, `R3F Perf`, and `Debug Overlay`; interaction-specific controls live in the `Interaction` folder. + +## 3D Component Domains + +- `src/components/three/models/` contains reusable model helpers such as `ExplodableModel`. +- `src/components/three/interaction/` contains reusable interaction wrappers such as `InteractableObject`, `TriggerObject`, and `GrabbableObject`. +- `src/components/three/handTracking/` contains R3F hand tracking debug models such as the glove overlays. +- `src/components/three/gameplay/` contains the current core repair gameplay prototype: the repair case, repair game zone, and module slots. +- `src/components/three/world/` contains reusable world/environment objects such as `SkyModel`. ## Editor System -- `src/pages/editor/EditorPage.tsx` is the route-level editor page for `/editor`. +- `src/pages/editor/page.tsx` is the route-level editor page for `/editor`. - `src/components/editor/EditorControls.tsx` renders the HTML editor control panel. - `src/components/editor/scene/EditorScene.tsx` composes the editor canvas scene, camera controls, lights, shortcuts, and map rendering. - `src/components/editor/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls. @@ -51,20 +65,20 @@ This document describes the code that exists today in the repository. - `src/hooks/editor/useEditorSceneData.ts` loads scene data and handles folder upload fallback. - `src/hooks/editor/useEditorHistory.ts` owns editor undo and redo state. - `src/utils/editor/loadEditorScene.ts` handles editor-only folder upload parsing. -- `src/utils/loadMapSceneData.ts` is shared by the game scene and editor to load `public/map.json` and resolve model URLs. -- `src/types/editor.ts` contains the shared `MapNode`, `SceneData`, and `TransformMode` types. +- `src/utils/map/loadMapSceneData.ts` is shared by the game scene and editor to load `public/map.json` and resolve model URLs. +- `src/types/editor/editor.ts` contains the shared `MapNode`, `SceneData`, and `TransformMode` types. ## Map Data - `public/map.json` is expected to be a `MapNode[]`. -- Each map node `name` maps to `public/models/{name}/model.gltf`. +- Each map node `name` maps to `public/models/{name}/model.glb` when available, with `public/models/{name}/model.gltf` kept as fallback. - The editor renders a fallback cube for missing models. - The game scene filters out nodes whose model cannot be resolved. ## Current Limitations - The repository is a prototype, not the full intended game runtime. -- `src/world/debug/TestScene.tsx` is part of the active scene composition. +- `src/world/debug/TestMap.tsx` is part of the active scene composition. - There is no central gameplay orchestrator such as `GameManager`. - Missions, zones, cinematics, and dialogue systems are not implemented. - The player uses octree collision and simple movement rules, not a complete gameplay physics stack. diff --git a/docs/technical/editor.md b/docs/technical/editor.md index fd0e8cb..6a0a4c0 100644 --- a/docs/technical/editor.md +++ b/docs/technical/editor.md @@ -34,11 +34,13 @@ src/ │ ├── useEditorHistory.ts │ └── useEditorSceneData.ts ├── types/ -│ └── editor.ts +│ └── editor/ +│ └── editor.ts └── utils/ ├── editor/ │ └── loadEditorScene.ts - └── loadMapSceneData.ts + └── map/ + └── loadMapSceneData.ts ``` ## Responsibilities @@ -57,13 +59,13 @@ src/ `src/controls/editor/FlyController.tsx` provides editor movement controls for player-style navigation. -`src/utils/loadMapSceneData.ts` is shared by the game map and editor. It loads `/map.json` and resolves available `public/models/{name}/model.gltf` files. +`src/utils/map/loadMapSceneData.ts` is shared by the game map and editor. It loads `/map.json` and resolves available `public/models/{name}/model.glb` files first, then falls back to `public/models/{name}/model.gltf`. `src/utils/editor/loadEditorScene.ts` contains editor-only upload handling for user-selected folders. ## Data Format -The shared editor type lives in `src/types/editor.ts`. +The shared editor type lives in `src/types/editor/editor.ts`. ```ts interface MapNode { @@ -96,10 +98,10 @@ public/ ├── map.json └── models/ └── pylone/ - └── model.gltf + └── model.glb ``` -If a model is missing, the editor renders a fallback cube so the node can still be selected and transformed. +If `model.glb` and `model.gltf` are both missing, the editor renders a fallback cube so the node can still be selected and transformed. ## Editor Flow @@ -138,7 +140,7 @@ Editor styles are in `src/index.css` under the `/* Editor page */` section. Clas ## Known Limitations -- Uploaded model object URLs are not currently revoked after replacement or unmount. +- Uploaded model object URLs are not revoked after replacement or unmount. - Large `map.json` files are not virtualized, culled, or LOD-managed. - There is no snap-to-grid, duplication, material editing, or object creation workflow. - Save to Server is a Vite dev-server helper, not a production backend API. diff --git a/docs/technical/hand-tracking.md b/docs/technical/hand-tracking.md new file mode 100644 index 0000000..6482ff3 --- /dev/null +++ b/docs/technical/hand-tracking.md @@ -0,0 +1,129 @@ +# Hand Tracking Technical Notes + +This document describes the hand tracking system that exists in the current codebase. + +## Purpose + +Hand tracking is a debug-stage interaction system used to test direct 3D object manipulation with a webcam. It allows a user to close their fist to grab a nearby object and move it in 3D space without relying on the center crosshair. + +The feature is scoped to the debug physics scene rather than production gameplay input. + +## Runtime Flow + +1. The browser captures webcam frames in `src/hooks/handTracking/useRemoteHandTracking.ts`. +2. Frames are sent to the local Python backend over WebSocket. +3. The backend runs MediaPipe hand landmark detection. +4. The backend returns hand data including landmarks, handedness, score, center point, and `isFist`. +5. React stores the latest snapshot in the hand tracking provider. +6. `GrabbableObject` reads that snapshot each frame and uses fist state plus raycasting to grab objects. +7. `HandTrackingGlove` reads the same snapshot and places the rigged `gant_l` and `gant_r` models on the detected hands in the debug physics scene. + +## Activation Rules + +Hand tracking is intentionally gated so the webcam and backend are not used all the time. + +The current activation conditions are: + +- debug mode is active with `?debug` +- scene mode is `physics` +- the player is near an interaction, is holding an object, or is hand-holding an object + +This keeps hand tracking active while the player is inside an interaction zone, even if the camera is not aimed directly at the object. + +## Backend + +The backend lives in `backend/` and exposes: + +- `GET /health` for health checks +- `WS /ws` for frame input and hand tracking output + +The Python process uses MediaPipe and the local model file: + +```txt +backend/hand_landmarker.task +``` + +The backend sends normalized hand coordinates and landmarks. The frontend treats the values as screen-space inputs, then maps them into world space with the active Three.js camera. + +## Frontend Data Shape + +The shared types live in `src/types/handTracking/handTracking.ts`. + +```ts +interface HandTrackingHand { + x: number; + y: number; + z: number; + landmarks: HandTrackingLandmark[]; + handedness: string; + isFist: boolean; + score: number; +} +``` + +`x` and `y` are normalized camera coordinates. `z` is a relative depth value from MediaPipe, not an absolute world-space distance. + +## Grab Targeting + +The hand grab logic lives in `src/components/three/interaction/GrabbableObject.tsx`. + +The object is moved toward the visual center of the hand. That center is computed from the bounding box of all landmarks: + +```txt +centerX = (minX + maxX) / 2 +centerY = (minY + maxY) / 2 +``` + +Starting a grab uses a slightly wider virtual hit zone. Instead of raycasting only from one point, the code casts several rays around the hand center: + +- center +- left +- right +- up +- down + +If any ray hits the object while the object is within `INTERACTION_RADIUS`, the object enters hand-holding mode. + +## Depth Handling + +Because MediaPipe `z` is relative, the frontend captures the starting depth when the grab begins: + +```txt +initialHandZ = hand.z +initialHoldDistance = hit.distance +``` + +While holding, the object distance from the camera is adjusted by the change in hand depth: + +```txt +holdDistance = initialHoldDistance + (hand.z - initialHandZ) * sensitivity +``` + +The final hold distance is clamped between the configured grab minimum and maximum distances to avoid unstable movement. + +## UI And Debug + +The current debug UI includes: + +- `HandTrackingDebugPanel` inside `DebugOverlayLayout` for status, usage, loaded glove model, server state, hand count, and fist state +- `HandTrackingVisualizer` for the SVG landmark wireframe fallback +- `HandTrackingGlove` for the left-hand `gant_l` and right-hand `gant_r` models in the R3F scene +- `r3f-perf` for render performance +- `lil-gui` for scene, camera, lighting, interaction, and grab controls + +The hand tracking debug panel is a compact HTML grid outside the canvas. `Model loaded` displays the successfully loaded glove models. The SVG hand wireframe is only a fallback while models are loading or if a glove model fails to load. + +## Glove Models + +The current glove MVP uses `public/models/gant_l/model.gltf` and `public/models/gant_r/model.gltf`, which contain GLTF skins and armatures. Each model is positioned, oriented, and scaled from palm landmarks, then each finger bone chain is rotated toward the matching MediaPipe landmark chain. + +The glove models are intentionally smaller than the raw SVG overlay so they do not dominate the camera view. + +## Known Limitations + +- The feature is debug-only and focused on the physics test scene. +- MediaPipe depth is relative and can be noisy. +- The virtual hit zone is an approximation based on multiple raycasts, not a real 3D collider. +- There is no smoothing layer for hand position or depth yet. +- The SVG hand visualization is a fallback, not the primary display when glove models load correctly. +- Finger bone animation is an approximate landmark-to-bone mapping; it still needs calibration for per-model twist, offsets, and smoothing. diff --git a/docs/technical/zustand.md b/docs/technical/zustand.md index 8e05717..80b6f5c 100644 --- a/docs/technical/zustand.md +++ b/docs/technical/zustand.md @@ -143,7 +143,8 @@ In React Three Fiber, mounting and unmounting JSX controls what appears in the T Current overlays: -- `GameStateHUD`: debug-only progression panel shown with `?debug` +- `DebugOverlayLayout`: debug-only overlay shown with `?debug`, including the `GameStateDebugPanel` progression panel +- `GameStateDebugPanel`: compact debug UI for viewing and switching main/sub states, stepping backward or forward, and resetting the store - `Crosshair`: player aiming helper - `InteractPrompt`: interaction prompt diff --git a/docs/user/editor.md b/docs/user/editor.md index 1c92b74..d98ba2a 100644 --- a/docs/user/editor.md +++ b/docs/user/editor.md @@ -9,7 +9,7 @@ Use the editor when you need to move, rotate, or scale existing map objects with The editor reads the same map data as the runtime scene: - `public/map.json` contains the object list. -- `public/models/{name}/model.gltf` contains the matching 3D model for each object name. +- `public/models/{name}/model.glb` contains the matching 3D model for each object name. `model.gltf` is still supported as a fallback during migration. - Missing models are displayed as gray fallback cubes, so incomplete maps remain editable. ## Map Node Format diff --git a/docs/user/features.md b/docs/user/features.md index a04fb80..1a3a210 100644 --- a/docs/user/features.md +++ b/docs/user/features.md @@ -5,7 +5,7 @@ This document lists features that are implemented in the current codebase. ## Scene - Fullscreen React Three Fiber scene -- Main map scene loaded from `public/map.json` and matching `public/models/{name}/model.gltf` assets +- Main map scene loaded from `public/map.json` and matching `public/models/{name}/model.glb` or `model.gltf` assets - Debug physics test scene selectable from the debug panel - Ambient and directional lighting - Environment background setup @@ -33,7 +33,8 @@ This document lists features that are implemented in the current codebase. ## Debug Tooling - `?debug` query param enables the debug panel -- `lil-gui` controls for camera mode, scene mode, and interaction spheres +- `lil-gui` controls for camera mode, scene mode, `R3F Perf`, `Debug Overlay`, and interaction tuning +- Compact debug overlay for game state controls and hand tracking status - Debug scene helpers - Free debug camera - `r3f-perf` overlay @@ -43,7 +44,7 @@ This document lists features that are implemented in the current codebase. - `/editor` route for inspecting and editing `public/map.json` - Automatic loading of `public/map.json` when available - Folder upload fallback when `map.json` is missing -- Rendering of available `public/models/{name}/model.gltf` assets +- Rendering of available `public/models/{name}/model.glb` or `model.gltf` assets - Fallback cubes for nodes whose model is missing - Object selection by click - Transform modes for translate, rotate, and scale diff --git a/docs/user/main-feature.md b/docs/user/main-feature.md new file mode 100644 index 0000000..7133196 --- /dev/null +++ b/docs/user/main-feature.md @@ -0,0 +1,80 @@ +# Main Feature + +This document explains the current repair-game prototype in La-Fabrik. + +## What It Does + +The main feature is a repair interaction sandbox mounted in the debug physics scene. It lets the player approach a repair case, open it, and interact with module slots that can show selectable models and exploded-model states. + +The current user flow is: + +1. Open the app with `?debug`. +2. Switch the scene to `Physics` in the debug panel. +3. Move close to the repair case. +4. Press the interaction key when prompted. +5. Watch the case open or close with sound feedback. +6. Interact with repair module slots to cycle/select repair models. + +## Why It Matters + +This feature validates the core repair fantasy before a full mission system exists. It tests whether repair objects, physical proximity, model selection, audio feedback, and exploded model visualization can work together in the 3D scene. + +## Current Behavior + +The repair case reacts to player proximity. When the player is close enough, it floats upward and rotates gently to signal interactivity. When the player moves away, it returns to its resting transform. + +Interacting with the case toggles its open state. The lid animation is handled with GSAP because it is a discrete interaction animation, not a continuous per-frame loop. + +Repair module slots are configured from static gameplay data. They render selectable repair models and can use exploded model visualization to show parts separated from their original positions. + +## Key Files + +- `src/world/debug/TestMap.tsx` mounts the repair-game prototype in the debug physics scene. +- `src/components/three/gameplay/RepairGameZone.tsx` composes the repair-game zone. +- `src/components/three/gameplay/RepairCaseObject.tsx` connects the repair case to trigger interaction and audio. +- `src/components/three/gameplay/RepairCaseModel.tsx` renders and animates the case model. +- `src/components/three/gameplay/RepairModuleSlot.tsx` renders repair slots and model selection behavior. +- `src/components/three/models/ExplodableModel.tsx` renders selectable models with split/exploded visualization. +- `src/data/gameplay/repairCaseConfig.ts` stores repair case model, sound, and animation constants. +- `src/data/gameplay/repairGameConfig.ts` stores repair zone and slot positions. +- `src/data/gameplay/repairGameModelCatalog.ts` stores selectable repair models. + +## Debug Requirements + +The repair-game prototype currently requires: + +- the app opened with `?debug` +- the debug scene set to `Physics` +- model assets available under `public/models/` +- sound assets available under `public/sounds/` + +Frontend command: + +```bash +npm run dev +``` + +Debug URL: + +```txt +http://localhost:5173/?debug +``` + +## Related Hand Tracking + +Hand tracking is a separate debug interaction layer. It can move grabbable physics objects with webcam input, but it is not yet integrated into the repair-game mission flow. + +For hand tracking, run the Python backend separately: + +```bash +source backend/.venv/bin/activate +python -m backend.main +``` + +## Current Limitations + +- It is mounted only in the debug physics scene. +- There is no mission progression system yet. +- There is no central `GameManager` or Zustand store in this branch. +- Hand tracking is available as debug interaction input, not as final repair gameplay. +- The repair-game content is configured statically in `src/data/gameplay/`. diff --git a/eslint.config.js b/eslint.config.js index 75d3c46..aef40fc 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,6 +2,7 @@ import js from "@eslint/js"; import globals from "globals"; import reactHooks from "eslint-plugin-react-hooks"; import reactRefresh from "eslint-plugin-react-refresh"; +import prettierRecommended from "eslint-plugin-prettier/recommended"; import tseslint from "typescript-eslint"; import { defineConfig, globalIgnores } from "eslint/config"; @@ -20,4 +21,5 @@ export default defineConfig([ globals: globals.browser, }, }, + prettierRecommended, ]); diff --git a/package-lock.json b/package-lock.json index a417dbf..3a4a64a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,9 @@ "name": "la-fabrik", "version": "0.0.1", "dependencies": { + "@mediapipe/tasks-vision": "^0.10.35", "@react-three/drei": "^10.7.7", - "@react-three/fiber": "^9.6.0", - "@react-three/postprocessing": "^3.0.4", + "@react-three/fiber": "^9.6.1", "@react-three/rapier": "^2.2.0", "@tanstack/react-router": "^1.168.25", "gsap": "^3.15.0", @@ -21,7 +21,7 @@ "react-dom": "^19.2.4", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", - "three": "^0.183.2", + "three": "0.182.0", "zustand": "^5.0.12" }, "devDependencies": { @@ -294,12 +294,6 @@ "node": ">=6.9.0" } }, - "node_modules/@dimforge/rapier3d-compat": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.19.2.tgz", - "integrity": "sha512-AZHL1jqUF55QJkJyU1yKeh4ImX2J93bVLIezT1+o0FZqTix6O06MOaqpKoJ4MmbDCsoZmwO+qc471/SDMDm2AA==", - "license": "Apache-2.0" - }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -608,9 +602,9 @@ } }, "node_modules/@mediapipe/tasks-vision": { - "version": "0.10.17", - "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", - "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==", + "version": "0.10.35", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.35.tgz", + "integrity": "sha512-HOvadwVRE6JC+45nyYhmnywnr5h/J8KZvOeUNVOG9q/0875pZgItznFB9bRTvLc264YSJqiZ1NsIpCStJw/egg==", "license": "Apache-2.0" }, "node_modules/@monogrid/gainmap-js": { @@ -716,10 +710,16 @@ } } }, + "node_modules/@react-three/drei/node_modules/@mediapipe/tasks-vision": { + "version": "0.10.17", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", + "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==", + "license": "Apache-2.0" + }, "node_modules/@react-three/fiber": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.0.tgz", - "integrity": "sha512-90abYK2q5/qDM+GACs9zRvc5KhEEpEWqWlHSd64zTPNxg+9wCJvTfyD9x2so7hlQhjRYO1Fa6flR3BC/kpTFkA==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.1.tgz", + "integrity": "sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.17.8", @@ -764,32 +764,6 @@ } } }, - "node_modules/@react-three/postprocessing": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@react-three/postprocessing/-/postprocessing-3.0.4.tgz", - "integrity": "sha512-e4+F5xtudDYvhxx3y0NtWXpZbwvQ0x1zdOXWTbXMK6fFLVDd4qucN90YaaStanZGS4Bd5siQm0lGL/5ogf8iDQ==", - "license": "MIT", - "dependencies": { - "maath": "^0.6.0", - "n8ao": "^1.9.4", - "postprocessing": "^6.36.6" - }, - "peerDependencies": { - "@react-three/fiber": "^9.0.0", - "react": "^19.0", - "three": ">= 0.156.0" - } - }, - "node_modules/@react-three/postprocessing/node_modules/maath": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/maath/-/maath-0.6.0.tgz", - "integrity": "sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==", - "license": "MIT", - "peerDependencies": { - "@types/three": ">=0.144.0", - "three": ">=0.144.0" - } - }, "node_modules/@react-three/rapier": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@react-three/rapier/-/rapier-2.2.0.tgz", @@ -805,6 +779,12 @@ "three": ">=0.159.0" } }, + "node_modules/@react-three/rapier/node_modules/@dimforge/rapier3d-compat": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.19.1.tgz", + "integrity": "sha512-xvFNtb/9xILxfvdFOa7NCnYUEF6cfn51R44C1xnKXtk5DpyAARqsC4sxZwiJAHRSzYT5FFe889t36iFnzb3vxg==", + "license": "Apache-2.0" + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-rc.17", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", @@ -4191,16 +4171,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/n8ao": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/n8ao/-/n8ao-1.10.1.tgz", - "integrity": "sha512-hhI1pC+BfOZBV1KMwynBrVlIm8wqLxj/abAWhF2nZ0qQKyzTSQa1QtLVS2veRiuoBQXojxobcnp0oe+PUoxf/w==", - "license": "ISC", - "peerDependencies": { - "postprocessing": ">=6.30.0", - "three": ">=0.137" - } - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -4390,15 +4360,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postprocessing": { - "version": "6.39.1", - "resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.39.1.tgz", - "integrity": "sha512-R2dG2zy+BAx3USl5EHw+PvnrlbT5PKnZVp3se0HCR0pWH8WQdh742yNG4YWOsq6c0bFpffk0Gd2RqPeoP/wKng==", - "license": "Zlib", - "peerDependencies": { - "three": ">= 0.168.0 < 0.185.0" - } - }, "node_modules/potpack": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", @@ -4909,9 +4870,9 @@ } }, "node_modules/three": { - "version": "0.183.2", - "resolved": "https://registry.npmjs.org/three/-/three-0.183.2.tgz", - "integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==", + "version": "0.182.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz", + "integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==", "license": "MIT" }, "node_modules/three-mesh-bvh": { diff --git a/package.json b/package.json index 00d7630..3a71c82 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,12 @@ "format": "prettier --write .", "format:check": "prettier --check .", "preview": "vite preview", - "typecheck": "tsc --noEmit" + "typecheck": "tsc -b" }, "dependencies": { + "@mediapipe/tasks-vision": "^0.10.35", "@react-three/drei": "^10.7.7", - "@react-three/fiber": "^9.6.0", - "@react-three/postprocessing": "^3.0.4", + "@react-three/fiber": "^9.6.1", "@react-three/rapier": "^2.2.0", "@tanstack/react-router": "^1.168.25", "gsap": "^3.15.0", @@ -30,7 +30,7 @@ "react-dom": "^19.2.4", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", - "three": "^0.183.2", + "three": "0.182.0", "zustand": "^5.0.12" }, "devDependencies": { @@ -51,6 +51,9 @@ "vite": "^8.0.4" }, "overrides": { + "@react-three/rapier": { + "@dimforge/rapier3d-compat": "0.19.1" + }, "r3f-perf": { "@react-three/drei": "$@react-three/drei" } diff --git a/public/assets/PDF/Gilbert Le Fermier - Doublage Altera.pdf b/public/assets/PDF/Gilbert Le Fermier - Doublage Altera.pdf new file mode 100644 index 0000000..a1a1ccc Binary files /dev/null and b/public/assets/PDF/Gilbert Le Fermier - Doublage Altera.pdf differ diff --git a/public/assets/PDF/Le Gérant - Doublage Altera.pdf b/public/assets/PDF/Le Gérant - Doublage Altera.pdf new file mode 100644 index 0000000..9ec89d7 Binary files /dev/null and b/public/assets/PDF/Le Gérant - Doublage Altera.pdf differ diff --git a/public/assets/PDF/Leonie Electricienne - Doublage Altera.pdf b/public/assets/PDF/Leonie Electricienne - Doublage Altera.pdf new file mode 100644 index 0000000..226a9bd Binary files /dev/null and b/public/assets/PDF/Leonie Electricienne - Doublage Altera.pdf differ diff --git a/public/assets/UI/cassé.webm b/public/assets/UI/cassé.webm new file mode 100644 index 0000000..bbbe317 --- /dev/null +++ b/public/assets/UI/cassé.webm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56f019508cbcb5c0c4770bdad0816c7b1d332fc809d582ee4e0d90904415c745 +size 252779 diff --git a/public/assets/UI/centrale.webm b/public/assets/UI/centrale.webm new file mode 100644 index 0000000..8287a94 --- /dev/null +++ b/public/assets/UI/centrale.webm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd2b2be96baaab171a5f68e23c785a0ea3583c7ba71b5a9b5745213fc3b0f5f8 +size 225885 diff --git a/public/assets/UI/ebike.webm b/public/assets/UI/ebike.webm new file mode 100644 index 0000000..003fbd1 --- /dev/null +++ b/public/assets/UI/ebike.webm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07684c556587213b7deae75e153594d8299acde4d3a28bfe88cbd49abdcda880 +size 197560 diff --git a/public/assets/UI/interagir.webm b/public/assets/UI/interagir.webm new file mode 100644 index 0000000..d38a4c4 --- /dev/null +++ b/public/assets/UI/interagir.webm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dd42a107d015aa0028823e248a3a3e32e6b8cb90173b547675145fefdba4581 +size 272167 diff --git a/public/assets/UI/laferme.webm b/public/assets/UI/laferme.webm new file mode 100644 index 0000000..4b16f79 --- /dev/null +++ b/public/assets/UI/laferme.webm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5872877c56253cb364aceb0f39551143e142150807c7e837759677b16d22741 +size 206993 diff --git a/public/assets/logo/logo.jpg b/public/assets/logo/logo.jpg new file mode 100644 index 0000000..3617df6 --- /dev/null +++ b/public/assets/logo/logo.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:814db18091a1a822dc2ebdef9f00400c4ff943e9aa1e43151e85b6ea1c4e98cc +size 149572 diff --git a/public/assets/world/dashboard.webm b/public/assets/world/dashboard.webm new file mode 100644 index 0000000..240a1e9 --- /dev/null +++ b/public/assets/world/dashboard.webm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a66ac7a200090365d7cb2623ebf9f1dedc6e052628c15b1701086786b3772bd +size 485140 diff --git a/public/models/arbre/arbre.bin b/public/models/arbre/arbre.bin new file mode 100644 index 0000000..889456c --- /dev/null +++ b/public/models/arbre/arbre.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad20a47149173711dfb6b0f7cecdf562e8ec1d6524d68ac8631996bd9b617359 +size 2321496 diff --git a/public/models/arbre/arbre.glb b/public/models/arbre/arbre.glb new file mode 100644 index 0000000..3dae041 --- /dev/null +++ b/public/models/arbre/arbre.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a55b404a579fd891c8bdf3e85519a38da73f81a4f5dc6e9355b48bba550d097d +size 14256732 diff --git a/public/models/arbre/arbre.spp b/public/models/arbre/arbre.spp new file mode 100644 index 0000000..be218a0 Binary files /dev/null and b/public/models/arbre/arbre.spp differ diff --git a/public/models/arbre/feuilles_baseColor.png b/public/models/arbre/feuilles_baseColor.png new file mode 100644 index 0000000..5774648 --- /dev/null +++ b/public/models/arbre/feuilles_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea960d6139edd7f4b488b5b9a9549331edad8aaad80d5c1de05dae0b523d5f15 +size 2396375 diff --git a/public/models/arbre/feuilles_normal.png b/public/models/arbre/feuilles_normal.png new file mode 100644 index 0000000..ebe7983 --- /dev/null +++ b/public/models/arbre/feuilles_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05ffb9add4169252be91a4ec8b53e707139a4e86910f1594d28c185bc5b536ae +size 3013728 diff --git a/public/models/arbre/feuilles_occlusionRoughnessMetallic.png b/public/models/arbre/feuilles_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..7706c8f --- /dev/null +++ b/public/models/arbre/feuilles_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b16f1e24e32c13d3b4511664d7c93c8a4c1b00a0013bc870f46269fc933e07b9 +size 629215 diff --git a/public/models/arbre/model.gltf b/public/models/arbre/model.gltf new file mode 100644 index 0000000..64c992e --- /dev/null +++ b/public/models/arbre/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b6bcc5bf5ade2fbfa909e63f59d7f9e3f491cedf81d34f068d12ae49dffc33b +size 2293054 diff --git a/public/models/arbre/tronc_baseColor.png b/public/models/arbre/tronc_baseColor.png new file mode 100644 index 0000000..1a634a2 --- /dev/null +++ b/public/models/arbre/tronc_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25d29e2a2144a873730d153e0d708fad6a616bb8355b48f4b1bada37c78e17ab +size 940797 diff --git a/public/models/arbre/tronc_normal.png b/public/models/arbre/tronc_normal.png new file mode 100644 index 0000000..801e9cd --- /dev/null +++ b/public/models/arbre/tronc_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbd2c70e1e64f74362b4e59c3782ad586e05f4b10a58e8ef07736c5b23f27d47 +size 2272612 diff --git a/public/models/arbre/tronc_occlusionRoughnessMetallic.png b/public/models/arbre/tronc_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..2048d29 --- /dev/null +++ b/public/models/arbre/tronc_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d54f925bd271efb021fddbb9b29fc8cd1a3917e0f7fe7d7b312aaca94ad940af +size 505205 diff --git a/public/models/buisson/buisson.bin b/public/models/buisson/buisson.bin new file mode 100644 index 0000000..ad2ce20 --- /dev/null +++ b/public/models/buisson/buisson.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69d47f96ddd28b4ffaed28fa40741a984d507264f2efdf6309f05c509c7d1eef +size 2682000 diff --git a/public/models/buisson/feuilles_baseColor.png b/public/models/buisson/feuilles_baseColor.png new file mode 100644 index 0000000..ecd2570 --- /dev/null +++ b/public/models/buisson/feuilles_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d7dfb57dde5a5d3d210dc1481b4ff2f4c1f5a46e31c55a3fa9baa9c675623f1 +size 2634187 diff --git a/public/models/buisson/feuilles_normal.png b/public/models/buisson/feuilles_normal.png new file mode 100644 index 0000000..1c5d9b6 --- /dev/null +++ b/public/models/buisson/feuilles_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60001dbfc98dffaaa2bcef3ba741add59a62e5a028c5d88d648333985685f4c4 +size 4354207 diff --git a/public/models/buisson/feuilles_occlusionRoughnessMetallic.png b/public/models/buisson/feuilles_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..62ecb53 --- /dev/null +++ b/public/models/buisson/feuilles_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b4dd1ae1b24fba7d55a8aae41838983d58a518175f268c051581fa53d05b8a7 +size 969003 diff --git a/public/models/buisson/model.gltf b/public/models/buisson/model.gltf new file mode 100644 index 0000000..e4f7abc --- /dev/null +++ b/public/models/buisson/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6228829da0a06a0f325ab8d11daff5ab5199797700ea3ea23842f34472771935 +size 3110 diff --git a/public/models/chemins/chemin_baseColor.png b/public/models/chemins/chemin_baseColor.png new file mode 100644 index 0000000..e8a5e8b --- /dev/null +++ b/public/models/chemins/chemin_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7d84c0a421b3053f622701fd7ed2650008314f0e21c100fbff9b95d62c74cd9 +size 1630361 diff --git a/public/models/chemins/chemin_normal.png b/public/models/chemins/chemin_normal.png new file mode 100644 index 0000000..7ecf444 --- /dev/null +++ b/public/models/chemins/chemin_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de996c090c39ddf8a061aa5d4a38ecada92556915bc63a49d8768ebf9f714ccd +size 2312861 diff --git a/public/models/chemins/chemin_occlusionRoughnessMetallic.png b/public/models/chemins/chemin_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..e631e74 --- /dev/null +++ b/public/models/chemins/chemin_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab2316f07a7260c1d154456c81f6f29cd558ebc58129451b41c566287cbe869d +size 1036737 diff --git a/public/models/chemins/chemins.bin b/public/models/chemins/chemins.bin new file mode 100644 index 0000000..2c41af6 --- /dev/null +++ b/public/models/chemins/chemins.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ba960011c92ed9d62232442d6244aaddffd76b0393679fcc8d99acbd0f2d708 +size 8208 diff --git a/public/models/chemins/chemins.glb b/public/models/chemins/chemins.glb new file mode 100644 index 0000000..1d58962 --- /dev/null +++ b/public/models/chemins/chemins.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acd28ce2e66a217e21f4bdee1e90a30179e880a7cf90dffcfd11208950506375 +size 4991272 diff --git a/public/models/chemins/model.gltf b/public/models/chemins/model.gltf new file mode 100644 index 0000000..00dea0b --- /dev/null +++ b/public/models/chemins/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a14e2139e9c3bfbbe53a0f09205ab6bbdb572297206a2b5fe04a13ae73cbe846 +size 2967 diff --git a/public/models/createurdepluie/bac_eau_basecolor.png b/public/models/createurdepluie/bac_eau_basecolor.png new file mode 100644 index 0000000..783cc94 --- /dev/null +++ b/public/models/createurdepluie/bac_eau_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b5a6f13068b4d264586739b2398b31ecc58cdaf888f57cd2f4436d4c6c881a1 +size 184487 diff --git a/public/models/createurdepluie/bac_eau_normal.png b/public/models/createurdepluie/bac_eau_normal.png new file mode 100644 index 0000000..de96fbc --- /dev/null +++ b/public/models/createurdepluie/bac_eau_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f023b5e27d540faec936a0cd17614375a682cbaffe9a1dc9a1bf3f57442bb32 +size 3221317 diff --git a/public/models/createurdepluie/bac_eau_occlusionroughnessmetallic.png b/public/models/createurdepluie/bac_eau_occlusionroughnessmetallic.png new file mode 100644 index 0000000..b8257a6 --- /dev/null +++ b/public/models/createurdepluie/bac_eau_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bfea8322d8fd6c268fef98caeb975e5382006a5524b8e50c870eb64cde78179 +size 492220 diff --git a/public/models/createurdepluie/cable_1_basecolor.png b/public/models/createurdepluie/cable_1_basecolor.png new file mode 100644 index 0000000..1e0cf93 --- /dev/null +++ b/public/models/createurdepluie/cable_1_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5075c237c1aa8b07f9ada565621c1b78a6be0ae996f34966e4ba679085caa81e +size 16903 diff --git a/public/models/createurdepluie/cable_1_normal.png b/public/models/createurdepluie/cable_1_normal.png new file mode 100644 index 0000000..801e9cd --- /dev/null +++ b/public/models/createurdepluie/cable_1_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbd2c70e1e64f74362b4e59c3782ad586e05f4b10a58e8ef07736c5b23f27d47 +size 2272612 diff --git a/public/models/createurdepluie/cable_1_occlusionroughnessmetallic.png b/public/models/createurdepluie/cable_1_occlusionroughnessmetallic.png new file mode 100644 index 0000000..5bcc0e3 --- /dev/null +++ b/public/models/createurdepluie/cable_1_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:245e50263e24661e727e285b9f15006638f9cc15feafed65e377d32497fe3738 +size 16902 diff --git a/public/models/createurdepluie/cable_2_basecolor.png b/public/models/createurdepluie/cable_2_basecolor.png new file mode 100644 index 0000000..963528c --- /dev/null +++ b/public/models/createurdepluie/cable_2_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1041f4944fd55c9ef174f68768151a0d47780fa7196113cf10cef793e3f9b78e +size 16905 diff --git a/public/models/createurdepluie/cable_2_normal.png b/public/models/createurdepluie/cable_2_normal.png new file mode 100644 index 0000000..801e9cd --- /dev/null +++ b/public/models/createurdepluie/cable_2_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbd2c70e1e64f74362b4e59c3782ad586e05f4b10a58e8ef07736c5b23f27d47 +size 2272612 diff --git a/public/models/createurdepluie/cable_2_occlusionroughnessmetallic.png b/public/models/createurdepluie/cable_2_occlusionroughnessmetallic.png new file mode 100644 index 0000000..5bcc0e3 --- /dev/null +++ b/public/models/createurdepluie/cable_2_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:245e50263e24661e727e285b9f15006638f9cc15feafed65e377d32497fe3738 +size 16902 diff --git a/public/models/createurdepluie/createurdepluie2.bin b/public/models/createurdepluie/createurdepluie2.bin new file mode 100644 index 0000000..deeb205 --- /dev/null +++ b/public/models/createurdepluie/createurdepluie2.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94557e24622e737f0576993ab8a2dff46e787abeccf77007366e4d9c489485a4 +size 171384 diff --git a/public/models/createurdepluie/model.gltf b/public/models/createurdepluie/model.gltf new file mode 100644 index 0000000..96bff1f --- /dev/null +++ b/public/models/createurdepluie/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45eaf8de6cd85e5ded7c706f263fdf6b899f7a8ea6fa9d2fc4f1aa9969726d4d +size 28842546 diff --git a/public/models/createurdepluie/refroidisseur_basecolor.png b/public/models/createurdepluie/refroidisseur_basecolor.png new file mode 100644 index 0000000..a6ea95d --- /dev/null +++ b/public/models/createurdepluie/refroidisseur_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9851775687a9d8102d5437c21551e8bba3567777cd0aa886e36cdb25df13453e +size 16902 diff --git a/public/models/createurdepluie/refroidisseur_normal.png b/public/models/createurdepluie/refroidisseur_normal.png new file mode 100644 index 0000000..801e9cd --- /dev/null +++ b/public/models/createurdepluie/refroidisseur_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbd2c70e1e64f74362b4e59c3782ad586e05f4b10a58e8ef07736c5b23f27d47 +size 2272612 diff --git a/public/models/createurdepluie/refroidisseur_occlusionroughnessmetallic.png b/public/models/createurdepluie/refroidisseur_occlusionroughnessmetallic.png new file mode 100644 index 0000000..5bcc0e3 --- /dev/null +++ b/public/models/createurdepluie/refroidisseur_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:245e50263e24661e727e285b9f15006638f9cc15feafed65e377d32497fe3738 +size 16902 diff --git a/public/models/createurdepluie/resistance_basecolor.png b/public/models/createurdepluie/resistance_basecolor.png new file mode 100644 index 0000000..0e8b4c0 --- /dev/null +++ b/public/models/createurdepluie/resistance_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a98ca251ec125b292ca933dcaabde7a8cffc671c196d0912317f5f4b78a02e6 +size 16905 diff --git a/public/models/createurdepluie/resistance_normal.png b/public/models/createurdepluie/resistance_normal.png new file mode 100644 index 0000000..88de4b1 --- /dev/null +++ b/public/models/createurdepluie/resistance_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08afc9f1d8399100be33bd233388a26a1b6b94de95810ab583c491022ae5fb15 +size 2553049 diff --git a/public/models/createurdepluie/resistance_occlusionroughnessmetallic.png b/public/models/createurdepluie/resistance_occlusionroughnessmetallic.png new file mode 100644 index 0000000..a41d659 --- /dev/null +++ b/public/models/createurdepluie/resistance_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:265f3084db7db2ea2fe1a1f259b05315ada8cbfcb2b16824ddde8500f904b1f2 +size 300665 diff --git a/public/models/createurdepluie/shell_basecolor.png b/public/models/createurdepluie/shell_basecolor.png new file mode 100644 index 0000000..ce0a7bf --- /dev/null +++ b/public/models/createurdepluie/shell_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b5045fd7273b46e817bf56da900602491c7894adc60660c5f08acc99e3d4c87 +size 212626 diff --git a/public/models/createurdepluie/shell_normal.png b/public/models/createurdepluie/shell_normal.png new file mode 100644 index 0000000..0d3c060 --- /dev/null +++ b/public/models/createurdepluie/shell_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05682068f4a19be1f3d40e0f463dc76567d79524f1bd1e99aa38b778b695a92e +size 2505766 diff --git a/public/models/createurdepluie/shell_occlusionroughnessmetallic.png b/public/models/createurdepluie/shell_occlusionroughnessmetallic.png new file mode 100644 index 0000000..8fa3717 --- /dev/null +++ b/public/models/createurdepluie/shell_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee07ba1b25f822098e72df9099dbbdbf73244acb6b0e61e3a735745185ff3c3e +size 347997 diff --git a/public/models/createurdepluie/tuyau_basecolor.png b/public/models/createurdepluie/tuyau_basecolor.png new file mode 100644 index 0000000..eae930e --- /dev/null +++ b/public/models/createurdepluie/tuyau_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae0258e8e81b889fcb2bfa625191c750413bfdd5630688321c3b5f4501760880 +size 718156 diff --git a/public/models/createurdepluie/tuyau_normal.png b/public/models/createurdepluie/tuyau_normal.png new file mode 100644 index 0000000..f3d383f --- /dev/null +++ b/public/models/createurdepluie/tuyau_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:922fc53127b0be5e6c39ae17c5fef31ec6a7a67c4db7e0c141301d5da8c60503 +size 3258002 diff --git a/public/models/createurdepluie/tuyau_occlusionroughnessmetallic.png b/public/models/createurdepluie/tuyau_occlusionroughnessmetallic.png new file mode 100644 index 0000000..1dd17a9 --- /dev/null +++ b/public/models/createurdepluie/tuyau_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f30eddef4ecebe9474111a423da53a68c247f77c067214dc77cd490db4b34d39 +size 421486 diff --git a/public/models/ecole/Panneau_baseColor.png b/public/models/ecole/Panneau_baseColor.png new file mode 100644 index 0000000..6a5a13d --- /dev/null +++ b/public/models/ecole/Panneau_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccd0b05ec597b6c659ff5839c5ea711e0f69812b5bbfe3bcae283d80524b3a2d +size 553813 diff --git a/public/models/ecole/Panneau_normal.png b/public/models/ecole/Panneau_normal.png new file mode 100644 index 0000000..f6bc270 --- /dev/null +++ b/public/models/ecole/Panneau_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c34c69d4745aecdf3be9ecf1ac598fcc83247ad8f56dcd154b37639f997d7fa0 +size 2598855 diff --git a/public/models/ecole/Panneau_occlusionRoughnessMetallic.png b/public/models/ecole/Panneau_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..0316814 --- /dev/null +++ b/public/models/ecole/Panneau_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90e855f9f4f329a67193ae5a72d80f958b57c51ee54bb30a9f8334e201ac0b3f +size 364876 diff --git a/public/models/ecole/ecole2.bin b/public/models/ecole/ecole2.bin new file mode 100644 index 0000000..f5fcd05 --- /dev/null +++ b/public/models/ecole/ecole2.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69ec620396ceb5440723b060af92c1420fa20c72ba2d61ba9f59d192bc7b1e11 +size 188088 diff --git a/public/models/ecole/fenetre_baseColor.png b/public/models/ecole/fenetre_baseColor.png new file mode 100644 index 0000000..ee24397 --- /dev/null +++ b/public/models/ecole/fenetre_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68f2d77c0b477b48080e1b27ad11074c5fd23c5928ae13577a92bd4bd1ff19f7 +size 107157 diff --git a/public/models/ecole/fenetre_normal.png b/public/models/ecole/fenetre_normal.png new file mode 100644 index 0000000..cb6a532 --- /dev/null +++ b/public/models/ecole/fenetre_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbef2db5e7556f13115744dfbbb4520e36379810a5340603e83159e8683f58ce +size 2413075 diff --git a/public/models/ecole/fenetre_occlusionRoughnessMetallic.png b/public/models/ecole/fenetre_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..e8f138a --- /dev/null +++ b/public/models/ecole/fenetre_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc52932715c82b80d92e7c020def7181f3f13936eaf8a4c8db35e8c20d39d078 +size 83608 diff --git a/public/models/ecole/maison_baseColor.png b/public/models/ecole/maison_baseColor.png new file mode 100644 index 0000000..4376b24 --- /dev/null +++ b/public/models/ecole/maison_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:550d3a896dd06ebc8e9abd668645f057dc45f0095628d209664991bc7cb8ff13 +size 794881 diff --git a/public/models/ecole/maison_normal.png b/public/models/ecole/maison_normal.png new file mode 100644 index 0000000..6cc1302 --- /dev/null +++ b/public/models/ecole/maison_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d123398ca52c2d4f38e15103029cc2e19f7b27fa8e2568cc3348359517cfe780 +size 3434766 diff --git a/public/models/ecole/maison_occlusionRoughnessMetallic.png b/public/models/ecole/maison_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..ed72cd8 --- /dev/null +++ b/public/models/ecole/maison_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6e8e5f141654f7cafa573f8362b33c50c53cd7e07ddaf28c4acc98f1309f6d8 +size 1065333 diff --git a/public/models/ecole/model.gltf b/public/models/ecole/model.gltf new file mode 100644 index 0000000..6056361 --- /dev/null +++ b/public/models/ecole/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d724bb99c0f36d0984018f4eb9f9304d02b1328a002caf3c175390d3972888b +size 22590156 diff --git a/public/models/ecole/porte_baseColor.png b/public/models/ecole/porte_baseColor.png new file mode 100644 index 0000000..ccdaaec --- /dev/null +++ b/public/models/ecole/porte_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2840b14685c2e82ead9acad4048be357ce2bd4f0084ed2100cbf3244609c5cc6 +size 28307 diff --git a/public/models/ecole/porte_normal.png b/public/models/ecole/porte_normal.png new file mode 100644 index 0000000..c482fa3 --- /dev/null +++ b/public/models/ecole/porte_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc4c48303b594a6c391c4a2d45bf7fffc5424a0b97d756af32cf45601c2816e6 +size 2477905 diff --git a/public/models/ecole/porte_occlusionRoughnessMetallic.png b/public/models/ecole/porte_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..d85ee2e --- /dev/null +++ b/public/models/ecole/porte_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06bce47a6b2308c10bdd2cac2ada91149892e8212bc290c2f01e29bd830b97c6 +size 31863 diff --git a/public/models/ecole/tiges_baseColor.png b/public/models/ecole/tiges_baseColor.png new file mode 100644 index 0000000..3bb4087 --- /dev/null +++ b/public/models/ecole/tiges_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a742cb3a3643caf18d499f48a4fe1ee9c8d19aa634e1e86c9c8b23cf08169ed +size 16906 diff --git a/public/models/ecole/tiges_normal.png b/public/models/ecole/tiges_normal.png new file mode 100644 index 0000000..801e9cd --- /dev/null +++ b/public/models/ecole/tiges_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbd2c70e1e64f74362b4e59c3782ad586e05f4b10a58e8ef07736c5b23f27d47 +size 2272612 diff --git a/public/models/ecole/tiges_occlusionRoughnessMetallic.png b/public/models/ecole/tiges_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..5bcc0e3 --- /dev/null +++ b/public/models/ecole/tiges_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:245e50263e24661e727e285b9f15006638f9cc15feafed65e377d32497fe3738 +size 16902 diff --git a/public/models/elec/model.bin b/public/models/elec/model.bin index 96e13e8..3a75871 100644 Binary files a/public/models/elec/model.bin and b/public/models/elec/model.bin differ diff --git a/public/models/elecsimple/electricienne.bin b/public/models/elecsimple/electricienne.bin deleted file mode 100644 index 4f743f0..0000000 Binary files a/public/models/elecsimple/electricienne.bin and /dev/null differ diff --git a/public/models/elecsimple/Mat_baseColor.png b/public/models/electricienne/Mat_baseColor.png similarity index 100% rename from public/models/elecsimple/Mat_baseColor.png rename to public/models/electricienne/Mat_baseColor.png diff --git a/public/models/elecsimple/Mat_normal.png b/public/models/electricienne/Mat_normal.png similarity index 100% rename from public/models/elecsimple/Mat_normal.png rename to public/models/electricienne/Mat_normal.png diff --git a/public/models/elecsimple/Mat_occlusionRoughnessMetallic.png b/public/models/electricienne/Mat_occlusionRoughnessMetallic.png similarity index 100% rename from public/models/elecsimple/Mat_occlusionRoughnessMetallic.png rename to public/models/electricienne/Mat_occlusionRoughnessMetallic.png diff --git a/public/models/electricienne/electricienne.bin b/public/models/electricienne/electricienne.bin new file mode 100644 index 0000000..c1cb8a9 --- /dev/null +++ b/public/models/electricienne/electricienne.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96139291cc9e5f46b3c6526b68d2560af05da817b9d96c8660802705dc0d4fd9 +size 3050928 diff --git a/public/models/elecsimple/model.gltf b/public/models/electricienne/model.gltf similarity index 100% rename from public/models/elecsimple/model.gltf rename to public/models/electricienne/model.gltf diff --git a/public/models/electricienne_animated/Mat_baseColor.png b/public/models/electricienne_animated/Mat_baseColor.png new file mode 100644 index 0000000..ba0de35 --- /dev/null +++ b/public/models/electricienne_animated/Mat_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:142be230a66ff6bebe321b373e4785283624c3bb5f3565114a6acca6e2d056f2 +size 691735 diff --git a/public/models/electricienne_animated/Mat_normal.png b/public/models/electricienne_animated/Mat_normal.png new file mode 100644 index 0000000..c72e6e2 --- /dev/null +++ b/public/models/electricienne_animated/Mat_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f4580790707fc1fc505b3b725a523eac3e985353bc2e566a73ae2d983e87029 +size 1229760 diff --git a/public/models/electricienne_animated/Mat_occlusionRoughnessMetallic.png b/public/models/electricienne_animated/Mat_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..dc6530c --- /dev/null +++ b/public/models/electricienne_animated/Mat_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2abdb28a5b27842d8958480f97357a3603b2c0ab46db9ff6bf08e474600c5d49 +size 650826 diff --git a/public/models/electricienne_animated/model.bin b/public/models/electricienne_animated/model.bin new file mode 100644 index 0000000..3a75871 --- /dev/null +++ b/public/models/electricienne_animated/model.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:247407ee9bdb8fa5730a56df06872a224888cde1a4a0592c62d0157608b83f02 +size 2954520 diff --git a/public/models/electricienne_animated/model.gltf b/public/models/electricienne_animated/model.gltf new file mode 100644 index 0000000..fc3c84f --- /dev/null +++ b/public/models/electricienne_animated/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35129131d3f1d70b9648b5ea09d704ffecaab6b52aa03c0ab32b476349b25f92 +size 47180 diff --git a/public/models/eolienne/cul_base_color.png b/public/models/eolienne/cul_base_color.png new file mode 100644 index 0000000..e144980 --- /dev/null +++ b/public/models/eolienne/cul_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:865edafdd3f22fd509f75c6ec5238dedad300f5fb2665f7f088ef1f810d6cb73 +size 432094 diff --git a/public/models/eolienne/cul_height.png b/public/models/eolienne/cul_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/eolienne/cul_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/eolienne/cul_metallic.png b/public/models/eolienne/cul_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/eolienne/cul_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/eolienne/cul_mixed_ao.png b/public/models/eolienne/cul_mixed_ao.png new file mode 100644 index 0000000..655dffa --- /dev/null +++ b/public/models/eolienne/cul_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5960ad1ade751d2b6a45137265507c9b98ec9a427bcb4cdd640b308288875929 +size 295249 diff --git a/public/models/eolienne/cul_normal.png b/public/models/eolienne/cul_normal.png new file mode 100644 index 0000000..3e142f2 --- /dev/null +++ b/public/models/eolienne/cul_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:761530eb1110b23e7eaa0f4dd7f1fc7e909e91d2d20204991b33932d21c83071 +size 203721 diff --git a/public/models/eolienne/cul_normal_opengl.png b/public/models/eolienne/cul_normal_opengl.png new file mode 100644 index 0000000..a7c7291 --- /dev/null +++ b/public/models/eolienne/cul_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bdf12b1dcc3ff804e53ff6a70c765da7e55f07afca01a8277aacf4685ceaaba +size 205011 diff --git a/public/models/eolienne/cul_roughness.png b/public/models/eolienne/cul_roughness.png new file mode 100644 index 0000000..8e05377 --- /dev/null +++ b/public/models/eolienne/cul_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1cbb69128ca93cce687503bf369b0e395cf82df46558780fd4e29d01675716f +size 91621 diff --git a/public/models/eolienne/feuilles1st_base_color.png b/public/models/eolienne/feuilles1st_base_color.png new file mode 100644 index 0000000..32d6629 --- /dev/null +++ b/public/models/eolienne/feuilles1st_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f57737c31c43f7160f4a66174f8fe8a012f75b0f982b2476e00e7bf55a9e022 +size 276451 diff --git a/public/models/eolienne/feuilles1st_height.png b/public/models/eolienne/feuilles1st_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/eolienne/feuilles1st_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/eolienne/feuilles1st_metallic.png b/public/models/eolienne/feuilles1st_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/eolienne/feuilles1st_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/eolienne/feuilles1st_mixed_ao.png b/public/models/eolienne/feuilles1st_mixed_ao.png new file mode 100644 index 0000000..3c00fbc --- /dev/null +++ b/public/models/eolienne/feuilles1st_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4dae82e8f1b8e0df14d9e89e9ea43da19f9f223f9e37c2aef2c0bd7192ea84a +size 308317 diff --git a/public/models/eolienne/feuilles1st_normal.png b/public/models/eolienne/feuilles1st_normal.png new file mode 100644 index 0000000..98f38fe --- /dev/null +++ b/public/models/eolienne/feuilles1st_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a37f8b5af1277ab6863943cd19340210dae7ee7727d2832edac1a465f793d7de +size 46561 diff --git a/public/models/eolienne/feuilles1st_normal_opengl.png b/public/models/eolienne/feuilles1st_normal_opengl.png new file mode 100644 index 0000000..b64245b --- /dev/null +++ b/public/models/eolienne/feuilles1st_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:172dd4a40c9342335f4b63534710090644048b5acbc282b0ce580f8f8dd40580 +size 52679 diff --git a/public/models/eolienne/feuilles1st_roughness.png b/public/models/eolienne/feuilles1st_roughness.png new file mode 100644 index 0000000..4ab879f --- /dev/null +++ b/public/models/eolienne/feuilles1st_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12e2e1b050172c55cf294c7b4293daa80e21c78845f2e75445d80005dde17280 +size 30413 diff --git a/public/models/eolienne/feuilles2nd_base_color.png b/public/models/eolienne/feuilles2nd_base_color.png new file mode 100644 index 0000000..8b3e32e --- /dev/null +++ b/public/models/eolienne/feuilles2nd_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e34d6b6bbbcb80f6c5d5c2c08f4db322c46cb04599342d83eba0f8371189d2c +size 251469 diff --git a/public/models/eolienne/feuilles2nd_height.png b/public/models/eolienne/feuilles2nd_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/eolienne/feuilles2nd_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/eolienne/feuilles2nd_metallic.png b/public/models/eolienne/feuilles2nd_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/eolienne/feuilles2nd_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/eolienne/feuilles2nd_mixed_ao.png b/public/models/eolienne/feuilles2nd_mixed_ao.png new file mode 100644 index 0000000..5a3a553 --- /dev/null +++ b/public/models/eolienne/feuilles2nd_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53a1cec0d4bd061c1cc55e3e9aaae8794b21ddca9f67873a2a394f358b7cf0d6 +size 311566 diff --git a/public/models/eolienne/feuilles2nd_normal.png b/public/models/eolienne/feuilles2nd_normal.png new file mode 100644 index 0000000..f608b95 --- /dev/null +++ b/public/models/eolienne/feuilles2nd_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c30da93bc0ffa7f590568bcffa8eff1aacb301e190dee97edfa4e3423dd53b91 +size 50006 diff --git a/public/models/eolienne/feuilles2nd_normal_opengl.png b/public/models/eolienne/feuilles2nd_normal_opengl.png new file mode 100644 index 0000000..70b6927 --- /dev/null +++ b/public/models/eolienne/feuilles2nd_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14511fa9847e7b22550a1babdc7b3fd92dd4bce4c37ed79ddddd565b720003e5 +size 55648 diff --git a/public/models/eolienne/feuilles2nd_roughness.png b/public/models/eolienne/feuilles2nd_roughness.png new file mode 100644 index 0000000..e1df83a --- /dev/null +++ b/public/models/eolienne/feuilles2nd_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c914892d63771015f4321d00daf5bb0893cb9652596e83f997997a43e1283ad3 +size 27279 diff --git a/public/models/eolienne/he_lisse_base_color.png b/public/models/eolienne/he_lisse_base_color.png new file mode 100644 index 0000000..18c7157 --- /dev/null +++ b/public/models/eolienne/he_lisse_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:545616d3ed16231900f5120616c6770a2d6dc9e0d64063085404ed58ff59d7ac +size 201972 diff --git a/public/models/eolienne/he_lisse_height.png b/public/models/eolienne/he_lisse_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/eolienne/he_lisse_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/eolienne/he_lisse_metallic.png b/public/models/eolienne/he_lisse_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/eolienne/he_lisse_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/eolienne/he_lisse_mixed_ao.png b/public/models/eolienne/he_lisse_mixed_ao.png new file mode 100644 index 0000000..c04344a --- /dev/null +++ b/public/models/eolienne/he_lisse_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15d8cb6b802231b5ed9c0632d06d9b5390302a7dbf689e293f504ff95e1ef17d +size 197880 diff --git a/public/models/eolienne/he_lisse_normal.png b/public/models/eolienne/he_lisse_normal.png new file mode 100644 index 0000000..9f5b774 --- /dev/null +++ b/public/models/eolienne/he_lisse_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ec43473f3a38dc95632aa7412bbae031cdb2b08dea899c23cf6f1208de2d60a +size 191989 diff --git a/public/models/eolienne/he_lisse_normal_opengl.png b/public/models/eolienne/he_lisse_normal_opengl.png new file mode 100644 index 0000000..1a26c7d --- /dev/null +++ b/public/models/eolienne/he_lisse_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a0ebfd8a08a1e93f5d53ec0d353b46d9729fc9e480a6f9e46a866b877722548 +size 192990 diff --git a/public/models/eolienne/he_lisse_opacity.png b/public/models/eolienne/he_lisse_opacity.png new file mode 100644 index 0000000..c4a8c52 --- /dev/null +++ b/public/models/eolienne/he_lisse_opacity.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad2c1abbcdf7221a041209a10c946df6fd9d55513f21023de542e29547b93308 +size 3179 diff --git a/public/models/eolienne/he_lisse_roughness.png b/public/models/eolienne/he_lisse_roughness.png new file mode 100644 index 0000000..a54d620 --- /dev/null +++ b/public/models/eolienne/he_lisse_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0fe2c810ca9e68690d90cfc16dc13fb776a21198d431efd5920fb3a03ca6e8aa +size 66162 diff --git a/public/models/eolienne/model.gltf b/public/models/eolienne/model.gltf new file mode 100644 index 0000000..87bda2b --- /dev/null +++ b/public/models/eolienne/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa68d5c4ff193351bd21b2d42a6b6d0d0983d66019c3344082a5210a00c036a1 +size 7667190 diff --git a/public/models/eolienne/moteur_base_color.png b/public/models/eolienne/moteur_base_color.png new file mode 100644 index 0000000..163565f --- /dev/null +++ b/public/models/eolienne/moteur_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:943b1c48b08b499fcd49eb28137cd5f669f435fcb4daa643e42b897bc4b012c2 +size 413119 diff --git a/public/models/eolienne/moteur_height.png b/public/models/eolienne/moteur_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/eolienne/moteur_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/eolienne/moteur_metallic.png b/public/models/eolienne/moteur_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/eolienne/moteur_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/eolienne/moteur_mixed_ao.png b/public/models/eolienne/moteur_mixed_ao.png new file mode 100644 index 0000000..4eb59ed --- /dev/null +++ b/public/models/eolienne/moteur_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:988de71cd3caf14138f2b85146371edbaf59cb898a285c5e965271663a8879d0 +size 207624 diff --git a/public/models/eolienne/moteur_normal.png b/public/models/eolienne/moteur_normal.png new file mode 100644 index 0000000..f9c8d9c --- /dev/null +++ b/public/models/eolienne/moteur_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcfeeb8fcf3ce4c04d1eb7e5e59fa5277b452943bc9432daca69d7549514c56b +size 212873 diff --git a/public/models/eolienne/moteur_normal_opengl.png b/public/models/eolienne/moteur_normal_opengl.png new file mode 100644 index 0000000..d5de202 --- /dev/null +++ b/public/models/eolienne/moteur_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d3f3ff93fb1f2fc595a7ca5801f3d45caeca5df261277625f2947f1d8ad9d3c +size 212505 diff --git a/public/models/eolienne/moteur_roughness.png b/public/models/eolienne/moteur_roughness.png new file mode 100644 index 0000000..1de9094 --- /dev/null +++ b/public/models/eolienne/moteur_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf3c8800dc70982300822307941260c5a7d848a041ba6f9746ba8450d6870f02 +size 100105 diff --git a/public/models/eolienne/pied_base_color.png b/public/models/eolienne/pied_base_color.png new file mode 100644 index 0000000..7fcdd16 --- /dev/null +++ b/public/models/eolienne/pied_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93620500fac2ad4d84a1917da89a2d19c9d040e709faea358e4331e0a051715d +size 311741 diff --git a/public/models/eolienne/pied_height.png b/public/models/eolienne/pied_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/eolienne/pied_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/eolienne/pied_metallic.png b/public/models/eolienne/pied_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/eolienne/pied_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/eolienne/pied_mixed_ao.png b/public/models/eolienne/pied_mixed_ao.png new file mode 100644 index 0000000..a4ee06c --- /dev/null +++ b/public/models/eolienne/pied_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:561c48e222a3e98d39aeea3e05e43b80787aa955ae499f0b612bf40a845c19e4 +size 245569 diff --git a/public/models/eolienne/pied_normal.png b/public/models/eolienne/pied_normal.png new file mode 100644 index 0000000..0b3d48a --- /dev/null +++ b/public/models/eolienne/pied_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c11d1ece99eed332e974d8fd51cfcee497380c22108a0998ade0fd8986647fa4 +size 274501 diff --git a/public/models/eolienne/pied_normal_opengl.png b/public/models/eolienne/pied_normal_opengl.png new file mode 100644 index 0000000..9c287a1 --- /dev/null +++ b/public/models/eolienne/pied_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:325fe9a2fa966fac89d5f62b4c5ba0df245ed265d2468357a3d3b412a3281be7 +size 274643 diff --git a/public/models/eolienne/pied_roughness.png b/public/models/eolienne/pied_roughness.png new file mode 100644 index 0000000..9ce7107 --- /dev/null +++ b/public/models/eolienne/pied_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cae97f2a492e39eebc1e323fbf38b80b9ad65ab00d44dc0b4268f7df907cc193 +size 109661 diff --git a/public/models/eolienne/tiges1st_base_color.png b/public/models/eolienne/tiges1st_base_color.png new file mode 100644 index 0000000..f49ec72 --- /dev/null +++ b/public/models/eolienne/tiges1st_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1a939182dac29e0439daea6270c9ab040adf3b0b95e6842911a5614715c0844 +size 130621 diff --git a/public/models/eolienne/tiges1st_height.png b/public/models/eolienne/tiges1st_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/eolienne/tiges1st_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/eolienne/tiges1st_metallic.png b/public/models/eolienne/tiges1st_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/eolienne/tiges1st_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/eolienne/tiges1st_mixed_ao.png b/public/models/eolienne/tiges1st_mixed_ao.png new file mode 100644 index 0000000..09e2f33 --- /dev/null +++ b/public/models/eolienne/tiges1st_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09bbb9842790097009510c5680db7141a56f92b9a6d0661544413bc010a4510e +size 98982 diff --git a/public/models/eolienne/tiges1st_normal.png b/public/models/eolienne/tiges1st_normal.png new file mode 100644 index 0000000..d7fabee --- /dev/null +++ b/public/models/eolienne/tiges1st_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:362b758d3732454f73665ac5f3ef163ac123726e6bc0f927f010ba17666b85de +size 150651 diff --git a/public/models/eolienne/tiges1st_normal_opengl.png b/public/models/eolienne/tiges1st_normal_opengl.png new file mode 100644 index 0000000..cada0f7 --- /dev/null +++ b/public/models/eolienne/tiges1st_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67472d13674b8dc0dacd4e097c5a152649a85f22e1ec3cd3680ef965aa0befb3 +size 151985 diff --git a/public/models/eolienne/tiges1st_roughness.png b/public/models/eolienne/tiges1st_roughness.png new file mode 100644 index 0000000..4439c84 --- /dev/null +++ b/public/models/eolienne/tiges1st_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7be021f31d6abb7cd71c1404af92f0a5567763a79fa1199feadc4267e2a4cb94 +size 44279 diff --git a/public/models/eolienne/tiges2nd_base_color.png b/public/models/eolienne/tiges2nd_base_color.png new file mode 100644 index 0000000..99cd3d9 --- /dev/null +++ b/public/models/eolienne/tiges2nd_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94d7d4c073d4a31dedf92d519582b3136c03f0ddc1062d186974d47fa18cced1 +size 389344 diff --git a/public/models/eolienne/tiges2nd_height.png b/public/models/eolienne/tiges2nd_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/eolienne/tiges2nd_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/eolienne/tiges2nd_metallic.png b/public/models/eolienne/tiges2nd_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/eolienne/tiges2nd_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/eolienne/tiges2nd_mixed_ao.png b/public/models/eolienne/tiges2nd_mixed_ao.png new file mode 100644 index 0000000..042822d --- /dev/null +++ b/public/models/eolienne/tiges2nd_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4e1cc7ab7b893b2f2f09b51f664222a9067bc985b7bcd5f2caff43d0e940f0d +size 547796 diff --git a/public/models/eolienne/tiges2nd_normal.png b/public/models/eolienne/tiges2nd_normal.png new file mode 100644 index 0000000..4662e58 --- /dev/null +++ b/public/models/eolienne/tiges2nd_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e441130d64f0b3a3385568e6e44a9b3e041a698ed64a0c3aed308c529239c370 +size 587352 diff --git a/public/models/eolienne/tiges2nd_normal_opengl.png b/public/models/eolienne/tiges2nd_normal_opengl.png new file mode 100644 index 0000000..82d406f --- /dev/null +++ b/public/models/eolienne/tiges2nd_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8eb363a42322e801e25cb08d957164c72bcf9110f84551899042b0ca0d554c38 +size 596706 diff --git a/public/models/eolienne/tiges2nd_roughness.png b/public/models/eolienne/tiges2nd_roughness.png new file mode 100644 index 0000000..3dfc0a1 --- /dev/null +++ b/public/models/eolienne/tiges2nd_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27563b099d877a21dcbe23c99ae000dff2a3cdac72c084d51423692d10ce19a6 +size 126408 diff --git a/public/models/fermeverticale/ferme verticale.bin b/public/models/fermeverticale/ferme verticale.bin new file mode 100644 index 0000000..69ed088 --- /dev/null +++ b/public/models/fermeverticale/ferme verticale.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cc8a6bba1b4ccaab30e04e4777963b5a751dbf9b8bba023609b5e9663c88423 +size 15648 diff --git a/public/models/fermeverticale/fermeverticale_baseColor.png b/public/models/fermeverticale/fermeverticale_baseColor.png new file mode 100644 index 0000000..818c751 --- /dev/null +++ b/public/models/fermeverticale/fermeverticale_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:daf907f69c82779b518b3beaebd298a3c5cd49f2abe97c3d7bac5635652e2d2c +size 1360951 diff --git a/public/models/fermeverticale/fermeverticale_normal.png b/public/models/fermeverticale/fermeverticale_normal.png new file mode 100644 index 0000000..01e3b69 --- /dev/null +++ b/public/models/fermeverticale/fermeverticale_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65d36f3571000ab4b2710486ee80a933bc1c13de4c5eb911794ca812d585f445 +size 2708830 diff --git a/public/models/fermeverticale/fermeverticale_occlusionRoughnessMetallic.png b/public/models/fermeverticale/fermeverticale_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..1055413 --- /dev/null +++ b/public/models/fermeverticale/fermeverticale_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6933f6afbcde5115950eb83878ac2be045e450d9a05f81d252a32e98e12f9cd3 +size 1182025 diff --git a/public/models/fermeverticale/model.gltf b/public/models/fermeverticale/model.gltf new file mode 100644 index 0000000..9467409 --- /dev/null +++ b/public/models/fermeverticale/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7696150952db561388cfc959f6506460776a5216cfa233679e176174be29059 +size 3226 diff --git a/public/models/fermier/defaultmaterial_basecolor.png b/public/models/fermier/defaultmaterial_basecolor.png new file mode 100644 index 0000000..8b84835 --- /dev/null +++ b/public/models/fermier/defaultmaterial_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7aa240560fc4e379098c14492577be668a24f021ef03e49af790e91bdb108fa3 +size 791788 diff --git a/public/models/fermier/defaultmaterial_normal.png b/public/models/fermier/defaultmaterial_normal.png new file mode 100644 index 0000000..378a5d7 --- /dev/null +++ b/public/models/fermier/defaultmaterial_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37290badb8d3112676e42ab34ea64576407eaece66399cac992fca791aa3e8ce +size 3383876 diff --git a/public/models/fermier/defaultmaterial_occlusionroughnessmetallic.png b/public/models/fermier/defaultmaterial_occlusionroughnessmetallic.png new file mode 100644 index 0000000..65ce6be --- /dev/null +++ b/public/models/fermier/defaultmaterial_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1305efe72137b5c013ffefc8d6ef26b2a256d4496238770bb37d943c21e132e +size 762856 diff --git a/public/models/fermier/fermier.bin b/public/models/fermier/fermier.bin new file mode 100644 index 0000000..c8ff9c9 --- /dev/null +++ b/public/models/fermier/fermier.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:26fb668d4fc991ac001846eedcf3cc23535baf92607b5b01ce7543d2aeb04963 +size 3186144 diff --git a/public/models/fermier/model.gltf b/public/models/fermier/model.gltf new file mode 100644 index 0000000..01e08d4 --- /dev/null +++ b/public/models/fermier/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1faa8f43d3026dc0a49e52be425539ba761793cda4fe24a421004af5d44da4b7 +size 3146 diff --git a/public/models/galet/galet.bin b/public/models/galet/galet.bin new file mode 100644 index 0000000..1445793 --- /dev/null +++ b/public/models/galet/galet.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a3897cd4fc5d1d2090716630dffc10bdafe748e3275f727ee90d91d307e6c02 +size 13968 diff --git a/public/models/galet/galet_basecolor.png b/public/models/galet/galet_basecolor.png new file mode 100644 index 0000000..a20f7df --- /dev/null +++ b/public/models/galet/galet_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5d5745b6a621268d1d3d4e6da45c1b92c946ef8c43a865493bae855844c547f +size 492995 diff --git a/public/models/galet/galet_normal.png b/public/models/galet/galet_normal.png new file mode 100644 index 0000000..d8f29af --- /dev/null +++ b/public/models/galet/galet_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:044bed9c465d55e5be571862e46aaf5f48172a8572665286068bd005847a5f17 +size 2613811 diff --git a/public/models/galet/galet_occlusionroughnessmetallic.png b/public/models/galet/galet_occlusionroughnessmetallic.png new file mode 100644 index 0000000..f262ab4 --- /dev/null +++ b/public/models/galet/galet_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:137ce434f528bd56a3a5eabc339173c5e8fb4f9306fb0ca624d996ee90b8d1f8 +size 16902 diff --git a/public/models/galet/model.gltf b/public/models/galet/model.gltf new file mode 100644 index 0000000..e2324f3 --- /dev/null +++ b/public/models/galet/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15a4dcd7c5faf03913dd79f1ebbc0970c9db4f2733c36dcf53bb5ef652974c9c +size 3495 diff --git a/public/models/gant_l/gant_basecolor.png b/public/models/gant_l/gant_basecolor.png new file mode 100644 index 0000000..51d12c4 --- /dev/null +++ b/public/models/gant_l/gant_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99011cfdde0fed73e7d8927bce22f38a4f33decf00ccf6eaaa2aadc5ef940b4f +size 330807 diff --git a/public/models/gant_l/gant_normal.png b/public/models/gant_l/gant_normal.png new file mode 100644 index 0000000..95e5e61 --- /dev/null +++ b/public/models/gant_l/gant_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82a45f350f230fafcd51c9e19db9f7cab4b86f08cb9b46ce9529517e17c53046 +size 2937255 diff --git a/public/models/gant_l/gant_occlusionroughnessmetallic.png b/public/models/gant_l/gant_occlusionroughnessmetallic.png new file mode 100644 index 0000000..f262ab4 --- /dev/null +++ b/public/models/gant_l/gant_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:137ce434f528bd56a3a5eabc339173c5e8fb4f9306fb0ca624d996ee90b8d1f8 +size 16902 diff --git a/public/models/gant_l/hanf_l.bin b/public/models/gant_l/hanf_l.bin new file mode 100644 index 0000000..56ed2f6 --- /dev/null +++ b/public/models/gant_l/hanf_l.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47a09e932f77bcd6d621caefd216ba3347b7ba3b3ccafad4d321d45bcb58eadf +size 486972 diff --git a/public/models/gant_l/model.gltf b/public/models/gant_l/model.gltf new file mode 100644 index 0000000..8145f42 --- /dev/null +++ b/public/models/gant_l/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3696880d4654a676341854d33a1a39b6358bd5a6af8e36c92adc8d42101b208 +size 10303 diff --git a/public/models/gant_l_pad/galet_basecolor.png b/public/models/gant_l_pad/galet_basecolor.png new file mode 100644 index 0000000..33f9861 --- /dev/null +++ b/public/models/gant_l_pad/galet_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8230f08a07b4a5cad22e68c563192a664741db2eaa9c6ac481b7b6e2c1f00a5 +size 492994 diff --git a/public/models/gant_l_pad/galet_normal.png b/public/models/gant_l_pad/galet_normal.png new file mode 100644 index 0000000..d8f29af --- /dev/null +++ b/public/models/gant_l_pad/galet_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:044bed9c465d55e5be571862e46aaf5f48172a8572665286068bd005847a5f17 +size 2613811 diff --git a/public/models/gant_l_pad/galet_occlusionroughnessmetallic.png b/public/models/gant_l_pad/galet_occlusionroughnessmetallic.png new file mode 100644 index 0000000..f262ab4 --- /dev/null +++ b/public/models/gant_l_pad/galet_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:137ce434f528bd56a3a5eabc339173c5e8fb4f9306fb0ca624d996ee90b8d1f8 +size 16902 diff --git a/public/models/gant_l_pad/gant_basecolor.png b/public/models/gant_l_pad/gant_basecolor.png new file mode 100644 index 0000000..51d12c4 --- /dev/null +++ b/public/models/gant_l_pad/gant_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99011cfdde0fed73e7d8927bce22f38a4f33decf00ccf6eaaa2aadc5ef940b4f +size 330807 diff --git a/public/models/gant_l_pad/gant_normal.png b/public/models/gant_l_pad/gant_normal.png new file mode 100644 index 0000000..95e5e61 --- /dev/null +++ b/public/models/gant_l_pad/gant_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82a45f350f230fafcd51c9e19db9f7cab4b86f08cb9b46ce9529517e17c53046 +size 2937255 diff --git a/public/models/gant_l_pad/gant_occlusionroughnessmetallic.png b/public/models/gant_l_pad/gant_occlusionroughnessmetallic.png new file mode 100644 index 0000000..f262ab4 --- /dev/null +++ b/public/models/gant_l_pad/gant_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:137ce434f528bd56a3a5eabc339173c5e8fb4f9306fb0ca624d996ee90b8d1f8 +size 16902 diff --git a/public/models/gant_l_pad/gants.bin b/public/models/gant_l_pad/gants.bin new file mode 100644 index 0000000..8bcb26d --- /dev/null +++ b/public/models/gant_l_pad/gants.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fdf3a195f75d3f3f0b2802756ff3441094c1924ca8aef85ead33090b8061191 +size 554472 diff --git a/public/models/gant_l_pad/model.gltf b/public/models/gant_l_pad/model.gltf new file mode 100644 index 0000000..846cf49 --- /dev/null +++ b/public/models/gant_l_pad/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fee94fb22e76ebc51ebc30fdd9415eb7db02f4298355965d99889b4329301393 +size 6192 diff --git a/public/models/gant_r/galet_baseColor.png b/public/models/gant_r/galet_baseColor.png new file mode 100644 index 0000000..33f9861 --- /dev/null +++ b/public/models/gant_r/galet_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8230f08a07b4a5cad22e68c563192a664741db2eaa9c6ac481b7b6e2c1f00a5 +size 492994 diff --git a/public/models/gant_r/galet_normal.png b/public/models/gant_r/galet_normal.png new file mode 100644 index 0000000..d8f29af --- /dev/null +++ b/public/models/gant_r/galet_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:044bed9c465d55e5be571862e46aaf5f48172a8572665286068bd005847a5f17 +size 2613811 diff --git a/public/models/gant_r/galet_occlusionRoughnessMetallic.png b/public/models/gant_r/galet_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..f262ab4 --- /dev/null +++ b/public/models/gant_r/galet_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:137ce434f528bd56a3a5eabc339173c5e8fb4f9306fb0ca624d996ee90b8d1f8 +size 16902 diff --git a/public/models/gant_r/gant_basecolor.png b/public/models/gant_r/gant_basecolor.png new file mode 100644 index 0000000..51d12c4 --- /dev/null +++ b/public/models/gant_r/gant_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99011cfdde0fed73e7d8927bce22f38a4f33decf00ccf6eaaa2aadc5ef940b4f +size 330807 diff --git a/public/models/gant_r/gant_normal.png b/public/models/gant_r/gant_normal.png new file mode 100644 index 0000000..95e5e61 --- /dev/null +++ b/public/models/gant_r/gant_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82a45f350f230fafcd51c9e19db9f7cab4b86f08cb9b46ce9529517e17c53046 +size 2937255 diff --git a/public/models/gant_r/gant_occlusionroughnessmetallic.png b/public/models/gant_r/gant_occlusionroughnessmetallic.png new file mode 100644 index 0000000..f262ab4 --- /dev/null +++ b/public/models/gant_r/gant_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:137ce434f528bd56a3a5eabc339173c5e8fb4f9306fb0ca624d996ee90b8d1f8 +size 16902 diff --git a/public/models/gant_r/gant_r.bin b/public/models/gant_r/gant_r.bin new file mode 100644 index 0000000..c9f74e4 --- /dev/null +++ b/public/models/gant_r/gant_r.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c36e76761c1b733c53f5ce307f8d05db7e74f04b52a14d0dfb745330d8a39f93 +size 540504 diff --git a/public/models/gant_r/model.bin b/public/models/gant_r/model.bin new file mode 100644 index 0000000..ddaf087 --- /dev/null +++ b/public/models/gant_r/model.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94caa72fe32b36c373174f83aad4658df02ddd55de862b5f62357e879597b592 +size 1384136 diff --git a/public/models/gant_r/model.glb b/public/models/gant_r/model.glb new file mode 100644 index 0000000..bd52ef8 --- /dev/null +++ b/public/models/gant_r/model.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09384178466e1dc10ae2db681655117d34ee8010a06a469cc07c9078bfaf512b +size 7815820 diff --git a/public/models/gant_r/model.gltf b/public/models/gant_r/model.gltf new file mode 100644 index 0000000..3618596 --- /dev/null +++ b/public/models/gant_r/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c82eb9596e0829193b8c860670ff9cad959dfcced8d17183c2347346870d267b +size 31499 diff --git a/public/models/gant_r_pad/galet_basecolor.png b/public/models/gant_r_pad/galet_basecolor.png new file mode 100644 index 0000000..33f9861 --- /dev/null +++ b/public/models/gant_r_pad/galet_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8230f08a07b4a5cad22e68c563192a664741db2eaa9c6ac481b7b6e2c1f00a5 +size 492994 diff --git a/public/models/gant_r_pad/galet_normal.png b/public/models/gant_r_pad/galet_normal.png new file mode 100644 index 0000000..d8f29af --- /dev/null +++ b/public/models/gant_r_pad/galet_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:044bed9c465d55e5be571862e46aaf5f48172a8572665286068bd005847a5f17 +size 2613811 diff --git a/public/models/gant_r_pad/galet_occlusionroughnessmetallic.png b/public/models/gant_r_pad/galet_occlusionroughnessmetallic.png new file mode 100644 index 0000000..f262ab4 --- /dev/null +++ b/public/models/gant_r_pad/galet_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:137ce434f528bd56a3a5eabc339173c5e8fb4f9306fb0ca624d996ee90b8d1f8 +size 16902 diff --git a/public/models/gant_r_pad/gant_basecolor.png b/public/models/gant_r_pad/gant_basecolor.png new file mode 100644 index 0000000..51d12c4 --- /dev/null +++ b/public/models/gant_r_pad/gant_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99011cfdde0fed73e7d8927bce22f38a4f33decf00ccf6eaaa2aadc5ef940b4f +size 330807 diff --git a/public/models/gant_r_pad/gant_normal.png b/public/models/gant_r_pad/gant_normal.png new file mode 100644 index 0000000..95e5e61 --- /dev/null +++ b/public/models/gant_r_pad/gant_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82a45f350f230fafcd51c9e19db9f7cab4b86f08cb9b46ce9529517e17c53046 +size 2937255 diff --git a/public/models/gant_r_pad/gant_occlusionroughnessmetallic.png b/public/models/gant_r_pad/gant_occlusionroughnessmetallic.png new file mode 100644 index 0000000..f262ab4 --- /dev/null +++ b/public/models/gant_r_pad/gant_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:137ce434f528bd56a3a5eabc339173c5e8fb4f9306fb0ca624d996ee90b8d1f8 +size 16902 diff --git a/public/models/gant_r_pad/gant_r_pad.bin b/public/models/gant_r_pad/gant_r_pad.bin new file mode 100644 index 0000000..8d9ce3d --- /dev/null +++ b/public/models/gant_r_pad/gant_r_pad.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9adc834223c132af1ebd48058e507dfbc7abff8f15c1b887c2b1ee6c07604869 +size 554472 diff --git a/public/models/gant_r_pad/model.gltf b/public/models/gant_r_pad/model.gltf new file mode 100644 index 0000000..ade1881 --- /dev/null +++ b/public/models/gant_r_pad/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4e97e95e1f38f6d76cc49e28e9a10d4d5d0e703511018e5933548faca3cd67b +size 6900 diff --git a/public/models/gerant/defaultmaterial_base_color.png b/public/models/gerant/defaultmaterial_base_color.png new file mode 100644 index 0000000..d87452f --- /dev/null +++ b/public/models/gerant/defaultmaterial_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc495d4822681d079139c794303c93aa6465f10b9cdf6e958f5b89c5932d2eb3 +size 834657 diff --git a/public/models/gerant/defaultmaterial_basecolor.png b/public/models/gerant/defaultmaterial_basecolor.png new file mode 100644 index 0000000..ecb4e45 --- /dev/null +++ b/public/models/gerant/defaultmaterial_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:531aaf4d7871c30e05cdc45a8bb665dcf1a101cfb4ff5c182af3384c18c03e21 +size 839128 diff --git a/public/models/gerant/defaultmaterial_height.png b/public/models/gerant/defaultmaterial_height.png new file mode 100644 index 0000000..30158eb --- /dev/null +++ b/public/models/gerant/defaultmaterial_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1cc29dfc5b14bbf16af1fd36e461e0b5bb3936fdddaf2807b56b4c2e9f71ee8 +size 12819 diff --git a/public/models/gerant/defaultmaterial_metallic.png b/public/models/gerant/defaultmaterial_metallic.png new file mode 100644 index 0000000..42f1206 --- /dev/null +++ b/public/models/gerant/defaultmaterial_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b0d3feb024b0cea2d5d8d2aee54bc18eaed203e166a9f53b5455b66e3e0f9d3 +size 238091 diff --git a/public/models/gerant/defaultmaterial_normal.png b/public/models/gerant/defaultmaterial_normal.png new file mode 100644 index 0000000..075e61b --- /dev/null +++ b/public/models/gerant/defaultmaterial_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8509d8874bbab587777f149efbde35c88201c56b6b452f17a793d4afce8320e6 +size 3446765 diff --git a/public/models/gerant/defaultmaterial_normal_opengl.png b/public/models/gerant/defaultmaterial_normal_opengl.png new file mode 100644 index 0000000..6c17dbb --- /dev/null +++ b/public/models/gerant/defaultmaterial_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f0b5352703be3e02c5ae8ed00462168052c58b2dae8255829fcd960c8accaf9 +size 7201168 diff --git a/public/models/gerant/defaultmaterial_occlusionroughnessmetallic.png b/public/models/gerant/defaultmaterial_occlusionroughnessmetallic.png new file mode 100644 index 0000000..d0b5bd5 --- /dev/null +++ b/public/models/gerant/defaultmaterial_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:097dbffd0cbc2290359072e275c5d9979638bb8907bd12feeed17498ba5a0e42 +size 912914 diff --git a/public/models/gerant/defaultmaterial_roughness.png b/public/models/gerant/defaultmaterial_roughness.png new file mode 100644 index 0000000..f27a80c --- /dev/null +++ b/public/models/gerant/defaultmaterial_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49db8c795dd162186490fd639c600c85cfdbaf6dac2a9b9b2f7a3eb3262098e2 +size 571947 diff --git a/public/models/gerant/gerant.bin b/public/models/gerant/gerant.bin new file mode 100644 index 0000000..4b0a711 --- /dev/null +++ b/public/models/gerant/gerant.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d91d4e3ae49ba75ceb3923f577566bf4b78d0ed4bfee96839945a52d27ec770 +size 1702368 diff --git a/public/models/gerant/model.gltf b/public/models/gerant/model.gltf new file mode 100644 index 0000000..bba9e92 --- /dev/null +++ b/public/models/gerant/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:143af47426c600085097223fb27f7ac8f3ab2e23071b90cda6840f5eabef7a62 +size 3141 diff --git a/public/models/immeuble1/fenetre_baseColor.png b/public/models/immeuble1/fenetre_baseColor.png new file mode 100644 index 0000000..e9ff418 --- /dev/null +++ b/public/models/immeuble1/fenetre_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0333ccfd6a8b93765faa75b9e4de74219c2c9aea6322d2739179d46a4a428599 +size 438684 diff --git a/public/models/immeuble1/fenetre_normal.png b/public/models/immeuble1/fenetre_normal.png new file mode 100644 index 0000000..2162385 --- /dev/null +++ b/public/models/immeuble1/fenetre_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec655e8917d20496f4b817e683644be638bd6ce8dda607257a7873edb974d82f +size 2152600 diff --git a/public/models/immeuble1/fenetre_occlusionRoughnessMetallic.png b/public/models/immeuble1/fenetre_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..b70c3be --- /dev/null +++ b/public/models/immeuble1/fenetre_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6171b80ff86434c9aca5dd7432bd02124b7a794d747084512dca66ed33b1fbf4 +size 156923 diff --git a/public/models/immeuble1/immeuble1.bin b/public/models/immeuble1/immeuble1.bin new file mode 100644 index 0000000..818eace --- /dev/null +++ b/public/models/immeuble1/immeuble1.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e047ba0f4242c619be71a33482914a9baa747fb53bf630facddb49d15a2bd214 +size 280656 diff --git a/public/models/immeuble1/maison_baseColor.png b/public/models/immeuble1/maison_baseColor.png new file mode 100644 index 0000000..1129d97 --- /dev/null +++ b/public/models/immeuble1/maison_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47a4bb98cf55e631dde6454338a21a18e8001427b605b0ef1456a438aafdfed1 +size 1342311 diff --git a/public/models/immeuble1/maison_normal.png b/public/models/immeuble1/maison_normal.png new file mode 100644 index 0000000..43f7096 --- /dev/null +++ b/public/models/immeuble1/maison_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d6666c7bc78a0c0d44239e831feba5c89c0b59b6a2ecd91029e15a733718267 +size 3387063 diff --git a/public/models/immeuble1/maison_occlusionRoughnessMetallic.png b/public/models/immeuble1/maison_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..d3f60a9 --- /dev/null +++ b/public/models/immeuble1/maison_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c00c1afbaf784c4ee1fecaaa1d0ce70179151786ff4076e16ad6f85076b786c +size 847795 diff --git a/public/models/immeuble1/model.gltf b/public/models/immeuble1/model.gltf new file mode 100644 index 0000000..61bdca2 --- /dev/null +++ b/public/models/immeuble1/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12038659fd8f1f307b3944dd85bb49b7dbae7d4d743ac2691cf8799ce28d2289 +size 21200262 diff --git a/public/models/immeuble1/panneau_baseColor.png b/public/models/immeuble1/panneau_baseColor.png new file mode 100644 index 0000000..612d3f2 --- /dev/null +++ b/public/models/immeuble1/panneau_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5395a39476a7b70e41ddbebf59ee9708ffe47ca394e335c0eef6c8d75593a545 +size 1694296 diff --git a/public/models/immeuble1/panneau_normal.png b/public/models/immeuble1/panneau_normal.png new file mode 100644 index 0000000..f84126e --- /dev/null +++ b/public/models/immeuble1/panneau_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f56909948a9d0a6a9cb54f0a6954a7261d3a22278a6f1898dd043c6bd8f8e076 +size 2114513 diff --git a/public/models/immeuble1/panneau_occlusionRoughnessMetallic.png b/public/models/immeuble1/panneau_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..7f9d8a3 --- /dev/null +++ b/public/models/immeuble1/panneau_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:967588a06b49fd51fb96e13bd87371586de467a2ca4d68e83f59e58abc51f928 +size 16902 diff --git a/public/models/immeuble1/porte_baseColor.png b/public/models/immeuble1/porte_baseColor.png new file mode 100644 index 0000000..10a3e61 --- /dev/null +++ b/public/models/immeuble1/porte_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fa0656f9c2b5e5d9ebf00b4fbd53367ae94e394529599dda2b3fc85bfa2dcc8 +size 18733 diff --git a/public/models/immeuble1/porte_normal.png b/public/models/immeuble1/porte_normal.png new file mode 100644 index 0000000..7d38fd6 --- /dev/null +++ b/public/models/immeuble1/porte_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87ad68ab55f27a0cf8e33899831521af30fbc323414df48f548532830328fd93 +size 1863657 diff --git a/public/models/immeuble1/porte_occlusionRoughnessMetallic.png b/public/models/immeuble1/porte_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..3a0a7ca --- /dev/null +++ b/public/models/immeuble1/porte_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8e5ccad40ed15664a16bcb6b3668a306f51d9fec1a580797a5afd9f8f0872a2 +size 20423 diff --git a/public/models/lafabrik/anneaux_base_color.png b/public/models/lafabrik/anneaux_base_color.png new file mode 100644 index 0000000..2fbdd54 --- /dev/null +++ b/public/models/lafabrik/anneaux_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53f37341d07663c2a9572d29dcade18e8b91ba32656578b359214d402ac47df8 +size 29467 diff --git a/public/models/lafabrik/anneaux_basecolor.png b/public/models/lafabrik/anneaux_basecolor.png new file mode 100644 index 0000000..cc33c8b --- /dev/null +++ b/public/models/lafabrik/anneaux_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f73e6bfe28e93608d9a6edcd105448cb34d96515b2a918a4e0fece15b814468 +size 532789 diff --git a/public/models/lafabrik/anneaux_height.png b/public/models/lafabrik/anneaux_height.png new file mode 100644 index 0000000..9daf8da --- /dev/null +++ b/public/models/lafabrik/anneaux_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a65cfe0c3a454ccdc9a92c4a1f3dd791529aba0fe120bb1c1ccda4357dc973be +size 12816 diff --git a/public/models/lafabrik/anneaux_metallic.png b/public/models/lafabrik/anneaux_metallic.png new file mode 100644 index 0000000..9384d55 --- /dev/null +++ b/public/models/lafabrik/anneaux_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ffded4f64bceb29f858e3f9462813f9d774174aca87ff7c49277bb37bf5e923 +size 12819 diff --git a/public/models/lafabrik/anneaux_mixed_ao.png b/public/models/lafabrik/anneaux_mixed_ao.png new file mode 100644 index 0000000..b30ee0f --- /dev/null +++ b/public/models/lafabrik/anneaux_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dee7c471c5cc3dafe941065326003df584b1256c5dfbd646a4f1a83faa91426d +size 520611 diff --git a/public/models/lafabrik/anneaux_normal.png b/public/models/lafabrik/anneaux_normal.png new file mode 100644 index 0000000..1e89761 --- /dev/null +++ b/public/models/lafabrik/anneaux_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d457c6ff4fbebf0c5b45480891a16a224140663026ad4a166ef22934af76dff6 +size 2829035 diff --git a/public/models/lafabrik/anneaux_normal_opengl.png b/public/models/lafabrik/anneaux_normal_opengl.png new file mode 100644 index 0000000..648f914 --- /dev/null +++ b/public/models/lafabrik/anneaux_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2394e539f3a4a3f8e52eafe30cb54ba5147b262d473ca1c66b3a71be99f4908 +size 475520 diff --git a/public/models/lafabrik/anneaux_occlusionroughnessmetallic.png b/public/models/lafabrik/anneaux_occlusionroughnessmetallic.png new file mode 100644 index 0000000..244620e --- /dev/null +++ b/public/models/lafabrik/anneaux_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90971d6a4d158daa5f19634d7c0b1e30af7891210415727de2828f55537a18c7 +size 1105974 diff --git a/public/models/lafabrik/anneaux_roughness.png b/public/models/lafabrik/anneaux_roughness.png new file mode 100644 index 0000000..f3dd692 --- /dev/null +++ b/public/models/lafabrik/anneaux_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:428157d1d553d359d25696ec5cdebb0a191dccc08b513f6f8a0a08edccadd8bf +size 12819 diff --git a/public/models/lafabrik/bat_base_color.png b/public/models/lafabrik/bat_base_color.png new file mode 100644 index 0000000..7c6dd4f --- /dev/null +++ b/public/models/lafabrik/bat_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fbb61332773aff8ee65fab755abce883996654c11f4ce40e6ce66d8e2a937de +size 1728398 diff --git a/public/models/lafabrik/bat_basecolor.png b/public/models/lafabrik/bat_basecolor.png new file mode 100644 index 0000000..6a780f4 --- /dev/null +++ b/public/models/lafabrik/bat_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be2e62b2e7a3c9a17c4898f6f9d29ca702196d500571635ae031cb2e377ca23a +size 1210857 diff --git a/public/models/lafabrik/bat_height.png b/public/models/lafabrik/bat_height.png new file mode 100644 index 0000000..9daf8da --- /dev/null +++ b/public/models/lafabrik/bat_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a65cfe0c3a454ccdc9a92c4a1f3dd791529aba0fe120bb1c1ccda4357dc973be +size 12816 diff --git a/public/models/lafabrik/bat_metallic.png b/public/models/lafabrik/bat_metallic.png new file mode 100644 index 0000000..353dceb --- /dev/null +++ b/public/models/lafabrik/bat_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:537c59bf6206c8a29d89a0f0433ae285254a2b7deac15ba730f547b6d678ca33 +size 8243 diff --git a/public/models/lafabrik/bat_mixed_ao.png b/public/models/lafabrik/bat_mixed_ao.png new file mode 100644 index 0000000..7df491c --- /dev/null +++ b/public/models/lafabrik/bat_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f724b7d24a420bdd99df5ec6baebacaa8442b1e84db034cb3390425b58deca9c +size 755091 diff --git a/public/models/lafabrik/bat_normal.png b/public/models/lafabrik/bat_normal.png new file mode 100644 index 0000000..52e5c50 --- /dev/null +++ b/public/models/lafabrik/bat_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a4dbe8b4a0b23bf5b5e44bc88e86ee5a92e157a39670c690ebe7f548a7a82ad +size 2403625 diff --git a/public/models/lafabrik/bat_normal_opengl.png b/public/models/lafabrik/bat_normal_opengl.png new file mode 100644 index 0000000..2c74cb7 --- /dev/null +++ b/public/models/lafabrik/bat_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:457844aaf45faafa9dd538839648e1bd88c4e9b0c544ad7d112b857dced44952 +size 733512 diff --git a/public/models/lafabrik/bat_occlusionroughnessmetallic.png b/public/models/lafabrik/bat_occlusionroughnessmetallic.png new file mode 100644 index 0000000..f3de26a --- /dev/null +++ b/public/models/lafabrik/bat_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:629b883b0c7f3d1d7da370d3204dbe3fe63616c42ea4f8fe2794b9660a9f80bb +size 1277896 diff --git a/public/models/lafabrik/bat_roughness.png b/public/models/lafabrik/bat_roughness.png new file mode 100644 index 0000000..052f3b1 --- /dev/null +++ b/public/models/lafabrik/bat_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4b2972c7d79f72f8cc484a0b45075325a2b61cc09ea59e4867f01b749a57d66 +size 172975 diff --git a/public/models/lafabrik/comptoir_base_color.png b/public/models/lafabrik/comptoir_base_color.png new file mode 100644 index 0000000..d86eeef --- /dev/null +++ b/public/models/lafabrik/comptoir_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1aa545d402e5f8fe8da5016a3c56f07bf5bc028923c5267fbd81b19b4cfdf278 +size 2863835 diff --git a/public/models/lafabrik/comptoir_basecolor.png b/public/models/lafabrik/comptoir_basecolor.png new file mode 100644 index 0000000..2c9c9da --- /dev/null +++ b/public/models/lafabrik/comptoir_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce482f3dedea2e359f7ad7ca342405a2bc39fa06e4ed5a40153421974810219e +size 2163973 diff --git a/public/models/lafabrik/comptoir_height.png b/public/models/lafabrik/comptoir_height.png new file mode 100644 index 0000000..9daf8da --- /dev/null +++ b/public/models/lafabrik/comptoir_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a65cfe0c3a454ccdc9a92c4a1f3dd791529aba0fe120bb1c1ccda4357dc973be +size 12816 diff --git a/public/models/lafabrik/comptoir_metallic.png b/public/models/lafabrik/comptoir_metallic.png new file mode 100644 index 0000000..353dceb --- /dev/null +++ b/public/models/lafabrik/comptoir_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:537c59bf6206c8a29d89a0f0433ae285254a2b7deac15ba730f547b6d678ca33 +size 8243 diff --git a/public/models/lafabrik/comptoir_mixed_ao.png b/public/models/lafabrik/comptoir_mixed_ao.png new file mode 100644 index 0000000..6d45fb8 --- /dev/null +++ b/public/models/lafabrik/comptoir_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2327b567772ca587796a0cfe5ba7627d3fd159b82509b340a80f027bf07885b +size 420863 diff --git a/public/models/lafabrik/comptoir_normal.png b/public/models/lafabrik/comptoir_normal.png new file mode 100644 index 0000000..456d843 --- /dev/null +++ b/public/models/lafabrik/comptoir_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfe1fe00767d0e31c086eed0e33e08449c5d5d598c52399a8dcc96696e350b03 +size 2001159 diff --git a/public/models/lafabrik/comptoir_normal_opengl.png b/public/models/lafabrik/comptoir_normal_opengl.png new file mode 100644 index 0000000..52f3c77 --- /dev/null +++ b/public/models/lafabrik/comptoir_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad8c5ca698fb98318f3f957d6e1d930fedf1a0f9c3167b3955a42ea87f6c6f0c +size 225093 diff --git a/public/models/lafabrik/comptoir_occlusionroughnessmetallic.png b/public/models/lafabrik/comptoir_occlusionroughnessmetallic.png new file mode 100644 index 0000000..cde3d83 --- /dev/null +++ b/public/models/lafabrik/comptoir_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d88b06666a337587b686fb31033f8a0729850418e2d01255f08d43a69aa8d9a1 +size 737294 diff --git a/public/models/lafabrik/comptoir_roughness.png b/public/models/lafabrik/comptoir_roughness.png new file mode 100644 index 0000000..ebf3115 --- /dev/null +++ b/public/models/lafabrik/comptoir_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85ddd3777aa48eee4ee54457c660ef337198918472ef9056dd2fa1622766ee7b +size 26345 diff --git a/public/models/lafabrik/dashboard_base_color.png b/public/models/lafabrik/dashboard_base_color.png new file mode 100644 index 0000000..794b9c5 --- /dev/null +++ b/public/models/lafabrik/dashboard_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd15c2193adc9bda3caa5a60a8dce7d8ad472abb77f9fda16714ec5ab57b2db8 +size 29467 diff --git a/public/models/lafabrik/dashboard_basecolor.png b/public/models/lafabrik/dashboard_basecolor.png new file mode 100644 index 0000000..e221f5b --- /dev/null +++ b/public/models/lafabrik/dashboard_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f68b75f9433beb193c5d3fc4e3ea9ab61f43c061cf3dd109120d4a2ab7e01012 +size 43662 diff --git a/public/models/lafabrik/dashboard_height.png b/public/models/lafabrik/dashboard_height.png new file mode 100644 index 0000000..9daf8da --- /dev/null +++ b/public/models/lafabrik/dashboard_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a65cfe0c3a454ccdc9a92c4a1f3dd791529aba0fe120bb1c1ccda4357dc973be +size 12816 diff --git a/public/models/lafabrik/dashboard_metallic.png b/public/models/lafabrik/dashboard_metallic.png new file mode 100644 index 0000000..353dceb --- /dev/null +++ b/public/models/lafabrik/dashboard_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:537c59bf6206c8a29d89a0f0433ae285254a2b7deac15ba730f547b6d678ca33 +size 8243 diff --git a/public/models/lafabrik/dashboard_mixed_ao.png b/public/models/lafabrik/dashboard_mixed_ao.png new file mode 100644 index 0000000..2d868b8 --- /dev/null +++ b/public/models/lafabrik/dashboard_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:295f092b90ee1eb726722d342785605e750030fc7bf2f655b2ef8adc94d6af11 +size 748061 diff --git a/public/models/lafabrik/dashboard_normal.png b/public/models/lafabrik/dashboard_normal.png new file mode 100644 index 0000000..dedbbd4 --- /dev/null +++ b/public/models/lafabrik/dashboard_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df545a97e90b13a35c85405a55d70c9afa828060bdc1366272eef1bcc9ef4fac +size 1856014 diff --git a/public/models/lafabrik/dashboard_normal_opengl.png b/public/models/lafabrik/dashboard_normal_opengl.png new file mode 100644 index 0000000..424a43a --- /dev/null +++ b/public/models/lafabrik/dashboard_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f5d50f352eec25661022033cb205d3e14c179531a0d59d58bc2e28e0fad7a20 +size 29466 diff --git a/public/models/lafabrik/dashboard_occlusionroughnessmetallic.png b/public/models/lafabrik/dashboard_occlusionroughnessmetallic.png new file mode 100644 index 0000000..b61ad3f --- /dev/null +++ b/public/models/lafabrik/dashboard_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbac8f9c13fbf2ab21c256e365448220f7ca678df5d6287d7151842b8f22a2c5 +size 894264 diff --git a/public/models/lafabrik/dashboard_roughness.png b/public/models/lafabrik/dashboard_roughness.png new file mode 100644 index 0000000..f3dd692 --- /dev/null +++ b/public/models/lafabrik/dashboard_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:428157d1d553d359d25696ec5cdebb0a191dccc08b513f6f8a0a08edccadd8bf +size 12819 diff --git a/public/models/lafabrik/fenetre_0_base_color.png b/public/models/lafabrik/fenetre_0_base_color.png new file mode 100644 index 0000000..8947b77 --- /dev/null +++ b/public/models/lafabrik/fenetre_0_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35a2dc58024f7c002d28477ea808cac85f13c3434363e15091f0216b025eb0d5 +size 478515 diff --git a/public/models/lafabrik/fenetre_0_basecolor.png b/public/models/lafabrik/fenetre_0_basecolor.png new file mode 100644 index 0000000..c8c4111 --- /dev/null +++ b/public/models/lafabrik/fenetre_0_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c36f58ae9eaf0dbc20abec9aa4d1e09e7fb5629e312041deaad1455bd73f59bd +size 356476 diff --git a/public/models/lafabrik/fenetre_0_height.png b/public/models/lafabrik/fenetre_0_height.png new file mode 100644 index 0000000..9daf8da --- /dev/null +++ b/public/models/lafabrik/fenetre_0_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a65cfe0c3a454ccdc9a92c4a1f3dd791529aba0fe120bb1c1ccda4357dc973be +size 12816 diff --git a/public/models/lafabrik/fenetre_0_metallic.png b/public/models/lafabrik/fenetre_0_metallic.png new file mode 100644 index 0000000..353dceb --- /dev/null +++ b/public/models/lafabrik/fenetre_0_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:537c59bf6206c8a29d89a0f0433ae285254a2b7deac15ba730f547b6d678ca33 +size 8243 diff --git a/public/models/lafabrik/fenetre_0_mixed_ao.png b/public/models/lafabrik/fenetre_0_mixed_ao.png new file mode 100644 index 0000000..b75fca3 --- /dev/null +++ b/public/models/lafabrik/fenetre_0_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8af3dd1a5235c2fdc972065fa86d0308395896b08433eab24732a2229c08fcf +size 305028 diff --git a/public/models/lafabrik/fenetre_0_normal.png b/public/models/lafabrik/fenetre_0_normal.png new file mode 100644 index 0000000..ab81f52 --- /dev/null +++ b/public/models/lafabrik/fenetre_0_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f025eaf205443cf90ee0cd019519e924751b54a207ac453ff482b88d2e50c09f +size 2180272 diff --git a/public/models/lafabrik/fenetre_0_normal_opengl.png b/public/models/lafabrik/fenetre_0_normal_opengl.png new file mode 100644 index 0000000..d360120 --- /dev/null +++ b/public/models/lafabrik/fenetre_0_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34f14ee3c574c084326265ba02e506905c6bfff647dcfc9661ee9426f26cacff +size 31073 diff --git a/public/models/lafabrik/fenetre_0_occlusionroughnessmetallic.png b/public/models/lafabrik/fenetre_0_occlusionroughnessmetallic.png new file mode 100644 index 0000000..11b01bb --- /dev/null +++ b/public/models/lafabrik/fenetre_0_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:815a8f6fe3077d32227e0be86b973fb83c0ae83cf4a11fec8a0638a171638bc6 +size 455042 diff --git a/public/models/lafabrik/fenetre_0_roughness.png b/public/models/lafabrik/fenetre_0_roughness.png new file mode 100644 index 0000000..f3dd692 --- /dev/null +++ b/public/models/lafabrik/fenetre_0_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:428157d1d553d359d25696ec5cdebb0a191dccc08b513f6f8a0a08edccadd8bf +size 12819 diff --git a/public/models/lafabrik/model.bin b/public/models/lafabrik/model.bin new file mode 100644 index 0000000..3383765 --- /dev/null +++ b/public/models/lafabrik/model.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e4b582136b91210549880ba151a0a5c58ca35ae99a23eca4d9bbfd4277c6c11 +size 1240608 diff --git a/public/models/lafabrik/model.gltf b/public/models/lafabrik/model.gltf index 13639cd..cb63296 100644 --- a/public/models/lafabrik/model.gltf +++ b/public/models/lafabrik/model.gltf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d623439755187553ac394395a4e8734e4ea7ca950c94b50649d2b00e6461387e -size 80888 +oid sha256:615bf4ed9c84530bdec465a6bf97262f6e1fc55dd5846bd8c3e698b82309fc68 +size 124737 diff --git a/public/models/lafabrik/panneau_base_color.png b/public/models/lafabrik/panneau_base_color.png new file mode 100644 index 0000000..729e1d9 --- /dev/null +++ b/public/models/lafabrik/panneau_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd4a33fcd267d793fe531bf64874d42c9fc4d1f5e59e7b899e5e1df294b65d1b +size 1635967 diff --git a/public/models/lafabrik/panneau_basecolor.png b/public/models/lafabrik/panneau_basecolor.png new file mode 100644 index 0000000..1e0a604 --- /dev/null +++ b/public/models/lafabrik/panneau_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7b9f0e6e1967bde9e127ccd047459bd9c228c2872aa6999d745e3bbbbc97a16 +size 751023 diff --git a/public/models/lafabrik/panneau_height.png b/public/models/lafabrik/panneau_height.png new file mode 100644 index 0000000..9daf8da --- /dev/null +++ b/public/models/lafabrik/panneau_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a65cfe0c3a454ccdc9a92c4a1f3dd791529aba0fe120bb1c1ccda4357dc973be +size 12816 diff --git a/public/models/lafabrik/panneau_metallic.png b/public/models/lafabrik/panneau_metallic.png new file mode 100644 index 0000000..353dceb --- /dev/null +++ b/public/models/lafabrik/panneau_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:537c59bf6206c8a29d89a0f0433ae285254a2b7deac15ba730f547b6d678ca33 +size 8243 diff --git a/public/models/lafabrik/panneau_mixed_ao.png b/public/models/lafabrik/panneau_mixed_ao.png new file mode 100644 index 0000000..510e8d9 --- /dev/null +++ b/public/models/lafabrik/panneau_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa0b8c95e9f6b230ed6eac3c20ca040bfda21e57e16fe955d038cc284f5eba28 +size 948009 diff --git a/public/models/lafabrik/panneau_normal.png b/public/models/lafabrik/panneau_normal.png new file mode 100644 index 0000000..8d99132 --- /dev/null +++ b/public/models/lafabrik/panneau_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efe381ab55bf16f6eb1210ec73a34218083379673f7f1a8dd208e68bf1de4291 +size 2817900 diff --git a/public/models/lafabrik/panneau_normal_opengl.png b/public/models/lafabrik/panneau_normal_opengl.png new file mode 100644 index 0000000..bcb470c --- /dev/null +++ b/public/models/lafabrik/panneau_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac4096ec4e956ddf3d1f998d43ca670e1025d5ddf0651e082d9cd7b48dc53626 +size 735202 diff --git a/public/models/lafabrik/panneau_occlusionroughnessmetallic.png b/public/models/lafabrik/panneau_occlusionroughnessmetallic.png new file mode 100644 index 0000000..c6b1bb3 --- /dev/null +++ b/public/models/lafabrik/panneau_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34067efc057afefbc87666b5ee55d436fdedbb29e33ec70e200cb0c39a5a4e7a +size 1203760 diff --git a/public/models/lafabrik/panneau_roughness.png b/public/models/lafabrik/panneau_roughness.png new file mode 100644 index 0000000..b405879 --- /dev/null +++ b/public/models/lafabrik/panneau_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51c60554690fec241be70aa9b2b56592bdaa3be58d9f9bacd95785e3571feeab +size 22482 diff --git a/public/models/lafabrik/plan_de_travail_base_color.png b/public/models/lafabrik/plan_de_travail_base_color.png new file mode 100644 index 0000000..d2f1ee6 --- /dev/null +++ b/public/models/lafabrik/plan_de_travail_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:764be1db58dd52c9cd87359b631aec6bafba7a858d075a67796c38482e5b4bf8 +size 1530465 diff --git a/public/models/lafabrik/plan_de_travail_basecolor.png b/public/models/lafabrik/plan_de_travail_basecolor.png new file mode 100644 index 0000000..8f42cc5 --- /dev/null +++ b/public/models/lafabrik/plan_de_travail_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6655eeaa9958faac0452d63a8607ba8a7bd942caddf5c5a835ddc27ece9a2f9 +size 904256 diff --git a/public/models/lafabrik/plan_de_travail_height.png b/public/models/lafabrik/plan_de_travail_height.png new file mode 100644 index 0000000..9daf8da --- /dev/null +++ b/public/models/lafabrik/plan_de_travail_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a65cfe0c3a454ccdc9a92c4a1f3dd791529aba0fe120bb1c1ccda4357dc973be +size 12816 diff --git a/public/models/lafabrik/plan_de_travail_metallic.png b/public/models/lafabrik/plan_de_travail_metallic.png new file mode 100644 index 0000000..353dceb --- /dev/null +++ b/public/models/lafabrik/plan_de_travail_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:537c59bf6206c8a29d89a0f0433ae285254a2b7deac15ba730f547b6d678ca33 +size 8243 diff --git a/public/models/lafabrik/plan_de_travail_mixed_ao.png b/public/models/lafabrik/plan_de_travail_mixed_ao.png new file mode 100644 index 0000000..c1b9686 --- /dev/null +++ b/public/models/lafabrik/plan_de_travail_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06d26d72c7f4fe867ea426dcf65f8ca0217118c743a0b6c435963f9bb6f35fe0 +size 532506 diff --git a/public/models/lafabrik/plan_de_travail_normal.png b/public/models/lafabrik/plan_de_travail_normal.png new file mode 100644 index 0000000..2ac2fd4 --- /dev/null +++ b/public/models/lafabrik/plan_de_travail_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b311b70ab688e432cff1832db9aeb822e0fbba3d82b5b3edbc30ccff9f9571ef +size 173105 diff --git a/public/models/lafabrik/plan_de_travail_normal_opengl.png b/public/models/lafabrik/plan_de_travail_normal_opengl.png new file mode 100644 index 0000000..7345aaf --- /dev/null +++ b/public/models/lafabrik/plan_de_travail_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34cfde7361c3f27819da5907ba475668e313aa01b177cb1a12832b86c6ae9457 +size 166055 diff --git a/public/models/lafabrik/plan_de_travail_occlusionroughnessmetallic.png b/public/models/lafabrik/plan_de_travail_occlusionroughnessmetallic.png new file mode 100644 index 0000000..fcf18d8 --- /dev/null +++ b/public/models/lafabrik/plan_de_travail_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f3e540d37d816e12dfad945e9482dc146d1cbc18efc3788e0d7eaacc9b51e3e +size 870633 diff --git a/public/models/lafabrik/plan_de_travail_roughness.png b/public/models/lafabrik/plan_de_travail_roughness.png new file mode 100644 index 0000000..0a510ff --- /dev/null +++ b/public/models/lafabrik/plan_de_travail_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39171a7a15cb7032fa4a9e15694f7f70105c5d0d458b23b642a5e9079ebfb768 +size 31254 diff --git a/public/models/lafabrik/porte_base_color.png b/public/models/lafabrik/porte_base_color.png new file mode 100644 index 0000000..82308db --- /dev/null +++ b/public/models/lafabrik/porte_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21a8b51dd41807b48a4ba12e963552dee18cf61263a1b84ba1e89493e62bdae9 +size 711076 diff --git a/public/models/lafabrik/porte_basecolor.png b/public/models/lafabrik/porte_basecolor.png new file mode 100644 index 0000000..6ef305f --- /dev/null +++ b/public/models/lafabrik/porte_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8113d63fde96700d5cfc937ea2a84b0cadba745da432bbddec099cae3ce3fe9 +size 601365 diff --git a/public/models/lafabrik/porte_height.png b/public/models/lafabrik/porte_height.png new file mode 100644 index 0000000..9daf8da --- /dev/null +++ b/public/models/lafabrik/porte_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a65cfe0c3a454ccdc9a92c4a1f3dd791529aba0fe120bb1c1ccda4357dc973be +size 12816 diff --git a/public/models/lafabrik/porte_metallic.png b/public/models/lafabrik/porte_metallic.png new file mode 100644 index 0000000..353dceb --- /dev/null +++ b/public/models/lafabrik/porte_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:537c59bf6206c8a29d89a0f0433ae285254a2b7deac15ba730f547b6d678ca33 +size 8243 diff --git a/public/models/lafabrik/porte_mixed_ao.png b/public/models/lafabrik/porte_mixed_ao.png new file mode 100644 index 0000000..40a663b --- /dev/null +++ b/public/models/lafabrik/porte_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2695f10aa8b88e30a2a9b29a9a3bdfa77700368e493733c4372729b0d57ab27c +size 539406 diff --git a/public/models/lafabrik/porte_normal.png b/public/models/lafabrik/porte_normal.png new file mode 100644 index 0000000..259ff9d --- /dev/null +++ b/public/models/lafabrik/porte_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46173406135afe7ffa4318196893f8cfe1b0aa7b292e64ed93cb6302392b3dd2 +size 2480991 diff --git a/public/models/lafabrik/porte_normal_opengl.png b/public/models/lafabrik/porte_normal_opengl.png new file mode 100644 index 0000000..62acdfd --- /dev/null +++ b/public/models/lafabrik/porte_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d08801f9d46b86af871fd890afa8cda49f69abc5588e961badd3ad0d843f83b2 +size 260513 diff --git a/public/models/lafabrik/porte_occlusionroughnessmetallic.png b/public/models/lafabrik/porte_occlusionroughnessmetallic.png new file mode 100644 index 0000000..a6a1fa6 --- /dev/null +++ b/public/models/lafabrik/porte_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f24311e5c1a3d9caacc34bebd628258fed45c279ac9760b13baa38b15356a7a +size 735539 diff --git a/public/models/lafabrik/porte_roughness.png b/public/models/lafabrik/porte_roughness.png new file mode 100644 index 0000000..ae229dd --- /dev/null +++ b/public/models/lafabrik/porte_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94117088d96bd2a2aa2c149269f9672b8b2f0c1a4580df9b9dfdb7128c654c9b +size 70513 diff --git a/public/models/lafabrik/porte_stock_base_color.png b/public/models/lafabrik/porte_stock_base_color.png new file mode 100644 index 0000000..6af3f7e --- /dev/null +++ b/public/models/lafabrik/porte_stock_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6af1af828108a27cff317cc91747c297acd6e44237bbc691c58c11132272fdcb +size 128309 diff --git a/public/models/lafabrik/porte_stock_basecolor.png b/public/models/lafabrik/porte_stock_basecolor.png new file mode 100644 index 0000000..50459d6 --- /dev/null +++ b/public/models/lafabrik/porte_stock_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78405d3af140658de85de17767f336f746cc98961694d7ce918c5379677bbe82 +size 64589 diff --git a/public/models/lafabrik/porte_stock_height.png b/public/models/lafabrik/porte_stock_height.png new file mode 100644 index 0000000..9daf8da --- /dev/null +++ b/public/models/lafabrik/porte_stock_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a65cfe0c3a454ccdc9a92c4a1f3dd791529aba0fe120bb1c1ccda4357dc973be +size 12816 diff --git a/public/models/lafabrik/porte_stock_metallic.png b/public/models/lafabrik/porte_stock_metallic.png new file mode 100644 index 0000000..d3378cf --- /dev/null +++ b/public/models/lafabrik/porte_stock_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3de096ecaa8b16d76f5d3feb75ca111738ae23e3ff4f5a1de384cc5f4a8dbc27 +size 41077 diff --git a/public/models/lafabrik/porte_stock_mixed_ao.png b/public/models/lafabrik/porte_stock_mixed_ao.png new file mode 100644 index 0000000..e9efda2 --- /dev/null +++ b/public/models/lafabrik/porte_stock_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00d3c917b768b4cd6bcdf6b1987436a209ad8a0fcc124594880fb9b47559d14e +size 9471 diff --git a/public/models/lafabrik/porte_stock_normal.png b/public/models/lafabrik/porte_stock_normal.png new file mode 100644 index 0000000..24fac91 --- /dev/null +++ b/public/models/lafabrik/porte_stock_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e8963241a9954ad1fb77174ae5fd54a9412e4827a23b17d72d253d29b48e1c3 +size 45527 diff --git a/public/models/lafabrik/porte_stock_normal_opengl.png b/public/models/lafabrik/porte_stock_normal_opengl.png new file mode 100644 index 0000000..a315580 --- /dev/null +++ b/public/models/lafabrik/porte_stock_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27c879ae9d20f2062e9f95ccad0d9e81c4291cbad52c4c95be60bbe3e767297d +size 46420 diff --git a/public/models/lafabrik/porte_stock_occlusionroughnessmetallic.png b/public/models/lafabrik/porte_stock_occlusionroughnessmetallic.png new file mode 100644 index 0000000..89877a7 --- /dev/null +++ b/public/models/lafabrik/porte_stock_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a40cc9bdbf324dc0464798f0570a83dc871cc3307c2b3a2a6b53293e9a3f415f +size 50302 diff --git a/public/models/lafabrik/porte_stock_roughness.png b/public/models/lafabrik/porte_stock_roughness.png new file mode 100644 index 0000000..e70646f --- /dev/null +++ b/public/models/lafabrik/porte_stock_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32d6f618a212c2d338e83150902550a61597de2447cd5fe65d91b9f89c4a23e5 +size 20796 diff --git a/public/models/lafabrik/stock_0_base_color.png b/public/models/lafabrik/stock_0_base_color.png new file mode 100644 index 0000000..5417094 --- /dev/null +++ b/public/models/lafabrik/stock_0_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca9617c3d3373b6b4c394b7a2de6b692d5561702ea895aea14c619c4d4abd20b +size 1173929 diff --git a/public/models/lafabrik/stock_0_basecolor.png b/public/models/lafabrik/stock_0_basecolor.png new file mode 100644 index 0000000..0bc382c --- /dev/null +++ b/public/models/lafabrik/stock_0_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f3d00e332756571cf0bbc4a600964f8b61c678924ff16f4785e5578cbb2df66 +size 924793 diff --git a/public/models/lafabrik/stock_0_height.png b/public/models/lafabrik/stock_0_height.png new file mode 100644 index 0000000..9daf8da --- /dev/null +++ b/public/models/lafabrik/stock_0_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a65cfe0c3a454ccdc9a92c4a1f3dd791529aba0fe120bb1c1ccda4357dc973be +size 12816 diff --git a/public/models/lafabrik/stock_0_metallic.png b/public/models/lafabrik/stock_0_metallic.png new file mode 100644 index 0000000..4039703 --- /dev/null +++ b/public/models/lafabrik/stock_0_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da6065158325302d0532437e05a303258dbe26b72a10f58f966f3f07c934ceed +size 536927 diff --git a/public/models/lafabrik/stock_0_mixed_ao.png b/public/models/lafabrik/stock_0_mixed_ao.png new file mode 100644 index 0000000..de5cf83 --- /dev/null +++ b/public/models/lafabrik/stock_0_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:029d5e47254d07db85860b7254fa21e001d81f711a947e82148a1f5b2e3785a1 +size 591661 diff --git a/public/models/lafabrik/stock_0_normal.png b/public/models/lafabrik/stock_0_normal.png new file mode 100644 index 0000000..6d0c946 --- /dev/null +++ b/public/models/lafabrik/stock_0_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac8b22757a518d7fdf166a35b85bf09b636ccf54185346d25cebbdebb220a37b +size 2328693 diff --git a/public/models/lafabrik/stock_0_normal_opengl.png b/public/models/lafabrik/stock_0_normal_opengl.png new file mode 100644 index 0000000..041bc9a --- /dev/null +++ b/public/models/lafabrik/stock_0_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6da2d6f12e1c61392e6f7648fbfeae38c1d0749cdf03f3f8e0c0a2cd63069dc7 +size 77507 diff --git a/public/models/lafabrik/stock_0_occlusionroughnessmetallic.png b/public/models/lafabrik/stock_0_occlusionroughnessmetallic.png new file mode 100644 index 0000000..2c5f5c2 --- /dev/null +++ b/public/models/lafabrik/stock_0_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfb8f0c868e4aa1a51df12475de69b4754297767abceaaaaac14b01e65c13ec8 +size 1292405 diff --git a/public/models/lafabrik/stock_0_roughness.png b/public/models/lafabrik/stock_0_roughness.png new file mode 100644 index 0000000..f3dd692 --- /dev/null +++ b/public/models/lafabrik/stock_0_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:428157d1d553d359d25696ec5cdebb0a191dccc08b513f6f8a0a08edccadd8bf +size 12819 diff --git a/public/models/lafabrik/tiges_base_color.png b/public/models/lafabrik/tiges_base_color.png new file mode 100644 index 0000000..9c519e4 --- /dev/null +++ b/public/models/lafabrik/tiges_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0671a54d36b101abea124afad01aa332b20f71abaf0bd55f3970c2944b9c360 +size 354282 diff --git a/public/models/lafabrik/tiges_basecolor.png b/public/models/lafabrik/tiges_basecolor.png new file mode 100644 index 0000000..cd25a14 --- /dev/null +++ b/public/models/lafabrik/tiges_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a2400ea8904396f705c7587e8613e5c7b6922d66a9d0eee51ad1361fcddc47e +size 210469 diff --git a/public/models/lafabrik/tiges_height.png b/public/models/lafabrik/tiges_height.png new file mode 100644 index 0000000..9daf8da --- /dev/null +++ b/public/models/lafabrik/tiges_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a65cfe0c3a454ccdc9a92c4a1f3dd791529aba0fe120bb1c1ccda4357dc973be +size 12816 diff --git a/public/models/lafabrik/tiges_metallic.png b/public/models/lafabrik/tiges_metallic.png new file mode 100644 index 0000000..353dceb --- /dev/null +++ b/public/models/lafabrik/tiges_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:537c59bf6206c8a29d89a0f0433ae285254a2b7deac15ba730f547b6d678ca33 +size 8243 diff --git a/public/models/lafabrik/tiges_mixed_ao.png b/public/models/lafabrik/tiges_mixed_ao.png new file mode 100644 index 0000000..aeafc39 --- /dev/null +++ b/public/models/lafabrik/tiges_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56dc583b2c84b6991a6f9d488885c6857f81f8b911ac1676070838398ab30f21 +size 109095 diff --git a/public/models/lafabrik/tiges_normal.png b/public/models/lafabrik/tiges_normal.png new file mode 100644 index 0000000..3c14cf5 --- /dev/null +++ b/public/models/lafabrik/tiges_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98460460fbfcf9185b93db02f56b95ab55d33776a93d057a561e20f1d60baaf6 +size 2300732 diff --git a/public/models/lafabrik/tiges_normal_opengl.png b/public/models/lafabrik/tiges_normal_opengl.png new file mode 100644 index 0000000..de08e22 --- /dev/null +++ b/public/models/lafabrik/tiges_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca5547c7d7b84139109d58992629be338f75becef2f0fb5616c6b195c0df588d +size 41343 diff --git a/public/models/lafabrik/tiges_occlusionroughnessmetallic.png b/public/models/lafabrik/tiges_occlusionroughnessmetallic.png new file mode 100644 index 0000000..19aabcd --- /dev/null +++ b/public/models/lafabrik/tiges_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f1b6cffe503202069b3eb74c21c6d55821ce50595998131399048998f06e0d0 +size 139216 diff --git a/public/models/lafabrik/tiges_roughness.png b/public/models/lafabrik/tiges_roughness.png new file mode 100644 index 0000000..f3dd692 --- /dev/null +++ b/public/models/lafabrik/tiges_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:428157d1d553d359d25696ec5cdebb0a191dccc08b513f6f8a0a08edccadd8bf +size 12819 diff --git a/public/models/lafabrik/toit_base_color.png b/public/models/lafabrik/toit_base_color.png new file mode 100644 index 0000000..11b9196 --- /dev/null +++ b/public/models/lafabrik/toit_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c25c112e446005552faf39dd36588427fc8862985010d49a23f7f27845c991c +size 859915 diff --git a/public/models/lafabrik/toit_basecolor.png b/public/models/lafabrik/toit_basecolor.png new file mode 100644 index 0000000..88cc317 --- /dev/null +++ b/public/models/lafabrik/toit_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a37bd6dd564ed91204cb52ac8e15b4538a96e5534ad887e8942cc13250c595c +size 693601 diff --git a/public/models/lafabrik/toit_height.png b/public/models/lafabrik/toit_height.png new file mode 100644 index 0000000..9daf8da --- /dev/null +++ b/public/models/lafabrik/toit_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a65cfe0c3a454ccdc9a92c4a1f3dd791529aba0fe120bb1c1ccda4357dc973be +size 12816 diff --git a/public/models/lafabrik/toit_metallic.png b/public/models/lafabrik/toit_metallic.png new file mode 100644 index 0000000..353dceb --- /dev/null +++ b/public/models/lafabrik/toit_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:537c59bf6206c8a29d89a0f0433ae285254a2b7deac15ba730f547b6d678ca33 +size 8243 diff --git a/public/models/lafabrik/toit_mixed_ao.png b/public/models/lafabrik/toit_mixed_ao.png new file mode 100644 index 0000000..5db1248 --- /dev/null +++ b/public/models/lafabrik/toit_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf924c03af4f85d59bbd990865f2aa3400876a0a97a69c88ee49d65026f9bc89 +size 349548 diff --git a/public/models/lafabrik/toit_normal.png b/public/models/lafabrik/toit_normal.png new file mode 100644 index 0000000..067baa9 --- /dev/null +++ b/public/models/lafabrik/toit_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66c3acf157d8bc160f3f2d97a536cf98350ff6170ce9912b0ddff62954bfdb99 +size 2429181 diff --git a/public/models/lafabrik/toit_normal_opengl.png b/public/models/lafabrik/toit_normal_opengl.png new file mode 100644 index 0000000..e8770c1 --- /dev/null +++ b/public/models/lafabrik/toit_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02f0ffecc48765defa40eec93ea43f349f3458d0310a1ae93e7885d8cff1094f +size 462395 diff --git a/public/models/lafabrik/toit_occlusionroughnessmetallic.png b/public/models/lafabrik/toit_occlusionroughnessmetallic.png new file mode 100644 index 0000000..04151cd --- /dev/null +++ b/public/models/lafabrik/toit_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a472ac98d398f511fba3d2518202e92371092bc34f597f79975b946b1d9f57c1 +size 754338 diff --git a/public/models/lafabrik/toit_roughness.png b/public/models/lafabrik/toit_roughness.png new file mode 100644 index 0000000..5d5c166 --- /dev/null +++ b/public/models/lafabrik/toit_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:146c2eaab113e24bca61833fcbe64a9f02f7c77dcc41975cef8d97ec8a23c93c +size 35132 diff --git a/public/models/lafabrik/tuyaux_base_color.png b/public/models/lafabrik/tuyaux_base_color.png new file mode 100644 index 0000000..66b12c1 --- /dev/null +++ b/public/models/lafabrik/tuyaux_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d84502af791ef0e062e59939e8c8df61b9697edcd972f3479dd5caf948ee27e +size 37945 diff --git a/public/models/lafabrik/tuyaux_basecolor.png b/public/models/lafabrik/tuyaux_basecolor.png new file mode 100644 index 0000000..a972f9f --- /dev/null +++ b/public/models/lafabrik/tuyaux_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecef5b234187cf0ea6811b7c4391a30ebd270716c6898737048574b8e91df6a4 +size 985402 diff --git a/public/models/lafabrik/tuyaux_height.png b/public/models/lafabrik/tuyaux_height.png new file mode 100644 index 0000000..5b5097a --- /dev/null +++ b/public/models/lafabrik/tuyaux_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90c60d0b0bdee17032df4fca741b6fffef5f8f7d3ea6d812b38a3c7182081348 +size 21246 diff --git a/public/models/lafabrik/tuyaux_metallic.png b/public/models/lafabrik/tuyaux_metallic.png new file mode 100644 index 0000000..5eb723e --- /dev/null +++ b/public/models/lafabrik/tuyaux_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7539ab53d58dbd6a3847ce0055806931569d9c43d37c04bd8c935c0983c23ec0 +size 16408 diff --git a/public/models/lafabrik/tuyaux_mixed_ao.png b/public/models/lafabrik/tuyaux_mixed_ao.png new file mode 100644 index 0000000..13172ba --- /dev/null +++ b/public/models/lafabrik/tuyaux_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d29f3cde5df3e61c74f2991b791f66894f7f5dd78cf9dd3d4e1c74c8db6864f8 +size 488217 diff --git a/public/models/lafabrik/tuyaux_normal.png b/public/models/lafabrik/tuyaux_normal.png new file mode 100644 index 0000000..ea8f469 --- /dev/null +++ b/public/models/lafabrik/tuyaux_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb63b22af905381525e94db42df33e962fa4438e6df7fc4b135fea66aff3d8ef +size 2733817 diff --git a/public/models/lafabrik/tuyaux_normal_opengl.png b/public/models/lafabrik/tuyaux_normal_opengl.png new file mode 100644 index 0000000..57c301c --- /dev/null +++ b/public/models/lafabrik/tuyaux_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2248bd2d602f2fa747ae8946aab269f58332f0485de6d36ae7addf1b179b3712 +size 601094 diff --git a/public/models/lafabrik/tuyaux_occlusionroughnessmetallic.png b/public/models/lafabrik/tuyaux_occlusionroughnessmetallic.png new file mode 100644 index 0000000..45e36fe --- /dev/null +++ b/public/models/lafabrik/tuyaux_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa1106f03322eb77da04566add258c040fe02f12745077250fdedc511b051329 +size 727564 diff --git a/public/models/lafabrik/tuyaux_roughness.png b/public/models/lafabrik/tuyaux_roughness.png new file mode 100644 index 0000000..ca04062 --- /dev/null +++ b/public/models/lafabrik/tuyaux_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8437db5f5dff9cd445e74195d68da20fcec1e5cfebb50ce71a99d586e28fc09b +size 21248 diff --git a/public/models/lafabrik/verre_fenetre_base_color.png b/public/models/lafabrik/verre_fenetre_base_color.png new file mode 100644 index 0000000..66b12c1 --- /dev/null +++ b/public/models/lafabrik/verre_fenetre_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d84502af791ef0e062e59939e8c8df61b9697edcd972f3479dd5caf948ee27e +size 37945 diff --git a/public/models/lafabrik/verre_fenetre_basecolor.png b/public/models/lafabrik/verre_fenetre_basecolor.png new file mode 100644 index 0000000..d7d07d3 --- /dev/null +++ b/public/models/lafabrik/verre_fenetre_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7efa341e9d62aab680a8bfa040ea0ef9516cd89f44fd67ad76166ff6a6ba867 +size 219055 diff --git a/public/models/lafabrik/verre_fenetre_height.png b/public/models/lafabrik/verre_fenetre_height.png new file mode 100644 index 0000000..5b5097a --- /dev/null +++ b/public/models/lafabrik/verre_fenetre_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90c60d0b0bdee17032df4fca741b6fffef5f8f7d3ea6d812b38a3c7182081348 +size 21246 diff --git a/public/models/lafabrik/verre_fenetre_metallic.png b/public/models/lafabrik/verre_fenetre_metallic.png new file mode 100644 index 0000000..5eb723e --- /dev/null +++ b/public/models/lafabrik/verre_fenetre_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7539ab53d58dbd6a3847ce0055806931569d9c43d37c04bd8c935c0983c23ec0 +size 16408 diff --git a/public/models/lafabrik/verre_fenetre_mixed_ao.png b/public/models/lafabrik/verre_fenetre_mixed_ao.png new file mode 100644 index 0000000..5da09c5 --- /dev/null +++ b/public/models/lafabrik/verre_fenetre_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8836e7e2a32d99efce5fe81842423c81e151f35938916ee10460bd6d811f56d5 +size 863299 diff --git a/public/models/lafabrik/verre_fenetre_normal.png b/public/models/lafabrik/verre_fenetre_normal.png new file mode 100644 index 0000000..543d4bd --- /dev/null +++ b/public/models/lafabrik/verre_fenetre_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7c4e3a495cac4c984bc0ee50435bd4f52e90ebf1c5cdbd22c3f3b392b07f354 +size 2366782 diff --git a/public/models/lafabrik/verre_fenetre_normal_opengl.png b/public/models/lafabrik/verre_fenetre_normal_opengl.png new file mode 100644 index 0000000..55c23b9 --- /dev/null +++ b/public/models/lafabrik/verre_fenetre_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58e64c8e335e92f76bebae1e0bf9bac8ce9d339dc896223f1be0f2d8223d30f0 +size 37652 diff --git a/public/models/lafabrik/verre_fenetre_occlusionroughnessmetallic.png b/public/models/lafabrik/verre_fenetre_occlusionroughnessmetallic.png new file mode 100644 index 0000000..72000d9 --- /dev/null +++ b/public/models/lafabrik/verre_fenetre_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85607499851e3dc3ebbff19886c40609dc81a3167940ffadd469731c222d56b3 +size 1021100 diff --git a/public/models/lafabrik/verre_fenetre_roughness.png b/public/models/lafabrik/verre_fenetre_roughness.png new file mode 100644 index 0000000..ca04062 --- /dev/null +++ b/public/models/lafabrik/verre_fenetre_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8437db5f5dff9cd445e74195d68da20fcec1e5cfebb50ce71a99d586e28fc09b +size 21248 diff --git a/public/models/maison1/contours_baseColor.png b/public/models/maison1/contours_baseColor.png new file mode 100644 index 0000000..278ef0d --- /dev/null +++ b/public/models/maison1/contours_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed22c250b879f0e66e30e2baf16ab290eea6c894f93e0105a45da960b26e5f21 +size 131187 diff --git a/public/models/maison1/contours_normal.png b/public/models/maison1/contours_normal.png new file mode 100644 index 0000000..642bd3b --- /dev/null +++ b/public/models/maison1/contours_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c93319fc83c2304d0b09ddaea32a33c32c5405b4340348ae2c45c0177df06b75 +size 2511652 diff --git a/public/models/maison1/contours_occlusionRoughnessMetallic.png b/public/models/maison1/contours_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..04535d6 --- /dev/null +++ b/public/models/maison1/contours_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0c8332caf3085222d4ffa4296cf454f5006605f686c750a0f6b7ca4b6ea3c6b +size 160217 diff --git a/public/models/maison1/fenetre_baseColor.png b/public/models/maison1/fenetre_baseColor.png new file mode 100644 index 0000000..80c71ce --- /dev/null +++ b/public/models/maison1/fenetre_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1ba9976d7416ace83e7ae729fed9f6e012254e75616405c7a60ed9436c50205 +size 216048 diff --git a/public/models/maison1/fenetre_normal.png b/public/models/maison1/fenetre_normal.png new file mode 100644 index 0000000..23fdaff --- /dev/null +++ b/public/models/maison1/fenetre_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c73191bf8151222ab91994ba282f78ca5b12cd2b3a6967e4259899802c526e62 +size 2389204 diff --git a/public/models/maison1/fenetre_occlusionRoughnessMetallic.png b/public/models/maison1/fenetre_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..eaf10e8 --- /dev/null +++ b/public/models/maison1/fenetre_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f9ed0f001f98c1f7c6549e118cfae46c2cbddfd998338c0c2f298f9cfaf55b9 +size 366670 diff --git a/public/models/maison1/maison.bin b/public/models/maison1/maison.bin new file mode 100644 index 0000000..af0bdae --- /dev/null +++ b/public/models/maison1/maison.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc606b94117b5aeb6ef04df7e3f314400ef6526370d1c070ca1e1f2aa0cdd31f +size 16288416 diff --git a/public/models/maison1/maison_baseColor.png b/public/models/maison1/maison_baseColor.png new file mode 100644 index 0000000..0c3cc07 --- /dev/null +++ b/public/models/maison1/maison_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71c03de33eda3c95e56652d86950ecbce978e6f224785e7f2bfe8bb8c462382f +size 452807 diff --git a/public/models/maison1/maison_normal.png b/public/models/maison1/maison_normal.png new file mode 100644 index 0000000..a775c1b --- /dev/null +++ b/public/models/maison1/maison_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06f73d59d07820ba9a7934955fb47e30066724996bb6ac138ff94733d314043c +size 2588052 diff --git a/public/models/maison1/maison_occlusionRoughnessMetallic.png b/public/models/maison1/maison_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..39f8fd5 --- /dev/null +++ b/public/models/maison1/maison_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07db7e6bf0f396669bccd8361a4deec0b7faccc528163e8e748e7333eed530b6 +size 1043188 diff --git a/public/models/maison1/model.gltf b/public/models/maison1/model.gltf new file mode 100644 index 0000000..4f61689 --- /dev/null +++ b/public/models/maison1/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e21381ca52401e439a97d898358cf8d0ea62c3b6aaf45efd625c4572de89319e +size 26605137 diff --git a/public/models/maison1/panneau_baseColor.png b/public/models/maison1/panneau_baseColor.png new file mode 100644 index 0000000..866ba7f --- /dev/null +++ b/public/models/maison1/panneau_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:499e858e82bb910f251386f57bb751743b0a557a70383a68e5b534a8e8e30854 +size 1138223 diff --git a/public/models/maison1/panneau_normal.png b/public/models/maison1/panneau_normal.png new file mode 100644 index 0000000..f513b6d --- /dev/null +++ b/public/models/maison1/panneau_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e7db9b77efbfb0c62ec4924b2522babb191ec4a5e51687dc4de8971f044a8f5 +size 2487930 diff --git a/public/models/maison1/panneau_occlusionRoughnessMetallic.png b/public/models/maison1/panneau_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..2604685 --- /dev/null +++ b/public/models/maison1/panneau_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d091dfbc9d62ae4a2bd978a66bb9efe0ad82e3cbcb9c01b07cd92cc16061ef22 +size 220552 diff --git a/public/models/maison1/porte_baseColor.png b/public/models/maison1/porte_baseColor.png new file mode 100644 index 0000000..a134b08 --- /dev/null +++ b/public/models/maison1/porte_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73f7d6de8d7e25c3c467d3629db998700f3174f0f72c54853fb8fdb5e57e08f6 +size 75625 diff --git a/public/models/maison1/porte_normal.png b/public/models/maison1/porte_normal.png new file mode 100644 index 0000000..98e8af4 --- /dev/null +++ b/public/models/maison1/porte_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db6673d76ec56a40ae3cfeb5618106698e61af67640da37d3c2bdb83fae1bd62 +size 1958641 diff --git a/public/models/maison1/porte_occlusionRoughnessMetallic.png b/public/models/maison1/porte_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..439356d --- /dev/null +++ b/public/models/maison1/porte_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b177a895acb638a7f2afd94b33dae973d0295ca1a4ab2ae591bb570b9465185 +size 99517 diff --git a/public/models/maison1/toit_baseColor.png b/public/models/maison1/toit_baseColor.png new file mode 100644 index 0000000..5fafc94 --- /dev/null +++ b/public/models/maison1/toit_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e3ba3cf444b3f8f93c95a0152595c1aed06f29e33e2222ebc129ee264489595 +size 837804 diff --git a/public/models/maison1/toit_normal.png b/public/models/maison1/toit_normal.png new file mode 100644 index 0000000..f711d78 --- /dev/null +++ b/public/models/maison1/toit_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0664dcc0ab1332c29cef088788981340d8244c5d6707a89a9fee25061945e021 +size 2795898 diff --git a/public/models/maison1/toit_occlusionRoughnessMetallic.png b/public/models/maison1/toit_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..3e356ba --- /dev/null +++ b/public/models/maison1/toit_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b4b5b1127a02b617fc739bc3736f12f4fd9cfae19aca5c4e82b4d495b1f6ea1 +size 1027685 diff --git a/public/models/packderelance/cabledroit_base_color.png b/public/models/packderelance/cabledroit_base_color.png new file mode 100644 index 0000000..17a1e8c --- /dev/null +++ b/public/models/packderelance/cabledroit_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:613f4ed9175f554bf1ec4b8425ecb2b81fde584414caaaab865bc47582f1ea61 +size 20078 diff --git a/public/models/packderelance/cabledroit_height.png b/public/models/packderelance/cabledroit_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/packderelance/cabledroit_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/packderelance/cabledroit_metallic.png b/public/models/packderelance/cabledroit_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/packderelance/cabledroit_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/packderelance/cabledroit_mixed_ao.png b/public/models/packderelance/cabledroit_mixed_ao.png new file mode 100644 index 0000000..5addc6e --- /dev/null +++ b/public/models/packderelance/cabledroit_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4808a2bb7e3ae292484eab89442fe12ec00f0c9e60df1f0a3c0a07cd60d31d92 +size 100286 diff --git a/public/models/packderelance/cabledroit_normal.png b/public/models/packderelance/cabledroit_normal.png new file mode 100644 index 0000000..573e9ce --- /dev/null +++ b/public/models/packderelance/cabledroit_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2870a832300161a35a832511aa6401a0ef8b301acf79d67eeb8aea6a6f0c6470 +size 144969 diff --git a/public/models/packderelance/cabledroit_normal_opengl.png b/public/models/packderelance/cabledroit_normal_opengl.png new file mode 100644 index 0000000..5f329f3 --- /dev/null +++ b/public/models/packderelance/cabledroit_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:300804d6b7edc1d1375366f4a88c78d5f5f11bceb404be860144041c638fd0e6 +size 145487 diff --git a/public/models/packderelance/cabledroit_roughness.png b/public/models/packderelance/cabledroit_roughness.png new file mode 100644 index 0000000..ad7f319 --- /dev/null +++ b/public/models/packderelance/cabledroit_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c284c7d004b8ac903385f47bd837da38d89bd62d6cb0ca373d0e7fb13ccebf90 +size 53658 diff --git a/public/models/packderelance/cablegauche_base_color.png b/public/models/packderelance/cablegauche_base_color.png new file mode 100644 index 0000000..d9adfcb --- /dev/null +++ b/public/models/packderelance/cablegauche_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb15c724f623830d464d72abecd429406c7a4338e6e4a6bf1afd841dbdd0c6f5 +size 31886 diff --git a/public/models/packderelance/cablegauche_height.png b/public/models/packderelance/cablegauche_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/packderelance/cablegauche_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/packderelance/cablegauche_metallic.png b/public/models/packderelance/cablegauche_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/packderelance/cablegauche_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/packderelance/cablegauche_mixed_ao.png b/public/models/packderelance/cablegauche_mixed_ao.png new file mode 100644 index 0000000..c421b49 --- /dev/null +++ b/public/models/packderelance/cablegauche_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:daf9d86a1f29600e4af115eb0d3f429240c4f8cdfb773dfc7ce7bff2785b6cd6 +size 98979 diff --git a/public/models/packderelance/cablegauche_normal.png b/public/models/packderelance/cablegauche_normal.png new file mode 100644 index 0000000..816c578 --- /dev/null +++ b/public/models/packderelance/cablegauche_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:050b424b60d026327146d44bf02cf5f8f3c06525b1ca99b234ade11bc6114ad2 +size 144355 diff --git a/public/models/packderelance/cablegauche_normal_opengl.png b/public/models/packderelance/cablegauche_normal_opengl.png new file mode 100644 index 0000000..8d99c85 --- /dev/null +++ b/public/models/packderelance/cablegauche_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ab8fd8bff205eea0af8292bc13ba4687b75e140645a654cdb3247c533ab646c +size 144797 diff --git a/public/models/packderelance/cablegauche_roughness.png b/public/models/packderelance/cablegauche_roughness.png new file mode 100644 index 0000000..f90ec1c --- /dev/null +++ b/public/models/packderelance/cablegauche_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a252defdb7a8f50a89c6cdad3937776f4aaf10578a6134b8dc8957b25fced770 +size 53435 diff --git a/public/models/packderelance/charnie_res_base_color.png b/public/models/packderelance/charnie_res_base_color.png new file mode 100644 index 0000000..c42f5fd --- /dev/null +++ b/public/models/packderelance/charnie_res_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea3df019860e60a2201d9d6caa374bb1500b9602153b1e3f08c48bfd016aff20 +size 25427 diff --git a/public/models/packderelance/charnie_res_height.png b/public/models/packderelance/charnie_res_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/packderelance/charnie_res_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/packderelance/charnie_res_metallic.png b/public/models/packderelance/charnie_res_metallic.png new file mode 100644 index 0000000..b5efe7f --- /dev/null +++ b/public/models/packderelance/charnie_res_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2ed466f79c723385da4cdf7cd5f1ed962e7523ff364a5df53336c57f925ae48 +size 3179 diff --git a/public/models/packderelance/charnie_res_mixed_ao.png b/public/models/packderelance/charnie_res_mixed_ao.png new file mode 100644 index 0000000..a599712 --- /dev/null +++ b/public/models/packderelance/charnie_res_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b22bc2d3bae0f4bafc2b8bde962baa91eb911aba02b61737e17bc356497ab2be +size 433143 diff --git a/public/models/packderelance/charnie_res_normal.png b/public/models/packderelance/charnie_res_normal.png new file mode 100644 index 0000000..656cd34 --- /dev/null +++ b/public/models/packderelance/charnie_res_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38ec0d30ce672b32199200e164eee640c7b168a3784a4ebce582eba63cb13bae +size 224792 diff --git a/public/models/packderelance/charnie_res_normal_opengl.png b/public/models/packderelance/charnie_res_normal_opengl.png new file mode 100644 index 0000000..51500ab --- /dev/null +++ b/public/models/packderelance/charnie_res_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9980c45e2e90cf6414249536d1df806e9ff550cfc88b31621680d833e99fad60 +size 224132 diff --git a/public/models/packderelance/charnie_res_roughness.png b/public/models/packderelance/charnie_res_roughness.png new file mode 100644 index 0000000..61b122d --- /dev/null +++ b/public/models/packderelance/charnie_res_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c386172f879e0a97ed4168bc2fa242430ae41bb256e97c08157e429c6915a389 +size 101620 diff --git a/public/models/packderelance/lock_base_color.png b/public/models/packderelance/lock_base_color.png new file mode 100644 index 0000000..4713e11 --- /dev/null +++ b/public/models/packderelance/lock_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b069014a3c3a8cec31e89eab2a3e5551be8719a923ab6631d68e35a339027de +size 23570 diff --git a/public/models/packderelance/lock_height.png b/public/models/packderelance/lock_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/packderelance/lock_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/packderelance/lock_metallic.png b/public/models/packderelance/lock_metallic.png new file mode 100644 index 0000000..9a54f06 --- /dev/null +++ b/public/models/packderelance/lock_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f2ae19c75124d18889a4682d0ef5681e6dc93b0ed764171edaa5bdfea147af5 +size 3179 diff --git a/public/models/packderelance/lock_mixed_ao.png b/public/models/packderelance/lock_mixed_ao.png new file mode 100644 index 0000000..2520a22 --- /dev/null +++ b/public/models/packderelance/lock_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:defceee926a30a842e6e3cc687f6aed1a725d23a113c4a0dc9043ccf60974b23 +size 169856 diff --git a/public/models/packderelance/lock_normal.png b/public/models/packderelance/lock_normal.png new file mode 100644 index 0000000..94ab739 --- /dev/null +++ b/public/models/packderelance/lock_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35dc1c655b8e0f8715eff72e36a8b69ae8145cc91c96bce23cad9fa8e429e9e1 +size 113730 diff --git a/public/models/packderelance/lock_normal_opengl.png b/public/models/packderelance/lock_normal_opengl.png new file mode 100644 index 0000000..739c551 --- /dev/null +++ b/public/models/packderelance/lock_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c90b0b6aaadf6cd80eaf61e8981d6f0f81ec6f1cc6d28ad9623b1d589da1aa64 +size 114262 diff --git a/public/models/packderelance/lock_roughness.png b/public/models/packderelance/lock_roughness.png new file mode 100644 index 0000000..a238764 --- /dev/null +++ b/public/models/packderelance/lock_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5e0dd0eb573b833b9507e9f5d36c0f5d696974699c48918feda6ee493633502 +size 78421 diff --git a/public/models/packderelance/manchemart_base_color.png b/public/models/packderelance/manchemart_base_color.png new file mode 100644 index 0000000..fc643a5 --- /dev/null +++ b/public/models/packderelance/manchemart_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:622a39812f202a2e5b3963af40b7d4998bc33cb5b25cbcd357c12dfda816f77b +size 41453 diff --git a/public/models/packderelance/manchemart_height.png b/public/models/packderelance/manchemart_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/packderelance/manchemart_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/packderelance/manchemart_metallic.png b/public/models/packderelance/manchemart_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/packderelance/manchemart_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/packderelance/manchemart_mixed_ao.png b/public/models/packderelance/manchemart_mixed_ao.png new file mode 100644 index 0000000..78ecb78 --- /dev/null +++ b/public/models/packderelance/manchemart_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15e963bea1bf721725a3e64685f373c63c0de664ce4128b9554666bae6b76349 +size 185302 diff --git a/public/models/packderelance/manchemart_normal.png b/public/models/packderelance/manchemart_normal.png new file mode 100644 index 0000000..5999a0e --- /dev/null +++ b/public/models/packderelance/manchemart_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01c8a610171b36fb12e26696818ec942c75bb47f7cfda34066f5d63dfcaf344c +size 164783 diff --git a/public/models/packderelance/manchemart_normal_opengl.png b/public/models/packderelance/manchemart_normal_opengl.png new file mode 100644 index 0000000..a9cac6e --- /dev/null +++ b/public/models/packderelance/manchemart_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4461d87006c85001498c5377c5228f4d83c496ef809acc8ec5e1747515eb7b1 +size 165827 diff --git a/public/models/packderelance/manchemart_roughness.png b/public/models/packderelance/manchemart_roughness.png new file mode 100644 index 0000000..eb0669e --- /dev/null +++ b/public/models/packderelance/manchemart_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7570bd18e98f42919a20d83d06cb3dcd2a11c4e2eb8e8d7a9b1806a43bf1e6d +size 71115 diff --git a/public/models/packderelance/model.gltf b/public/models/packderelance/model.gltf new file mode 100644 index 0000000..ebf8a47 --- /dev/null +++ b/public/models/packderelance/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb94679c14d1b0019b98fa04bbb66d9bc77b31b944201734e25c4a2d6104772d +size 4435656 diff --git a/public/models/packderelance/mousse_bas_base_color.png b/public/models/packderelance/mousse_bas_base_color.png new file mode 100644 index 0000000..09ee7d0 --- /dev/null +++ b/public/models/packderelance/mousse_bas_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21b609193f25d95233a4d4edf7b6bf90e73537370949d40c2d923425a589476c +size 13843 diff --git a/public/models/packderelance/mousse_bas_height.png b/public/models/packderelance/mousse_bas_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/packderelance/mousse_bas_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/packderelance/mousse_bas_metallic.png b/public/models/packderelance/mousse_bas_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/packderelance/mousse_bas_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/packderelance/mousse_bas_mixed_ao.png b/public/models/packderelance/mousse_bas_mixed_ao.png new file mode 100644 index 0000000..7c94204 --- /dev/null +++ b/public/models/packderelance/mousse_bas_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bebff7465f570ccd48063d37e1ace0eec9b8db8715360992acaf7dbbf63804d6 +size 251328 diff --git a/public/models/packderelance/mousse_bas_normal.png b/public/models/packderelance/mousse_bas_normal.png new file mode 100644 index 0000000..0ac089c --- /dev/null +++ b/public/models/packderelance/mousse_bas_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c704ef890feb0fb1c46b671f8e8a6230371dad7a13c4e4b18e342aec560cbb7 +size 175208 diff --git a/public/models/packderelance/mousse_bas_normal_opengl.png b/public/models/packderelance/mousse_bas_normal_opengl.png new file mode 100644 index 0000000..6d76b50 --- /dev/null +++ b/public/models/packderelance/mousse_bas_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdf523b3fda14ef39dfa2006cc66088f75228e555a842859d6bb45b5d8d12036 +size 177660 diff --git a/public/models/packderelance/mousse_bas_roughness.png b/public/models/packderelance/mousse_bas_roughness.png new file mode 100644 index 0000000..e493c11 --- /dev/null +++ b/public/models/packderelance/mousse_bas_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3435c1e95fefd08d123099c3a1a88e5cda7f8d5d9787d8e007312267f3d35386 +size 185557 diff --git a/public/models/packderelance/mousse_base_color.png b/public/models/packderelance/mousse_base_color.png new file mode 100644 index 0000000..3c8843d --- /dev/null +++ b/public/models/packderelance/mousse_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce5d4eedb0f41f260baa236aa45e5f96443d8f8cd11756ae6054c0f9a5aea937 +size 8751 diff --git a/public/models/packderelance/mousse_height.png b/public/models/packderelance/mousse_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/packderelance/mousse_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/packderelance/mousse_metallic.png b/public/models/packderelance/mousse_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/packderelance/mousse_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/packderelance/mousse_mixed_ao.png b/public/models/packderelance/mousse_mixed_ao.png new file mode 100644 index 0000000..ba99c9a --- /dev/null +++ b/public/models/packderelance/mousse_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3a86a57b95d13c5028c2a4b5f7b7d9720de33fe46890fc759b72eac37dbc64f +size 238031 diff --git a/public/models/packderelance/mousse_normal.png b/public/models/packderelance/mousse_normal.png new file mode 100644 index 0000000..4653be8 --- /dev/null +++ b/public/models/packderelance/mousse_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f44adbf23be3bb97904dbf1bcf5ba654899e02d08eb6a5e121815770473629a +size 193683 diff --git a/public/models/packderelance/mousse_normal_opengl.png b/public/models/packderelance/mousse_normal_opengl.png new file mode 100644 index 0000000..de2f4de --- /dev/null +++ b/public/models/packderelance/mousse_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22199dddbb6b1f581fa45dfd52f9edd523f2836ee2ee626c573c0705a62ce768 +size 193969 diff --git a/public/models/packderelance/mousse_roughness.png b/public/models/packderelance/mousse_roughness.png new file mode 100644 index 0000000..dc7b8ab --- /dev/null +++ b/public/models/packderelance/mousse_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59787e816503401eaa8c20fe8ab472283e09523dac5f5f689c200dad5763a10b +size 215919 diff --git a/public/models/packderelance/patinf_base_color.png b/public/models/packderelance/patinf_base_color.png new file mode 100644 index 0000000..bbcac5e --- /dev/null +++ b/public/models/packderelance/patinf_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a27fdc53a87e373739863a6bb87b5de2e744f9d5ef230260f166c88ab3ce962d +size 19530 diff --git a/public/models/packderelance/patinf_height.png b/public/models/packderelance/patinf_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/packderelance/patinf_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/packderelance/patinf_metallic.png b/public/models/packderelance/patinf_metallic.png new file mode 100644 index 0000000..d1c34c7 --- /dev/null +++ b/public/models/packderelance/patinf_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:501418a6e2d02b552799edd80e8c5a53198ff3d499eee9b783bddb5d72afd603 +size 3179 diff --git a/public/models/packderelance/patinf_mixed_ao.png b/public/models/packderelance/patinf_mixed_ao.png new file mode 100644 index 0000000..e8bf2ec --- /dev/null +++ b/public/models/packderelance/patinf_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae589f2edea4fc2147a57b5fb9e33a25a63f16d2aef7f4c9fc71a5ca6ae5bac5 +size 90431 diff --git a/public/models/packderelance/patinf_normal.png b/public/models/packderelance/patinf_normal.png new file mode 100644 index 0000000..2711e4d --- /dev/null +++ b/public/models/packderelance/patinf_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ebb2add69c6a08441a30c8975cc4f6b7e5b69c165def6b1bf42b98a420a606b +size 146695 diff --git a/public/models/packderelance/patinf_normal_opengl.png b/public/models/packderelance/patinf_normal_opengl.png new file mode 100644 index 0000000..377c13b --- /dev/null +++ b/public/models/packderelance/patinf_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b9d4baedf21c6ed4fec8a6e80fea2b8db3e9107bc0721fad63e90ecb309bbd8 +size 146723 diff --git a/public/models/packderelance/patinf_roughness.png b/public/models/packderelance/patinf_roughness.png new file mode 100644 index 0000000..b517e25 --- /dev/null +++ b/public/models/packderelance/patinf_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50749697660eff0a5ebbb8c9d6c4182ed2b7cf847fb366da756551f9d828c2ba +size 135722 diff --git a/public/models/packderelance/patsup_base_color.png b/public/models/packderelance/patsup_base_color.png new file mode 100644 index 0000000..ead3f2a --- /dev/null +++ b/public/models/packderelance/patsup_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc8e2bd801f75f272838e441eb2784457e38b8aae9483ea0f697adb85f12b0b5 +size 30583 diff --git a/public/models/packderelance/patsup_height.png b/public/models/packderelance/patsup_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/packderelance/patsup_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/packderelance/patsup_metallic.png b/public/models/packderelance/patsup_metallic.png new file mode 100644 index 0000000..892f4d3 --- /dev/null +++ b/public/models/packderelance/patsup_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:768bcad38062d5d33b6e4a8c9f012a199deb6a05366efa280fd6e4e093e8d35b +size 3178 diff --git a/public/models/packderelance/patsup_mixed_ao.png b/public/models/packderelance/patsup_mixed_ao.png new file mode 100644 index 0000000..038a8f7 --- /dev/null +++ b/public/models/packderelance/patsup_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d7eb9f5b370d007ff868c46817f65d23b0a613e683a95b421d037f59ab5e481 +size 201541 diff --git a/public/models/packderelance/patsup_normal.png b/public/models/packderelance/patsup_normal.png new file mode 100644 index 0000000..e0eedac --- /dev/null +++ b/public/models/packderelance/patsup_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edad5421cdd8c87b3310743fd24ea2649ede92d4d598f7a5511ec14a280477ba +size 182455 diff --git a/public/models/packderelance/patsup_normal_opengl.png b/public/models/packderelance/patsup_normal_opengl.png new file mode 100644 index 0000000..5755f0c --- /dev/null +++ b/public/models/packderelance/patsup_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05668e2a74f62600f5d6e2737c402600e7381a36b4f83f69d1df216d8df77369 +size 184924 diff --git a/public/models/packderelance/patsup_roughness.png b/public/models/packderelance/patsup_roughness.png new file mode 100644 index 0000000..befe7d9 --- /dev/null +++ b/public/models/packderelance/patsup_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:062b461aad1c49a752ed767ec8f1dbb70c5d6e8b4824a55e740c945163620c9b +size 131459 diff --git a/public/models/packderelance/puces_base_color.png b/public/models/packderelance/puces_base_color.png new file mode 100644 index 0000000..6c91328 --- /dev/null +++ b/public/models/packderelance/puces_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4945a42ce365fecb6b64549cae5519a141ff9a7e14d1ab834182ac9737c05a82 +size 46976 diff --git a/public/models/packderelance/puces_height.png b/public/models/packderelance/puces_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/packderelance/puces_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/packderelance/puces_metallic.png b/public/models/packderelance/puces_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/packderelance/puces_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/packderelance/puces_mixed_ao.png b/public/models/packderelance/puces_mixed_ao.png new file mode 100644 index 0000000..e3507ef --- /dev/null +++ b/public/models/packderelance/puces_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4e9cd7f981995050b483e59ac123b397d7ec5ca3d30d10215b4a9ec01a27a69 +size 222566 diff --git a/public/models/packderelance/puces_normal.png b/public/models/packderelance/puces_normal.png new file mode 100644 index 0000000..fd77f2d --- /dev/null +++ b/public/models/packderelance/puces_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9be38b03c2aa41d54235e0bf6fa491c13a24ff2ecb4db609ce62c2e83fbf6c19 +size 149845 diff --git a/public/models/packderelance/puces_normal_opengl.png b/public/models/packderelance/puces_normal_opengl.png new file mode 100644 index 0000000..aa432ed --- /dev/null +++ b/public/models/packderelance/puces_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b991ea9ce0ec7d61e33bafdae0526a0f9285777685a8e98b4e69fd22234a67a6 +size 152096 diff --git a/public/models/packderelance/puces_roughness.png b/public/models/packderelance/puces_roughness.png new file mode 100644 index 0000000..0855db0 --- /dev/null +++ b/public/models/packderelance/puces_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d89d63628794f450ce8f6503010aaf9e257b3839242a41f92a472a9ca75a3af +size 101983 diff --git a/public/models/packderelance/tetemart_base_color.png b/public/models/packderelance/tetemart_base_color.png new file mode 100644 index 0000000..6cc7455 --- /dev/null +++ b/public/models/packderelance/tetemart_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c0bf42e601e8fd391b8a3e27db24c0d94d3cb0af794eb8bfa5ce9e011ee9bd9 +size 46981 diff --git a/public/models/packderelance/tetemart_height.png b/public/models/packderelance/tetemart_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/packderelance/tetemart_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/packderelance/tetemart_metallic.png b/public/models/packderelance/tetemart_metallic.png new file mode 100644 index 0000000..393212c --- /dev/null +++ b/public/models/packderelance/tetemart_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf0ada122b6dd77e41fd6358ace63aea3aa9b6a328665e0bad4e3c2cdfcd7f3b +size 3179 diff --git a/public/models/packderelance/tetemart_mixed_ao.png b/public/models/packderelance/tetemart_mixed_ao.png new file mode 100644 index 0000000..597ea8e --- /dev/null +++ b/public/models/packderelance/tetemart_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a04252dab606affc6d82e133a650d94a4f75fe26bfb2c47ec9287610c157a393 +size 323501 diff --git a/public/models/packderelance/tetemart_normal.png b/public/models/packderelance/tetemart_normal.png new file mode 100644 index 0000000..611b71a --- /dev/null +++ b/public/models/packderelance/tetemart_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86c9801f4685f956cf607ee117b0c10aaf2030cf1889c8c923d20df74a97c77f +size 199509 diff --git a/public/models/packderelance/tetemart_normal_opengl.png b/public/models/packderelance/tetemart_normal_opengl.png new file mode 100644 index 0000000..db4d77f --- /dev/null +++ b/public/models/packderelance/tetemart_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:660f0f4985920305bbe62e5d6af0f1d2e00167136f7462cf71afbf33af29fe5f +size 199424 diff --git a/public/models/packderelance/tetemart_roughness.png b/public/models/packderelance/tetemart_roughness.png new file mode 100644 index 0000000..279c947 --- /dev/null +++ b/public/models/packderelance/tetemart_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7934ddff7acc3867e7f22ef450ee992c6e84a51c2f6175fbff76bb702a39f309 +size 114593 diff --git a/public/models/persoprincipal/defaultmaterial_basecolor.png b/public/models/persoprincipal/defaultmaterial_basecolor.png new file mode 100644 index 0000000..1e783b3 --- /dev/null +++ b/public/models/persoprincipal/defaultmaterial_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7cc0ae8aedd986a5b6a7d635c18760249deb991fd5169e08db95a8199dea6316 +size 567490 diff --git a/public/models/persoprincipal/defaultmaterial_normal.png b/public/models/persoprincipal/defaultmaterial_normal.png new file mode 100644 index 0000000..d89f970 --- /dev/null +++ b/public/models/persoprincipal/defaultmaterial_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc9f8d709231086437b89baf6877f856acc99a3d44e237c575ca4bfb93967c04 +size 3161415 diff --git a/public/models/persoprincipal/defaultmaterial_occlusionroughnessmetallic.png b/public/models/persoprincipal/defaultmaterial_occlusionroughnessmetallic.png new file mode 100644 index 0000000..f3c42a8 --- /dev/null +++ b/public/models/persoprincipal/defaultmaterial_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e688a68595bb24f65e30562eec2b70a2d97b279d3257b1284f6cd1d7035ba297 +size 698280 diff --git a/public/models/persoprincipal/mc.bin b/public/models/persoprincipal/mc.bin new file mode 100644 index 0000000..7563476 --- /dev/null +++ b/public/models/persoprincipal/mc.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6da5321caee0edce2629f0c5cd322e3924cbc921adf6fc55c498536889aa48d4 +size 1916472 diff --git a/public/models/persoprincipal/model.gltf b/public/models/persoprincipal/model.gltf new file mode 100644 index 0000000..faa8ff2 --- /dev/null +++ b/public/models/persoprincipal/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:955c5c954519afef152989a1fb4d187e115fd5893a6a8f7119e4818bf87dc3ac +size 3136 diff --git a/public/models/potager/potager.bin b/public/models/potager/potager.bin new file mode 100644 index 0000000..2c41af6 --- /dev/null +++ b/public/models/potager/potager.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ba960011c92ed9d62232442d6244aaddffd76b0393679fcc8d99acbd0f2d708 +size 8208 diff --git a/public/models/potager/potager.gltf b/public/models/potager/potager.gltf new file mode 100644 index 0000000..b75caf5 --- /dev/null +++ b/public/models/potager/potager.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a98064f56d0e27f8cdd66d616fce7f5efe9182e2558ec5273e7c5005bb0bbdf +size 2971 diff --git a/public/models/potager/potager_baseColor.png b/public/models/potager/potager_baseColor.png new file mode 100644 index 0000000..1525482 --- /dev/null +++ b/public/models/potager/potager_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d303060d9b4c7b25697ecef91b94e85e3a7441e9338b4e5d63822522e92b1dd +size 572329 diff --git a/public/models/potager/potager_normal.png b/public/models/potager/potager_normal.png new file mode 100644 index 0000000..e9a7798 --- /dev/null +++ b/public/models/potager/potager_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:828b0d2fd954e6badc77023e5eddfce5bf583d0bb27a3b946e169b617af9c1f3 +size 2260045 diff --git a/public/models/potager/potager_occlusionRoughnessMetallic.png b/public/models/potager/potager_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..ef86030 --- /dev/null +++ b/public/models/potager/potager_occlusionRoughnessMetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92cf3d89aae778188dff99071d86e1c2ef911748c87f0ecec4eb5ef8d8149935 +size 16901 diff --git a/public/models/refroidisseur/model.bin b/public/models/refroidisseur/model.bin new file mode 100644 index 0000000..122a51e --- /dev/null +++ b/public/models/refroidisseur/model.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88f845a1a0cd3e5724e12128d69d5877924ac2e064da2192ff15366dfd40d979 +size 86688 diff --git a/public/models/refroidisseur/model.gltf b/public/models/refroidisseur/model.gltf new file mode 100644 index 0000000..06003c7 --- /dev/null +++ b/public/models/refroidisseur/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a73bf75ca13f413baeb14cf582801f8a5e91bd2d579d0f164e168a06cdd21591 +size 5443 diff --git a/public/models/refroidisseur/refroidisseur_base_color.png b/public/models/refroidisseur/refroidisseur_base_color.png new file mode 100644 index 0000000..d2b432c --- /dev/null +++ b/public/models/refroidisseur/refroidisseur_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db7b8f995889058dcd455939d00ace96a81b19d18819246bfbc7b7149b43d00c +size 459805 diff --git a/public/models/refroidisseur/refroidisseur_height.png b/public/models/refroidisseur/refroidisseur_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/refroidisseur/refroidisseur_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/refroidisseur/refroidisseur_metallic.png b/public/models/refroidisseur/refroidisseur_metallic.png new file mode 100644 index 0000000..b2057f8 --- /dev/null +++ b/public/models/refroidisseur/refroidisseur_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc4e1bf8a2a60bd9e2499ec2368396003339a5d226baf0cf7eba560ae96730d2 +size 315061 diff --git a/public/models/refroidisseur/refroidisseur_mixed_ao.png b/public/models/refroidisseur/refroidisseur_mixed_ao.png new file mode 100644 index 0000000..be616b5 --- /dev/null +++ b/public/models/refroidisseur/refroidisseur_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e054fe664150576e4a7295baa72eee6d6cd8d9793906bb2700b04d0bcfd18843 +size 450738 diff --git a/public/models/refroidisseur/refroidisseur_normal.png b/public/models/refroidisseur/refroidisseur_normal.png new file mode 100644 index 0000000..07a427e --- /dev/null +++ b/public/models/refroidisseur/refroidisseur_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4aae16604ac46db6f50bc3931326a4b8aaf6e6ca5282bd0142a3347240e3107c +size 344439 diff --git a/public/models/refroidisseur/refroidisseur_normal_opengl.png b/public/models/refroidisseur/refroidisseur_normal_opengl.png new file mode 100644 index 0000000..f47ff7d --- /dev/null +++ b/public/models/refroidisseur/refroidisseur_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cec6fc9f03c3cbca3f59df127f1b4f00898d5856d6b60d9a1343b7f6bba018dd +size 344394 diff --git a/public/models/refroidisseur/refroidisseur_roughness.png b/public/models/refroidisseur/refroidisseur_roughness.png new file mode 100644 index 0000000..6d75f96 --- /dev/null +++ b/public/models/refroidisseur/refroidisseur_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de7b20fbb9a50c6028ae4f2b8fee66879e1d553421c4a99755fdf21f11eb9b74 +size 262573 diff --git a/public/models/sapin/mat.1_basecolor.png b/public/models/sapin/mat.1_basecolor.png new file mode 100644 index 0000000..6240aa1 --- /dev/null +++ b/public/models/sapin/mat.1_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5d83cca6440eecb67ac2e87ad3a156985fabe746831f165370c9fc88c7d7b3d +size 388119 diff --git a/public/models/sapin/mat.1_normal.png b/public/models/sapin/mat.1_normal.png new file mode 100644 index 0000000..635fc57 --- /dev/null +++ b/public/models/sapin/mat.1_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8fdcaeb984716286644ac178fa1eb519bb8e64d0873afaa3b33406ae037c51a +size 2727481 diff --git a/public/models/sapin/mat.1_occlusionroughnessmetallic.png b/public/models/sapin/mat.1_occlusionroughnessmetallic.png new file mode 100644 index 0000000..78d1a6e --- /dev/null +++ b/public/models/sapin/mat.1_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d4e50b51b8c9d21af2ba47257a22d64fd92492686dc38bee94aa8f85660a01b +size 154331 diff --git a/public/models/sapin/mat_basecolor.png b/public/models/sapin/mat_basecolor.png new file mode 100644 index 0000000..c6f769d --- /dev/null +++ b/public/models/sapin/mat_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bc329c432869cece7f319a4b48bc55874eeca11905b94bdc6537692a92edb57 +size 267335 diff --git a/public/models/sapin/mat_normal.png b/public/models/sapin/mat_normal.png new file mode 100644 index 0000000..988577d --- /dev/null +++ b/public/models/sapin/mat_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:408c065a37a1092b8e0f54935e7c428650c640ec8d63391a6c5689804c9068f1 +size 2408254 diff --git a/public/models/sapin/mat_occlusionroughnessmetallic.png b/public/models/sapin/mat_occlusionroughnessmetallic.png new file mode 100644 index 0000000..0b2584b --- /dev/null +++ b/public/models/sapin/mat_occlusionroughnessmetallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e159b9b88d08812ffefe9f8a3b4605be01277038e89a37d0b1f9a7728a38cd0e +size 16901 diff --git a/public/models/sapin/model.gltf b/public/models/sapin/model.gltf new file mode 100644 index 0000000..df975c6 --- /dev/null +++ b/public/models/sapin/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:898e7cea4edeb0a5f3d46312fe6b63ec5f5db8d5642e2202ad86decf3817e40f +size 5811 diff --git a/public/models/sapin/sapin.bin b/public/models/sapin/sapin.bin new file mode 100644 index 0000000..6138a88 --- /dev/null +++ b/public/models/sapin/sapin.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52871cebeb728b03bf56a8c44ebc8bd90b0c075fddbb91c45a82a16847ee3d3c +size 1814928 diff --git a/public/models/sky/model.glb b/public/models/sky/model.glb new file mode 100644 index 0000000..0f3feb7 --- /dev/null +++ b/public/models/sky/model.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f2a39ec378538c2b28064437f794963fad4f3091dc960e5feca63dff7c60d85 +size 329420 diff --git a/public/models/talkie/antenne_Base_color.png b/public/models/talkie/antenne_Base_color.png new file mode 100644 index 0000000..db60934 --- /dev/null +++ b/public/models/talkie/antenne_Base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc5ba4130daed3b1edae078cc73ad5a4d9955c8c464abcbc6af2a80077842f7a +size 312866 diff --git a/public/models/talkie/antenne_Height.png b/public/models/talkie/antenne_Height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/talkie/antenne_Height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/talkie/antenne_Metallic.png b/public/models/talkie/antenne_Metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/talkie/antenne_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/talkie/antenne_Mixed_AO.png b/public/models/talkie/antenne_Mixed_AO.png new file mode 100644 index 0000000..3ba6e13 --- /dev/null +++ b/public/models/talkie/antenne_Mixed_AO.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3ec5ee97080be475cf3f3da71ca440dac8ccbe104510f519d1b4ee928d5b2b9 +size 187817 diff --git a/public/models/talkie/antenne_Roughness.png b/public/models/talkie/antenne_Roughness.png new file mode 100644 index 0000000..e6233e4 --- /dev/null +++ b/public/models/talkie/antenne_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91c3fb77f1204027e6d0d46e59dc6b68cd13fc68099b20612b75749460a3c8b8 +size 69233 diff --git a/public/models/talkie/antenne_normal.png b/public/models/talkie/antenne_normal.png new file mode 100644 index 0000000..bf16203 --- /dev/null +++ b/public/models/talkie/antenne_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da8e995d1260a97d5f1c099aa51ccf35123316264751c940bb5e71e3d115c133 +size 205065 diff --git a/public/models/talkie/antenne_normal_opengl.png b/public/models/talkie/antenne_normal_opengl.png new file mode 100644 index 0000000..7c1dc2b --- /dev/null +++ b/public/models/talkie/antenne_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1ec8c53b5e60fb3f13da3db4b787a6dd667f51c05eb92e925902b33c5fb68f5 +size 204007 diff --git a/public/models/talkie/boutona_Base_color.png b/public/models/talkie/boutona_Base_color.png new file mode 100644 index 0000000..f493f55 --- /dev/null +++ b/public/models/talkie/boutona_Base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef47a91bfd020de0983e75bc8bc03277b651c894da0f006dc49d5bfb91e86623 +size 473199 diff --git a/public/models/talkie/boutona_Height.png b/public/models/talkie/boutona_Height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/talkie/boutona_Height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/talkie/boutona_Metallic.png b/public/models/talkie/boutona_Metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/talkie/boutona_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/talkie/boutona_Mixed_AO.png b/public/models/talkie/boutona_Mixed_AO.png new file mode 100644 index 0000000..d28322d --- /dev/null +++ b/public/models/talkie/boutona_Mixed_AO.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f45671cafcc91e40f67120f22f7b58c75a9bf5289f8dd67019d7c836253e8942 +size 244343 diff --git a/public/models/talkie/boutona_Roughness.png b/public/models/talkie/boutona_Roughness.png new file mode 100644 index 0000000..daa6677 --- /dev/null +++ b/public/models/talkie/boutona_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:adaedf0e287c99669b47a0e3e3b99ed2c6f1f5a761e69168418e56610d7d8f9a +size 54561 diff --git a/public/models/talkie/boutona_normal.png b/public/models/talkie/boutona_normal.png new file mode 100644 index 0000000..4cf6138 --- /dev/null +++ b/public/models/talkie/boutona_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c0e761804d0546dc6cd9d929fdecab8f6852866a6f2025c03c76c34b2040ee0 +size 175939 diff --git a/public/models/talkie/boutona_normal_opengl.png b/public/models/talkie/boutona_normal_opengl.png new file mode 100644 index 0000000..80416e2 --- /dev/null +++ b/public/models/talkie/boutona_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86c390222c74160382b92a5736664c37e3861937db2e2d58699643516fc52995 +size 176366 diff --git a/public/models/talkie/boutonb_Base_color.png b/public/models/talkie/boutonb_Base_color.png new file mode 100644 index 0000000..ed59d49 --- /dev/null +++ b/public/models/talkie/boutonb_Base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84973c1c06bf83beacd0987c5d252560ec92e09701b3ac6b73289f39392c21da +size 496575 diff --git a/public/models/talkie/boutonb_Height.png b/public/models/talkie/boutonb_Height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/talkie/boutonb_Height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/talkie/boutonb_Metallic.png b/public/models/talkie/boutonb_Metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/talkie/boutonb_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/talkie/boutonb_Mixed_AO.png b/public/models/talkie/boutonb_Mixed_AO.png new file mode 100644 index 0000000..8292910 --- /dev/null +++ b/public/models/talkie/boutonb_Mixed_AO.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d58e8ec5e90125edd38f279ee1b9fee863b75d8396fef6a0c22a9af6a9c0a341 +size 239658 diff --git a/public/models/talkie/boutonb_Roughness.png b/public/models/talkie/boutonb_Roughness.png new file mode 100644 index 0000000..acba391 --- /dev/null +++ b/public/models/talkie/boutonb_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad833228a99505d0fea7495970bfbf47d3942a86d457aca379b99d8224d8633f +size 54522 diff --git a/public/models/talkie/boutonb_normal.png b/public/models/talkie/boutonb_normal.png new file mode 100644 index 0000000..943917e --- /dev/null +++ b/public/models/talkie/boutonb_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53e5d76fcd40986674d93f289cea51c6b839d720787d98ed4112164e20558bd2 +size 176052 diff --git a/public/models/talkie/boutonb_normal_opengl.png b/public/models/talkie/boutonb_normal_opengl.png new file mode 100644 index 0000000..47539e6 --- /dev/null +++ b/public/models/talkie/boutonb_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a1ea79411217de422f6f7fd160c673e01a2a09ebce1d95a6b5e63c8c1e5de18 +size 176723 diff --git a/public/models/talkie/cable1_Base_color.png b/public/models/talkie/cable1_Base_color.png new file mode 100644 index 0000000..0aa378c --- /dev/null +++ b/public/models/talkie/cable1_Base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:967d8771fbcd5427bf0005b147ecae38ab53271c4a99980f34db1102d0cb35f0 +size 178767 diff --git a/public/models/talkie/cable1_Height.png b/public/models/talkie/cable1_Height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/talkie/cable1_Height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/talkie/cable1_Metallic.png b/public/models/talkie/cable1_Metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/talkie/cable1_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/talkie/cable1_Mixed_AO.png b/public/models/talkie/cable1_Mixed_AO.png new file mode 100644 index 0000000..2003ade --- /dev/null +++ b/public/models/talkie/cable1_Mixed_AO.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73378e05b925c39d37e3cfdb1048d891b6216927fbd9eab111a61051d9c5fadd +size 142820 diff --git a/public/models/talkie/cable1_Roughness.png b/public/models/talkie/cable1_Roughness.png new file mode 100644 index 0000000..c1c9096 --- /dev/null +++ b/public/models/talkie/cable1_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a2a10008bf8a90ca1e7ef831944d8e6b2c41d5aa9b5a1f20f49d7dcc942b9f2 +size 56819 diff --git a/public/models/talkie/cable1_normal.png b/public/models/talkie/cable1_normal.png new file mode 100644 index 0000000..8f1338d --- /dev/null +++ b/public/models/talkie/cable1_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb4d0e26c8d16fcb471e2812585fb27ddfbce4500c526fce512d61f7611c5b99 +size 186292 diff --git a/public/models/talkie/cable1_normal_opengl.png b/public/models/talkie/cable1_normal_opengl.png new file mode 100644 index 0000000..456bcdb --- /dev/null +++ b/public/models/talkie/cable1_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8854c9b53ac97ce6113d351aa528d9a6664e90947678aee05731695e034d1c79 +size 187073 diff --git a/public/models/talkie/cable2_Base_color.png b/public/models/talkie/cable2_Base_color.png new file mode 100644 index 0000000..ba3be72 --- /dev/null +++ b/public/models/talkie/cable2_Base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b1bdd610bf135a20471bc2b65e53f2a2333ab6e845380f55fed93d45dce1897 +size 210840 diff --git a/public/models/talkie/cable2_Height.png b/public/models/talkie/cable2_Height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/talkie/cable2_Height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/talkie/cable2_Metallic.png b/public/models/talkie/cable2_Metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/talkie/cable2_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/talkie/cable2_Mixed_AO.png b/public/models/talkie/cable2_Mixed_AO.png new file mode 100644 index 0000000..e3a64cc --- /dev/null +++ b/public/models/talkie/cable2_Mixed_AO.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30e8e315f7950a91135bba6ac43d04563b1e501e83cfb4b57048320768602bc5 +size 167450 diff --git a/public/models/talkie/cable2_Roughness.png b/public/models/talkie/cable2_Roughness.png new file mode 100644 index 0000000..a9440d4 --- /dev/null +++ b/public/models/talkie/cable2_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d9e3cc7a29c90c1712196c05915bea480124dddc0db47db9415f738f895660c +size 60898 diff --git a/public/models/talkie/cable2_normal.png b/public/models/talkie/cable2_normal.png new file mode 100644 index 0000000..acc99fd --- /dev/null +++ b/public/models/talkie/cable2_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b09eae40cd4d52c348933f49347691f08dc6f25d929ddadbeddd2157444c706f +size 195384 diff --git a/public/models/talkie/cable2_normal_opengl.png b/public/models/talkie/cable2_normal_opengl.png new file mode 100644 index 0000000..d184f94 --- /dev/null +++ b/public/models/talkie/cable2_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff1ae64895328cae7bf8816a43eb38ca4db82b7979d02db927bc307fa7ec66c6 +size 196010 diff --git a/public/models/talkie/cadre_Base_color.png b/public/models/talkie/cadre_Base_color.png new file mode 100644 index 0000000..afbe925 --- /dev/null +++ b/public/models/talkie/cadre_Base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:637e73b56419d8390e8aeb27433fdee21264c9d5b28e1caaf7e1357f6c191fd3 +size 280049 diff --git a/public/models/talkie/cadre_Height.png b/public/models/talkie/cadre_Height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/talkie/cadre_Height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/talkie/cadre_Metallic.png b/public/models/talkie/cadre_Metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/talkie/cadre_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/talkie/cadre_Mixed_AO.png b/public/models/talkie/cadre_Mixed_AO.png new file mode 100644 index 0000000..0151384 --- /dev/null +++ b/public/models/talkie/cadre_Mixed_AO.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1f9912860e5f508e0e29ef1e03943101cdb287a1c34bd0278ac237824255da5 +size 157683 diff --git a/public/models/talkie/cadre_Roughness.png b/public/models/talkie/cadre_Roughness.png new file mode 100644 index 0000000..264a6e4 --- /dev/null +++ b/public/models/talkie/cadre_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afb28f17a6f419356f49088c8c5b8d12b6aaf2471ee7e35ef42e0fda3a5365f2 +size 53784 diff --git a/public/models/talkie/cadre_normal.png b/public/models/talkie/cadre_normal.png new file mode 100644 index 0000000..8d2a5a3 --- /dev/null +++ b/public/models/talkie/cadre_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:982a042d28061ce926c80293129985733aba79bc7115e0f5b55e4f4186808f5a +size 52817 diff --git a/public/models/talkie/cadre_normal_opengl.png b/public/models/talkie/cadre_normal_opengl.png new file mode 100644 index 0000000..46032a3 --- /dev/null +++ b/public/models/talkie/cadre_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58d63de38e7fefca7ec94b4057b37256c477dbd2749f950f0bd373932b52cbf9 +size 52737 diff --git a/public/models/talkie/e_cran_base_color.png b/public/models/talkie/e_cran_base_color.png new file mode 100644 index 0000000..a8c2149 --- /dev/null +++ b/public/models/talkie/e_cran_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f75363e70ffed07a182a3e61d7c301cb8fd053ac8ec784d56e9450335167dce8 +size 6502 diff --git a/public/models/talkie/e_cran_height.png b/public/models/talkie/e_cran_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/talkie/e_cran_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/talkie/e_cran_metallic.png b/public/models/talkie/e_cran_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/talkie/e_cran_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/talkie/e_cran_mixed_ao.png b/public/models/talkie/e_cran_mixed_ao.png new file mode 100644 index 0000000..e91eb6e --- /dev/null +++ b/public/models/talkie/e_cran_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4f3fd92f328f0733f9cb9865209466c251f4edf4750654eb0385b519300cf34 +size 329712 diff --git a/public/models/talkie/e_cran_normal.png b/public/models/talkie/e_cran_normal.png new file mode 100644 index 0000000..175152b --- /dev/null +++ b/public/models/talkie/e_cran_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a07dd2bf8fe1d37b9a74d2ca09b8a77d8a97029d41a8483e34ba30bd8e9efc04 +size 74315 diff --git a/public/models/talkie/e_cran_normal_opengl.png b/public/models/talkie/e_cran_normal_opengl.png new file mode 100644 index 0000000..e17d9b3 --- /dev/null +++ b/public/models/talkie/e_cran_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca90a32ea8b1dadb11a5cf2e41d4d096920fd7a2ab493dec96682a479d4e7a8c +size 74316 diff --git a/public/models/talkie/e_cran_roughness.png b/public/models/talkie/e_cran_roughness.png new file mode 100644 index 0000000..ad642fa --- /dev/null +++ b/public/models/talkie/e_cran_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:700f97e34547f14a62a45858218e56eb3715a278380c2f1f14be9ca6feaae959 +size 72768 diff --git a/public/models/talkie/hautparleur_Base_color.png b/public/models/talkie/hautparleur_Base_color.png new file mode 100644 index 0000000..9e43314 --- /dev/null +++ b/public/models/talkie/hautparleur_Base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36168017b9c2d6961f869f461812a53ed4bdb49f24f350452f4604009acb6fe8 +size 658014 diff --git a/public/models/talkie/hautparleur_Height.png b/public/models/talkie/hautparleur_Height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/talkie/hautparleur_Height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/talkie/hautparleur_Metallic.png b/public/models/talkie/hautparleur_Metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/talkie/hautparleur_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/talkie/hautparleur_Mixed_AO.png b/public/models/talkie/hautparleur_Mixed_AO.png new file mode 100644 index 0000000..f2bfd28 --- /dev/null +++ b/public/models/talkie/hautparleur_Mixed_AO.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29ac8737a9eccff201ad351b4b403e93ed65c7a082052c1070455bc7c795bcbb +size 201135 diff --git a/public/models/talkie/hautparleur_Roughness.png b/public/models/talkie/hautparleur_Roughness.png new file mode 100644 index 0000000..17ac038 --- /dev/null +++ b/public/models/talkie/hautparleur_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f7c1e238150662981d8a4d003045021ee0cca21af14dc79a9180671c6a82621 +size 74349 diff --git a/public/models/talkie/hautparleur_normal.png b/public/models/talkie/hautparleur_normal.png new file mode 100644 index 0000000..5d89a1c --- /dev/null +++ b/public/models/talkie/hautparleur_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65edaf688e31787da8deb864079aed068cc71775dbbde4c350481896c2d8a204 +size 98635 diff --git a/public/models/talkie/hautparleur_normal_opengl.png b/public/models/talkie/hautparleur_normal_opengl.png new file mode 100644 index 0000000..814a2b3 --- /dev/null +++ b/public/models/talkie/hautparleur_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c689c9d28d984f54e5647de38423f34da442b8e8cac076052bfa5df07457fb1c +size 98768 diff --git a/public/models/talkie/model.bin b/public/models/talkie/model.bin new file mode 100644 index 0000000..7e13375 --- /dev/null +++ b/public/models/talkie/model.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:970717ff52a7275d1e6090a8699021ae57b74127b05c016eafb6dca45158420f +size 198456 diff --git a/public/models/talkie/model.gltf b/public/models/talkie/model.gltf new file mode 100644 index 0000000..5caa83b --- /dev/null +++ b/public/models/talkie/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:694acec3219e3cb0dbce0e9739bc3c6655dbf5360f104d50a52618e06d90c946 +size 63007 diff --git a/public/models/talkie/prise_Base_color.png b/public/models/talkie/prise_Base_color.png new file mode 100644 index 0000000..30d846f --- /dev/null +++ b/public/models/talkie/prise_Base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e012c8775c6eb274a80856394fac6673f7beb754c2f78e1c51c842ac017fdd5 +size 472487 diff --git a/public/models/talkie/prise_Height.png b/public/models/talkie/prise_Height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/talkie/prise_Height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/talkie/prise_Metallic.png b/public/models/talkie/prise_Metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/talkie/prise_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/talkie/prise_Mixed_AO.png b/public/models/talkie/prise_Mixed_AO.png new file mode 100644 index 0000000..1ed1f71 --- /dev/null +++ b/public/models/talkie/prise_Mixed_AO.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a7ae156409544d00bf3332fb542aaec0711d4ce4c9d39d4bd573fb4f0212051 +size 230780 diff --git a/public/models/talkie/prise_Roughness.png b/public/models/talkie/prise_Roughness.png new file mode 100644 index 0000000..c827dba --- /dev/null +++ b/public/models/talkie/prise_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:161c97c6ad56619ad702cc955a26ea92203575f31bb35ebc8ca398b48aae9889 +size 100073 diff --git a/public/models/talkie/prise_normal.png b/public/models/talkie/prise_normal.png new file mode 100644 index 0000000..5bcffa1 --- /dev/null +++ b/public/models/talkie/prise_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c00bec06b1bbd954eb91872fa8cd817f247b275c16e6c372db52189ba6f3778 +size 212856 diff --git a/public/models/talkie/prise_normal_opengl.png b/public/models/talkie/prise_normal_opengl.png new file mode 100644 index 0000000..6bc3741 --- /dev/null +++ b/public/models/talkie/prise_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa8e8d378a595f8aafc4cd34ecac65426a03847a402f955b7b0280f0383d40eb +size 224961 diff --git a/public/models/talkie/talkie_Base_color.png b/public/models/talkie/talkie_Base_color.png new file mode 100644 index 0000000..295b322 --- /dev/null +++ b/public/models/talkie/talkie_Base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aead25dde6c940f353fa4a53f43e37dde80cd924ccced3c4f88a45dcc5fb4751 +size 610827 diff --git a/public/models/talkie/talkie_Height.png b/public/models/talkie/talkie_Height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/talkie/talkie_Height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/talkie/talkie_Metallic.png b/public/models/talkie/talkie_Metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/talkie/talkie_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/talkie/talkie_Mixed_AO.png b/public/models/talkie/talkie_Mixed_AO.png new file mode 100644 index 0000000..a015f96 --- /dev/null +++ b/public/models/talkie/talkie_Mixed_AO.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78a4e5bc8affb8c7f529712ddcc4958982729d4bbc64163d93b4e3df8192f3e9 +size 229809 diff --git a/public/models/talkie/talkie_Roughness.png b/public/models/talkie/talkie_Roughness.png new file mode 100644 index 0000000..091f911 --- /dev/null +++ b/public/models/talkie/talkie_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:638da789f9d21dfa546cf053c5f0c4e47fc18eef3d7ac412d39bdbdf3b3974b1 +size 134252 diff --git a/public/models/talkie/talkie_normal.png b/public/models/talkie/talkie_normal.png new file mode 100644 index 0000000..d3e9649 --- /dev/null +++ b/public/models/talkie/talkie_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fba8c5bce232fd16f3d3da18228438443bfcfab6f1673884e2c6439a799a342 +size 285066 diff --git a/public/models/talkie/talkie_normal_opengl.png b/public/models/talkie/talkie_normal_opengl.png new file mode 100644 index 0000000..e58b36f --- /dev/null +++ b/public/models/talkie/talkie_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:708382f0b0340e845f0cd4127b84ec5f837f5ca3a51f0c4064ba790374643c21 +size 287356 diff --git a/public/models/talkie/touches_Base_color.png b/public/models/talkie/touches_Base_color.png new file mode 100644 index 0000000..eb7631c --- /dev/null +++ b/public/models/talkie/touches_Base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47ef413967a461f9768b74d15a2ab1c8e3e3fe6fc9be027d8cf1e7268bb8a888 +size 126096 diff --git a/public/models/talkie/touches_Height.png b/public/models/talkie/touches_Height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/talkie/touches_Height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/talkie/touches_Metallic.png b/public/models/talkie/touches_Metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/talkie/touches_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/talkie/touches_Mixed_AO.png b/public/models/talkie/touches_Mixed_AO.png new file mode 100644 index 0000000..5673800 --- /dev/null +++ b/public/models/talkie/touches_Mixed_AO.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6884417f47afb4330b00f9f42fa1faf7b8bbd06d2ae9595cffe409a4d38cd461 +size 246760 diff --git a/public/models/talkie/touches_Roughness.png b/public/models/talkie/touches_Roughness.png new file mode 100644 index 0000000..bd73a19 --- /dev/null +++ b/public/models/talkie/touches_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5804a17a4fd8691a606cbb3e671234f2c876dfc0e88e16d2f781bd35785167cb +size 56002 diff --git a/public/models/talkie/touches_normal.png b/public/models/talkie/touches_normal.png new file mode 100644 index 0000000..07f83c4 --- /dev/null +++ b/public/models/talkie/touches_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09d922e17cd400c32ef51462723e9c5ee9dfbe4c668e88e6057f9e7d60c8830e +size 59216 diff --git a/public/models/talkie/touches_normal_opengl.png b/public/models/talkie/touches_normal_opengl.png new file mode 100644 index 0000000..ec98ce7 --- /dev/null +++ b/public/models/talkie/touches_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cdadc48d7d365fa0f8e4f3b64a61a6fa95cf9bcafaf50dc83ea7cf90173f68b +size 59178 diff --git a/public/models/talkie/écran_Base_color.png b/public/models/talkie/écran_Base_color.png new file mode 100644 index 0000000..b28a367 --- /dev/null +++ b/public/models/talkie/écran_Base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfbf65890f6d5019bf113246dcecb83c97e31d6e10b0429d5e89df9df67a58bd +size 6498 diff --git a/public/models/talkie/écran_Height.png b/public/models/talkie/écran_Height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/talkie/écran_Height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/talkie/écran_Metallic.png b/public/models/talkie/écran_Metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/talkie/écran_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/talkie/écran_Mixed_AO.png b/public/models/talkie/écran_Mixed_AO.png new file mode 100644 index 0000000..e91eb6e --- /dev/null +++ b/public/models/talkie/écran_Mixed_AO.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4f3fd92f328f0733f9cb9865209466c251f4edf4750654eb0385b519300cf34 +size 329712 diff --git a/public/models/talkie/écran_Normal.png b/public/models/talkie/écran_Normal.png new file mode 100644 index 0000000..175152b --- /dev/null +++ b/public/models/talkie/écran_Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a07dd2bf8fe1d37b9a74d2ca09b8a77d8a97029d41a8483e34ba30bd8e9efc04 +size 74315 diff --git a/public/models/talkie/écran_Normal_OpenGL.png b/public/models/talkie/écran_Normal_OpenGL.png new file mode 100644 index 0000000..e17d9b3 --- /dev/null +++ b/public/models/talkie/écran_Normal_OpenGL.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca90a32ea8b1dadb11a5cf2e41d4d096920fd7a2ab493dec96682a479d4e7a8c +size 74316 diff --git a/public/models/talkie/écran_Roughness.png b/public/models/talkie/écran_Roughness.png new file mode 100644 index 0000000..d89bffa --- /dev/null +++ b/public/models/talkie/écran_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d560c80d755fdfff917c63d96d664cc4c9be963cc38ac63533418f7df1b0642c +size 72772 diff --git a/public/models/terrain/model.gltf b/public/models/terrain/model.gltf new file mode 100644 index 0000000..b296aac --- /dev/null +++ b/public/models/terrain/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f554cc203748ddb5ec6c43e0761b9767f72177d94b388a6d9195cbdc8f5895f4 +size 84222 diff --git a/public/skybox/sky.exr b/public/skybox/sky.exr deleted file mode 100644 index c6ff8e1..0000000 --- a/public/skybox/sky.exr +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:904b303c98f865526b9524b955f440e630f29d8e18a57bb7bf443fcd9715add1 -size 83079911 diff --git a/public/sounds/dialogue/fermier_coupdemain.mp3 b/public/sounds/dialogue/fermier_coupdemain.mp3 new file mode 100644 index 0000000..8e12e14 --- /dev/null +++ b/public/sounds/dialogue/fermier_coupdemain.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4767335bdefacbd4e0b8ea45b8ffe26ff5bcfc7312d1dfa75e552fb48a496ad4 +size 96667 diff --git a/public/sounds/dialogue/fermier_coupdemain_2.mp3 b/public/sounds/dialogue/fermier_coupdemain_2.mp3 new file mode 100644 index 0000000..90dda6c --- /dev/null +++ b/public/sounds/dialogue/fermier_coupdemain_2.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:728992dad4e5073ccba58956dafdd41f4cc820128f0624870195f8a77b4b60ab +size 106992 diff --git a/public/sounds/dialogue/fermier_findemission.mp3 b/public/sounds/dialogue/fermier_findemission.mp3 new file mode 100644 index 0000000..05ed588 --- /dev/null +++ b/public/sounds/dialogue/fermier_findemission.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9095d211234b93dd83e8f49b5ca51e2598e1c9ecd8c9b53421708f4f61aa08c +size 197904 diff --git a/public/sounds/dialogue/narrateur_arrivéferme.mp3 b/public/sounds/dialogue/narrateur_arrivéferme.mp3 new file mode 100644 index 0000000..be19ff8 --- /dev/null +++ b/public/sounds/dialogue/narrateur_arrivéferme.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af280daa0bd0ff8ebcbdcbc76be836491c55d341c97e6525a600272dd568e1b9 +size 319922 diff --git a/public/sounds/dialogue/narrateur_bienvenueaaltera.mp3 b/public/sounds/dialogue/narrateur_bienvenueaaltera.mp3 new file mode 100644 index 0000000..6d8d66f --- /dev/null +++ b/public/sounds/dialogue/narrateur_bienvenueaaltera.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99728d683e5f3f74d84655895892df963c583a14c08c74a6099a8d741457b73f +size 75410 diff --git a/public/sounds/dialogue/narrateur_bonnechance.mp3 b/public/sounds/dialogue/narrateur_bonnechance.mp3 new file mode 100644 index 0000000..a7081a8 --- /dev/null +++ b/public/sounds/dialogue/narrateur_bonnechance.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d2d41427237827c6f5788f720f3615236f89621a4218479710d74ec434249da +size 63169 diff --git a/public/sounds/dialogue/narrateur_coupureélec.mp3 b/public/sounds/dialogue/narrateur_coupureélec.mp3 new file mode 100644 index 0000000..e6188f0 --- /dev/null +++ b/public/sounds/dialogue/narrateur_coupureélec.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9137ebcf24a5899161a695b841a1d9f8e6c86d6bc75e8c66797cedce279c0931 +size 249409 diff --git a/public/sounds/dialogue/narrateur_courantréparé.mp3 b/public/sounds/dialogue/narrateur_courantréparé.mp3 new file mode 100644 index 0000000..d43d4e0 --- /dev/null +++ b/public/sounds/dialogue/narrateur_courantréparé.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5258f576ffdaaf8235ae1eab8caecc6bc083654887b402bc102277ebb62663e +size 199873 diff --git a/public/sounds/dialogue/narrateur_createurdepluiecréé.mp3 b/public/sounds/dialogue/narrateur_createurdepluiecréé.mp3 new file mode 100644 index 0000000..f781a77 --- /dev/null +++ b/public/sounds/dialogue/narrateur_createurdepluiecréé.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1742b6b4011af34e0e006a8d8b904f8eb6a68b5dc8466a121be720828a5099b +size 274418 diff --git a/public/sounds/dialogue/narrateur_ebikecassé.mp3 b/public/sounds/dialogue/narrateur_ebikecassé.mp3 new file mode 100644 index 0000000..e7ab4e9 --- /dev/null +++ b/public/sounds/dialogue/narrateur_ebikecassé.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9888daae0831bfe5d8241f752af757ddcf6f0772ddaf846e6c4889998b0e88f3 +size 345673 diff --git a/public/sounds/dialogue/narrateur_ebikeréparé.mp3 b/public/sounds/dialogue/narrateur_ebikeréparé.mp3 new file mode 100644 index 0000000..e4cebad --- /dev/null +++ b/public/sounds/dialogue/narrateur_ebikeréparé.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88a27ae225b836c06f2ae24bf61185b33afa8a8b7cbed1f070091bfee6392f54 +size 136273 diff --git a/public/sounds/dialogue/narrateur_fouillelecentre.mp3 b/public/sounds/dialogue/narrateur_fouillelecentre.mp3 new file mode 100644 index 0000000..fb1609b --- /dev/null +++ b/public/sounds/dialogue/narrateur_fouillelecentre.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:271be4d67456da4d55d0b4d1a2573fac86df3e7afb624e40621eca01f0278e89 +size 126217 diff --git a/public/sounds/dialogue/narrateur_galetscan.mp3 b/public/sounds/dialogue/narrateur_galetscan.mp3 new file mode 100644 index 0000000..625e204 --- /dev/null +++ b/public/sounds/dialogue/narrateur_galetscan.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f5a7b6f8db95d208991c295369f64e8de3eaecf86132be3f40670bf855a633f +size 129183 diff --git a/public/sounds/dialogue/narrateur_histoireleonie.mp3 b/public/sounds/dialogue/narrateur_histoireleonie.mp3 new file mode 100644 index 0000000..0720a84 --- /dev/null +++ b/public/sounds/dialogue/narrateur_histoireleonie.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d035a908c418b0a9bf2e2d783b63562f49175b62aad0ca0ca4e077cb34fdb40 +size 864159 diff --git a/public/sounds/dialogue/narrateur_interactionrefroidisseur.mp3 b/public/sounds/dialogue/narrateur_interactionrefroidisseur.mp3 new file mode 100644 index 0000000..4955f0d --- /dev/null +++ b/public/sounds/dialogue/narrateur_interactionrefroidisseur.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2830f29cea45a14cce19af337898a544c5e76263b5431d8ec271675688a53ef +size 175264 diff --git a/public/sounds/dialogue/narrateur_interactiontuyauxlac.mp3 b/public/sounds/dialogue/narrateur_interactiontuyauxlac.mp3 new file mode 100644 index 0000000..a544b06 --- /dev/null +++ b/public/sounds/dialogue/narrateur_interactiontuyauxlac.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a50f7ddce924006ce4a52fd46885744727fea8b408fe5fbb2e2e666a51b5810 +size 109984 diff --git a/public/sounds/dialogue/narrateur_intro_apresprenom.mp3 b/public/sounds/dialogue/narrateur_intro_apresprenom.mp3 new file mode 100644 index 0000000..6f2f0e5 --- /dev/null +++ b/public/sounds/dialogue/narrateur_intro_apresprenom.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3723695e427d8256e0d846363cea5314a9a82051aeefbee0c9ab96755577192 +size 173343 diff --git a/public/sounds/dialogue/narrateur_intro_prenom.mp3 b/public/sounds/dialogue/narrateur_intro_prenom.mp3 new file mode 100644 index 0000000..a8e240b --- /dev/null +++ b/public/sounds/dialogue/narrateur_intro_prenom.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d03f835b8fbbf11529fe8ffba18ff4c4e39d5ff68940bd4f93e5b50cb5c44ea9 +size 185631 diff --git a/public/sounds/dialogue/narrateur_ordredemandedelaide.mp3 b/public/sounds/dialogue/narrateur_ordredemandedelaide.mp3 new file mode 100644 index 0000000..63a659d --- /dev/null +++ b/public/sounds/dialogue/narrateur_ordredemandedelaide.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5189a8bee6af605b10896c97ee2afc0221f4fa1e4c98203039f9b6377e28ac2d +size 72351 diff --git a/public/sounds/dialogue/narrateur_ordreebike.mp3 b/public/sounds/dialogue/narrateur_ordreebike.mp3 new file mode 100644 index 0000000..6884f39 --- /dev/null +++ b/public/sounds/dialogue/narrateur_ordreebike.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6960bb65986d0ae0377a30d50717004599da9df0dec2b4376ded72b70baae18f +size 97311 diff --git a/public/sounds/dialogue/narrateur_poteauéleccassé.mp3 b/public/sounds/dialogue/narrateur_poteauéleccassé.mp3 new file mode 100644 index 0000000..c3508b5 --- /dev/null +++ b/public/sounds/dialogue/narrateur_poteauéleccassé.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b23172b44542d0c8d98214043f3c2858ad27876e6294192ae23ee77c7681e73 +size 157599 diff --git a/public/sounds/dialogue/narrateur_présentationatelier.mp3 b/public/sounds/dialogue/narrateur_présentationatelier.mp3 new file mode 100644 index 0000000..6ca1c5a --- /dev/null +++ b/public/sounds/dialogue/narrateur_présentationatelier.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3aea5ee659fbe5845435ab65287f5b87d2c9c7afa146cb1bb555a8aaa10b7db +size 537759 diff --git a/public/sounds/dialogue/narrateur_présentationoutils.mp3 b/public/sounds/dialogue/narrateur_présentationoutils.mp3 new file mode 100644 index 0000000..5d19d60 --- /dev/null +++ b/public/sounds/dialogue/narrateur_présentationoutils.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a3125a2212377d581293ed7cb88562fbc523f9831586185a3f8d1f74b6e93d2 +size 236319 diff --git a/public/sounds/dialogue/narrateur_refroidisseurcassé.mp3 b/public/sounds/dialogue/narrateur_refroidisseurcassé.mp3 new file mode 100644 index 0000000..fcabe15 --- /dev/null +++ b/public/sounds/dialogue/narrateur_refroidisseurcassé.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66c1a53b72ff92d57bb1b412b3b2174d1060f438c3bd7e5847308d3b347e61fc +size 91551 diff --git a/public/sounds/dialogue/narrateur_remerciement.mp3 b/public/sounds/dialogue/narrateur_remerciement.mp3 new file mode 100644 index 0000000..f5ad231 --- /dev/null +++ b/public/sounds/dialogue/narrateur_remerciement.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:452927c3c28f5c05d2380b3af3022547187b0f9cd62a805c3d7ec089443ed356 +size 184480 diff --git a/public/sounds/dialogue/narrateur_routeversferme.mp3 b/public/sounds/dialogue/narrateur_routeversferme.mp3 new file mode 100644 index 0000000..44c908b --- /dev/null +++ b/public/sounds/dialogue/narrateur_routeversferme.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba38769e5ff93779f16b2b3355753c42d20506fdb3be482e3e54ee7265eff63f +size 85791 diff --git a/public/sounds/effect/close-malette.mp3 b/public/sounds/effect/close-malette.mp3 new file mode 100644 index 0000000..1f3f1e8 --- /dev/null +++ b/public/sounds/effect/close-malette.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bb923d3b44ca6ebecdfcc45a6cba4a5666d7f7ecb66250fc427d7dcb6b5b3b1 +size 25077 diff --git a/public/sounds/fa.mp3 b/public/sounds/effect/fa.mp3 similarity index 100% rename from public/sounds/fa.mp3 rename to public/sounds/effect/fa.mp3 diff --git a/public/sounds/effect/open-malette.mp3 b/public/sounds/effect/open-malette.mp3 new file mode 100644 index 0000000..44433f6 --- /dev/null +++ b/public/sounds/effect/open-malette.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93c7a033aa7eef8ce9fa012b5ad3ef1fa961e1f842e21641fc02025ee4d6f4fd +size 12288 diff --git a/public/sounds/musique/test.mp3 b/public/sounds/musique/test.mp3 new file mode 100644 index 0000000..d4095f6 --- /dev/null +++ b/public/sounds/musique/test.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de4036838a022ff91e10dabf4db2c0e9e41f49c5fc6e59b571100d6aaba29cd2 +size 48225313 diff --git a/src/components/debug/DebugPerf.tsx b/src/components/debug/DebugPerf.tsx index 9053017..f86f61a 100644 --- a/src/components/debug/DebugPerf.tsx +++ b/src/components/debug/DebugPerf.tsx @@ -1,18 +1,24 @@ import { Suspense, lazy } from "react"; -import { Debug } from "@/utils/debug/Debug"; +import { useShowDebugPerf } from "@/hooks/debug/useShowDebugPerf"; const Perf = lazy(() => import("r3f-perf").then((m) => ({ default: m.Perf }))); -export function DebugPerf(): React.JSX.Element | null { - const debug = Debug.getInstance(); +const DEBUG_GUI_WIDTH = 245; +const DEBUG_PANEL_GAP = 20; - if (!debug.active) { +export function DebugPerf(): React.JSX.Element | null { + const showDebugPerf = useShowDebugPerf(); + + if (!showDebugPerf) { return null; } return ( - + ); } diff --git a/src/components/debug/scene/DebugCameraControls.tsx b/src/components/debug/scene/DebugCameraControls.tsx index 11acdb0..8a83c21 100644 --- a/src/components/debug/scene/DebugCameraControls.tsx +++ b/src/components/debug/scene/DebugCameraControls.tsx @@ -8,7 +8,7 @@ import { PLAYER_EYE_HEIGHT, PLAYER_SPAWN_POSITION_GAME, } from "@/data/player/playerConfig"; -import type { Vector3Tuple } from "@/types/three"; +import type { Vector3Tuple } from "@/types/three/three"; const DEBUG_CAMERA_TARGET: Vector3Tuple = [ PLAYER_SPAWN_POSITION_GAME[0], diff --git a/src/components/editor/EditorControls.tsx b/src/components/editor/EditorControls.tsx index e68cd70..143d666 100644 --- a/src/components/editor/EditorControls.tsx +++ b/src/components/editor/EditorControls.tsx @@ -12,7 +12,7 @@ import { Save, Undo2, } from "lucide-react"; -import type { MapNode, TransformMode } from "@/types/editor"; +import type { MapNode, TransformMode } from "@/types/editor/editor"; interface EditorControlsProps { transformMode: TransformMode; diff --git a/src/components/editor/scene/EditorMap.tsx b/src/components/editor/scene/EditorMap.tsx index 8b94718..baea5a6 100644 --- a/src/components/editor/scene/EditorMap.tsx +++ b/src/components/editor/scene/EditorMap.tsx @@ -1,9 +1,11 @@ -import { useMemo, useRef, useEffect, useState } from "react"; -import { Grid, TransformControls, useGLTF } from "@react-three/drei"; +import { useRef, useEffect, useState } from "react"; +import { Grid, TransformControls } from "@react-three/drei"; import type { ThreeEvent } from "@react-three/fiber"; import * as THREE from "three"; -import type { SceneData, MapNode, TransformMode } from "@/types/editor"; +import { useClonedObject } from "@/hooks/three/useClonedObject"; +import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; +import type { SceneData, MapNode, TransformMode } from "@/types/editor/editor"; interface EditorMapProps { sceneData: SceneData; @@ -138,7 +140,7 @@ export function EditorMap({ const objectsMapRef = useRef>(new Map()); const handleTransformMouseDown = () => { - onTransformStart?.(); + onTransformStart(); }; const handleTransformMouseUp = () => { @@ -153,10 +155,10 @@ export function EditorMap({ rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], scale: [obj.scale.x, obj.scale.y, obj.scale.z], }; - onNodeTransform?.(selectedNodeIndex, updatedNode); + onNodeTransform(selectedNodeIndex, updatedNode); } } - onTransformEnd?.(); + onTransformEnd(); }; const [selectedObject, setSelectedObject] = useState( @@ -257,9 +259,13 @@ function EditorModelNode({ const originalMaterialsRef = useRef( new Map(), ); - const { scene } = useGLTF(modelUrl); - - const sceneInstance = useMemo(() => scene.clone(true), [scene]); + const { scene } = useLoggedGLTF(modelUrl, { + scope: "EditorMap.EditorModelNode", + position: node.position, + rotation: node.rotation, + scale: node.scale, + }); + const sceneInstance = useClonedObject(scene); const pointerHandlers = createEditorNodePointerHandlers( index, onSelectNode, diff --git a/src/components/editor/scene/EditorScene.tsx b/src/components/editor/scene/EditorScene.tsx index 000b681..0e1ec58 100644 --- a/src/components/editor/scene/EditorScene.tsx +++ b/src/components/editor/scene/EditorScene.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { OrbitControls } from "@react-three/drei"; import { EditorMap } from "@/components/editor/scene/EditorMap"; import { FlyController } from "@/controls/editor/FlyController"; -import type { MapNode, TransformMode, SceneData } from "@/types/editor"; +import type { MapNode, TransformMode, SceneData } from "@/types/editor/editor"; interface EditorSceneProps { sceneData: SceneData; diff --git a/src/components/three/GrabbableObject.tsx b/src/components/three/GrabbableObject.tsx deleted file mode 100644 index abd580e..0000000 --- a/src/components/three/GrabbableObject.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { useRef } from "react"; -import { useFrame, useThree } from "@react-three/fiber"; -import { RigidBody } from "@react-three/rapier"; -import type { RapierRigidBody } from "@react-three/rapier"; -import * as THREE from "three"; -import { InteractableObject } from "@/components/three/InteractableObject"; -import { - GRAB_DEFAULT_COLLIDERS, - GRAB_DEFAULT_LABEL, - GRAB_HOLD_DISTANCE_DEFAULT, - GRAB_HOLD_DISTANCE_MAX, - GRAB_HOLD_DISTANCE_MIN, - GRAB_HOLD_DISTANCE_STEP, - GRAB_STIFFNESS_DEFAULT, - GRAB_STIFFNESS_MAX, - GRAB_STIFFNESS_MIN, - GRAB_STIFFNESS_STEP, - GRAB_THROW_BOOST_DEFAULT, - GRAB_THROW_BOOST_MAX, - GRAB_THROW_BOOST_MIN, - GRAB_THROW_BOOST_STEP, -} from "@/data/interaction/grabConfig"; -import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; -import type { ColliderShape, Vector3Tuple } from "@/types/three"; - -interface GrabbableObjectProps { - position: Vector3Tuple; - children: React.ReactNode; - colliders?: ColliderShape; - label?: string; -} - -// Shared params let one debug folder drive every instance. -const params = { - stiffness: GRAB_STIFFNESS_DEFAULT, - throwBoost: GRAB_THROW_BOOST_DEFAULT, - holdDistance: GRAB_HOLD_DISTANCE_DEFAULT, -}; - -const ZERO_ANGULAR_VELOCITY = { x: 0, y: 0, z: 0 }; - -const _holdTarget = new THREE.Vector3(); -const _currentPos = new THREE.Vector3(); -const _velocity = new THREE.Vector3(); - -export function GrabbableObject({ - position, - children, - colliders = GRAB_DEFAULT_COLLIDERS, - label = GRAB_DEFAULT_LABEL, -}: GrabbableObjectProps): React.JSX.Element { - const camera = useThree((state) => state.camera); - const rbRef = useRef(null); - const isHolding = useRef(false); - - useDebugFolder("GrabbableObject", (folder) => { - folder - .add( - params, - "stiffness", - GRAB_STIFFNESS_MIN, - GRAB_STIFFNESS_MAX, - GRAB_STIFFNESS_STEP, - ) - .name("Hold stiffness"); - folder - .add( - params, - "throwBoost", - GRAB_THROW_BOOST_MIN, - GRAB_THROW_BOOST_MAX, - GRAB_THROW_BOOST_STEP, - ) - .name("Throw boost"); - folder - .add( - params, - "holdDistance", - GRAB_HOLD_DISTANCE_MIN, - GRAB_HOLD_DISTANCE_MAX, - GRAB_HOLD_DISTANCE_STEP, - ) - .name("Hold distance"); - }); - - useFrame(() => { - if (!isHolding.current || !rbRef.current) return; - - camera.getWorldDirection(_holdTarget); - _holdTarget.multiplyScalar(params.holdDistance).add(camera.position); - - const t = rbRef.current.translation(); - _currentPos.set(t.x, t.y, t.z); - - _velocity - .subVectors(_holdTarget, _currentPos) - .multiplyScalar(params.stiffness); - - rbRef.current.setLinvel( - { x: _velocity.x, y: _velocity.y, z: _velocity.z }, - true, - ); - rbRef.current.setAngvel(ZERO_ANGULAR_VELOCITY, true); - }); - - return ( - - { - isHolding.current = true; - }} - onRelease={() => { - isHolding.current = false; - if (!rbRef.current || params.throwBoost === GRAB_THROW_BOOST_DEFAULT) - return; - const v = rbRef.current.linvel(); - rbRef.current.setLinvel( - { - x: v.x * params.throwBoost, - y: v.y * params.throwBoost, - z: v.z * params.throwBoost, - }, - true, - ); - }} - > - {children} - - - ); -} diff --git a/src/components/three/gameplay/RepairCaseModel.tsx b/src/components/three/gameplay/RepairCaseModel.tsx new file mode 100644 index 0000000..01485db --- /dev/null +++ b/src/components/three/gameplay/RepairCaseModel.tsx @@ -0,0 +1,170 @@ +import { useEffect, useRef } from "react"; +import { useFrame, useThree } from "@react-three/fiber"; +import gsap from "gsap"; +import * as THREE from "three"; +import { + REPAIR_CASE_ANIMATION_DURATION, + REPAIR_CASE_CLOSED_ROTATION_OFFSET_DEGREES, + REPAIR_CASE_FLOAT_ACTIVATION_DISTANCE, + REPAIR_CASE_FLOAT_DOWN_SPEED, + REPAIR_CASE_FLOAT_HEIGHT, + REPAIR_CASE_FLOAT_UP_SPEED, + REPAIR_CASE_LID_NODE_NAME, + REPAIR_CASE_OPEN_ROTATION_OFFSET_DEGREES, + REPAIR_CASE_ROTATION_AMPLITUDE_DEGREES, + REPAIR_CASE_ROTATION_RESET_SPEED, +} from "@/data/gameplay/repairCaseConfig"; +import { useClonedObject } from "@/hooks/three/useClonedObject"; +import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; +import type { ModelTransformProps } from "@/types/three/three"; +import { toVector3Scale } from "@/utils/three/scale"; + +interface RepairCaseModelProps extends ModelTransformProps { + modelPath: string; + open: boolean; +} + +const CASE_CLOSED_ROTATION_OFFSET_Z = THREE.MathUtils.degToRad( + REPAIR_CASE_CLOSED_ROTATION_OFFSET_DEGREES, +); +const CASE_OPEN_ROTATION_OFFSET_Z = THREE.MathUtils.degToRad( + REPAIR_CASE_OPEN_ROTATION_OFFSET_DEGREES, +); +const ROTATION_AMPLITUDE = THREE.MathUtils.degToRad( + REPAIR_CASE_ROTATION_AMPLITUDE_DEGREES, +); + +export function RepairCaseModel({ + modelPath, + open, + position = [0, 0, 0], + rotation = [0, 0, 0], + scale = 1, +}: RepairCaseModelProps): React.JSX.Element { + const camera = useThree((state) => state.camera); + const { scene } = useLoggedGLTF(modelPath, { + scope: "RepairCaseModel", + position, + rotation, + scale, + }); + const model = useClonedObject(scene); + const groupRef = useRef(null); + const lidRef = useRef(null); + const worldPosition = useRef(new THREE.Vector3()); + const floatHeight = useRef(0); + const animationActiveRef = useRef(false); + const phase = useRef({ x: 0, y: 0, z: 0 }); + const initialOpen = useRef(open); + const openedRotationZ = useRef(0); + const parsedScale = toVector3Scale(scale); + + useEffect(() => { + phase.current = { + x: Math.random() * Math.PI * 2, + y: Math.random() * Math.PI * 2, + z: Math.random() * Math.PI * 2, + }; + }, []); + + useEffect(() => { + const lid = model.getObjectByName(REPAIR_CASE_LID_NODE_NAME); + lidRef.current = lid ?? null; + openedRotationZ.current = lid?.rotation.z ?? 0; + + if (lid) { + lid.rotation.z = + openedRotationZ.current + + (initialOpen.current + ? CASE_OPEN_ROTATION_OFFSET_Z + : CASE_CLOSED_ROTATION_OFFSET_Z); + } + }, [model]); + + useEffect(() => { + const lid = lidRef.current; + if (!lid) return; + + const targetRotation = + openedRotationZ.current + + (open ? CASE_OPEN_ROTATION_OFFSET_Z : CASE_CLOSED_ROTATION_OFFSET_Z); + gsap.to(lid.rotation, { + z: targetRotation, + duration: REPAIR_CASE_ANIMATION_DURATION, + ease: "power2.inOut", + overwrite: true, + }); + + return () => { + gsap.killTweensOf(lid.rotation); + }; + }, [open]); + + useFrame(({ clock }, delta) => { + const group = groupRef.current; + if (!group) return; + + group.getWorldPosition(worldPosition.current); + const isNear = + worldPosition.current.distanceTo(camera.position) <= + REPAIR_CASE_FLOAT_ACTIVATION_DISTANCE; + const targetHeight = isNear ? REPAIR_CASE_FLOAT_HEIGHT : 0; + const floatSpeed = isNear + ? REPAIR_CASE_FLOAT_UP_SPEED + : REPAIR_CASE_FLOAT_DOWN_SPEED; + + floatHeight.current = THREE.MathUtils.damp( + floatHeight.current, + targetHeight, + floatSpeed, + delta, + ); + group.position.y = position[1] + floatHeight.current; + + animationActiveRef.current = isNear; + + if (animationActiveRef.current) { + const time = clock.elapsedTime; + group.rotation.x = + rotation[0] + + Math.sin(time * 0.7 + phase.current.x) * ROTATION_AMPLITUDE; + group.rotation.y = + rotation[1] + + Math.sin(time * 0.55 + phase.current.y) * ROTATION_AMPLITUDE; + group.rotation.z = + rotation[2] + + Math.sin(time * 0.8 + phase.current.z) * ROTATION_AMPLITUDE; + return; + } + + group.rotation.x = THREE.MathUtils.damp( + group.rotation.x, + rotation[0], + REPAIR_CASE_ROTATION_RESET_SPEED, + delta, + ); + group.rotation.y = THREE.MathUtils.damp( + group.rotation.y, + rotation[1], + REPAIR_CASE_ROTATION_RESET_SPEED, + delta, + ); + group.rotation.z = THREE.MathUtils.damp( + group.rotation.z, + rotation[2], + REPAIR_CASE_ROTATION_RESET_SPEED, + delta, + ); + }); + + return ( + + + + ); +} diff --git a/src/components/three/gameplay/RepairCaseObject.tsx b/src/components/three/gameplay/RepairCaseObject.tsx new file mode 100644 index 0000000..2307794 --- /dev/null +++ b/src/components/three/gameplay/RepairCaseObject.tsx @@ -0,0 +1,102 @@ +import type { ReactNode } from "react"; +import { Component } from "react"; +import { TriggerObject } from "@/components/three/interaction/TriggerObject"; +import { RepairCaseModel } from "@/components/three/gameplay/RepairCaseModel"; +import { + REPAIR_CASE_MODEL_PATH, + REPAIR_CASE_OPEN_SOUND_PATH, +} from "@/data/gameplay/repairCaseConfig"; +import { AudioManager } from "@/managers/AudioManager"; +import type { Vector3Tuple } from "@/types/three/three"; +import { logModelLoadError } from "@/utils/three/modelLoadLogger"; + +interface RepairCaseErrorBoundaryProps { + children: ReactNode; +} + +interface RepairCaseErrorBoundaryState { + hasError: boolean; +} + +class RepairCaseErrorBoundary extends Component< + RepairCaseErrorBoundaryProps, + RepairCaseErrorBoundaryState +> { + constructor(props: RepairCaseErrorBoundaryProps) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(): RepairCaseErrorBoundaryState { + return { hasError: true }; + } + + componentDidCatch(error: Error): void { + logModelLoadError( + { + modelPath: REPAIR_CASE_MODEL_PATH, + scope: "RepairCaseObject", + position: [0, -0.45, 0], + scale: 1.5, + }, + error, + ); + } + + render(): ReactNode { + if (this.state.hasError) { + return ; + } + + return this.props.children; + } +} + +interface RepairCaseObjectProps { + position: Vector3Tuple; + open: boolean; + onInspect: () => void; +} + +export function RepairCaseObject({ + position, + open, + onInspect, +}: RepairCaseObjectProps): React.JSX.Element { + return ( + { + if (open) return; + AudioManager.getInstance().playSound(REPAIR_CASE_OPEN_SOUND_PATH); + onInspect(); + }} + > + + + + + ); +} + +function RepairCaseFallback(): React.JSX.Element { + return ( + + + + + + + + + + + ); +} diff --git a/src/components/three/gameplay/RepairGameZone.tsx b/src/components/three/gameplay/RepairGameZone.tsx new file mode 100644 index 0000000..cf0aaed --- /dev/null +++ b/src/components/three/gameplay/RepairGameZone.tsx @@ -0,0 +1,121 @@ +import { Text } from "@react-three/drei"; +import { RepairCaseObject } from "@/components/three/gameplay/RepairCaseObject"; +import { RepairModuleSlot } from "@/components/three/gameplay/RepairModuleSlot"; +import { + REPAIR_GAME_MODULE_SLOTS, + REPAIR_GAME_ZONE_LABEL, + REPAIR_GAME_ZONE_ORIGIN, + REPAIR_GAME_ZONE_RADIUS, +} from "@/data/gameplay/repairGameConfig"; +import { useGameStore } from "@/managers/stores/useGameStore"; + +const CASE_CLOSED_STEPS = new Set(["locked", "waiting"]); + +export function RepairGameZone(): React.JSX.Element { + const mainState = useGameStore((state) => state.mainState); + const bikeStep = useGameStore((state) => state.bike.currentStep); + const setMainState = useGameStore((state) => state.setMainState); + const setBikeState = useGameStore((state) => state.setBikeState); + const caseOpen = !CASE_CLOSED_STEPS.has(bikeStep); + const slotsDisabled = !caseOpen; + + const inspectRepairCase = (): void => { + if (mainState !== "bike") { + setMainState("bike"); + } + + if (CASE_CLOSED_STEPS.has(bikeStep)) { + setBikeState({ currentStep: "inspected" }); + } + }; + + const markModelSelected = (): void => { + if (mainState !== "bike") { + setMainState("bike"); + } + + if (bikeStep === "inspected") { + setBikeState({ currentStep: "fragmented" }); + } + }; + + const markModuleSplit = (): void => { + if (mainState !== "bike") { + setMainState("bike"); + } + + if (bikeStep === "fragmented") { + setBikeState({ currentStep: "scanning" }); + } + }; + + return ( + + + + + + + + + + + + + {REPAIR_GAME_ZONE_LABEL} + + + + + {REPAIR_GAME_MODULE_SLOTS.map((slot) => ( + + ))} + + ); +} diff --git a/src/components/three/gameplay/RepairModuleSlot.tsx b/src/components/three/gameplay/RepairModuleSlot.tsx new file mode 100644 index 0000000..6ffed47 --- /dev/null +++ b/src/components/three/gameplay/RepairModuleSlot.tsx @@ -0,0 +1,113 @@ +import { Html } from "@react-three/drei"; +import { useCallback, useState } from "react"; +import { TriggerObject } from "@/components/three/interaction/TriggerObject"; +import { ExplodableModel } from "@/components/three/models/ExplodableModel"; +import { REPAIR_GAME_MODEL_CATALOG } from "@/data/gameplay/repairGameModelCatalog"; +import type { ModelCatalogItem } from "@/data/gameplay/repairGameModelCatalog"; +import { useModelSelection } from "@/hooks/gameplay/useModelSelection"; +import type { Vector3Tuple } from "@/types/three/three"; + +interface RepairModuleSlotProps { + position: Vector3Tuple; + label: string; + disabled?: boolean; + onModelSelected?: () => void; + onSplit?: () => void; +} + +export function RepairModuleSlot({ + position, + label, + disabled = false, + onModelSelected, + onSplit, +}: RepairModuleSlotProps): React.JSX.Element { + const [selectedModel, setSelectedModel] = useState( + null, + ); + const [split, setSplit] = useState(false); + const handleSelect = useCallback( + (model: ModelCatalogItem) => { + setSelectedModel(model); + setSplit(false); + onModelSelected?.(); + }, + [onModelSelected], + ); + const selection = useModelSelection(REPAIR_GAME_MODEL_CATALOG, handleSelect); + const triggerLabel = disabled + ? "Ouvrir la mallette d'abord" + : selectedModel + ? split + ? `Réassembler ${label}` + : `Démonter ${label}` + : `Choisir ${label}`; + + return ( + + { + if (disabled) return; + + if (selectedModel) { + setSplit((value) => { + const nextSplit = !value; + if (nextSplit) { + onSplit?.(); + } + return nextSplit; + }); + return; + } + + selection.open(); + }} + > + {selectedModel ? ( + + ) : ( + + + + + )} + + + {selection.isOpen ? ( + +
+ {label} + Fleches: choisir + E/Enter: valider +
    + {REPAIR_GAME_MODEL_CATALOG.map((model, index) => ( +
  • + {model.name} +
  • + ))} +
+
+ + ) : null} +
+ ); +} diff --git a/src/components/three/handTracking/HandTrackingGlove.tsx b/src/components/three/handTracking/HandTrackingGlove.tsx new file mode 100644 index 0000000..1158b6d --- /dev/null +++ b/src/components/three/handTracking/HandTrackingGlove.tsx @@ -0,0 +1,373 @@ +import type { ReactNode } from "react"; +import { Component, useEffect, useMemo, useRef } from "react"; +import { useFrame, useThree } from "@react-three/fiber"; +import { useGLTF } from "@react-three/drei"; +import * as THREE from "three"; +import { clone } from "three/addons/utils/SkeletonUtils.js"; +import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot"; +import { + useHandTrackingGloveStatus, + type HandTrackingGloveHandedness, +} from "@/hooks/handTracking/useHandTrackingGloveStatus"; +import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; +import type { HandTrackingLandmark } from "@/types/handTracking/handTracking"; +import { logModelLoadError } from "@/utils/three/modelLoadLogger"; + +const GLOVE_CONFIGS: Record< + HandTrackingGloveHandedness, + { + modelPath: string; + rootNodeName: string; + } +> = { + left: { + modelPath: "/models/gant_l/model.gltf", + rootNodeName: "Armature", + }, + right: { + modelPath: "/models/gant_r/model.gltf", + rootNodeName: "Hand_r", + }, +}; + +const GLOVE_MODEL_SCALE = 0.33; +const HAND_SPACE_DISTANCE = 0.5; +const HAND_DEPTH_SCALE = 0.5; +const HAND_TRACKING_HIDE_DELAY_MS = 250; + +const FINGER_LANDMARK_CHAINS = [ + [0, 1, 2, 3, 4], + [0, 5, 6, 7, 8], + [0, 9, 10, 11, 12], + [0, 13, 14, 15, 16], + [0, 17, 18, 19, 20], +] as const; + +const _cameraPosition = new THREE.Vector3(); +const _direction = new THREE.Vector3(); +const _xAxis = new THREE.Vector3(); +const _yAxis = new THREE.Vector3(); +const _zAxis = new THREE.Vector3(); +const _matrix = new THREE.Matrix4(); +const _parentInverse = new THREE.Matrix4(); +const _targetQuaternion = new THREE.Quaternion(); +const _boneTargetQuaternion = new THREE.Quaternion(); +const _boneDeltaQuaternion = new THREE.Quaternion(); +const _targetPosition = new THREE.Vector3(); +const _localSegmentStart = new THREE.Vector3(); +const _localSegmentEnd = new THREE.Vector3(); +const _localSegmentDirection = new THREE.Vector3(); +const _wristPosition = new THREE.Vector3(); +const _indexPosition = new THREE.Vector3(); +const _middlePosition = new THREE.Vector3(); +const _ringPosition = new THREE.Vector3(); +const _pinkyPosition = new THREE.Vector3(); + +interface FingerBonePose { + bone: THREE.Object3D; + restDirection: THREE.Vector3; + restQuaternion: THREE.Quaternion; +} + +type FingerPoseChain = FingerBonePose[]; + +interface HandTrackingGloveProps { + handedness: HandTrackingGloveHandedness; +} + +interface HandTrackingGloveErrorBoundaryProps { + children: ReactNode; + handedness: HandTrackingGloveHandedness; + modelPath: string; +} + +class HandTrackingGloveErrorBoundary extends Component< + HandTrackingGloveErrorBoundaryProps, + { hasError: boolean } +> { + constructor(props: HandTrackingGloveErrorBoundaryProps) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(): { hasError: boolean } { + return { hasError: true }; + } + + componentDidCatch(error: Error): void { + useHandTrackingGloveStatus + .getState() + .setGloveStatus(this.props.handedness, "error"); + + logModelLoadError( + { + modelPath: this.props.modelPath, + scope: `HandTrackingGlove.${this.props.handedness}`, + scale: GLOVE_MODEL_SCALE, + }, + error, + ); + } + + render(): ReactNode { + if (this.state.hasError) return null; + + return this.props.children; + } +} + +function landmarkToWorldPoint( + landmark: HandTrackingLandmark, + camera: THREE.Camera, + target: THREE.Vector3, +): THREE.Vector3 { + _cameraPosition.setFromMatrixPosition(camera.matrixWorld); + target.set((1 - landmark.x) * 2 - 1, -landmark.y * 2 + 1, 0.5); + target.unproject(camera); + + _direction.copy(target).sub(_cameraPosition).normalize(); + target + .copy(_cameraPosition) + .addScaledVector( + _direction, + HAND_SPACE_DISTANCE - landmark.z * HAND_DEPTH_SCALE, + ); + + return target; +} + +function matchesHandedness( + handHandedness: string, + targetHandedness: HandTrackingGloveHandedness, +): boolean { + return handHandedness.toLowerCase() === targetHandedness; +} + +function getFirstChildBone(object: THREE.Object3D): THREE.Object3D | null { + return object.children.find((child) => child.type === "Bone") ?? null; +} + +function createFingerBonePose(bone: THREE.Object3D): FingerBonePose { + const firstChild = getFirstChildBone(bone); + const restDirection = firstChild + ? firstChild.position.clone() + : new THREE.Vector3(0, 1, 0); + + restDirection.applyQuaternion(bone.quaternion).normalize(); + + return { + bone, + restDirection, + restQuaternion: bone.quaternion.clone(), + }; +} + +function createFingerPoseChain(startBone: THREE.Object3D): FingerPoseChain { + const chain: FingerPoseChain = []; + let currentBone: THREE.Object3D | null = startBone; + + while (currentBone && chain.length < 4) { + chain.push(createFingerBonePose(currentBone)); + currentBone = getFirstChildBone(currentBone); + } + + return chain; +} + +function createFingerPoseChains(root: THREE.Object3D): FingerPoseChain[] { + const rootBone = root.getObjectByName("Bone"); + + if (!rootBone) return []; + + return rootBone.children + .filter((child) => child.type === "Bone") + .slice(0, FINGER_LANDMARK_CHAINS.length) + .map(createFingerPoseChain); +} + +function resetFingerPose(chains: FingerPoseChain[]): void { + for (const chain of chains) { + for (const pose of chain) { + pose.bone.quaternion.copy(pose.restQuaternion); + } + } +} + +function applyFingerPose( + chains: FingerPoseChain[], + landmarks: HandTrackingLandmark[], + camera: THREE.Camera, +): void { + for (let fingerIndex = 0; fingerIndex < chains.length; fingerIndex += 1) { + const chain = chains[fingerIndex]; + const landmarkChain = FINGER_LANDMARK_CHAINS[fingerIndex]; + + if (!chain || !landmarkChain) continue; + + for (let boneIndex = 0; boneIndex < chain.length; boneIndex += 1) { + const pose = chain[boneIndex]; + const fromLandmark = landmarks[landmarkChain[boneIndex] ?? -1]; + const toLandmark = landmarks[landmarkChain[boneIndex + 1] ?? -1]; + const parent = pose?.bone.parent; + + if (!pose || !fromLandmark || !toLandmark || !parent) continue; + + landmarkToWorldPoint(fromLandmark, camera, _localSegmentStart); + landmarkToWorldPoint(toLandmark, camera, _localSegmentEnd); + + parent.updateWorldMatrix(true, false); + _parentInverse.copy(parent.matrixWorld).invert(); + _localSegmentStart.applyMatrix4(_parentInverse); + _localSegmentEnd.applyMatrix4(_parentInverse); + _localSegmentDirection + .copy(_localSegmentEnd) + .sub(_localSegmentStart) + .normalize(); + + if (_localSegmentDirection.lengthSq() === 0) continue; + + _boneDeltaQuaternion.setFromUnitVectors( + pose.restDirection, + _localSegmentDirection, + ); + _boneTargetQuaternion + .copy(_boneDeltaQuaternion) + .multiply(pose.restQuaternion); + pose.bone.quaternion.slerp(_boneTargetQuaternion, 0.45); + } + } +} + +function HandTrackingGloveModel({ + handedness, +}: HandTrackingGloveProps): React.JSX.Element | null { + const groupRef = useRef(null); + const { camera } = useThree(); + const { hands } = useHandTrackingSnapshot(); + const setGloveStatus = useHandTrackingGloveStatus( + (state) => state.setGloveStatus, + ); + const config = GLOVE_CONFIGS[handedness]; + const modelPath = config.modelPath; + const gltf = useLoggedGLTF(modelPath, { + scope: `HandTrackingGlove.${handedness}`, + scale: GLOVE_MODEL_SCALE, + }); + const lastTrackedAtRef = useRef(null); + const gloveScene = useMemo(() => { + const rootNode = gltf.scene.getObjectByName(config.rootNodeName); + + if (!rootNode) { + throw new Error(`Missing glove root node ${config.rootNodeName}`); + } + + const clonedRootNode = clone(rootNode); + clonedRootNode.visible = false; + + return clonedRootNode; + }, [config.rootNodeName, gltf.scene]); + const fingerPoseChains = useMemo( + () => createFingerPoseChains(gloveScene), + [gloveScene], + ); + + useEffect(() => { + setGloveStatus(handedness, "loaded"); + }, [handedness, setGloveStatus]); + + useFrame((_, delta) => { + const group = groupRef.current; + const trackedHand = hands.find((candidate) => + matchesHandedness(candidate.handedness, handedness), + ); + + if (!group) return; + + if (!trackedHand || trackedHand.landmarks.length < 21) { + const lastTrackedAt = lastTrackedAtRef.current; + const shouldHide = + lastTrackedAt === null || + performance.now() - lastTrackedAt > HAND_TRACKING_HIDE_DELAY_MS; + + if (shouldHide) { + group.visible = false; + resetFingerPose(fingerPoseChains); + } + + return; + } + + lastTrackedAtRef.current = performance.now(); + group.visible = true; + + const wrist = trackedHand.landmarks[0]; + const indexMcp = trackedHand.landmarks[5]; + const middleMcp = trackedHand.landmarks[9]; + const ringMcp = trackedHand.landmarks[13]; + const pinkyMcp = trackedHand.landmarks[17]; + + if (!wrist || !indexMcp || !middleMcp || !ringMcp || !pinkyMcp) { + group.visible = false; + return; + } + + landmarkToWorldPoint(wrist, camera, _wristPosition); + landmarkToWorldPoint(indexMcp, camera, _indexPosition); + landmarkToWorldPoint(middleMcp, camera, _middlePosition); + landmarkToWorldPoint(ringMcp, camera, _ringPosition); + landmarkToWorldPoint(pinkyMcp, camera, _pinkyPosition); + + _targetPosition + .copy(_wristPosition) + .add(_indexPosition) + .add(_middlePosition) + .add(_ringPosition) + .add(_pinkyPosition) + .multiplyScalar(0.2); + + _yAxis.copy(_middlePosition).sub(_wristPosition).normalize(); + _xAxis.copy(_indexPosition).sub(_pinkyPosition).normalize(); + _zAxis.crossVectors(_xAxis, _yAxis).normalize(); + + if ( + _xAxis.lengthSq() === 0 || + _yAxis.lengthSq() === 0 || + _zAxis.lengthSq() === 0 + ) { + return; + } + + _xAxis.crossVectors(_yAxis, _zAxis).normalize(); + _matrix.makeBasis(_xAxis, _yAxis, _zAxis); + _targetQuaternion.setFromRotationMatrix(_matrix); + + group.position.lerp(_targetPosition, Math.min(1, delta * 18)); + group.quaternion.slerp(_targetQuaternion, Math.min(1, delta * 18)); + + const palmLength = _wristPosition.distanceTo(_middlePosition); + const scale = palmLength * GLOVE_MODEL_SCALE; + group.scale.setScalar(scale); + group.updateMatrixWorld(true); + applyFingerPose(fingerPoseChains, trackedHand.landmarks, camera); + }); + + return ; +} + +export function HandTrackingGlove({ + handedness, +}: HandTrackingGloveProps): React.JSX.Element { + const modelPath = GLOVE_CONFIGS[handedness].modelPath; + + return ( + + + + ); +} + +useGLTF.preload(GLOVE_CONFIGS.left.modelPath); +useGLTF.preload(GLOVE_CONFIGS.right.modelPath); diff --git a/src/components/three/index.ts b/src/components/three/index.ts deleted file mode 100644 index 9ff35d8..0000000 --- a/src/components/three/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { AnimatedModel } from "./AnimatedModel"; -export type { AnimatedModelConfig } from "./AnimatedModel"; - -export { SimpleModel } from "./SimpleModel"; -export type { SimpleModelConfig } from "./SimpleModel"; - -export { useCharacterAnimation } from "@/hooks/useCharacterAnimation"; -export type { CharacterAnimationConfig } from "@/hooks/useCharacterAnimation"; diff --git a/src/components/three/interaction/GrabbableObject.tsx b/src/components/three/interaction/GrabbableObject.tsx new file mode 100644 index 0000000..059273d --- /dev/null +++ b/src/components/three/interaction/GrabbableObject.tsx @@ -0,0 +1,279 @@ +import { useRef } from "react"; +import { useFrame, useThree } from "@react-three/fiber"; +import { RigidBody } from "@react-three/rapier"; +import type { RapierRigidBody } from "@react-three/rapier"; +import * as THREE from "three"; +import { InteractableObject } from "@/components/three/interaction/InteractableObject"; +import { + GRAB_DEFAULT_COLLIDERS, + GRAB_DEFAULT_LABEL, + GRAB_HOLD_DISTANCE_DEFAULT, + GRAB_HOLD_DISTANCE_MAX, + GRAB_HOLD_DISTANCE_MIN, + GRAB_HOLD_DISTANCE_STEP, + GRAB_STIFFNESS_DEFAULT, + GRAB_STIFFNESS_MAX, + GRAB_STIFFNESS_MIN, + GRAB_STIFFNESS_STEP, + GRAB_THROW_BOOST_DEFAULT, + GRAB_THROW_BOOST_MAX, + GRAB_THROW_BOOST_MIN, + GRAB_THROW_BOOST_STEP, +} from "@/data/interaction/grabConfig"; +import { INTERACTION_RADIUS } from "@/data/interaction/interactionConfig"; +import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; +import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot"; +import { InteractionManager } from "@/managers/InteractionManager"; +import type { + HandTrackingHand, + HandTrackingLandmark, +} from "@/types/handTracking/handTracking"; +import type { ColliderShape, Vector3Tuple } from "@/types/three/three"; + +interface GrabbableObjectProps { + position: Vector3Tuple; + children: React.ReactNode; + colliders?: ColliderShape; + label?: string; + handControlled?: boolean; +} + +const grabDebugParams = { + stiffness: GRAB_STIFFNESS_DEFAULT, + throwBoost: GRAB_THROW_BOOST_DEFAULT, + holdDistance: GRAB_HOLD_DISTANCE_DEFAULT, +}; + +const ZERO_ANGULAR_VELOCITY = { x: 0, y: 0, z: 0 }; + +const _holdTarget = new THREE.Vector3(); +const _currentPos = new THREE.Vector3(); +const _velocity = new THREE.Vector3(); +const _handNdc = new THREE.Vector3(); +const _handHitNdc = new THREE.Vector3(); +const _handDirection = new THREE.Vector3(); +const _handHitDirection = new THREE.Vector3(); +const _cameraPos = new THREE.Vector3(); +const _objectPos = new THREE.Vector3(); +const _handRaycaster = new THREE.Raycaster(); + +const HAND_GRAB_SCREEN_RADIUS = 0.04; +const HAND_DEPTH_SENSITIVITY = 4; +const HAND_HIT_OFFSETS: Array<[number, number]> = [ + [0, 0], + [HAND_GRAB_SCREEN_RADIUS, 0], + [-HAND_GRAB_SCREEN_RADIUS, 0], + [0, HAND_GRAB_SCREEN_RADIUS], + [0, -HAND_GRAB_SCREEN_RADIUS], +]; + +function getHandCenterPoint(hand: HandTrackingHand): HandTrackingLandmark { + const landmarks = hand.landmarks ?? []; + if (landmarks.length === 0) { + return { x: hand.x, y: hand.y, z: hand.z }; + } + + let minX = landmarks[0]!.x; + let maxX = landmarks[0]!.x; + let minY = landmarks[0]!.y; + let maxY = landmarks[0]!.y; + + landmarks.forEach((landmark) => { + minX = Math.min(minX, landmark.x); + maxX = Math.max(maxX, landmark.x); + minY = Math.min(minY, landmark.y); + maxY = Math.max(maxY, landmark.y); + }); + + return { + x: (minX + maxX) / 2, + y: (minY + maxY) / 2, + z: hand.z, + }; +} + +function getHandHit( + group: THREE.Group | null, + camera: THREE.Camera, + cameraPos: THREE.Vector3, + handCenter: HandTrackingLandmark, +): THREE.Intersection | null { + if (!group) return null; + + const baseX = (1 - handCenter.x) * 2 - 1; + const baseY = -handCenter.y * 2 + 1; + + for (const [offsetX, offsetY] of HAND_HIT_OFFSETS) { + _handHitNdc.set(baseX + offsetX, baseY + offsetY, 0.5); + _handHitNdc.unproject(camera); + _handHitDirection.subVectors(_handHitNdc, cameraPos).normalize(); + _handRaycaster.set(cameraPos, _handHitDirection); + _handRaycaster.far = INTERACTION_RADIUS; + + const hits = _handRaycaster.intersectObject(group, true); + if (hits?.length > 0) return hits[0] ?? null; + } + + return null; +} + +export function GrabbableObject({ + position, + children, + colliders = GRAB_DEFAULT_COLLIDERS, + label = GRAB_DEFAULT_LABEL, + handControlled = false, +}: GrabbableObjectProps): React.JSX.Element { + const camera = useThree((state) => state.camera); + const { hands } = useHandTrackingSnapshot(); + const groupRef = useRef(null); + const rbRef = useRef(null); + const isHolding = useRef(false); + const isHandHolding = useRef(false); + const handHoldDistance = useRef(null); + const handHoldStartZ = useRef(null); + + useDebugFolder("GrabbableObject", (folder) => { + folder + .add( + grabDebugParams, + "stiffness", + GRAB_STIFFNESS_MIN, + GRAB_STIFFNESS_MAX, + GRAB_STIFFNESS_STEP, + ) + .name("Hold stiffness"); + folder + .add( + grabDebugParams, + "throwBoost", + GRAB_THROW_BOOST_MIN, + GRAB_THROW_BOOST_MAX, + GRAB_THROW_BOOST_STEP, + ) + .name("Throw boost"); + folder + .add( + grabDebugParams, + "holdDistance", + GRAB_HOLD_DISTANCE_MIN, + GRAB_HOLD_DISTANCE_MAX, + GRAB_HOLD_DISTANCE_STEP, + ) + .name("Hold distance"); + }); + + useFrame(() => { + if (!rbRef.current) return; + + const fistHand = handControlled + ? hands.find((hand) => hand.isFist) + : undefined; + + const t = rbRef.current.translation(); + _currentPos.set(t.x, t.y, t.z); + + if (fistHand) { + const handCenter = getHandCenterPoint(fistHand); + + _handNdc.set((1 - handCenter.x) * 2 - 1, -handCenter.y * 2 + 1, 0.5); + _handNdc.unproject(camera); + camera.getWorldPosition(_cameraPos); + _handDirection.subVectors(_handNdc, _cameraPos).normalize(); + + if (!isHandHolding.current) { + _objectPos.copy(_currentPos); + + const isObjectInRange = + _cameraPos.distanceTo(_objectPos) <= INTERACTION_RADIUS; + const hit = isObjectInRange + ? getHandHit(groupRef.current, camera, _cameraPos, handCenter) + : null; + + isHandHolding.current = Boolean(hit); + handHoldDistance.current = hit ? GRAB_HOLD_DISTANCE_DEFAULT : null; + handHoldStartZ.current = hit ? fistHand.z : null; + InteractionManager.getInstance().setHandHolding(isHandHolding.current); + } + } else { + isHandHolding.current = false; + handHoldDistance.current = null; + handHoldStartZ.current = null; + InteractionManager.getInstance().setHandHolding(false); + } + + if (!isHolding.current && !isHandHolding.current) return; + + if (fistHand && isHandHolding.current) { + const depthOffset = + handHoldStartZ.current === null + ? 0 + : (fistHand.z - handHoldStartZ.current) * HAND_DEPTH_SENSITIVITY; + const holdDistance = THREE.MathUtils.clamp( + (handHoldDistance.current ?? grabDebugParams.holdDistance) + + depthOffset, + GRAB_HOLD_DISTANCE_MIN, + GRAB_HOLD_DISTANCE_MAX, + ); + + _holdTarget + .copy(_cameraPos) + .addScaledVector(_handDirection, holdDistance); + } else { + camera.getWorldDirection(_holdTarget); + _holdTarget + .multiplyScalar(grabDebugParams.holdDistance) + .add(camera.position); + } + + _velocity + .subVectors(_holdTarget, _currentPos) + .multiplyScalar(grabDebugParams.stiffness); + + rbRef.current.setLinvel( + { x: _velocity.x, y: _velocity.y, z: _velocity.z }, + true, + ); + rbRef.current.setAngvel(ZERO_ANGULAR_VELOCITY, true); + }); + + return ( + + + { + isHolding.current = true; + }} + onRelease={() => { + isHolding.current = false; + if ( + !rbRef.current || + grabDebugParams.throwBoost === GRAB_THROW_BOOST_DEFAULT + ) + return; + const v = rbRef.current.linvel(); + rbRef.current.setLinvel( + { + x: v.x * grabDebugParams.throwBoost, + y: v.y * grabDebugParams.throwBoost, + z: v.z * grabDebugParams.throwBoost, + }, + true, + ); + }} + > + {children} + + + + ); +} diff --git a/src/components/three/InteractableObject.tsx b/src/components/three/interaction/InteractableObject.tsx similarity index 83% rename from src/components/three/InteractableObject.tsx rename to src/components/three/interaction/InteractableObject.tsx index bcca255..21264fe 100644 --- a/src/components/three/InteractableObject.tsx +++ b/src/components/three/interaction/InteractableObject.tsx @@ -13,8 +13,8 @@ import { Debug } from "@/utils/debug/Debug"; import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; import { InteractionManager } from "@/managers/InteractionManager"; import { INTERACTION_RADIUS } from "@/data/interaction/interactionConfig"; -import type { Vector3Tuple } from "@/types/three"; -import type { InteractableHandle } from "@/types/interaction"; +import type { InteractableHandle } from "@/types/interaction/interaction"; +import type { Vector3Tuple } from "@/types/three/three"; interface InteractableObjectBaseProps { label: string; @@ -74,6 +74,7 @@ export function InteractableObject( useEffect(() => { const currentHandle = handle.current; + const manager = InteractionManager.getInstance(); if (currentHandle.kind === kind) { currentHandle.label = label; @@ -87,6 +88,8 @@ export function InteractableObject( return; } + manager.setNearby(currentHandle, false); + if (kind === "grab") { if (!onRelease) return; handle.current = { kind, label, onPress, onRelease }; @@ -94,13 +97,36 @@ export function InteractableObject( handle.current = { kind, label, onPress }; } - const manager = InteractionManager.getInstance(); if (manager.getState().focused === currentHandle) { manager.setFocused(handle.current); } }, [kind, label, onPress, onRelease]); + useEffect(() => { + const currentHandle = handle.current; + + return () => { + const manager = InteractionManager.getInstance(); + manager.setNearby(currentHandle, false); + if (manager.getState().focused === currentHandle) { + manager.setFocused(null); + } + }; + }, []); + const setupInteractionDebugFolder = useCallback((folder: GUI) => { + const debug = Debug.getInstance(); + const controls = { + showInteractionSpheres: debug.getShowInteractionSpheres(), + }; + + folder + .add(controls, "showInteractionSpheres") + .name("Interaction Spheres") + .onChange((value: boolean) => { + debug.setShowInteractionSpheres(value); + }); + folder .add({ radius: INTERACTION_RADIUS }, "radius") .name("Interaction radius") @@ -128,8 +154,11 @@ export function InteractableObject( camera.getWorldPosition(_cameraPos); const dist = _cameraPos.distanceTo(_objectPos); + const isNearby = dist <= INTERACTION_RADIUS; - if (dist > INTERACTION_RADIUS) { + manager.setNearby(handle.current, isNearby); + + if (!isNearby) { if (manager.getState().focused === handle.current) { manager.setFocused(null); } diff --git a/src/components/three/TriggerObject.tsx b/src/components/three/interaction/TriggerObject.tsx similarity index 76% rename from src/components/three/TriggerObject.tsx rename to src/components/three/interaction/TriggerObject.tsx index 4e11c96..77f4cc6 100644 --- a/src/components/three/TriggerObject.tsx +++ b/src/components/three/interaction/TriggerObject.tsx @@ -1,7 +1,8 @@ import { useState } from "react"; -import { useGLTF } from "@react-three/drei"; import { RigidBody } from "@react-three/rapier"; -import { InteractableObject } from "@/components/three/InteractableObject"; +import { InteractableObject } from "@/components/three/interaction/InteractableObject"; +import { useClonedObject } from "@/hooks/three/useClonedObject"; +import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; import { TRIGGER_DEFAULT_COLLIDERS, TRIGGER_DEFAULT_LABEL, @@ -9,7 +10,7 @@ import { TRIGGER_DEFAULT_SPAWN_OFFSET, } from "@/data/interaction/triggerConfig"; import { AudioManager } from "@/managers/AudioManager"; -import type { ColliderShape, Vector3Tuple } from "@/types/three"; +import type { ColliderShape, Vector3Tuple } from "@/types/three/three"; interface SpawnedModel { id: number; @@ -25,9 +26,10 @@ interface TriggerObjectProps { soundVolume?: number; spawnModel?: string; spawnOffset?: Vector3Tuple; + onTrigger?: () => void; } -let _spawnCounter = 0; +let spawnCounter = 0; function SpawnedModelInstance({ path, @@ -36,8 +38,13 @@ function SpawnedModelInstance({ path: string; position: Vector3Tuple; }): React.JSX.Element { - const { scene } = useGLTF(path); - return ; + const { scene } = useLoggedGLTF(path, { + scope: "TriggerObject.SpawnedModel", + position, + }); + const model = useClonedObject(scene); + + return ; } export function TriggerObject({ @@ -49,6 +56,7 @@ export function TriggerObject({ soundVolume = TRIGGER_DEFAULT_SOUND_VOLUME, spawnModel, spawnOffset = TRIGGER_DEFAULT_SPAWN_OFFSET, + onTrigger, }: TriggerObjectProps): React.JSX.Element { const [spawned, setSpawned] = useState([]); @@ -64,6 +72,8 @@ export function TriggerObject({ AudioManager.getInstance().playSound(soundPath, soundVolume); } + onTrigger?.(); + if (spawnModel) { const spawnPos: Vector3Tuple = [ position[0] + spawnOffset[0], @@ -72,7 +82,7 @@ export function TriggerObject({ ]; setSpawned((prev) => [ ...prev, - { id: ++_spawnCounter, position: spawnPos }, + { id: ++spawnCounter, position: spawnPos }, ]); } }} diff --git a/src/components/three/AnimatedModel.tsx b/src/components/three/models/AnimatedModel.tsx similarity index 62% rename from src/components/three/AnimatedModel.tsx rename to src/components/three/models/AnimatedModel.tsx index 099f14c..c467e03 100644 --- a/src/components/three/AnimatedModel.tsx +++ b/src/components/three/models/AnimatedModel.tsx @@ -1,9 +1,13 @@ -/* eslint-disable react-hooks/immutability */ -import { createContext, useRef, useState, useEffect, useCallback } from "react"; -import { useGLTF, useAnimations } from "@react-three/drei"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useAnimations } from "@react-three/drei"; import type { AnimationAction } from "three"; import * as THREE from "three"; -import type { Vector3Tuple } from "@/types/three"; +import { + AnimatedModelContext, + type AnimatedModelContextValue, +} from "@/components/three/models/useAnimatedModel"; +import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; +import type { Vector3Tuple } from "@/types/three/three"; export interface AnimatedModelConfig { modelPath: string; @@ -19,22 +23,6 @@ export interface AnimatedModelConfig { onAnimationEnd?: (animationName: string) => void; } -export interface AnimatedModelContextValue { - play: (name: string, fade?: number) => void; - stop: (fade?: number) => void; - fadeTo: (name: string, fade?: number) => void; - currentAnimation: string; - isReady: boolean; - setSpeed: (speed: number) => void; - names: string[]; -} - -const AnimatedModelContext = createContext( - null, -); - -export { AnimatedModelContext }; - interface AnimatedModelProps extends AnimatedModelConfig { children?: React.ReactNode; } @@ -53,19 +41,23 @@ export function AnimatedModel({ children, }: AnimatedModelProps): React.JSX.Element { const groupRef = useRef(null); - - void groupRef; - const { scene, animations } = useGLTF(modelPath); - const { actions, names, mixer } = useAnimations(animations, scene); + const { scene, animations } = useLoggedGLTF(modelPath, { + scope: "AnimatedModel", + position, + rotation, + scale, + }); + const model = useMemo(() => scene.clone(true), [scene]); + const { actions, names, mixer } = useAnimations(animations, groupRef); const [currentAnim, setCurrentAnim] = useState(defaultAnimation); - const [isReady, setIsReady] = useState(false); + const isReady = names.length > 0; useEffect(() => { - if (mixer) { - mixer.timeScale = speed; - } - }, [mixer, speed]); + Object.values(actions).forEach((action) => { + action?.setEffectiveTimeScale(speed); + }); + }, [actions, speed]); useEffect(() => { const handleFinished = (e: { action: AnimationAction }) => { @@ -123,39 +115,27 @@ export function AnimatedModel({ const setSpeed = useCallback( (newSpeed: number) => { - if (mixer) { - mixer.timeScale = newSpeed; - } + Object.values(actions).forEach((action) => { + action?.setEffectiveTimeScale(newSpeed); + }); }, - [mixer], + [actions], ); useEffect(() => { if (!autoPlay || names.length === 0) { - console.log("[AnimatedModel] No animation found in model"); return; } - console.log(`[AnimatedModel] Available animations: ${names.join(", ")}`); - let defaultAction = actions[defaultAnimation as string]; if (!defaultAction && names.length > 0) { - console.log( - `[AnimatedModel] "${defaultAnimation}" not found, using: ${names[0]}`, - ); defaultAction = actions[names[0] as string]; } if (defaultAction) { defaultAction.play(); - // eslint-disable-next-line react-hooks/set-state-in-effect - setIsReady(true); - - setCurrentAnim(defaultAction.getClip().name); onLoaded?.(); - } else { - console.log("[AnimatedModel] No available animation in actions"); } }, [actions, defaultAnimation, names, autoPlay, onLoaded]); @@ -169,21 +149,19 @@ export function AnimatedModel({ names, }; - useEffect(() => { - scene.position.set(...position); - scene.rotation.set( - (rotation[0] * Math.PI) / 180, - (rotation[1] * Math.PI) / 180, - (rotation[2] * Math.PI) / 180, - ); - const s = - typeof scale === "number" ? [scale, scale, scale] : (scale ?? [1, 1, 1]); - scene.scale.set(s[0] ?? 1, s[1] ?? 1, s[2] ?? 1); - }, [scene, position, rotation, scale]); + const parsedScale = + typeof scale === "number" ? ([scale, scale, scale] as Vector3Tuple) : scale; return ( - + + + {children} ); diff --git a/src/components/three/models/ExplodableModel.tsx b/src/components/three/models/ExplodableModel.tsx new file mode 100644 index 0000000..c8542c7 --- /dev/null +++ b/src/components/three/models/ExplodableModel.tsx @@ -0,0 +1,121 @@ +import type { ReactNode } from "react"; +import { Component, useEffect, useMemo } from "react"; +import { useFrame } from "@react-three/fiber"; +import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; +import { useClonedObject } from "@/hooks/three/useClonedObject"; +import { ExplodedModel } from "@/utils/three/ExplodedModel"; +import type { ModelTransformProps, Vector3Tuple } from "@/types/three/three"; +import { logModelLoadError } from "@/utils/three/modelLoadLogger"; +import { toVector3Scale } from "@/utils/three/scale"; + +interface ModelErrorBoundaryProps { + children: ReactNode; + modelPath: string; + position?: Vector3Tuple | undefined; +} + +interface ModelErrorBoundaryState { + hasError: boolean; +} + +class ModelErrorBoundary extends Component< + ModelErrorBoundaryProps, + ModelErrorBoundaryState +> { + constructor(props: ModelErrorBoundaryProps) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(): ModelErrorBoundaryState { + return { hasError: true }; + } + + componentDidCatch(error: Error): void { + logModelLoadError( + { + modelPath: this.props.modelPath, + scope: "ExplodableModel", + position: this.props.position, + }, + error, + ); + } + + render(): ReactNode { + if (this.state.hasError) { + return ; + } + + return this.props.children; + } +} + +interface ExplodableModelInnerProps extends ModelTransformProps { + modelPath: string; + split: boolean; + splitDistance?: number; +} + +export function ExplodableModel( + props: ExplodableModelInnerProps, +): React.JSX.Element { + return ( + + + + ); +} + +function ExplodableModelInner({ + modelPath, + split, + position = [0, 0, 0], + rotation = [0, 0, 0], + scale = 1, + splitDistance = 1.2, +}: ExplodableModelInnerProps): React.JSX.Element { + const { scene } = useLoggedGLTF(modelPath, { + scope: "ExplodableModel", + position, + rotation, + scale, + }); + const model = useClonedObject(scene); + const explodedModel = useMemo( + () => new ExplodedModel(model, { distance: splitDistance }), + [model, splitDistance], + ); + const parsedScale = toVector3Scale(scale); + + useEffect(() => { + explodedModel.setSplit(split); + }, [explodedModel, split]); + + useFrame((_, delta) => { + explodedModel.update(delta); + }); + + return ( + + + + ); +} + +function MissingModelFallback({ + position = [0, 0, 0], +}: { + position?: Vector3Tuple | undefined; +}): React.JSX.Element { + return ( + + + + + ); +} diff --git a/src/components/three/SimpleModel.tsx b/src/components/three/models/SimpleModel.tsx similarity index 70% rename from src/components/three/SimpleModel.tsx rename to src/components/three/models/SimpleModel.tsx index 6c7b9c2..c559db2 100644 --- a/src/components/three/SimpleModel.tsx +++ b/src/components/three/models/SimpleModel.tsx @@ -1,5 +1,6 @@ -import { useGLTF } from "@react-three/drei"; -import type { Vector3Tuple } from "@/types/three"; +import { useMemo } from "react"; +import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; +import type { Vector3Tuple } from "@/types/three/three"; export interface SimpleModelConfig { modelPath: string; @@ -23,7 +24,13 @@ export function SimpleModel({ receiveShadow = true, children, }: SimpleModelProps): React.JSX.Element { - const { scene } = useGLTF(modelPath); + const { scene } = useLoggedGLTF(modelPath, { + scope: "SimpleModel", + position, + rotation, + scale, + }); + const model = useMemo(() => scene.clone(true), [scene]); const parsedScale = typeof scale === "number" ? ([scale, scale, scale] as Vector3Tuple) : scale; @@ -32,7 +39,7 @@ export function SimpleModel({ {children ?? ( diff --git a/src/components/three/models/useAnimatedModel.ts b/src/components/three/models/useAnimatedModel.ts new file mode 100644 index 0000000..8fee82a --- /dev/null +++ b/src/components/three/models/useAnimatedModel.ts @@ -0,0 +1,23 @@ +import { createContext, useContext } from "react"; + +export interface AnimatedModelContextValue { + play: (name: string, fade?: number) => void; + stop: (fade?: number) => void; + fadeTo: (name: string, fade?: number) => void; + currentAnimation: string; + isReady: boolean; + setSpeed: (speed: number) => void; + names: string[]; +} + +export const AnimatedModelContext = + createContext(null); + +export function useAnimatedModel(): AnimatedModelContextValue { + const context = useContext(AnimatedModelContext); + if (!context) { + throw new Error("useAnimatedModel must be used inside AnimatedModel"); + } + + return context; +} diff --git a/src/components/three/world/SkyModel.tsx b/src/components/three/world/SkyModel.tsx new file mode 100644 index 0000000..56fd5eb --- /dev/null +++ b/src/components/three/world/SkyModel.tsx @@ -0,0 +1,34 @@ +import { useFrame, useThree } from "@react-three/fiber"; +import { useGLTF } from "@react-three/drei"; +import { useRef } from "react"; +import * as THREE from "three"; +import { useClonedObject } from "@/hooks/three/useClonedObject"; +import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; + +interface SkyModelProps { + modelPath: string; +} + +const SKY_MODEL_SCALE = 1; + +export function SkyModel({ modelPath }: SkyModelProps): React.JSX.Element { + const camera = useThree((state) => state.camera); + const groupRef = useRef(null); + const { scene } = useLoggedGLTF(modelPath, { + scope: "SkyModel", + scale: SKY_MODEL_SCALE, + }); + const model = useClonedObject(scene); + + useFrame(() => { + groupRef.current?.position.copy(camera.position); + }); + + return ( + + + + ); +} + +useGLTF.preload("/models/sky/model.glb"); diff --git a/src/components/ui/Crosshair.tsx b/src/components/ui/Crosshair.tsx index dae485e..bc032fe 100644 --- a/src/components/ui/Crosshair.tsx +++ b/src/components/ui/Crosshair.tsx @@ -1,5 +1,5 @@ import { useCameraMode } from "@/hooks/debug/useCameraMode"; -import { useInteraction } from "@/hooks/useInteraction"; +import { useInteraction } from "@/hooks/interaction/useInteraction"; export function Crosshair(): React.JSX.Element | null { const cameraMode = useCameraMode(); diff --git a/src/components/ui/GameStateHUD.tsx b/src/components/ui/GameStateHUD.tsx deleted file mode 100644 index f38bafd..0000000 --- a/src/components/ui/GameStateHUD.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Debug } from "@/utils/debug/Debug"; -import { - type MainGameState, - useGameStore, -} from "@/managers/stores/useGameStore"; - -const MAIN_STATES: MainGameState[] = [ - "intro", - "bike", - "pylone", - "ferme", - "outro", -]; - -export function GameStateHUD(): React.JSX.Element | null { - const debug = Debug.getInstance(); - const mainState = useGameStore((state) => state.mainState); - const detail = useGameStore((state) => { - switch (state.mainState) { - case "intro": - return state.intro.hasCompleted ? "completed" : "waiting"; - case "bike": - return state.bike.currentStep; - case "pylone": - return state.pylone.currentStep; - case "ferme": - return state.ferme.currentStep; - case "outro": - return state.outro.hasStarted ? "started" : "waiting"; - } - }); - const setMainState = useGameStore((state) => state.setMainState); - const advanceGameState = useGameStore((state) => state.advanceGameState); - const resetGame = useGameStore((state) => state.resetGame); - - if (!debug.active) return null; - - return ( - - ); -} diff --git a/src/components/ui/GameUI.tsx b/src/components/ui/GameUI.tsx index 2581d02..6b3482a 100644 --- a/src/components/ui/GameUI.tsx +++ b/src/components/ui/GameUI.tsx @@ -1,13 +1,15 @@ import { Crosshair } from "@/components/ui/Crosshair"; -import { GameStateHUD } from "@/components/ui/GameStateHUD"; +import { DebugOverlayLayout } from "@/components/ui/debug/DebugOverlayLayout"; +import { HandTrackingVisualizer } from "@/components/ui/HandTrackingVisualizer"; import { InteractPrompt } from "@/components/ui/InteractPrompt"; export function GameUI(): React.JSX.Element { return ( <> - + + ); } diff --git a/src/components/ui/HandTrackingVisualizer.tsx b/src/components/ui/HandTrackingVisualizer.tsx new file mode 100644 index 0000000..98c6e1b --- /dev/null +++ b/src/components/ui/HandTrackingVisualizer.tsx @@ -0,0 +1,90 @@ +import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot"; +import { useHandTrackingGloveStatus } from "@/hooks/handTracking/useHandTrackingGloveStatus"; +import { useDebugStore } from "@/hooks/debug/useDebugStore"; + +const HAND_CONNECTIONS: Array<[number, number]> = [ + [0, 1], + [1, 2], + [2, 3], + [3, 4], + [0, 5], + [5, 6], + [6, 7], + [7, 8], + [5, 9], + [9, 10], + [10, 11], + [11, 12], + [9, 13], + [13, 14], + [14, 15], + [15, 16], + [13, 17], + [17, 18], + [18, 19], + [19, 20], + [0, 17], +]; + +export function HandTrackingVisualizer(): React.JSX.Element | null { + const { hands, status } = useHandTrackingSnapshot(); + const showHandTrackingSvg = useDebugStore((debug) => + debug.getShowHandTrackingSvg(), + ); + const gloves = useHandTrackingGloveStatus((state) => state.gloves); + const hasLoadedGlove = Object.values(gloves).some( + (gloveStatus) => gloveStatus === "loaded", + ); + + if ( + status === "idle" || + hands.length === 0 || + (hasLoadedGlove && !showHandTrackingSvg) + ) { + return null; + } + + return ( + + ); +} diff --git a/src/components/ui/InteractPrompt.tsx b/src/components/ui/InteractPrompt.tsx index 70b09db..56d21db 100644 --- a/src/components/ui/InteractPrompt.tsx +++ b/src/components/ui/InteractPrompt.tsx @@ -1,6 +1,6 @@ import { INTERACT_KEY } from "@/data/input/keybindings"; import { useCameraMode } from "@/hooks/debug/useCameraMode"; -import { useInteraction } from "@/hooks/useInteraction"; +import { useInteraction } from "@/hooks/interaction/useInteraction"; export function InteractPrompt(): React.JSX.Element | null { const cameraMode = useCameraMode(); diff --git a/src/components/ui/debug/DebugOverlayLayout.tsx b/src/components/ui/debug/DebugOverlayLayout.tsx new file mode 100644 index 0000000..9bfa71c --- /dev/null +++ b/src/components/ui/debug/DebugOverlayLayout.tsx @@ -0,0 +1,22 @@ +import { GameStateDebugPanel } from "@/components/ui/debug/GameStateDebugPanel"; +import { HandTrackingDebugPanel } from "@/components/ui/debug/HandTrackingDebugPanel"; +import { useShowDebugOverlay } from "@/hooks/debug/useShowDebugOverlay"; + +export function DebugOverlayLayout(): React.JSX.Element | null { + const showDebugOverlay = useShowDebugOverlay(); + + if (!showDebugOverlay) return null; + + return ( + + ); +} diff --git a/src/components/ui/debug/GameStateDebugPanel.tsx b/src/components/ui/debug/GameStateDebugPanel.tsx new file mode 100644 index 0000000..f2a8591 --- /dev/null +++ b/src/components/ui/debug/GameStateDebugPanel.tsx @@ -0,0 +1,164 @@ +import { RotateCcw, StepBack, StepForward } from "lucide-react"; +import { + type MainGameState, + type MissionStep, + useGameStore, +} from "@/managers/stores/useGameStore"; + +const MAIN_STATES: MainGameState[] = [ + "intro", + "bike", + "pylone", + "ferme", + "outro", +]; + +const MISSION_STEPS: MissionStep[] = [ + "locked", + "waiting", + "inspected", + "fragmented", + "scanning", + "repairing", + "done", +]; + +function toPascalCase(value: string): string { + return value + .split(/[-_\s]+/) + .filter(Boolean) + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(""); +} + +export function GameStateDebugPanel(): React.JSX.Element { + const mainState = useGameStore((state) => state.mainState); + const detail = useGameStore((state) => { + switch (state.mainState) { + case "intro": + return state.intro.hasCompleted ? "completed" : "waiting"; + case "bike": + return state.bike.currentStep; + case "pylone": + return state.pylone.currentStep; + case "ferme": + return state.ferme.currentStep; + case "outro": + return state.outro.hasStarted ? "started" : "waiting"; + } + }); + const setMainState = useGameStore((state) => state.setMainState); + const setIntroState = useGameStore((state) => state.setIntroState); + const setBikeState = useGameStore((state) => state.setBikeState); + const setPyloneState = useGameStore((state) => state.setPyloneState); + const setFermeState = useGameStore((state) => state.setFermeState); + const setOutroState = useGameStore((state) => state.setOutroState); + const advanceGameState = useGameStore((state) => state.advanceGameState); + const rewindGameState = useGameStore((state) => state.rewindGameState); + const resetGame = useGameStore((state) => state.resetGame); + + const subStateOptions = + mainState === "intro" + ? ["waiting", "completed"] + : mainState === "outro" + ? ["waiting", "started"] + : MISSION_STEPS; + + function setSubState(nextSubState: string): void { + if (mainState === "intro") { + setIntroState({ hasCompleted: nextSubState === "completed" }); + return; + } + + if (mainState === "bike") { + setBikeState({ currentStep: nextSubState as MissionStep }); + return; + } + + if (mainState === "pylone") { + setPyloneState({ currentStep: nextSubState as MissionStep }); + return; + } + + if (mainState === "ferme") { + setFermeState({ currentStep: nextSubState as MissionStep }); + return; + } + + setOutroState({ hasStarted: nextSubState === "started" }); + } + + return ( +
+
+

Game State

+
+ +
+
+ Main state + {toPascalCase(mainState)} +
+
+ {MAIN_STATES.map((state) => ( + + ))} +
+
+ +
+
+ Sub state + {toPascalCase(detail)} +
+
+ {subStateOptions.map((subState) => ( + + ))} +
+
+ +
+ + + +
+
+ ); +} diff --git a/src/components/ui/debug/HandTrackingDebugPanel.tsx b/src/components/ui/debug/HandTrackingDebugPanel.tsx new file mode 100644 index 0000000..b765557 --- /dev/null +++ b/src/components/ui/debug/HandTrackingDebugPanel.tsx @@ -0,0 +1,81 @@ +import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot"; +import { useHandTrackingGloveStatus } from "@/hooks/handTracking/useHandTrackingGloveStatus"; +import type { HandTrackingStatus } from "@/types/handTracking/handTracking"; + +const STATUS_LABELS: Record = { + idle: "Idle", + requesting_camera: "Requesting camera", + starting_camera: "Starting camera", + connecting_server: "Connecting server", + connecting: "Connecting", + connected: "Connected", + disconnected: "Disconnected", + error: "Error", +}; + +export function HandTrackingDebugPanel(): React.JSX.Element | null { + const { hands, status, usageStatus, serverStatus, error } = + useHandTrackingSnapshot(); + const gloves = useHandTrackingGloveStatus((state) => state.gloves); + + if (status === "idle") { + return null; + } + + const fist = hands.some((hand) => hand.isFist); + const modelLoaded = + [ + gloves.left === "loaded" ? "gant_l" : null, + gloves.right === "loaded" ? "gant_r" : null, + ] + .filter(Boolean) + .join(", ") || "none"; + const modelFallback = !Object.values(gloves).some( + (gloveStatus) => gloveStatus === "loaded", + ); + + return ( +
+
+

Hand tracking

+ {STATUS_LABELS[status]} +
+ +
+
+
Usage
+
{usageStatus}
+
+
+
Model loaded
+
{modelLoaded}
+
+
+
SVG fallback
+
{modelFallback ? "yes" : "no"}
+
+ {serverStatus ? ( +
+
Server
+
{serverStatus}
+
+ ) : null} +
+
Hands
+
{hands.length}
+
+
+
Fist
+
{fist ? "yes" : "no"}
+
+
+ + {error ? ( + {error} + ) : null} +
+ ); +} diff --git a/src/data/debug/testSceneConfig.ts b/src/data/debug/testSceneConfig.ts index da1edc2..568967b 100644 --- a/src/data/debug/testSceneConfig.ts +++ b/src/data/debug/testSceneConfig.ts @@ -1,4 +1,4 @@ -import type { Vector3Tuple } from "@/types/three"; +import type { Vector3Tuple } from "@/types/three/three"; export const TEST_SCENE_FLOOR_POSITION: Vector3Tuple = [0, -0.5, 0]; export const TEST_SCENE_FLOOR_SIZE: Vector3Tuple = [200, 1, 200]; @@ -13,7 +13,7 @@ export const TEST_SCENE_GRABBABLE_ROUGHNESS = 0.6; export const TEST_SCENE_GRABBABLE_METALNESS = 0.1; export const TEST_SCENE_TRIGGER_POSITION: Vector3Tuple = [3, 2, -3]; -export const TEST_SCENE_TRIGGER_SOUND_PATH = "/sounds/fa.mp3"; +export const TEST_SCENE_TRIGGER_SOUND_PATH = "/sounds/effect/fa.mp3"; export const TEST_SCENE_TRIGGER_RADIUS = 0.4; export const TEST_SCENE_TRIGGER_SEGMENTS = 32; export const TEST_SCENE_TRIGGER_COLOR = "#3b82f6"; diff --git a/src/data/docs/docsSections.ts b/src/data/docs/docsSections.ts index 07343c6..86bc869 100644 --- a/src/data/docs/docsSections.ts +++ b/src/data/docs/docsSections.ts @@ -38,11 +38,17 @@ export const docGroups: DocGroup[] = [ subtitle: "Implementation details", meta: "04", }, + { + path: "/docs/hand-tracking", + title: "Hand Tracking Technical Notes", + subtitle: "Webcam interaction pipeline", + meta: "05", + }, { path: "/docs/zustand", title: "Zustand Game State", subtitle: "Progression store", - meta: "05", + meta: "06", }, ], }, @@ -53,19 +59,25 @@ export const docGroups: DocGroup[] = [ path: "/docs/features", title: "Features", subtitle: "Implemented scope", - meta: "06", + meta: "07", + }, + { + path: "/docs/main-feature", + title: "Main Feature", + subtitle: "Repair-game prototype", + meta: "08", }, { path: "/docs/editor", title: "Editor User Guide", subtitle: "Editing workflow", - meta: "07", + meta: "09", }, { path: "/docs/animation", title: "Animation & 3D Model System", subtitle: "Components and usage", - meta: "08", + meta: "010", }, ], }, diff --git a/src/data/docs/docsTranslations.ts b/src/data/docs/docsTranslations.ts index d7a95b3..166cfc0 100644 --- a/src/data/docs/docsTranslations.ts +++ b/src/data/docs/docsTranslations.ts @@ -24,7 +24,6 @@ Construit avec React, Three.js et Vite. Fonctionne dans le navigateur, sans inst | [@react-three/fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction) | | [@react-three/drei](https://pmndrs.github.io/drei) | | [@react-three/rapier](https://rapier.rs/docs/) | -| [@react-three/postprocessing](https://github.com/pmndrs/postprocessing) | | [GSAP](https://gsap.com/docs/v3/Installation/) | ### Performance et effets @@ -48,17 +47,17 @@ la-fabrik/ │ └── sounds/ │ └── src/ - ├── world/ # Monde 3D persistant - │ ├── World.tsx # Composition principale de la scène - │ ├── Map.tsx # Carte de base, toujours montée + ├── world/ # Composition du monde 3D persistant + │ ├── World.tsx # Composition de la scène active + │ ├── GameMap.tsx # Chargement de carte et collision octree │ ├── Lighting.tsx # Lumières ambiante, directionnelle et ponctuelles - │ ├── Environment.tsx # HDRI, brouillard, ciel - │ ├── PostFX.tsx # Bloom, SSAO, aberration chromatique - │ ├── zones/ # Zones spatiales, LOD par zone + │ ├── Environment.tsx # Arrière-plan et modèle de ciel + │ ├── GameMusic.tsx # Cycle de vie de la musique de jeu + │ ├── debug/ # Scène de test debug │ └── player/ # Contrôleur joueur et caméra │ ├── components/ - │ ├── 3d/ # Éléments 3D réutilisables + │ ├── three/ # Composants R3F par domaine │ └── ui/ # Overlays HTML hors Canvas │ ├── managers/ # Logique, état et orchestration @@ -99,17 +98,17 @@ Ce document décrit le code réellement présent aujourd'hui dans le dépôt. - soit la carte principale, soit la scène de test physique debug - le rig joueur quand le mode caméra actif est \`player\` - \`src/world/GameMap.tsx\` charge les modèles de carte disponibles et construit l'octree de collision. -- \`src/world/debug/TestScene.tsx\` fournit une scène orientée debug pour les interactions et la physique. +- \`src/world/debug/TestMap.tsx\` fournit une carte orientée debug pour les interactions et la physique. - \`src/world/player/Player.tsx\` monte la caméra et le contrôleur. - \`src/world/player/PlayerController.tsx\` gère le mouvement pointer lock, le saut et les inputs d'interaction. ## Modèle d'interaction - \`src/managers/InteractionManager.ts\` est la source d'état actuelle des interactions. -- \`src/components/three/InteractableObject.tsx\` gère la détection de focus par distance et raycasting. -- \`src/components/three/TriggerObject.tsx\` implémente les interactions de type trigger. -- \`src/components/three/GrabbableObject.tsx\` implémente les interactions saisir / relâcher. -- \`src/hooks/useInteraction.ts\` expose un snapshot d'interaction à l'UI React. +- \`src/components/three/interaction/InteractableObject.tsx\` gère la détection de focus par distance et raycasting. +- \`src/components/three/interaction/TriggerObject.tsx\` implémente les interactions de type trigger. +- \`src/components/three/interaction/GrabbableObject.tsx\` implémente les interactions saisir / relâcher. +- \`src/hooks/interaction/useInteraction.ts\` expose un snapshot d'interaction à l'UI React. - \`src/components/ui/InteractPrompt.tsx\` affiche le prompt \`E\` pour les interactions trigger. ## Audio @@ -123,13 +122,18 @@ Ce document décrit le code réellement présent aujourd'hui dans le dépôt. - \`src/utils/debug/Debug.ts\` possède l'instance \`lil-gui\` et les contrôles debug. - \`src/hooks/debug/useCameraMode.ts\` et \`src/hooks/debug/useSceneMode.ts\` s'abonnent à l'état debug. - \`src/components/debug/DebugPerf.tsx\` monte \`r3f-perf\` en lazy uniquement en mode debug. +- \`src/components/ui/debug/DebugOverlayLayout.tsx\` monte l'overlay HTML debug compact quand il est activé depuis \`lil-gui\`. +- \`src/components/ui/debug/GameStateDebugPanel.tsx\` expose l'état de jeu courant, le changement de main/sub-state, les contrôles previous/next step et le reset. +- \`src/components/ui/debug/HandTrackingDebugPanel.tsx\` affiche le statut hand tracking, l'usage, le modèle de gant chargé, le nombre de mains et l'état fist pendant l'activation du hand tracking. +- \`src/components/three/handTracking/HandTrackingGlove.tsx\` place les modèles riggés \`gant_l\` et \`gant_r\` sur les mains détectées dans la scène physics debug. - \`src/components/debug/scene/DebugHelpers.tsx\` monte les helpers debug. - \`src/components/debug/scene/DebugCameraControls.tsx\` monte la caméra libre debug. +- Les contrôles globaux \`lil-gui\` incluent camera mode, scene mode, \`R3F Perf\` et \`Debug Overlay\`; les contrôles d'interaction vivent dans le dossier \`Interaction\`. ## Limites actuelles - Le dépôt est encore un prototype, pas le runtime complet du jeu. -- \`src/world/debug/TestScene.tsx\` fait encore partie de la composition active. +- \`src/world/debug/TestMap.tsx\` fait encore partie de la composition active. - Il n'existe pas encore d'orchestrateur gameplay central comme \`GameManager\`. - Les systèmes de missions, zones, cinématiques et dialogues ne sont pas implémentés. - Le joueur utilise une collision octree et des règles simples, pas une pile physique gameplay complète. @@ -142,7 +146,7 @@ Ce document décrit l'architecture visée à moyen terme pour le projet. ## Relation avec le code actuel - \`docs/technical/architecture.md\` reste la source de vérité de ce qui existe maintenant. -- Ce document est volontairement aspirational. +- Ce document décrit une direction d'architecture, pas un comportement implémenté. - Si ce document contredit l'implémentation actuelle, l'implémentation actuelle gagne. ## Objectifs @@ -346,7 +350,8 @@ Dans React Three Fiber, monter ou démonter du JSX contrôle ce qui apparaît da Overlays actuels : -- \`GameStateHUD\` : panneau de progression debug visible avec \`?debug\` +- \`DebugOverlayLayout\` : layout compact des panels debug HTML visible avec \`?debug\` +- \`GameStateDebugPanel\` : panneau de progression debug pour consulter/changer le main state, le sub state, avancer/reculer et reset le store - \`Crosshair\` : aide de visée joueur - \`InteractPrompt\` : prompt d'interaction @@ -373,7 +378,7 @@ Ce document liste les fonctionnalités présentes dans le code actuel. ## Scène - Scène React Three Fiber plein écran -- Carte principale chargée depuis \`public/models/map/model.gltf\` +- Carte principale chargée depuis \`public/models/{name}/model.glb\`, avec fallback vers \`model.gltf\` - Scène de test physique debug sélectionnable depuis le panneau debug - Éclairage ambiant et directionnel - Configuration de l'environnement de fond @@ -401,7 +406,8 @@ Ce document liste les fonctionnalités présentes dans le code actuel. ## Outils debug - Le paramètre \`?debug\` active le panneau debug -- Contrôles \`lil-gui\` pour le mode caméra, le mode scène et les sphères d'interaction +- Contrôles \`lil-gui\` pour le mode caméra, le mode scène, \`R3F Perf\`, \`Debug Overlay\` et le tuning d'interaction +- Overlay debug compact pour les contrôles de game state et le statut hand tracking - Helpers de scène debug - Caméra libre debug - Overlay \`r3f-perf\` @@ -427,7 +433,7 @@ L'éditeur travaille sur la liste de nodes stockée dans "/public/map.json". Chaque node décrit un objet de la scène : -- "name" : nom du dossier modèle dans "/public/models/{name}/model.gltf" +- "name" : nom du dossier modèle dans "/public/models/{name}/model.glb", avec fallback vers "model.gltf" - "type" : catégorie de l'objet - "position" : "[x, y, z]" - "rotation" : "[x, y, z]" diff --git a/src/data/gameplay/repairCaseConfig.ts b/src/data/gameplay/repairCaseConfig.ts new file mode 100644 index 0000000..60dbcbc --- /dev/null +++ b/src/data/gameplay/repairCaseConfig.ts @@ -0,0 +1,15 @@ +export const REPAIR_CASE_MODEL_PATH = "/models/packderelance/model.gltf"; +export const REPAIR_CASE_OPEN_SOUND_PATH = "/sounds/effect/open-malette.mp3"; +export const REPAIR_CASE_CLOSE_SOUND_PATH = "/sounds/effect/close-malette.mp3"; + +export const REPAIR_CASE_LID_NODE_NAME = "partiesup"; +export const REPAIR_CASE_CLOSED_ROTATION_OFFSET_DEGREES = 0; +export const REPAIR_CASE_OPEN_ROTATION_OFFSET_DEGREES = 115; +export const REPAIR_CASE_ANIMATION_DURATION = 0.8; + +export const REPAIR_CASE_FLOAT_ACTIVATION_DISTANCE = 5; +export const REPAIR_CASE_FLOAT_HEIGHT = 1; +export const REPAIR_CASE_FLOAT_UP_SPEED = 2.4; +export const REPAIR_CASE_FLOAT_DOWN_SPEED = 1.8; +export const REPAIR_CASE_ROTATION_RESET_SPEED = 3; +export const REPAIR_CASE_ROTATION_AMPLITUDE_DEGREES = 5; diff --git a/src/data/gameplay/repairGameConfig.ts b/src/data/gameplay/repairGameConfig.ts new file mode 100644 index 0000000..6fd67af --- /dev/null +++ b/src/data/gameplay/repairGameConfig.ts @@ -0,0 +1,11 @@ +import type { Vector3Tuple } from "@/types/three/three"; + +export const REPAIR_GAME_ZONE_ORIGIN: Vector3Tuple = [10, 0.4, -8]; +export const REPAIR_GAME_ZONE_RADIUS = 4.2; +export const REPAIR_GAME_ZONE_LABEL = "Pack de Relance Feature"; + +export const REPAIR_GAME_MODULE_SLOTS = [ + { label: "Module A", offset: [-2.2, 0, 2.2] }, + { label: "Module B", offset: [0, 0, 2.6] }, + { label: "Module C", offset: [2.2, 0, 2.2] }, +] satisfies Array<{ label: string; offset: Vector3Tuple }>; diff --git a/src/data/gameplay/repairGameModelCatalog.ts b/src/data/gameplay/repairGameModelCatalog.ts new file mode 100644 index 0000000..1fffdcc --- /dev/null +++ b/src/data/gameplay/repairGameModelCatalog.ts @@ -0,0 +1,29 @@ +export interface ModelCatalogItem { + name: string; + path: string; +} + +export const REPAIR_GAME_MODEL_CATALOG: ModelCatalogItem[] = [ + { name: "Electricienne", path: "/models/elecsimple/model.gltf" }, + { + name: "Electricienne complete", + path: "/models/electricienne_animated/model.gltf", + }, + { name: "Eolienne", path: "/models/eolienne/model.gltf" }, + { name: "Fermier", path: "/models/fermier/model.gltf" }, + { name: "Galet", path: "/models/galet/model.gltf" }, + { name: "Gant", path: "/models/gant/model.gltf" }, + { name: "Gants", path: "/models/gants/model.gltf" }, + { name: "Gerant", path: "/models/gerant/model.gltf" }, + { name: "Immeuble", path: "/models/immeuble1/model.gltf" }, + { name: "Kit de relance", path: "/models/packderelance/model.gltf" }, + { name: "La Fabrik", path: "/models/lafabrik/model.gltf" }, + { name: "Maison", path: "/models/maison1/model.gltf" }, + { name: "Map", path: "/models/map/model.gltf" }, + { name: "Perso principal", path: "/models/persoprincipal/model.gltf" }, + { name: "Pylone", path: "/models/pylone/model.gltf" }, + { name: "Refroidisseur", path: "/models/refroidisseur/model.gltf" }, + { name: "Sapin", path: "/models/sapin/model.gltf" }, + { name: "Talkie", path: "/models/talkie/model.gltf" }, + { name: "Terrain", path: "/models/terrain/model.gltf" }, +]; diff --git a/src/data/handTrackingConfig.ts b/src/data/handTrackingConfig.ts new file mode 100644 index 0000000..e24bbe5 --- /dev/null +++ b/src/data/handTrackingConfig.ts @@ -0,0 +1,25 @@ +const HAND_TRACKING_LOCAL_WS_URL = "ws://localhost:8000/ws"; +const HAND_TRACKING_PROD_WS_URL = "wss://handtracking.la-fabrik.fr/ws"; + +export const HAND_TRACKING_FRAME_WIDTH = 320; +export const HAND_TRACKING_FRAME_HEIGHT = 240; +export const HAND_TRACKING_TARGET_FPS = 10; +export const HAND_TRACKING_JPEG_QUALITY = 0.55; +export const HAND_TRACKING_CAMERA_TIMEOUT_MS = 8_000; +export const HAND_TRACKING_RESPONSE_TIMEOUT_MS = 1_500; +export const HAND_TRACKING_BROWSER_WASM_URL = + "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.35/wasm"; +export const HAND_TRACKING_BROWSER_MODEL_URL = + "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task"; + +export function getHandTrackingWsUrl(): string { + const configuredUrl = import.meta.env.VITE_HAND_TRACKING_WS_URL; + + if (configuredUrl) { + return configuredUrl; + } + + return import.meta.env.DEV + ? HAND_TRACKING_LOCAL_WS_URL + : HAND_TRACKING_PROD_WS_URL; +} diff --git a/src/data/interaction/grabConfig.ts b/src/data/interaction/grabConfig.ts index 774b515..8eaa086 100644 --- a/src/data/interaction/grabConfig.ts +++ b/src/data/interaction/grabConfig.ts @@ -3,7 +3,7 @@ export const GRAB_DEFAULT_LABEL = "Prendre"; export const GRAB_STIFFNESS_DEFAULT = 15; export const GRAB_THROW_BOOST_DEFAULT = 1.0; -export const GRAB_HOLD_DISTANCE_DEFAULT = 2; +export const GRAB_HOLD_DISTANCE_DEFAULT = 3; export const GRAB_STIFFNESS_MIN = 1; export const GRAB_STIFFNESS_MAX = 50; @@ -13,6 +13,6 @@ export const GRAB_THROW_BOOST_MIN = 0.5; export const GRAB_THROW_BOOST_MAX = 3.0; export const GRAB_THROW_BOOST_STEP = 0.1; -export const GRAB_HOLD_DISTANCE_MIN = 0.5; +export const GRAB_HOLD_DISTANCE_MIN = 1; export const GRAB_HOLD_DISTANCE_MAX = 5.0; export const GRAB_HOLD_DISTANCE_STEP = 0.1; diff --git a/src/data/interaction/triggerConfig.ts b/src/data/interaction/triggerConfig.ts index 46bf396..d9120f0 100644 --- a/src/data/interaction/triggerConfig.ts +++ b/src/data/interaction/triggerConfig.ts @@ -1,4 +1,4 @@ -import type { Vector3Tuple } from "@/types/three"; +import type { Vector3Tuple } from "@/types/three/three"; export const TRIGGER_DEFAULT_COLLIDERS = "ball"; export const TRIGGER_DEFAULT_LABEL = "Interagir"; diff --git a/src/data/player/playerConfig.ts b/src/data/player/playerConfig.ts index a8b60ea..699ee49 100644 --- a/src/data/player/playerConfig.ts +++ b/src/data/player/playerConfig.ts @@ -1,4 +1,4 @@ -import type { Vector3Tuple } from "@/types/three"; +import type { Vector3Tuple } from "@/types/three/three"; export const PLAYER_EYE_HEIGHT = 1.75; export const PLAYER_CAPSULE_RADIUS = 0.35; diff --git a/src/data/world/environmentConfig.ts b/src/data/world/environmentConfig.ts index fe277fa..f5f1222 100644 --- a/src/data/world/environmentConfig.ts +++ b/src/data/world/environmentConfig.ts @@ -1,2 +1,2 @@ -export const GAME_SCENE_SKYBOX_PATH = "/skybox/sky.exr"; +export const GAME_SCENE_SKY_MODEL_PATH = "/models/sky/model.glb"; export const PHYSICS_SCENE_BACKGROUND_COLOR = "#0b1018"; diff --git a/src/hooks/useCharacterAnimation.ts b/src/hooks/animation/useCharacterAnimation.ts similarity index 84% rename from src/hooks/useCharacterAnimation.ts rename to src/hooks/animation/useCharacterAnimation.ts index c9dcdb5..193bc04 100644 --- a/src/hooks/useCharacterAnimation.ts +++ b/src/hooks/animation/useCharacterAnimation.ts @@ -1,8 +1,8 @@ -/* eslint-disable react-hooks/immutability */ -import { useRef, useEffect, useState, useCallback } from "react"; -import { useGLTF, useAnimations } from "@react-three/drei"; +import { useRef, useEffect, useState, useCallback, useMemo } from "react"; +import { useAnimations } from "@react-three/drei"; import type { AnimationAction, AnimationMixer } from "three"; import * as THREE from "three"; +import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; export interface CharacterAnimationConfig { modelPath: string; @@ -35,7 +35,10 @@ export function useCharacterAnimation( } = config; const groupRef = useRef(null); - const { scene, animations } = useGLTF(modelPath); + const { scene, animations } = useLoggedGLTF(modelPath, { + scope: "useCharacterAnimation", + }); + const model = useMemo(() => scene.clone(true), [scene]); const { actions, names, mixer } = useAnimations(animations, groupRef); const [currentAnimation, setCurrentAnimation] = useState(initialAnimation); @@ -78,11 +81,11 @@ export function useCharacterAnimation( const setAnimationSpeed = useCallback( (speed: number) => { - if (mixer) { - mixer.timeScale = speed; - } + Object.values(actions).forEach((action) => { + action?.setEffectiveTimeScale(speed); + }); }, - [mixer], + [actions], ); useEffect(() => { @@ -93,7 +96,7 @@ export function useCharacterAnimation( }, [actions, initialAnimation]); return { - scene, + scene: model, actions, names, mixer, diff --git a/src/hooks/debug/useCameraMode.ts b/src/hooks/debug/useCameraMode.ts index 90976f6..73cfa93 100644 --- a/src/hooks/debug/useCameraMode.ts +++ b/src/hooks/debug/useCameraMode.ts @@ -1,4 +1,4 @@ -import type { CameraMode } from "@/types/debug"; +import type { CameraMode } from "@/types/debug/debug"; import { useDebugStore } from "@/hooks/debug/useDebugStore"; export function useCameraMode(): CameraMode { diff --git a/src/hooks/debug/useSceneMode.ts b/src/hooks/debug/useSceneMode.ts index 5d14254..4af9129 100644 --- a/src/hooks/debug/useSceneMode.ts +++ b/src/hooks/debug/useSceneMode.ts @@ -1,4 +1,4 @@ -import type { SceneMode } from "@/types/debug"; +import type { SceneMode } from "@/types/debug/debug"; import { useDebugStore } from "@/hooks/debug/useDebugStore"; export function useSceneMode(): SceneMode { diff --git a/src/hooks/debug/useShowDebugOverlay.ts b/src/hooks/debug/useShowDebugOverlay.ts new file mode 100644 index 0000000..b930e8e --- /dev/null +++ b/src/hooks/debug/useShowDebugOverlay.ts @@ -0,0 +1,5 @@ +import { useDebugStore } from "@/hooks/debug/useDebugStore"; + +export function useShowDebugOverlay(): boolean { + return useDebugStore((debug) => debug.getShowDebugOverlay()); +} diff --git a/src/hooks/debug/useShowDebugPerf.ts b/src/hooks/debug/useShowDebugPerf.ts new file mode 100644 index 0000000..99c3b4a --- /dev/null +++ b/src/hooks/debug/useShowDebugPerf.ts @@ -0,0 +1,5 @@ +import { useDebugStore } from "@/hooks/debug/useDebugStore"; + +export function useShowDebugPerf(): boolean { + return useDebugStore((debug) => debug.getShowPerf()); +} diff --git a/src/hooks/editor/useEditorHistory.ts b/src/hooks/editor/useEditorHistory.ts index 275bcad..4d27554 100644 --- a/src/hooks/editor/useEditorHistory.ts +++ b/src/hooks/editor/useEditorHistory.ts @@ -1,5 +1,5 @@ import { useCallback, useRef, useState } from "react"; -import type { MapNode, SceneData } from "@/types/editor"; +import type { MapNode, SceneData } from "@/types/editor/editor"; interface ObjectTransform { uuid: string; diff --git a/src/hooks/editor/useEditorSceneData.ts b/src/hooks/editor/useEditorSceneData.ts index ffdab7b..29b9c42 100644 --- a/src/hooks/editor/useEditorSceneData.ts +++ b/src/hooks/editor/useEditorSceneData.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from "react"; import { createSceneDataFromFiles } from "@/utils/editor/loadEditorScene"; -import { loadMapSceneData } from "@/utils/loadMapSceneData"; -import type { SceneData } from "@/types/editor"; +import { loadMapSceneData } from "@/utils/map/loadMapSceneData"; +import type { SceneData } from "@/types/editor/editor"; interface UseEditorSceneDataResult { hasMapJson: boolean; diff --git a/src/hooks/gameplay/useModelSelection.ts b/src/hooks/gameplay/useModelSelection.ts new file mode 100644 index 0000000..f31aea0 --- /dev/null +++ b/src/hooks/gameplay/useModelSelection.ts @@ -0,0 +1,79 @@ +import { useCallback, useEffect, useState } from "react"; +import type { ModelCatalogItem } from "@/data/gameplay/repairGameModelCatalog"; + +interface UseModelSelectionResult { + isOpen: boolean; + selectedIndex: number; + selectedModel: ModelCatalogItem; + open: () => void; + close: () => void; +} + +export function useModelSelection( + models: ModelCatalogItem[], + onSelect: (model: ModelCatalogItem) => void, +): UseModelSelectionResult { + const [isOpen, setIsOpen] = useState(false); + const [selectedIndex, setSelectedIndex] = useState(0); + const firstModel = models[0]; + + if (!firstModel) { + throw new Error("useModelSelection requires at least one model"); + } + + const selectedModel = models[selectedIndex] ?? firstModel; + + const close = useCallback(() => setIsOpen(false), []); + const open = useCallback(() => setIsOpen(true), []); + + useEffect(() => { + if (!isOpen) return; + + const handleKeyDown = (event: KeyboardEvent): void => { + const key = event.key.toLowerCase(); + + if (["arrowup", "arrowleft"].includes(key)) { + setSelectedIndex((index) => + index === 0 ? models.length - 1 : index - 1, + ); + event.preventDefault(); + event.stopPropagation(); + return; + } + + if (["arrowdown", "arrowright"].includes(key)) { + setSelectedIndex((index) => (index + 1) % models.length); + event.preventDefault(); + event.stopPropagation(); + return; + } + + if (key === "e" || key === "enter") { + onSelect(selectedModel); + close(); + event.preventDefault(); + event.stopPropagation(); + return; + } + + if (key === "escape") { + close(); + event.preventDefault(); + event.stopPropagation(); + } + }; + + window.addEventListener("keydown", handleKeyDown, { capture: true }); + return () => { + window.removeEventListener("keydown", handleKeyDown, { capture: true }); + }; + }, [close, isOpen, models, onSelect, selectedModel]); + + return { + isOpen, + selectedIndex, + selectedModel, + open, + close, + }; +} diff --git a/src/hooks/handTracking/useBrowserHandTracking.ts b/src/hooks/handTracking/useBrowserHandTracking.ts new file mode 100644 index 0000000..b9ffd78 --- /dev/null +++ b/src/hooks/handTracking/useBrowserHandTracking.ts @@ -0,0 +1,184 @@ +import { useEffect, useRef, useState } from "react"; +import { + HAND_TRACKING_CAMERA_TIMEOUT_MS, + HAND_TRACKING_FRAME_HEIGHT, + HAND_TRACKING_FRAME_WIDTH, + HAND_TRACKING_TARGET_FPS, +} from "@/data/handTrackingConfig"; +import { + convertBrowserHandResult, + getBrowserHandLandmarker, +} from "@/lib/handTracking/browserHandTracking"; +import type { HandTrackingSnapshot } from "@/types/handTracking/handTracking"; + +interface UseBrowserHandTrackingOptions { + enabled: boolean; +} + +const INITIAL_SNAPSHOT: HandTrackingSnapshot = { + hands: [], + status: "idle", + usageStatus: "inactive", + serverStatus: null, + error: null, +}; + +function getCameraStreamWithTimeout( + constraints: MediaStreamConstraints, +): Promise { + let didTimeout = false; + const streamPromise = navigator.mediaDevices.getUserMedia(constraints); + + const timeoutPromise = new Promise((_, reject) => { + window.setTimeout(() => { + didTimeout = true; + reject( + new Error( + "Camera request timed out. Restart Arc or check camera permissions for localhost:5173.", + ), + ); + }, HAND_TRACKING_CAMERA_TIMEOUT_MS); + }); + + streamPromise.then((stream) => { + if (didTimeout) { + stream.getTracks().forEach((track) => track.stop()); + } + }); + + return Promise.race([streamPromise, timeoutPromise]); +} + +export function useBrowserHandTracking({ + enabled, +}: UseBrowserHandTrackingOptions): HandTrackingSnapshot { + const [snapshot, setSnapshot] = + useState(INITIAL_SNAPSHOT); + const videoRef = useRef(null); + const streamRef = useRef(null); + const intervalRef = useRef(null); + + useEffect(() => { + if (!enabled) { + return undefined; + } + + let cancelled = false; + + const cleanup = (): void => { + if (intervalRef.current !== null) { + window.clearInterval(intervalRef.current); + intervalRef.current = null; + } + + streamRef.current?.getTracks().forEach((track) => track.stop()); + streamRef.current = null; + videoRef.current = null; + }; + + const start = async (): Promise => { + setSnapshot({ + hands: [], + status: "requesting_camera", + usageStatus: "available", + serverStatus: "Browser JS", + error: null, + }); + + try { + const stream = await getCameraStreamWithTimeout({ + video: { + width: HAND_TRACKING_FRAME_WIDTH, + height: HAND_TRACKING_FRAME_HEIGHT, + facingMode: "user", + }, + audio: false, + }); + + if (cancelled) { + stream.getTracks().forEach((track) => track.stop()); + return; + } + + setSnapshot((current) => ({ + ...current, + status: "starting_camera", + })); + + const video = document.createElement("video"); + video.muted = true; + video.playsInline = true; + video.srcObject = stream; + await video.play(); + + if (cancelled) { + stream.getTracks().forEach((track) => track.stop()); + return; + } + + setSnapshot((current) => ({ + ...current, + status: "connecting", + serverStatus: "Loading Browser JS model", + })); + + const handLandmarker = await getBrowserHandLandmarker(); + + if (cancelled) { + stream.getTracks().forEach((track) => track.stop()); + return; + } + + streamRef.current = stream; + videoRef.current = video; + + setSnapshot((current) => ({ + ...current, + status: "connected", + serverStatus: "Browser JS", + })); + + intervalRef.current = window.setInterval(() => { + if (video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) return; + + const result = handLandmarker.detectForVideo( + video, + performance.now(), + ); + const hands = convertBrowserHandResult(result); + + setSnapshot((current) => ({ + ...current, + hands, + usageStatus: hands.some((hand) => hand.isFist) + ? "active" + : "available", + error: null, + })); + }, 1_000 / HAND_TRACKING_TARGET_FPS); + } catch (error) { + if (cancelled) return; + + setSnapshot({ + hands: [], + status: "error", + usageStatus: "inactive", + serverStatus: "Browser JS", + error: + error instanceof Error + ? error.message + : "Browser hand tracking failed", + }); + } + }; + + void start(); + + return () => { + cancelled = true; + cleanup(); + }; + }, [enabled]); + + return snapshot; +} diff --git a/src/hooks/handTracking/useHandTrackingGloveStatus.ts b/src/hooks/handTracking/useHandTrackingGloveStatus.ts new file mode 100644 index 0000000..fb9dd0e --- /dev/null +++ b/src/hooks/handTracking/useHandTrackingGloveStatus.ts @@ -0,0 +1,28 @@ +import { create } from "zustand"; + +export type HandTrackingGloveHandedness = "left" | "right"; + +type HandTrackingGloveLoadState = "idle" | "loaded" | "error"; + +interface HandTrackingGloveStatusState { + gloves: Record; + setGloveStatus: ( + handedness: HandTrackingGloveHandedness, + status: HandTrackingGloveLoadState, + ) => void; +} + +export const useHandTrackingGloveStatus = + create()((set) => ({ + gloves: { + left: "idle", + right: "idle", + }, + setGloveStatus: (handedness, status) => + set((state) => ({ + gloves: { + ...state.gloves, + [handedness]: status, + }, + })), + })); diff --git a/src/hooks/handTracking/useHandTrackingSnapshot.ts b/src/hooks/handTracking/useHandTrackingSnapshot.ts new file mode 100644 index 0000000..4360cc8 --- /dev/null +++ b/src/hooks/handTracking/useHandTrackingSnapshot.ts @@ -0,0 +1,18 @@ +import { createContext, useContext } from "react"; +import type { HandTrackingSnapshot } from "@/types/handTracking/handTracking"; + +export const HAND_TRACKING_IDLE_SNAPSHOT: HandTrackingSnapshot = { + hands: [], + status: "idle", + usageStatus: "inactive", + serverStatus: null, + error: null, +}; + +export const HandTrackingContext = createContext( + HAND_TRACKING_IDLE_SNAPSHOT, +); + +export function useHandTrackingSnapshot(): HandTrackingSnapshot { + return useContext(HandTrackingContext); +} diff --git a/src/hooks/handTracking/useRemoteHandTracking.ts b/src/hooks/handTracking/useRemoteHandTracking.ts new file mode 100644 index 0000000..0056c05 --- /dev/null +++ b/src/hooks/handTracking/useRemoteHandTracking.ts @@ -0,0 +1,360 @@ +import { useEffect, useRef, useState } from "react"; +import { + HAND_TRACKING_CAMERA_TIMEOUT_MS, + HAND_TRACKING_FRAME_HEIGHT, + HAND_TRACKING_FRAME_WIDTH, + HAND_TRACKING_JPEG_QUALITY, + HAND_TRACKING_RESPONSE_TIMEOUT_MS, + HAND_TRACKING_TARGET_FPS, + getHandTrackingWsUrl, +} from "@/data/handTrackingConfig"; +import type { + HandTrackingFrameMessage, + HandTrackingHand, + HandTrackingServerMessage, + HandTrackingSnapshot, +} from "@/types/handTracking/handTracking"; + +interface UseRemoteHandTrackingOptions { + enabled: boolean; + websocketUrl?: string; +} + +const INITIAL_SNAPSHOT: HandTrackingSnapshot = { + hands: [], + status: "idle", + usageStatus: "inactive", + serverStatus: null, + error: null, +}; + +function getBase64Payload(dataUrl: string): string { + return dataUrl.slice(dataUrl.indexOf(",") + 1); +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function isFiniteNumber(value: unknown): value is number { + return typeof value === "number" && Number.isFinite(value); +} + +function isHandTrackingLandmark(value: unknown): boolean { + return ( + isRecord(value) && + isFiniteNumber(value.x) && + isFiniteNumber(value.y) && + isFiniteNumber(value.z) + ); +} + +function isHandTrackingHand(value: unknown): value is HandTrackingHand { + return ( + isRecord(value) && + isFiniteNumber(value.x) && + isFiniteNumber(value.y) && + isFiniteNumber(value.z) && + Array.isArray(value.landmarks) && + value.landmarks.every(isHandTrackingLandmark) && + typeof value.handedness === "string" && + typeof value.isFist === "boolean" && + isFiniteNumber(value.score) + ); +} + +function isHandTrackingServerMessage( + value: unknown, +): value is HandTrackingServerMessage { + if (!isRecord(value) || !isFiniteNumber(value.timestamp)) return false; + + if (value.type === "hands") { + return Array.isArray(value.hands) && value.hands.every(isHandTrackingHand); + } + + if (value.type === "status") { + return typeof value.status === "string"; + } + + return ( + value.type === "error" && + Array.isArray(value.hands) && + value.hands.every(isHandTrackingHand) && + typeof value.message === "string" + ); +} + +function getCameraStreamWithTimeout( + constraints: MediaStreamConstraints, +): Promise { + let didTimeout = false; + const streamPromise = navigator.mediaDevices.getUserMedia(constraints); + + const timeoutPromise = new Promise((_, reject) => { + window.setTimeout(() => { + didTimeout = true; + reject( + new Error( + "Camera request timed out. Restart Arc or check camera permissions for localhost:5173.", + ), + ); + }, HAND_TRACKING_CAMERA_TIMEOUT_MS); + }); + + streamPromise.then((stream) => { + if (didTimeout) { + stream.getTracks().forEach((track) => track.stop()); + } + }); + + return Promise.race([streamPromise, timeoutPromise]); +} + +export function useRemoteHandTracking({ + enabled, + websocketUrl = getHandTrackingWsUrl(), +}: UseRemoteHandTrackingOptions): HandTrackingSnapshot { + const [snapshot, setSnapshot] = + useState(INITIAL_SNAPSHOT); + const videoRef = useRef(null); + const canvasRef = useRef(null); + const streamRef = useRef(null); + const wsRef = useRef(null); + const sendIntervalRef = useRef(null); + const responseTimeoutRef = useRef(null); + const waitingForResponseRef = useRef(false); + + useEffect(() => { + if (!enabled) { + return undefined; + } + + let cancelled = false; + + const clearResponseTimeout = (): void => { + if (responseTimeoutRef.current === null) return; + window.clearTimeout(responseTimeoutRef.current); + responseTimeoutRef.current = null; + }; + + const cleanup = (): void => { + if (sendIntervalRef.current !== null) { + window.clearInterval(sendIntervalRef.current); + sendIntervalRef.current = null; + } + + clearResponseTimeout(); + waitingForResponseRef.current = false; + wsRef.current?.close(); + wsRef.current = null; + + streamRef.current?.getTracks().forEach((track) => track.stop()); + streamRef.current = null; + videoRef.current = null; + canvasRef.current = null; + }; + + const markResponseReceived = (): void => { + waitingForResponseRef.current = false; + clearResponseTimeout(); + }; + + const markInvalidResponse = (): void => { + setSnapshot((current) => ({ + ...current, + hands: [], + status: "error", + usageStatus: "inactive", + error: "Invalid hand tracking response", + })); + }; + + const sendFrame = (): void => { + const ws = wsRef.current; + const video = videoRef.current; + const canvas = canvasRef.current; + const context = canvas?.getContext("2d"); + + if (!ws || ws.readyState !== WebSocket.OPEN) return; + if (!video || !canvas || !context) return; + if (video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) return; + if (waitingForResponseRef.current) return; + + context.drawImage(video, 0, 0, canvas.width, canvas.height); + const dataUrl = canvas.toDataURL( + "image/jpeg", + HAND_TRACKING_JPEG_QUALITY, + ); + const message: HandTrackingFrameMessage = { + type: "frame", + timestamp: Date.now(), + width: canvas.width, + height: canvas.height, + image: getBase64Payload(dataUrl), + }; + + waitingForResponseRef.current = true; + ws.send(JSON.stringify(message)); + responseTimeoutRef.current = window.setTimeout(() => { + waitingForResponseRef.current = false; + responseTimeoutRef.current = null; + }, HAND_TRACKING_RESPONSE_TIMEOUT_MS); + }; + + const start = async (): Promise => { + await Promise.resolve(); + if (cancelled) return; + + setSnapshot({ + hands: [], + status: "requesting_camera", + usageStatus: "available", + serverStatus: null, + error: null, + }); + + try { + const stream = await getCameraStreamWithTimeout({ + video: { + width: HAND_TRACKING_FRAME_WIDTH, + height: HAND_TRACKING_FRAME_HEIGHT, + facingMode: "user", + }, + audio: false, + }); + + if (cancelled) { + stream.getTracks().forEach((track) => track.stop()); + return; + } + + setSnapshot((current) => ({ + ...current, + status: "starting_camera", + })); + + const video = document.createElement("video"); + video.muted = true; + video.playsInline = true; + video.srcObject = stream; + await video.play(); + + if (cancelled) { + stream.getTracks().forEach((track) => track.stop()); + return; + } + + setSnapshot((current) => ({ + ...current, + status: "connecting_server", + })); + + const canvas = document.createElement("canvas"); + canvas.width = HAND_TRACKING_FRAME_WIDTH; + canvas.height = HAND_TRACKING_FRAME_HEIGHT; + + const ws = new WebSocket(websocketUrl); + ws.onopen = () => { + setSnapshot((current) => ({ + ...current, + status: "connected", + usageStatus: "available", + error: null, + })); + }; + ws.onmessage = (event) => { + markResponseReceived(); + if (typeof event.data !== "string") { + markInvalidResponse(); + return; + } + + let data: unknown; + try { + data = JSON.parse(event.data); + } catch { + markInvalidResponse(); + return; + } + + if (!isHandTrackingServerMessage(data)) { + markInvalidResponse(); + return; + } + + if (data.type === "hands") { + setSnapshot((current) => ({ + ...current, + hands: data.hands, + usageStatus: data.hands.some((hand) => hand.isFist) + ? "active" + : "available", + serverStatus: null, + error: null, + })); + return; + } + + if (data.type === "status") { + setSnapshot((current) => ({ + ...current, + serverStatus: data.status, + })); + return; + } + + setSnapshot((current) => ({ + ...current, + hands: [], + status: "error", + usageStatus: "inactive", + error: data.message, + })); + }; + ws.onerror = () => { + markResponseReceived(); + setSnapshot((current) => ({ + ...current, + status: "error", + error: "Hand tracking WebSocket error", + })); + }; + ws.onclose = () => { + markResponseReceived(); + setSnapshot((current) => ({ + ...current, + status: cancelled ? "idle" : "disconnected", + })); + }; + + streamRef.current = stream; + videoRef.current = video; + canvasRef.current = canvas; + wsRef.current = ws; + sendIntervalRef.current = window.setInterval( + sendFrame, + 1_000 / HAND_TRACKING_TARGET_FPS, + ); + } catch (error) { + if (cancelled) return; + setSnapshot({ + hands: [], + status: "error", + usageStatus: "inactive", + serverStatus: null, + error: + error instanceof Error ? error.message : "Hand tracking failed", + }); + } + }; + + void start(); + + return () => { + cancelled = true; + cleanup(); + }; + }, [enabled, websocketUrl]); + + return snapshot; +} diff --git a/src/hooks/useInteraction.ts b/src/hooks/interaction/useInteraction.ts similarity index 81% rename from src/hooks/useInteraction.ts rename to src/hooks/interaction/useInteraction.ts index 34a2a86..13d2534 100644 --- a/src/hooks/useInteraction.ts +++ b/src/hooks/interaction/useInteraction.ts @@ -1,6 +1,6 @@ import { useSyncExternalStore } from "react"; import { InteractionManager } from "@/managers/InteractionManager"; -import type { InteractionSnapshot } from "@/types/interaction"; +import type { InteractionSnapshot } from "@/types/interaction/interaction"; const manager = InteractionManager.getInstance(); diff --git a/src/hooks/three/useClonedObject.ts b/src/hooks/three/useClonedObject.ts new file mode 100644 index 0000000..bd7c87a --- /dev/null +++ b/src/hooks/three/useClonedObject.ts @@ -0,0 +1,6 @@ +import { useMemo } from "react"; +import type * as THREE from "three"; + +export function useClonedObject(object: T): T { + return useMemo(() => object.clone(true) as T, [object]); +} diff --git a/src/hooks/three/useLoggedGLTF.ts b/src/hooks/three/useLoggedGLTF.ts new file mode 100644 index 0000000..8e08ce3 --- /dev/null +++ b/src/hooks/three/useLoggedGLTF.ts @@ -0,0 +1,24 @@ +import { useEffect, useRef } from "react"; +import { useGLTF } from "@react-three/drei"; +import { + logModelLoadSuccess, + type ModelLoadLogContext, +} from "@/utils/three/modelLoadLogger"; + +export function useLoggedGLTF( + modelPath: string, + context: Omit, +) { + const gltf = useGLTF(modelPath); + const hasLoggedRef = useRef(false); + const { position, rotation, scale, scope } = context; + + useEffect(() => { + if (hasLoggedRef.current) return; + + hasLoggedRef.current = true; + logModelLoadSuccess({ modelPath, position, rotation, scale, scope }, gltf); + }, [gltf, modelPath, position, rotation, scale, scope]); + + return gltf; +} diff --git a/src/hooks/useOctreeGraphNode.ts b/src/hooks/three/useOctreeGraphNode.ts similarity index 92% rename from src/hooks/useOctreeGraphNode.ts rename to src/hooks/three/useOctreeGraphNode.ts index 3a4f3a3..4706fd0 100644 --- a/src/hooks/useOctreeGraphNode.ts +++ b/src/hooks/three/useOctreeGraphNode.ts @@ -2,7 +2,7 @@ import { useEffect, useRef } from "react"; import type { RefObject } from "react"; import type { Object3D } from "three"; import { Octree } from "three/addons/math/Octree.js"; -import type { OctreeReadyHandler } from "@/types/three"; +import type { OctreeReadyHandler } from "@/types/three/three"; export function useOctreeGraphNode( graphNodeRef: RefObject, diff --git a/src/index.css b/src/index.css index 763ef06..6ea0915 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,6 @@ @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"); +/* Base document reset */ :root { color-scheme: dark; font-family: "Helvetica Neue", Helvetica, Inter, Arial, sans-serif; @@ -29,6 +30,7 @@ canvas { display: block; } +/* Docs layout */ .docs-page { display: grid; grid-template-columns: 300px minmax(0, 1fr); @@ -40,6 +42,7 @@ canvas { font-family: "Helvetica Neue", Helvetica, Inter, Arial, sans-serif; } +/* Docs sidebar navigation */ .docs-sidebar { border-right: 2px solid #d8d0c4; background: #050505; @@ -150,6 +153,7 @@ canvas { color: #050505; } +/* Docs content */ .docs-content { overflow-y: auto; scroll-behavior: smooth; @@ -321,6 +325,7 @@ canvas { color: #a9a196; } +/* Docs responsive layout */ @media (max-width: 760px) { .docs-page { display: block; @@ -338,6 +343,7 @@ canvas { } } +/* In-game interaction UI */ .crosshair { position: fixed; top: 50%; @@ -391,78 +397,275 @@ canvas { letter-spacing: 0.03em; } -.game-state-hud { +/* Debug overlay panels */ +.debug-overlay-layout { position: fixed; - top: 18px; - right: 18px; + top: 12px; + left: 12px; z-index: 20; - display: grid; - gap: 12px; - width: min(320px, calc(100vw - 36px)); - padding: 14px; - border: 1px solid rgba(255, 255, 255, 0.18); + display: flex; + flex-direction: column; + width: min(260px, calc(100vw - 24px)); + max-height: calc(100vh - 24px); + overflow-y: auto; + color: #f8f8f8; + background: rgba(8, 8, 8, 0.88); + border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 18px; - background: rgba(4, 7, 13, 0.78); - box-shadow: 0 18px 60px rgba(0, 0, 0, 0.35); - color: #f8fafc; - backdrop-filter: blur(16px); + box-shadow: 0 18px 60px rgba(0, 0, 0, 0.42); + backdrop-filter: blur(18px); + scrollbar-width: thin; + scrollbar-color: #3a3a3a transparent; + pointer-events: auto; } -.game-state-hud__header { +.debug-overlay-layout::-webkit-scrollbar { + width: 6px; +} + +.debug-overlay-layout::-webkit-scrollbar-thumb { + background: #3a3a3a; + border-radius: 999px; +} + +.debug-overlay-layout__header { + padding: 12px 12px 10px; +} + +.debug-overlay-layout__kicker { + color: #8f8f8f; + font-size: 0.58rem; + font-weight: 700; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +.debug-overlay-layout__header h2 { + margin: 0.2rem 0 0; + color: #ffffff; + font-size: 1rem; + font-weight: 720; + letter-spacing: -0.06em; +} + +.debug-overlay-layout__sections { + display: flex; + flex-direction: column; +} + +.debug-overlay-section { + padding: 10px 12px 12px; + border-top: 1px solid rgba(255, 255, 255, 0.09); +} + +.debug-overlay-section__heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; + margin-bottom: 8px; } -.game-state-hud__header span, -.game-state-hud__detail { - color: rgba(248, 250, 252, 0.68); - font-size: 12px; +.debug-overlay-section__heading h3, +.game-state-debug-panel__header h3 { + margin: 0; + color: #ffffff; + font-size: 0.66rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.debug-overlay-section__heading span { + color: #a3a3a3; + font-size: 0.66rem; + font-weight: 650; +} + +.debug-overlay-metrics { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 6px; + margin: 0; +} + +.debug-overlay-metrics div { + display: grid; + gap: 3px; + min-height: 0; + padding: 7px 8px; + background: #101010; + border: 1px solid #242424; + border-radius: 10px; +} + +.debug-overlay-metrics dt { + color: #8f8f8f; + font-size: 0.56rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; } -.game-state-hud__header strong { - font-size: 16px; - letter-spacing: -0.03em; - text-transform: uppercase; -} - -.game-state-hud__detail { +.debug-overlay-metrics dd { margin: 0; - text-transform: none; + color: #ffffff; + font-size: 0.72rem; + font-weight: 650; } -.game-state-hud__states, -.game-state-hud__actions { +.hand-tracking-debug-panel__error { + color: #fca5a5; + display: block; + margin-top: 8px; + font-size: 0.68rem; +} + +.hand-tracking-visualizer { + position: fixed; + inset: 0; + z-index: 15; + width: 100vw; + height: 100vh; + pointer-events: none; + filter: drop-shadow(0 0 8px rgba(56, 189, 248, 0.55)); +} + +/* Repair model selector UI */ +.model-selector-panel { display: flex; - flex-wrap: wrap; + flex-direction: column; + gap: 6px; + min-width: 190px; + padding: 12px; + color: rgba(255, 255, 255, 0.92); + background: rgba(4, 7, 13, 0.88); + border: 1px solid rgba(56, 189, 248, 0.5); + border-radius: 8px; + font-size: 12px; + pointer-events: none; + user-select: none; +} + +.model-selector-panel strong { + color: white; + font-size: 13px; +} + +.model-selector-panel ul { + display: flex; + flex-direction: column; + gap: 3px; + margin: 4px 0 0; + padding: 0; + list-style: none; +} + +.model-selector-panel li { + padding: 3px 6px; + border-radius: 4px; +} + +.model-selector-panel li.is-selected { + color: #020617; + background: #38bdf8; +} + +/* Zustand game state debug UI */ +.game-state-debug-panel { + display: grid; + gap: 10px; +} + +.game-state-debug-panel__header { + display: flex; + align-items: center; + justify-content: space-between; gap: 8px; } -.game-state-hud button { - min-height: 32px; - padding: 0 10px; - border: 1px solid rgba(255, 255, 255, 0.2); - border-radius: 999px; - background: rgba(255, 255, 255, 0.08); - color: #f8fafc; - font-size: 12px; +.game-state-debug-panel__switch-heading span { + color: #8f8f8f; + font-size: 0.56rem; font-weight: 700; - cursor: pointer; + letter-spacing: 0.1em; + text-transform: uppercase; } -.game-state-hud button:hover, -.game-state-hud button:focus-visible, -.game-state-hud button.is-active { - border-color: rgba(125, 211, 252, 0.75); - background: rgba(125, 211, 252, 0.18); +.game-state-debug-panel__switch-heading strong { + color: #ffffff; + font-size: 0.68rem; + font-weight: 720; + letter-spacing: -0.03em; +} + +.game-state-debug-panel__switch-group { + display: grid; + gap: 7px; +} + +.game-state-debug-panel__switch-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.game-state-debug-panel__states, +.game-state-debug-panel__actions { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 6px; +} + +.game-state-debug-panel__actions { + grid-template-columns: 1fr; + margin-top: 2px; + padding-top: 10px; + border-top: 1px solid rgba(255, 255, 255, 0.09); +} + +.game-state-debug-panel button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 5px; + min-height: 28px; + padding: 0 8px; + border: 1px solid #2f2f2f; + border-radius: 10px; + background: #101010; + color: #d9d9d9; + font-size: 0.68rem; + font-weight: 650; + text-align: center; + cursor: pointer; + transition: + background 160ms ease, + border-color 160ms ease, + color 160ms ease, + transform 160ms ease; +} + +.game-state-debug-panel button:hover, +.game-state-debug-panel button:focus-visible, +.game-state-debug-panel button.is-active { + color: #ffffff; + border-color: #ffffff; + background: #181818; outline: none; } -/* Editor page */ +.game-state-debug-panel button:hover { + transform: translateY(-1px); +} + +.game-state-debug-panel button.is-active { + border-color: rgba(56, 189, 248, 0.75); + background: rgba(56, 189, 248, 0.14); +} + +/* Editor page shell */ .editor-container { position: fixed; top: 0; @@ -528,6 +731,7 @@ canvas { font-family: "SFMono-Regular", "Courier New", monospace; } +/* Editor loading and upload states */ .editor-upload-section { width: min(520px, calc(100vw - 2rem)); background: #0d0d0d; @@ -603,6 +807,7 @@ canvas { white-space: pre-wrap; } +/* Editor scene overlays */ .editor-camera-info { position: absolute; top: 16px; @@ -631,6 +836,7 @@ canvas { font-weight: 600; } +/* Editor controls panel */ .editor-controls-panel { position: absolute; right: 16px; @@ -925,6 +1131,7 @@ canvas { text-align: right; } +/* Editor JSON inspector */ .editor-json-section { display: flex; flex-direction: column; @@ -1000,6 +1207,7 @@ canvas { font-size: 0.74rem; } +/* Editor responsive layout */ @media (max-width: 768px) { .editor-error h2 { font-size: 1.5rem; diff --git a/src/lib/handTracking/browserHandTracking.ts b/src/lib/handTracking/browserHandTracking.ts new file mode 100644 index 0000000..06b80b4 --- /dev/null +++ b/src/lib/handTracking/browserHandTracking.ts @@ -0,0 +1,117 @@ +import { + HAND_TRACKING_BROWSER_MODEL_URL, + HAND_TRACKING_BROWSER_WASM_URL, +} from "@/data/handTrackingConfig"; +import type { + HandTrackingHand, + HandTrackingLandmark, +} from "@/types/handTracking/handTracking"; + +type HandLandmarkerModule = typeof import("@mediapipe/tasks-vision"); +type HandLandmarker = Awaited< + ReturnType +>; +type HandLandmarkerResult = ReturnType; + +let handLandmarkerPromise: Promise | null = null; + +function averageLandmarks( + landmarks: HandTrackingLandmark[], + indices: number[], +): HandTrackingLandmark { + const point = indices.reduce( + (current, index) => { + const landmark = landmarks[index]; + if (!landmark) return current; + + return { + x: current.x + landmark.x, + y: current.y + landmark.y, + z: current.z + landmark.z, + }; + }, + { x: 0, y: 0, z: 0 }, + ); + + return { + x: point.x / indices.length, + y: point.y / indices.length, + z: point.z / indices.length, + }; +} + +function distance( + pointA: HandTrackingLandmark, + pointB: HandTrackingLandmark, +): number { + return Math.sqrt( + (pointA.x - pointB.x) ** 2 + + (pointA.y - pointB.y) ** 2 + + (pointA.z - pointB.z) ** 2, + ); +} + +function isFist(landmarks: HandTrackingLandmark[]): boolean { + const palmCenter = averageLandmarks(landmarks, [0, 5, 9, 13, 17]); + const wrist = landmarks[0]; + const middleMcp = landmarks[9]; + + if (!wrist || !middleMcp) return false; + + const palmSize = distance(wrist, middleMcp); + if (palmSize <= 0) return false; + + const foldedFingerCount = [8, 12, 16, 20].filter((index) => { + const landmark = landmarks[index]; + if (!landmark) return false; + + return distance(landmark, palmCenter) / palmSize < 1.05; + }).length; + + return foldedFingerCount >= 4; +} + +export async function getBrowserHandLandmarker(): Promise { + handLandmarkerPromise ??= import("@mediapipe/tasks-vision").then( + async ({ FilesetResolver, HandLandmarker }) => { + const vision = await FilesetResolver.forVisionTasks( + HAND_TRACKING_BROWSER_WASM_URL, + ); + + return HandLandmarker.createFromOptions(vision, { + baseOptions: { + modelAssetPath: HAND_TRACKING_BROWSER_MODEL_URL, + delegate: "GPU", + }, + numHands: 2, + runningMode: "VIDEO", + }); + }, + ); + + return handLandmarkerPromise; +} + +export function convertBrowserHandResult( + result: HandLandmarkerResult, +): HandTrackingHand[] { + return result.landmarks.map((landmarks, index) => { + const normalizedLandmarks = landmarks.map((landmark) => ({ + x: landmark.x, + y: landmark.y, + z: landmark.z, + })); + const palmCenter = averageLandmarks(normalizedLandmarks, [0, 5, 9, 13, 17]); + const handedness = result.handedness[index]?.[0]; + + return { + x: palmCenter.x, + y: palmCenter.y, + z: palmCenter.z, + landmarks: normalizedLandmarks, + handedness: handedness?.categoryName ?? "Unknown", + isFist: isFist(normalizedLandmarks), + score: handedness?.score ?? 0, + }; + }); +} diff --git a/src/managers/AudioManager.ts b/src/managers/AudioManager.ts index 1fcc256..6c2a534 100644 --- a/src/managers/AudioManager.ts +++ b/src/managers/AudioManager.ts @@ -1,8 +1,15 @@ -import { logger } from "@/utils/logger"; +import { logger } from "@/utils/core/logger"; + +interface PlaySoundOptions { + playbackRate?: number; +} export class AudioManager { private static _instance: AudioManager | null = null; private readonly _audioPools = new Map(); + private _music: HTMLAudioElement | null = null; + private _musicPath: string | null = null; + private _musicUnlockHandler: (() => void) | null = null; private static readonly MAX_POOL_SIZE_PER_SOUND = 6; private static readonly IGNORED_PLAYBACK_ERRORS = new Set([ @@ -20,9 +27,10 @@ export class AudioManager { private constructor() {} - playSound(path: string, volume = 1): void { + playSound(path: string, volume = 1, options: PlaySoundOptions = {}): void { const audio = this._acquireAudio(path); audio.volume = Math.max(0, Math.min(1, volume)); + audio.playbackRate = options.playbackRate ?? 1; audio.currentTime = 0; void audio.play().catch((error: unknown) => { @@ -40,7 +48,44 @@ export class AudioManager { }); } + playMusic(path: string, volume = 1): void { + if (this._musicPath === path && this._music) { + this._music.volume = Math.max(0, Math.min(1, volume)); + if (!this._music.paused) return; + } else { + this.stopMusic(); + this._music = new Audio(path); + this._music.loop = true; + this._musicPath = path; + } + + this._music.volume = Math.max(0, Math.min(1, volume)); + + void this._music.play().catch((error: unknown) => { + if ( + error instanceof DOMException && + AudioManager.IGNORED_PLAYBACK_ERRORS.has(error.name) + ) { + this._waitForUserGestureToPlayMusic(); + return; + } + + logger.error("AudioManager", "Failed to play music", { + path, + error: AudioManager._toLogValue(error), + }); + }); + } + + stopMusic(): void { + this._removeMusicUnlockHandler(); + this._music?.pause(); + this._music = null; + this._musicPath = null; + } + destroy(): void { + this.stopMusic(); this._audioPools.forEach((pool) => { pool.forEach((audio) => { audio.pause(); @@ -75,6 +120,30 @@ export class AudioManager { return initialAudio; } + private _waitForUserGestureToPlayMusic(): void { + if (this._musicUnlockHandler) return; + + this._musicUnlockHandler = () => { + this._removeMusicUnlockHandler(); + void this._music?.play(); + }; + + window.addEventListener("pointerdown", this._musicUnlockHandler, { + once: true, + }); + window.addEventListener("keydown", this._musicUnlockHandler, { + once: true, + }); + } + + private _removeMusicUnlockHandler(): void { + if (!this._musicUnlockHandler) return; + + window.removeEventListener("pointerdown", this._musicUnlockHandler); + window.removeEventListener("keydown", this._musicUnlockHandler); + this._musicUnlockHandler = null; + } + private static _toLogValue(error: unknown): Error | DOMException | string { if (error instanceof Error || error instanceof DOMException) { return error; diff --git a/src/managers/InteractionManager.ts b/src/managers/InteractionManager.ts index 6dcdd45..1b27e89 100644 --- a/src/managers/InteractionManager.ts +++ b/src/managers/InteractionManager.ts @@ -2,17 +2,21 @@ import type { GrabInteractableHandle, InteractableHandle, InteractionSnapshot, -} from "@/types/interaction"; +} from "@/types/interaction/interaction"; export class InteractionManager { private static _instance: InteractionManager | null = null; private _focused: InteractableHandle | null = null; + private readonly _nearbyHandles = new Set(); private _holding = false; + private _handHolding = false; private _holdingHandle: GrabInteractableHandle | null = null; private _snapshot: InteractionSnapshot = { focused: null, + nearby: false, holding: false, + handHolding: false, }; private readonly _listeners = new Set<() => void>(); @@ -38,6 +42,26 @@ export class InteractionManager { this._emit(); } + setNearby(handle: InteractableHandle, nearby: boolean): void { + const hadHandle = this._nearbyHandles.has(handle); + if (nearby === hadHandle) return; + + if (nearby) { + this._nearbyHandles.add(handle); + } else { + this._nearbyHandles.delete(handle); + } + + this._emit(); + } + + setHandHolding(holding: boolean): void { + if (this._handHolding === holding) return; + + this._handHolding = holding; + this._emit(); + } + pressInteract(): void { if (!this._focused) return; @@ -73,11 +97,15 @@ export class InteractionManager { destroy(): void { this._focused = null; + this._nearbyHandles.clear(); this._holding = false; + this._handHolding = false; this._holdingHandle = null; this._snapshot = { focused: null, + nearby: false, holding: false, + handHolding: false, }; this._listeners.clear(); InteractionManager._instance = null; @@ -86,7 +114,9 @@ export class InteractionManager { private _emit(): void { this._snapshot = { focused: this._focused, + nearby: this._nearbyHandles.size > 0, holding: this._holding, + handHolding: this._handHolding, }; this._listeners.forEach((cb) => cb()); } diff --git a/src/managers/stores/useGameStore.ts b/src/managers/stores/useGameStore.ts index db0a093..c58bc89 100644 --- a/src/managers/stores/useGameStore.ts +++ b/src/managers/stores/useGameStore.ts @@ -10,18 +10,18 @@ export type MissionStep = | "repairing" | "done"; -export interface IntroState { +interface IntroState { dialogueAudio: string | null; hasCompleted: boolean; isBikeUnlocked: boolean; } -export interface MissionState { +interface MissionState { currentStep: MissionStep; dialogueAudio: string | null; } -export interface GameState { +interface GameState { mainState: MainGameState; intro: IntroState; bike: MissionState & { @@ -52,10 +52,11 @@ interface GameActions { completeFerme: () => void; startOutro: () => void; advanceGameState: () => void; + rewindGameState: () => void; resetGame: () => void; } -export type GameStore = GameState & GameActions; +type GameStore = GameState & GameActions; type GameStateUpdate = Partial; function getNextMissionStep(step: MissionStep): MissionStep { @@ -76,6 +77,24 @@ function getNextMissionStep(step: MissionStep): MissionStep { } } +function getPreviousMissionStep(step: MissionStep): MissionStep { + switch (step) { + case "locked": + case "waiting": + return "locked"; + case "inspected": + return "waiting"; + case "fragmented": + return "inspected"; + case "scanning": + return "fragmented"; + case "repairing": + return "scanning"; + case "done": + return "repairing"; + } +} + function completeIntroState(state: GameState): GameStateUpdate { return { mainState: "bike", @@ -229,5 +248,40 @@ export const useGameStore = create()((set) => ({ return startOutroState(state); }), + rewindGameState: () => + set((state) => { + if (state.mainState === "intro") { + return { intro: { ...state.intro, hasCompleted: false } }; + } + + if (state.mainState === "bike") { + return { + bike: { + ...state.bike, + currentStep: getPreviousMissionStep(state.bike.currentStep), + }, + }; + } + + if (state.mainState === "pylone") { + return { + pylone: { + ...state.pylone, + currentStep: getPreviousMissionStep(state.pylone.currentStep), + }, + }; + } + + if (state.mainState === "ferme") { + return { + ferme: { + ...state.ferme, + currentStep: getPreviousMissionStep(state.ferme.currentStep), + }, + }; + } + + return { outro: { ...state.outro, hasStarted: false } }; + }), resetGame: () => set(createInitialGameState()), })); diff --git a/src/pages/docs/editor/page.tsx b/src/pages/docs/editor/page.tsx index deb3960..d44f7cd 100644 --- a/src/pages/docs/editor/page.tsx +++ b/src/pages/docs/editor/page.tsx @@ -7,7 +7,7 @@ export function DocsEditorPage(): React.JSX.Element { ); diff --git a/src/pages/docs/hand-tracking/page.tsx b/src/pages/docs/hand-tracking/page.tsx new file mode 100644 index 0000000..26e7176 --- /dev/null +++ b/src/pages/docs/hand-tracking/page.tsx @@ -0,0 +1,13 @@ +import handTracking from "../../../../docs/technical/hand-tracking.md?raw"; +import { DocsDocument } from "@/components/docs/DocsDocument"; + +export function DocsHandTrackingPage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/pages/docs/main-feature/page.tsx b/src/pages/docs/main-feature/page.tsx new file mode 100644 index 0000000..db324be --- /dev/null +++ b/src/pages/docs/main-feature/page.tsx @@ -0,0 +1,13 @@ +import mainFeature from "../../../../docs/user/main-feature.md?raw"; +import { DocsDocument } from "@/components/docs/DocsDocument"; + +export function DocsMainFeaturePage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/pages/editor/page.tsx b/src/pages/editor/page.tsx index e06cc51..fac7e24 100644 --- a/src/pages/editor/page.tsx +++ b/src/pages/editor/page.tsx @@ -4,7 +4,7 @@ import { EditorControls } from "@/components/editor/EditorControls"; import { EditorScene } from "@/components/editor/scene/EditorScene"; import { useEditorHistory } from "@/hooks/editor/useEditorHistory"; import { useEditorSceneData } from "@/hooks/editor/useEditorSceneData"; -import type { MapNode, SceneData, TransformMode } from "@/types/editor"; +import type { MapNode, SceneData, TransformMode } from "@/types/editor/editor"; const SAVE_ERROR_MESSAGE = "Erreur lors de l'enregistrement"; @@ -138,7 +138,7 @@ export function EditorPage(): React.JSX.Element {

Structure requise :

                 public/ ├── map.json (à la racine) └── models/
-                ├── arbre/ │ └── model.gltf ├── building/ │ └── model.gltf └──
+                ├── arbre/ │ └── model.glb ├── building/ │ └── model.gltf └──
                 ...
               
diff --git a/src/pages/page.tsx b/src/pages/page.tsx index dad9a41..09a8b9b 100644 --- a/src/pages/page.tsx +++ b/src/pages/page.tsx @@ -1,19 +1,24 @@ import { Suspense } from "react"; import { Canvas } from "@react-three/fiber"; +import * as THREE from "three"; import { DebugPerf } from "@/components/debug/DebugPerf"; import { GameUI } from "@/components/ui/GameUI"; +import { HandTrackingProvider } from "@/providers/gameplay/HandTrackingProvider"; import { World } from "@/world/World"; export function HomePage(): React.JSX.Element { return ( - <> - + + - + ); } diff --git a/src/providers/gameplay/HandTrackingProvider.tsx b/src/providers/gameplay/HandTrackingProvider.tsx new file mode 100644 index 0000000..e17fbfd --- /dev/null +++ b/src/providers/gameplay/HandTrackingProvider.tsx @@ -0,0 +1,39 @@ +import type { ReactNode } from "react"; +import { useSceneMode } from "@/hooks/debug/useSceneMode"; +import { useDebugStore } from "@/hooks/debug/useDebugStore"; +import { useInteraction } from "@/hooks/interaction/useInteraction"; +import { + HAND_TRACKING_IDLE_SNAPSHOT, + HandTrackingContext, +} from "@/hooks/handTracking/useHandTrackingSnapshot"; +import { useBrowserHandTracking } from "@/hooks/handTracking/useBrowserHandTracking"; +import { useRemoteHandTracking } from "@/hooks/handTracking/useRemoteHandTracking"; + +export function HandTrackingProvider({ + children, +}: { + children: ReactNode; +}): React.JSX.Element { + const sceneMode = useSceneMode(); + const handTrackingSource = useDebugStore((debug) => + debug.getHandTrackingSource(), + ); + const { nearby, holding, handHolding } = useInteraction(); + const enabled = sceneMode === "physics" && (nearby || holding || handHolding); + const backendSnapshot = useRemoteHandTracking({ + enabled: enabled && handTrackingSource === "backend", + }); + const browserSnapshot = useBrowserHandTracking({ + enabled: enabled && handTrackingSource === "browser", + }); + const snapshot = + handTrackingSource === "browser" ? browserSnapshot : backendSnapshot; + + return ( + + {children} + + ); +} diff --git a/src/router.tsx b/src/router.tsx index 6e53dcd..7be6634 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -11,12 +11,14 @@ import { DocsArchitectureRoute, DocsEditorRoute, DocsFeaturesRoute, + DocsHandTrackingRoute, DocsLayoutRoute, + DocsMainFeatureRoute, DocsReadmeRoute, DocsTargetArchitectureRoute, DocsTechnicalEditorRoute, DocsZustandRoute, -} from "@/routes/docs/DocsRouteComponents"; +} from "@/routes/DocsRoute"; const rootRoute = createRootRoute({ component: Outlet, @@ -45,8 +47,10 @@ const docsChildRoutes = [ { path: "architecture", component: DocsArchitectureRoute }, { path: "target-architecture", component: DocsTargetArchitectureRoute }, { path: "technical-editor", component: DocsTechnicalEditorRoute }, + { path: "hand-tracking", component: DocsHandTrackingRoute }, { path: "zustand", component: DocsZustandRoute }, { path: "features", component: DocsFeaturesRoute }, + { path: "main-feature", component: DocsMainFeatureRoute }, { path: "editor", component: DocsEditorRoute }, { path: "animation", component: DocsAnimationRoute }, ].map(({ path, component }) => diff --git a/src/routes/DocsRoute.tsx b/src/routes/DocsRoute.tsx new file mode 100644 index 0000000..b33f578 --- /dev/null +++ b/src/routes/DocsRoute.tsx @@ -0,0 +1,109 @@ +import { Suspense, lazy } from "react"; + +function lazyNamed>( + loader: () => Promise, + exportName: keyof T, +): React.LazyExoticComponent { + return lazy(() => + loader().then((module) => ({ default: module[exportName] })), + ); +} + +function withDocsSuspense( + Component: React.LazyExoticComponent, +): React.JSX.Element { + return ( + + + + ); +} + +const LazyDocsLayout = lazyNamed( + () => import("@/components/docs/DocsLayout"), + "DocsLayout", +); +const LazyDocsReadmePage = lazyNamed( + () => import("@/pages/docs/page"), + "DocsReadmePage", +); +const LazyDocsArchitecturePage = lazyNamed( + () => import("@/pages/docs/architecture/page"), + "DocsArchitecturePage", +); +const LazyDocsTargetArchitecturePage = lazyNamed( + () => import("@/pages/docs/target-architecture/page"), + "DocsTargetArchitecturePage", +); +const LazyDocsTechnicalEditorPage = lazyNamed( + () => import("@/pages/docs/technical-editor/page"), + "DocsTechnicalEditorPage", +); +const LazyDocsHandTrackingPage = lazyNamed( + () => import("@/pages/docs/hand-tracking/page"), + "DocsHandTrackingPage", +); +const LazyDocsZustandPage = lazyNamed( + () => import("@/pages/docs/zustand/page"), + "DocsZustandPage", +); +const LazyDocsFeaturesPage = lazyNamed( + () => import("@/pages/docs/features/page"), + "DocsFeaturesPage", +); +const LazyDocsMainFeaturePage = lazyNamed( + () => import("@/pages/docs/main-feature/page"), + "DocsMainFeaturePage", +); +const LazyDocsEditorPage = lazyNamed( + () => import("@/pages/docs/editor/page"), + "DocsEditorPage", +); +const LazyDocsAnimationPage = lazyNamed( + () => import("@/pages/docs/animation/page"), + "DocsAnimationPage", +); + +export function DocsLayoutRoute(): React.JSX.Element { + return withDocsSuspense(LazyDocsLayout); +} + +export function DocsReadmeRoute(): React.JSX.Element { + return withDocsSuspense(LazyDocsReadmePage); +} + +export function DocsArchitectureRoute(): React.JSX.Element { + return withDocsSuspense(LazyDocsArchitecturePage); +} + +export function DocsTargetArchitectureRoute(): React.JSX.Element { + return withDocsSuspense(LazyDocsTargetArchitecturePage); +} + +export function DocsTechnicalEditorRoute(): React.JSX.Element { + return withDocsSuspense(LazyDocsTechnicalEditorPage); +} + +export function DocsHandTrackingRoute(): React.JSX.Element { + return withDocsSuspense(LazyDocsHandTrackingPage); +} + +export function DocsZustandRoute(): React.JSX.Element { + return withDocsSuspense(LazyDocsZustandPage); +} + +export function DocsFeaturesRoute(): React.JSX.Element { + return withDocsSuspense(LazyDocsFeaturesPage); +} + +export function DocsMainFeatureRoute(): React.JSX.Element { + return withDocsSuspense(LazyDocsMainFeaturePage); +} + +export function DocsEditorRoute(): React.JSX.Element { + return withDocsSuspense(LazyDocsEditorPage); +} + +export function DocsAnimationRoute(): React.JSX.Element { + return withDocsSuspense(LazyDocsAnimationPage); +} diff --git a/src/routes/docs/DocsRouteComponents.tsx b/src/routes/docs/DocsRouteComponents.tsx deleted file mode 100644 index 3e591ea..0000000 --- a/src/routes/docs/DocsRouteComponents.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { Suspense, lazy } from "react"; - -const LazyDocsLayout = lazy(() => - import("@/components/docs/DocsLayout").then((module) => ({ - default: module.DocsLayout, - })), -); - -const LazyDocsReadmePage = lazy(() => - import("@/pages/docs/page").then((module) => ({ - default: module.DocsReadmePage, - })), -); - -const LazyDocsArchitecturePage = lazy(() => - import("@/pages/docs/architecture/page").then((module) => ({ - default: module.DocsArchitecturePage, - })), -); - -const LazyDocsTargetArchitecturePage = lazy(() => - import("@/pages/docs/target-architecture/page").then((module) => ({ - default: module.DocsTargetArchitecturePage, - })), -); - -const LazyDocsTechnicalEditorPage = lazy(() => - import("@/pages/docs/technical-editor/page").then((module) => ({ - default: module.DocsTechnicalEditorPage, - })), -); - -const LazyDocsZustandPage = lazy(() => - import("@/pages/docs/zustand/page").then((module) => ({ - default: module.DocsZustandPage, - })), -); - -const LazyDocsFeaturesPage = lazy(() => - import("@/pages/docs/features/page").then((module) => ({ - default: module.DocsFeaturesPage, - })), -); - -const LazyDocsEditorPage = lazy(() => - import("@/pages/docs/editor/page").then((module) => ({ - default: module.DocsEditorPage, - })), -); - -const LazyDocsAnimationPage = lazy(() => - import("@/pages/docs/animation/page").then((module) => ({ - default: module.DocsAnimationPage, - })), -); - -export function DocsLayoutRoute(): React.JSX.Element { - return ( - - - - ); -} - -export function DocsReadmeRoute(): React.JSX.Element { - return ( - - - - ); -} - -export function DocsArchitectureRoute(): React.JSX.Element { - return ( - - - - ); -} - -export function DocsTargetArchitectureRoute(): React.JSX.Element { - return ( - - - - ); -} - -export function DocsTechnicalEditorRoute(): React.JSX.Element { - return ( - - - - ); -} - -export function DocsZustandRoute(): React.JSX.Element { - return ( - - - - ); -} - -export function DocsFeaturesRoute(): React.JSX.Element { - return ( - - - - ); -} - -export function DocsEditorRoute(): React.JSX.Element { - return ( - - - - ); -} - -export function DocsAnimationRoute(): React.JSX.Element { - return ( - - - - ); -} diff --git a/src/types/debug.ts b/src/types/debug/debug.ts similarity index 100% rename from src/types/debug.ts rename to src/types/debug/debug.ts diff --git a/src/types/editor.ts b/src/types/editor/editor.ts similarity index 84% rename from src/types/editor.ts rename to src/types/editor/editor.ts index 73306c4..dea3097 100644 --- a/src/types/editor.ts +++ b/src/types/editor/editor.ts @@ -1,4 +1,4 @@ -import type { Vector3Tuple } from "./three"; +import type { Vector3Tuple } from "../three/three"; export interface MapNode { name: string; diff --git a/src/types/handTracking/handTracking.ts b/src/types/handTracking/handTracking.ts new file mode 100644 index 0000000..905a57d --- /dev/null +++ b/src/types/handTracking/handTracking.ts @@ -0,0 +1,69 @@ +export interface HandTrackingLandmark { + x: number; + y: number; + z: number; +} + +export type HandTrackingSource = "backend" | "browser"; + +export interface HandTrackingHand { + x: number; + y: number; + z: number; + landmarks: HandTrackingLandmark[]; + handedness: string; + isFist: boolean; + score: number; +} + +type HandTrackingUsageStatus = "inactive" | "available" | "active"; + +export type HandTrackingStatus = + | "idle" + | "requesting_camera" + | "starting_camera" + | "connecting_server" + | "connecting" + | "connected" + | "disconnected" + | "error"; + +export interface HandTrackingSnapshot { + hands: HandTrackingHand[]; + status: HandTrackingStatus; + usageStatus: HandTrackingUsageStatus; + serverStatus: string | null; + error: string | null; +} + +export interface HandTrackingFrameMessage { + type: "frame"; + timestamp: number; + width: number; + height: number; + image: string; +} + +interface HandTrackingHandsMessage { + type: "hands"; + timestamp: number; + hands: HandTrackingHand[]; +} + +interface HandTrackingStatusMessage { + type: "status"; + timestamp: number; + status: string; +} + +interface HandTrackingErrorMessage { + type: "error"; + timestamp: number; + hands: HandTrackingHand[]; + message: string; +} + +export type HandTrackingServerMessage = + | HandTrackingHandsMessage + | HandTrackingStatusMessage + | HandTrackingErrorMessage; diff --git a/src/types/interaction.ts b/src/types/interaction/interaction.ts similarity index 88% rename from src/types/interaction.ts rename to src/types/interaction/interaction.ts index 5e65bf6..6737aa8 100644 --- a/src/types/interaction.ts +++ b/src/types/interaction/interaction.ts @@ -1,5 +1,3 @@ -export type InteractableKind = "grab" | "trigger"; - interface TriggerInteractableHandle { kind: "trigger"; label: string; @@ -19,5 +17,7 @@ export type InteractableHandle = export interface InteractionSnapshot { focused: InteractableHandle | null; + nearby: boolean; holding: boolean; + handHolding: boolean; } diff --git a/src/types/logger.ts b/src/types/logger/logger.ts similarity index 100% rename from src/types/logger.ts rename to src/types/logger/logger.ts diff --git a/src/types/three-addons.d.ts b/src/types/three/three-addons.d.ts similarity index 100% rename from src/types/three-addons.d.ts rename to src/types/three/three-addons.d.ts diff --git a/src/types/three.ts b/src/types/three/three.ts similarity index 57% rename from src/types/three.ts rename to src/types/three/three.ts index 9435e5a..86c32e7 100644 --- a/src/types/three.ts +++ b/src/types/three/three.ts @@ -2,6 +2,14 @@ import type { Octree } from "three/addons/math/Octree.js"; export type Vector3Tuple = [number, number, number]; +export type Vector3Scale = Vector3Tuple | number; + +export interface ModelTransformProps { + position?: Vector3Tuple; + rotation?: Vector3Tuple; + scale?: Vector3Scale; +} + export type ColliderShape = "cuboid" | "ball" | "hull"; export type OctreeReadyHandler = (octree: Octree) => void; diff --git a/src/utils/EventEmitter.ts b/src/utils/core/EventEmitter.ts similarity index 100% rename from src/utils/EventEmitter.ts rename to src/utils/core/EventEmitter.ts diff --git a/src/utils/Sizes.ts b/src/utils/core/Sizes.ts similarity index 100% rename from src/utils/Sizes.ts rename to src/utils/core/Sizes.ts diff --git a/src/utils/Time.ts b/src/utils/core/Time.ts similarity index 100% rename from src/utils/Time.ts rename to src/utils/core/Time.ts diff --git a/src/utils/logger.ts b/src/utils/core/logger.ts similarity index 98% rename from src/utils/logger.ts rename to src/utils/core/logger.ts index b1629de..a89ee8a 100644 --- a/src/utils/logger.ts +++ b/src/utils/core/logger.ts @@ -3,7 +3,7 @@ import type { LogEntry, LogLevel, LoggerConfig, -} from "@/types/logger"; +} from "@/types/logger/logger"; import { isDebugEnabled } from "@/utils/debug/isDebugEnabled"; const LEVEL_PRIORITY: Record = { diff --git a/src/utils/debug/Debug.ts b/src/utils/debug/Debug.ts index 2c97d30..3b68fcc 100644 --- a/src/utils/debug/Debug.ts +++ b/src/utils/debug/Debug.ts @@ -1,7 +1,48 @@ import GUI from "lil-gui"; -import type { CameraMode, SceneMode } from "@/types/debug"; +import type { CameraMode, SceneMode } from "@/types/debug/debug"; +import type { HandTrackingSource } from "@/types/handTracking/handTracking"; import { isDebugEnabled } from "@/utils/debug/isDebugEnabled"; +const DEBUG_CONTROLS_STORAGE_KEY = "la-fabrik-debug-controls"; + +interface StoredDebugControls { + cameraMode: CameraMode; + sceneMode: SceneMode; +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function isCameraMode(value: unknown): value is CameraMode { + return value === "player" || value === "debug"; +} + +function isSceneMode(value: unknown): value is SceneMode { + return value === "game" || value === "physics"; +} + +function getStoredDebugControls(): Partial { + try { + const rawValue = window.localStorage.getItem(DEBUG_CONTROLS_STORAGE_KEY); + if (!rawValue) return {}; + + const parsedValue: unknown = JSON.parse(rawValue); + if (!isRecord(parsedValue)) return {}; + + return { + ...(isCameraMode(parsedValue.cameraMode) + ? { cameraMode: parsedValue.cameraMode } + : {}), + ...(isSceneMode(parsedValue.sceneMode) + ? { sceneMode: parsedValue.sceneMode } + : {}), + }; + } catch { + return {}; + } +} + export class Debug { private static instance: Debug | null = null; @@ -12,12 +53,12 @@ export class Debug { private readonly listeners = new Set<() => void>(); private readonly controls: { cameraMode: CameraMode; + handTrackingSource: HandTrackingSource; + showDebugOverlay: boolean; + showHandTrackingSvg: boolean; showInteractionSpheres: boolean; + showPerf: boolean; sceneMode: SceneMode; - } = { - cameraMode: "player", - showInteractionSpheres: false, - sceneMode: "game", }; static getInstance(): Debug { @@ -30,6 +71,18 @@ export class Debug { private constructor() { this.active = isDebugEnabled(); + const storedControls = getStoredDebugControls(); + + this.controls = { + cameraMode: storedControls.cameraMode ?? "player", + handTrackingSource: "backend", + showDebugOverlay: true, + showHandTrackingSvg: false, + showInteractionSpheres: false, + showPerf: true, + sceneMode: storedControls.sceneMode ?? "game", + }; + this.gui = this.active ? new GUI({ title: "La-Fabrik Debug" }) : null; if (this.gui) { @@ -42,7 +95,7 @@ export class Debug { .name("Camera Mode") .onChange((value: CameraMode) => { this.controls.cameraMode = value; - this.emit(); + this.saveAndEmit(); }); folder @@ -50,14 +103,43 @@ export class Debug { .name("Scene") .onChange((value: SceneMode) => { this.controls.sceneMode = value; + this.saveAndEmit(); + }); + + folder + .add(this.controls, "showPerf") + .name("R3F Perf") + .onChange((value: boolean) => { + this.controls.showPerf = value; this.emit(); }); folder - .add(this.controls, "showInteractionSpheres") - .name("Interaction Spheres") + .add(this.controls, "showDebugOverlay") + .name("Debug Overlay") .onChange((value: boolean) => { - this.controls.showInteractionSpheres = value; + this.controls.showDebugOverlay = value; + this.emit(); + }); + + const handTrackingFolder = this.createFolder("Hand Tracking"); + + handTrackingFolder + ?.add(this.controls, "showHandTrackingSvg") + .name("Show SVG") + .onChange((value: boolean) => { + this.controls.showHandTrackingSvg = value; + this.emit(); + }); + + handTrackingFolder + ?.add(this.controls, "handTrackingSource", { + Backend: "backend", + "Browser JS": "browser", + }) + .name("Source") + .onChange((value: HandTrackingSource) => { + this.controls.handTrackingSource = value; this.emit(); }); } @@ -115,11 +197,53 @@ export class Debug { return this.controls.sceneMode; } + getShowDebugOverlay(): boolean { + return this.active && this.controls.showDebugOverlay; + } + + getHandTrackingSource(): HandTrackingSource { + return this.controls.handTrackingSource; + } + getShowInteractionSpheres(): boolean { return this.controls.showInteractionSpheres; } + getShowHandTrackingSvg(): boolean { + return this.controls.showHandTrackingSvg; + } + + setShowHandTrackingSvg(value: boolean): void { + this.controls.showHandTrackingSvg = value; + this.emit(); + } + + setShowInteractionSpheres(value: boolean): void { + this.controls.showInteractionSpheres = value; + this.emit(); + } + + getShowPerf(): boolean { + return this.active && this.controls.showPerf; + } + private emit(): void { this.listeners.forEach((listener) => listener()); } + + private saveAndEmit(): void { + try { + window.localStorage.setItem( + DEBUG_CONTROLS_STORAGE_KEY, + JSON.stringify({ + cameraMode: this.controls.cameraMode, + sceneMode: this.controls.sceneMode, + }), + ); + } catch { + // Debug persistence is optional; controls still work if storage is blocked. + } + + this.emit(); + } } diff --git a/src/utils/editor/loadEditorScene.ts b/src/utils/editor/loadEditorScene.ts index 62e8e4c..8be736d 100644 --- a/src/utils/editor/loadEditorScene.ts +++ b/src/utils/editor/loadEditorScene.ts @@ -1,5 +1,5 @@ -import type { SceneData } from "@/types/editor"; -import { parseMapNodes } from "@/utils/mapNodeValidation"; +import type { SceneData } from "@/types/editor/editor"; +import { parseMapNodes } from "@/utils/map/mapNodeValidation"; const MAP_JSON_PATH = "/map.json"; @@ -21,9 +21,12 @@ export async function createSceneDataFromFiles( const models = new Map(); for (const [path, file] of fileMap.entries()) { - const modelMatch = path.match(/^\/models\/(.+)\/model\.gltf$/); - if (modelMatch?.[1]) { - models.set(modelMatch[1], URL.createObjectURL(file)); + const modelMatch = path.match(/^\/models\/(.+)\/model\.(glb|gltf)$/); + const modelName = modelMatch?.[1]; + const modelExtension = modelMatch?.[2]; + + if (modelName && (modelExtension === "glb" || !models.has(modelName))) { + models.set(modelName, URL.createObjectURL(file)); } } diff --git a/src/utils/loadMapSceneData.ts b/src/utils/map/loadMapSceneData.ts similarity index 50% rename from src/utils/loadMapSceneData.ts rename to src/utils/map/loadMapSceneData.ts index 4735f07..0040c74 100644 --- a/src/utils/loadMapSceneData.ts +++ b/src/utils/map/loadMapSceneData.ts @@ -1,8 +1,9 @@ -import type { MapNode, SceneData } from "@/types/editor"; -import { parseMapNodes } from "@/utils/mapNodeValidation"; +import type { MapNode, SceneData } from "@/types/editor/editor"; +import { parseMapNodes } from "@/utils/map/mapNodeValidation"; const MAP_JSON_PATH = "/map.json"; -const MODEL_FILE_NAME = "model.gltf"; +const MODEL_FILE_NAMES = ["model.glb", "model.gltf"]; +const HTML_CONTENT_TYPE = "text/html"; type ModelEntry = [modelName: string, modelUrl: string]; export async function loadMapSceneData(): Promise { @@ -26,18 +27,26 @@ async function loadMapModelUrls( ): Promise> { const uniqueModelNames = [...new Set(mapNodes.map((node) => node.name))]; const modelEntries = await Promise.all( - uniqueModelNames.map(async (modelName) => { - const modelUrl = `/models/${modelName}/${MODEL_FILE_NAME}`; - - try { - const response = await fetch(modelUrl, { method: "HEAD" }); - const modelEntry: ModelEntry = [modelName, modelUrl]; - return response.ok ? modelEntry : null; - } catch { - return null; - } - }), + uniqueModelNames.map((modelName) => loadModelEntry(modelName)), ); return new Map(modelEntries.filter((entry) => entry !== null)); } + +async function loadModelEntry(modelName: string): Promise { + for (const fileName of MODEL_FILE_NAMES) { + const modelUrl = `/models/${modelName}/${fileName}`; + + try { + const response = await fetch(modelUrl, { method: "HEAD" }); + const contentType = response.headers.get("content-type") ?? ""; + if (response.ok && !contentType.includes(HTML_CONTENT_TYPE)) { + return [modelName, modelUrl]; + } + } catch { + continue; + } + } + + return null; +} diff --git a/src/utils/mapNodeValidation.ts b/src/utils/map/mapNodeValidation.ts similarity index 50% rename from src/utils/mapNodeValidation.ts rename to src/utils/map/mapNodeValidation.ts index cbe8973..42d3c47 100644 --- a/src/utils/mapNodeValidation.ts +++ b/src/utils/map/mapNodeValidation.ts @@ -1,4 +1,8 @@ -import type { MapNode } from "../types/editor"; +import type { MapNode } from "../../types/editor/editor"; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} function isVector3Tuple(value: unknown): value is [number, number, number] { return ( @@ -8,18 +12,17 @@ function isVector3Tuple(value: unknown): value is [number, number, number] { ); } -export function isMapNode(value: unknown): value is MapNode { - if (typeof value !== "object" || value === null) { +function isMapNode(value: unknown): value is MapNode { + if (!isRecord(value)) { return false; } - const node = value as Record; return ( - typeof node.name === "string" && - typeof node.type === "string" && - isVector3Tuple(node.position) && - isVector3Tuple(node.rotation) && - isVector3Tuple(node.scale) + typeof value.name === "string" && + typeof value.type === "string" && + isVector3Tuple(value.position) && + isVector3Tuple(value.rotation) && + isVector3Tuple(value.scale) ); } diff --git a/src/utils/three/ExplodedModel.ts b/src/utils/three/ExplodedModel.ts new file mode 100644 index 0000000..1b62428 --- /dev/null +++ b/src/utils/three/ExplodedModel.ts @@ -0,0 +1,106 @@ +import * as THREE from "three"; + +interface ExplodedPart { + object: THREE.Object3D; + originalPosition: THREE.Vector3; + targetPosition: THREE.Vector3; +} + +interface ExplodedModelOptions { + distance?: number; + speed?: number; +} + +const _center = new THREE.Vector3(); +const _direction = new THREE.Vector3(); + +export class ExplodedModel { + private readonly parts: ExplodedPart[] = []; + private readonly distance: number; + private readonly speed: number; + private progress = 0; + private targetProgress = 0; + + constructor(model: THREE.Object3D, options: ExplodedModelOptions = {}) { + this.distance = options.distance ?? 1.2; + this.speed = options.speed ?? 6; + this.parts = this.createParts(model); + } + + setSplit(split: boolean): void { + this.targetProgress = split ? 1 : 0; + } + + update(delta: number): void { + const diff = this.targetProgress - this.progress; + if (Math.abs(diff) < 0.001) { + this.progress = this.targetProgress; + } else { + this.progress += diff * Math.min(delta * this.speed, 1); + } + + this.parts.forEach((part) => { + part.object.position.lerpVectors( + part.originalPosition, + part.targetPosition, + this.progress, + ); + }); + } + + private createParts(model: THREE.Object3D): ExplodedPart[] { + const root = + model.children.length === 1 && model.children[0] + ? model.children[0] + : model; + const directChildren = root.children.filter((child) => hasMesh(child)); + const sourceObjects = + directChildren.length > 1 ? directChildren : getMeshes(root); + + if (sourceObjects.length === 0) return []; + + _center.set(0, 0, 0); + sourceObjects.forEach((object) => _center.add(object.position)); + _center.divideScalar(sourceObjects.length); + + return sourceObjects.map((object, index) => { + const originalPosition = object.position.clone(); + _direction.subVectors(originalPosition, _center); + + if (_direction.lengthSq() < 0.0001) { + const angle = (index / sourceObjects.length) * Math.PI * 2; + _direction.set(Math.cos(angle), 0.25, Math.sin(angle)); + } + + _direction.normalize(); + + return { + object, + originalPosition, + targetPosition: originalPosition + .clone() + .addScaledVector(_direction, this.distance), + }; + }); + } +} + +function hasMesh(object: THREE.Object3D): boolean { + let found = false; + object.traverse((child) => { + if (child instanceof THREE.Mesh) { + found = true; + } + }); + return found; +} + +function getMeshes(object: THREE.Object3D): THREE.Object3D[] { + const meshes: THREE.Object3D[] = []; + object.traverse((child) => { + if (child instanceof THREE.Mesh) { + meshes.push(child); + } + }); + return meshes; +} diff --git a/src/utils/three/modelLoadLogger.ts b/src/utils/three/modelLoadLogger.ts new file mode 100644 index 0000000..9160a9d --- /dev/null +++ b/src/utils/three/modelLoadLogger.ts @@ -0,0 +1,68 @@ +import { logger } from "@/utils/core/logger"; +import type { Vector3Tuple } from "@/types/three/three"; + +export interface ModelLoadLogContext { + modelPath: string; + scope: string; + position?: Vector3Tuple | undefined; + rotation?: Vector3Tuple | undefined; + scale?: Vector3Tuple | number | undefined; +} + +interface LoadedModelInfo { + scene: { + name: string; + }; + animations: Array<{ + name: string; + }>; +} + +function getModelLoadHint(error: Error): string | undefined { + const message = error.message.toLowerCase(); + + if ( + message.includes("unexpected token 'v'") || + message.includes("version https://git-lfs") || + message.includes("git-lfs") + ) { + return "This file looks like a Git LFS pointer instead of a real GLTF asset. Run `git lfs pull` or replace the asset."; + } + + if (message.includes("couldn't load texture")) { + return "A texture referenced by the GLTF could not be loaded. Check file names, casing, and paths next to the model."; + } + + return undefined; +} + +export function logModelLoadSuccess( + context: ModelLoadLogContext, + gltf: LoadedModelInfo, +): void { + logger.debug("ModelLoader", "Model loaded", { + modelPath: context.modelPath, + scope: context.scope, + position: context.position, + rotation: context.rotation, + scale: context.scale, + sceneName: gltf.scene.name || null, + animations: gltf.animations.map((animation) => animation.name), + animationCount: gltf.animations.length, + }); +} + +export function logModelLoadError( + context: ModelLoadLogContext, + error: Error, +): void { + logger.error("ModelLoader", "Model failed to load", { + modelPath: context.modelPath, + scope: context.scope, + position: context.position, + rotation: context.rotation, + scale: context.scale, + reason: error.message, + hint: getModelLoadHint(error), + }); +} diff --git a/src/utils/three/scale.ts b/src/utils/three/scale.ts new file mode 100644 index 0000000..1b7ae3c --- /dev/null +++ b/src/utils/three/scale.ts @@ -0,0 +1,5 @@ +import type { Vector3Scale, Vector3Tuple } from "@/types/three/three"; + +export function toVector3Scale(scale: Vector3Scale): Vector3Tuple { + return typeof scale === "number" ? [scale, scale, scale] : scale; +} diff --git a/src/world/Environment.tsx b/src/world/Environment.tsx index b81a4ef..152234f 100644 --- a/src/world/Environment.tsx +++ b/src/world/Environment.tsx @@ -1,9 +1,9 @@ -import { Environment as DreiEnvironment } from "@react-three/drei"; import { - GAME_SCENE_SKYBOX_PATH, + GAME_SCENE_SKY_MODEL_PATH, PHYSICS_SCENE_BACKGROUND_COLOR, } from "@/data/world/environmentConfig"; import { useSceneMode } from "@/hooks/debug/useSceneMode"; +import { SkyModel } from "@/components/three/world/SkyModel"; export function Environment(): React.JSX.Element { const sceneMode = useSceneMode(); @@ -14,5 +14,5 @@ export function Environment(): React.JSX.Element { ); } - return ; + return ; } diff --git a/src/world/GameMap.tsx b/src/world/GameMap.tsx index 929f93e..f5a4097 100644 --- a/src/world/GameMap.tsx +++ b/src/world/GameMap.tsx @@ -1,16 +1,24 @@ import type { ReactNode } from "react"; -import { Component } from "react"; -import { useEffect, useMemo, useRef, useState } from "react"; -import { useGLTF } from "@react-three/drei"; +import { Component, useEffect, useRef, useState } from "react"; import * as THREE from "three"; -import { useOctreeGraphNode } from "@/hooks/useOctreeGraphNode"; -import { loadMapSceneData } from "@/utils/loadMapSceneData"; -import type { OctreeReadyHandler } from "@/types/three"; -import type { MapNode } from "@/types/editor"; +import { useClonedObject } from "@/hooks/three/useClonedObject"; +import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; +import { useOctreeGraphNode } from "@/hooks/three/useOctreeGraphNode"; +import { logger } from "@/utils/core/logger"; +import { loadMapSceneData } from "@/utils/map/loadMapSceneData"; +import { logModelLoadError } from "@/utils/three/modelLoadLogger"; +import type { MapNode } from "@/types/editor/editor"; +import type { OctreeReadyHandler } from "@/types/three/three"; + +interface LoadedMapNode { + node: MapNode; + modelUrl: string; +} interface ErrorBoundaryProps { children: ReactNode; - fallback?: ReactNode; + modelUrl: string; + node: MapNode; } interface ErrorBoundaryState { @@ -26,20 +34,28 @@ class ModelErrorBoundary extends Component< this.state = { hasError: false }; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - static getDerivedStateFromError(_error: Error): ErrorBoundaryState { + static getDerivedStateFromError(): ErrorBoundaryState { return { hasError: true }; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - componentDidCatch(_error: Error): void { - console.warn(`Failed to load model`); + componentDidCatch(error: Error): void { + logModelLoadError( + { + modelPath: this.props.modelUrl, + scope: "GameMap.ModelInstance", + position: this.props.node.position, + rotation: this.props.node.rotation, + scale: this.props.node.scale, + }, + error, + ); } render(): ReactNode { if (this.state.hasError) { - return this.props.fallback ?? null; + return null; } + return this.props.children; } } @@ -49,8 +65,7 @@ interface GameMapProps { } export function GameMap({ onOctreeReady }: GameMapProps): React.JSX.Element { - const [mapNodes, setMapNodes] = useState([]); - const [isLoading, setIsLoading] = useState(true); + const [mapNodes, setMapNodes] = useState([]); const groupRef = useRef(null); useOctreeGraphNode(groupRef, onOctreeReady, mapNodes.length); @@ -60,28 +75,32 @@ export function GameMap({ onOctreeReady }: GameMapProps): React.JSX.Element { try { const sceneData = await loadMapSceneData(); if (!sceneData) { - console.warn("map.json not found"); - setIsLoading(false); + logger.warn("GameMap", "map.json not found"); return; } - const loadedMapNodes = sceneData.mapNodes.filter((node) => - sceneData.models.has(node.name), - ); + const loadedMapNodes = sceneData.mapNodes.flatMap((node) => { + const modelUrl = sceneData.models.get(node.name); + return modelUrl ? [{ node, modelUrl }] : []; + }); const missingModelCount = sceneData.mapNodes.length - loadedMapNodes.length; if (missingModelCount > 0) { - console.warn( - `${missingModelCount} map nodes were skipped because their model files are missing.`, + logger.warn( + "GameMap", + "Map nodes skipped because model files are missing", + { + missingModelCount, + }, ); } setMapNodes(loadedMapNodes); } catch (error) { - console.error("Error loading map:", error); - } finally { - setIsLoading(false); + logger.error("GameMap", "Error loading map", { + error: error instanceof Error ? error : new Error(String(error)), + }); } }; @@ -90,35 +109,37 @@ export function GameMap({ onOctreeReady }: GameMapProps): React.JSX.Element { return ( - {!isLoading && - mapNodes.map((node, index) => ( - - - - ))} + {mapNodes.map((mapNode, index) => ( + + + + ))} ); } -function ModelInstance({ node }: { node: MapNode }): React.JSX.Element | null { - const modelPath = `/models/${node.name}/model.gltf`; - - const groupRef = useRef(null); - const { scene } = useGLTF(modelPath); - const sceneInstance = useMemo(() => scene.clone(true), [scene]); +function ModelInstance({ + node, + modelUrl, +}: { + node: MapNode; + modelUrl: string; +}): React.JSX.Element { const { position, rotation, scale } = node; - - useEffect(() => { - if (groupRef.current) { - groupRef.current.position.set(...position); - groupRef.current.rotation.set(...rotation); - groupRef.current.scale.set(...scale); - } - }, [position, rotation, scale]); + const { scene } = useLoggedGLTF(modelUrl, { + scope: "GameMap.ModelInstance", + position, + rotation, + scale, + }); + const sceneInstance = useClonedObject(scene); return ( { + const audio = AudioManager.getInstance(); + audio.playMusic(GAME_MUSIC_PATH, GAME_MUSIC_VOLUME); + + return () => { + audio.stopMusic(); + }; + }, []); + + return null; +} diff --git a/src/world/GameStageContent.tsx b/src/world/GameStageContent.tsx index ca16da8..4d8c936 100644 --- a/src/world/GameStageContent.tsx +++ b/src/world/GameStageContent.tsx @@ -1,5 +1,5 @@ import { useGameStore } from "@/managers/stores/useGameStore"; -import type { Vector3Tuple } from "@/types/three"; +import type { Vector3Tuple } from "@/types/three/three"; interface StageAnchorProps { color: string; diff --git a/src/world/World.tsx b/src/world/World.tsx index fe0344b..4737abb 100644 --- a/src/world/World.tsx +++ b/src/world/World.tsx @@ -8,12 +8,14 @@ import { useCameraMode } from "@/hooks/debug/useCameraMode"; import { useSceneMode } from "@/hooks/debug/useSceneMode"; import { DebugCameraControls } from "@/components/debug/scene/DebugCameraControls"; import { DebugHelpers } from "@/components/debug/scene/DebugHelpers"; +import { HandTrackingGlove } from "@/components/three/handTracking/HandTrackingGlove"; import { Environment } from "@/world/Environment"; +import { GameMusic } from "@/world/GameMusic"; import { Lighting } from "@/world/Lighting"; import { GameMap } from "@/world/GameMap"; import { GameStageContent } from "@/world/GameStageContent"; import { Player } from "@/world/player/Player"; -import { TestScene } from "@/world/debug/TestScene"; +import { TestMap } from "@/world/debug/TestMap"; export function World(): React.JSX.Element { const cameraMode = useCameraMode(); @@ -29,15 +31,22 @@ export function World(): React.JSX.Element { + {sceneMode === "physics" ? ( + <> + + + + ) : null} {cameraMode === "debug" ? : null} {sceneMode === "game" ? ( <> + ) : ( - + )} {cameraMode !== "debug" ? ( diff --git a/src/world/debug/TestScene.tsx b/src/world/debug/TestMap.tsx similarity index 54% rename from src/world/debug/TestScene.tsx rename to src/world/debug/TestMap.tsx index c3bd528..dd53bc9 100644 --- a/src/world/debug/TestScene.tsx +++ b/src/world/debug/TestMap.tsx @@ -1,9 +1,11 @@ -import { useRef } from "react"; +import type { ReactNode } from "react"; +import { Component, useRef } from "react"; import * as THREE from "three"; import { Physics, RigidBody, CuboidCollider } from "@react-three/rapier"; -import { GrabbableObject } from "@/components/three/GrabbableObject"; -import { TriggerObject } from "@/components/three/TriggerObject"; -import { AnimatedModel } from "@/components/three/AnimatedModel"; +import { RepairGameZone } from "@/components/three/gameplay/RepairGameZone"; +import { GrabbableObject } from "@/components/three/interaction/GrabbableObject"; +import { AnimatedModel } from "@/components/three/models/AnimatedModel"; +import { TriggerObject } from "@/components/three/interaction/TriggerObject"; import { TEST_SCENE_FLOOR_COLLIDER_HALF_EXTENTS, TEST_SCENE_FLOOR_POSITION, @@ -21,16 +23,61 @@ import { TEST_SCENE_TRIGGER_SEGMENTS, TEST_SCENE_TRIGGER_SOUND_PATH, } from "@/data/debug/testSceneConfig"; -import { useOctreeGraphNode } from "@/hooks/useOctreeGraphNode"; -import type { OctreeReadyHandler } from "@/types/three"; +import { useOctreeGraphNode } from "@/hooks/three/useOctreeGraphNode"; +import type { OctreeReadyHandler } from "@/types/three/three"; +import { logModelLoadError } from "@/utils/three/modelLoadLogger"; -interface TestSceneProps { +const ELECTRICIENNE_ANIMATED_MODEL_PATH = + "/models/electricienne_animated/model.gltf"; + +interface TestMapProps { onOctreeReady: OctreeReadyHandler; } -export function TestScene({ - onOctreeReady, -}: TestSceneProps): React.JSX.Element { +interface ModelPreviewErrorBoundaryProps { + children: ReactNode; + modelPath: string; +} + +interface ModelPreviewErrorBoundaryState { + hasError: boolean; +} + +class ModelPreviewErrorBoundary extends Component< + ModelPreviewErrorBoundaryProps, + ModelPreviewErrorBoundaryState +> { + constructor(props: ModelPreviewErrorBoundaryProps) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(): ModelPreviewErrorBoundaryState { + return { hasError: true }; + } + + componentDidCatch(error: Error): void { + logModelLoadError( + { + modelPath: this.props.modelPath, + scope: "TestMap.ModelPreview", + position: [0, 0, -5], + scale: 1, + }, + error, + ); + } + + render(): ReactNode { + if (this.state.hasError) { + return null; + } + + return this.props.children; + } +} + +export function TestMap({ onOctreeReady }: TestMapProps): React.JSX.Element { const floorRef = useRef(null); useOctreeGraphNode(floorRef, onOctreeReady); @@ -55,6 +102,7 @@ export function TestScene({ @@ -85,14 +133,18 @@ export function TestScene({ /> + + - + + + ); } diff --git a/src/world/player/Player.tsx b/src/world/player/Player.tsx index 8fdc489..d30ac2a 100644 --- a/src/world/player/Player.tsx +++ b/src/world/player/Player.tsx @@ -1,7 +1,7 @@ import { useEffect } from "react"; import { useThree } from "@react-three/fiber"; import type { Octree } from "three/addons/math/Octree.js"; -import type { Vector3Tuple } from "@/types/three"; +import type { Vector3Tuple } from "@/types/three/three"; import { PlayerCamera } from "@/world/player/PlayerCamera"; import { PlayerController } from "@/world/player/PlayerController"; diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx index b9b714b..6467d84 100644 --- a/src/world/player/PlayerController.tsx +++ b/src/world/player/PlayerController.tsx @@ -24,7 +24,7 @@ import { PLAYER_XZ_DAMPING_FACTOR, } from "@/data/player/playerConfig"; import { InteractionManager } from "@/managers/InteractionManager"; -import type { Vector3Tuple } from "@/types/three"; +import type { Vector3Tuple } from "@/types/three/three"; type Keys = { forward: boolean; diff --git a/vite.config.ts b/vite.config.ts index 4a65037..8b0376c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,17 +5,18 @@ import fs from "node:fs"; import { fileURLToPath } from "node:url"; import type { ServerResponse } from "node:http"; import type { Plugin } from "vite"; -import { parseMapNodes } from "./src/utils/mapNodeValidation"; +import { parseMapNodes } from "./src/utils/map/mapNodeValidation"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); const MAX_MAP_PAYLOAD_BYTES = 1024 * 1024; const JSON_HEADERS = { "Content-Type": "application/json" }; +type JsonResponseBody = Readonly>; function sendJson( res: ServerResponse, status: number, - body: unknown, + body: JsonResponseBody, headers: Record = {}, ): void { res