From f34817d5b6022ad5fce6213e412c8c897ed89f66 Mon Sep 17 00:00:00 2001 From: gardentech Date: Fri, 7 Aug 2026 14:26:00 +0900 Subject: [PATCH] Initial commit: scan_web integrated recording UI FastAPI backend + vanilla JS frontend for LiDAR/camera startup, camera settings, and rosbag recording, unifying the previous scan_gui/scan_gui_dual/ scan_gui_triple desktop tools into one web app. --- .gitignore | 5 + .hallmark/log.json | 12 + backend/app.py | 67 + backend/bag_metadata.py | 55 + backend/camera_params.py | 158 ++ backend/config.py | 235 +++ backend/health.py | 85 + backend/log_tail.py | 54 + backend/pointcloud_codec.py | 145 ++ backend/process_manager.py | 141 ++ backend/requirements.txt | 5 + backend/ros_bridge.py | 282 +++ backend/routers/__init__.py | 0 backend/routers/camera.py | 82 + backend/routers/recording.py | 119 ++ backend/routers/system.py | 212 +++ backend/routers/telemetry.py | 74 + design.md | 102 ++ docs/운용가이드.md | 217 +++ frontend/css/fonts.css | 25 + frontend/css/style.css | 699 ++++++++ frontend/css/tokens.css | 96 ++ frontend/index.html | 154 ++ frontend/js/app.js | 132 ++ frontend/js/camera-preview.js | 33 + frontend/js/camera-settings.js | 148 ++ frontend/js/format.js | 21 + frontend/js/log-panel.js | 45 + frontend/js/pointcloud-view.js | 169 ++ frontend/js/recording-panel.js | 123 ++ frontend/js/status-panel.js | 138 ++ frontend/vendor/fonts/Geist-Variable.woff2 | Bin 0 -> 29400 bytes .../vendor/fonts/GeistMono-Variable.woff2 | Bin 0 -> 23128 bytes frontend/vendor/three/LICENSE | 21 + frontend/vendor/three/OrbitControls.js | 1523 +++++++++++++++++ frontend/vendor/three/three.module.min.js | 6 + open_scan_web.sh | 21 + run_scan_web.sh | 26 + 38 files changed, 5430 insertions(+) create mode 100644 .gitignore create mode 100644 .hallmark/log.json create mode 100644 backend/app.py create mode 100644 backend/bag_metadata.py create mode 100644 backend/camera_params.py create mode 100644 backend/config.py create mode 100644 backend/health.py create mode 100644 backend/log_tail.py create mode 100644 backend/pointcloud_codec.py create mode 100644 backend/process_manager.py create mode 100644 backend/requirements.txt create mode 100644 backend/ros_bridge.py create mode 100644 backend/routers/__init__.py create mode 100644 backend/routers/camera.py create mode 100644 backend/routers/recording.py create mode 100644 backend/routers/system.py create mode 100644 backend/routers/telemetry.py create mode 100644 design.md create mode 100644 docs/운용가이드.md create mode 100644 frontend/css/fonts.css create mode 100644 frontend/css/style.css create mode 100644 frontend/css/tokens.css create mode 100644 frontend/index.html create mode 100644 frontend/js/app.js create mode 100644 frontend/js/camera-preview.js create mode 100644 frontend/js/camera-settings.js create mode 100644 frontend/js/format.js create mode 100644 frontend/js/log-panel.js create mode 100644 frontend/js/pointcloud-view.js create mode 100644 frontend/js/recording-panel.js create mode 100644 frontend/js/status-panel.js create mode 100644 frontend/vendor/fonts/Geist-Variable.woff2 create mode 100644 frontend/vendor/fonts/GeistMono-Variable.woff2 create mode 100644 frontend/vendor/three/LICENSE create mode 100644 frontend/vendor/three/OrbitControls.js create mode 100644 frontend/vendor/three/three.module.min.js create mode 100755 open_scan_web.sh create mode 100755 run_scan_web.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f817de0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +*.pyc +.venv/ +venv/ +*.log diff --git a/.hallmark/log.json b/.hallmark/log.json new file mode 100644 index 0000000..616f839 --- /dev/null +++ b/.hallmark/log.json @@ -0,0 +1,12 @@ +[ + { + "date": "2026-08-04", + "scope": "app", + "macrostructure": "n/a (app/dashboard — no marketing macrostructure)", + "theme": "custom", + "theme_axes": "light / grotesk-sans / cool-cobalt", + "vibe": "Tally-referenced modern-minimal, instrument-panel discipline", + "enrichment": "none", + "brief": "scan_web control panel redesign — hallmark applied to app UI, not a marketing page" + } +] diff --git a/backend/app.py b/backend/app.py new file mode 100644 index 0000000..5142185 --- /dev/null +++ b/backend/app.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import logging +from contextlib import asynccontextmanager +from pathlib import Path + +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +from starlette.types import Scope + +import config +from process_manager import ProcessManager +from ros_bridge import ROSBridge + +logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s") +logger = logging.getLogger("scan_web.app") + +FRONTEND_DIR = Path(__file__).resolve().parent.parent / "frontend" + + +class SessionState: + def __init__(self): + self.camera_count = config.DEFAULT_CAMERA_COUNT + self.recording = False + self.recording_path: str | None = None + self.recording_started_at: float | None = None + self.camera_values: dict = {} # cam_id -> camera_params.CameraParamValues + + +@asynccontextmanager +async def lifespan(app: FastAPI): + app.state.pm = ProcessManager() + app.state.session = SessionState() + app.state.ros = ROSBridge(camera_count=app.state.session.camera_count) + app.state.ros.start() + logger.info("scan_web backend started (camera_count=%s)", app.state.session.camera_count) + try: + yield + finally: + logger.info("shutting down — stopping all managed processes") + app.state.pm.stop_all() + app.state.ros.stop() + + +app = FastAPI(title="scan_web", lifespan=lifespan) + +from routers import system, camera, recording, telemetry # noqa: E402 (needs app.state types defined above) + +app.include_router(system.router, prefix="/api") +app.include_router(camera.router) +app.include_router(recording.router, prefix="/api") +app.include_router(telemetry.router) + +class NoCacheStaticFiles(StaticFiles): + """Frontend is actively edited during field testing — a browser caching a + stale index.html/app.js/style.css (no Cache-Control was set before) led + to confusing "it's not applying" reports that were actually just the + prior version still running. Static assets here are small and local, so + the no-caching cost is negligible.""" + + async def get_response(self, path: str, scope: Scope): + response = await super().get_response(path, scope) + response.headers["Cache-Control"] = "no-store" + return response + + +app.mount("/", NoCacheStaticFiles(directory=str(FRONTEND_DIR), html=True), name="frontend") diff --git a/backend/bag_metadata.py b/backend/bag_metadata.py new file mode 100644 index 0000000..1f29180 --- /dev/null +++ b/backend/bag_metadata.py @@ -0,0 +1,55 @@ +"""Read back what ros2 bag already knows about a recording — duration, +message counts, per-topic counts — instead of just listing directory names. +Also a plain on-disk size sum (metadata.yaml doesn't carry a byte size in +the ros2 bag version installed here).""" +from __future__ import annotations + +import os +from pathlib import Path +from typing import Optional + +import yaml + + +def dir_size_bytes(path: Path) -> int: + total = 0 + try: + for entry in path.iterdir(): + if entry.is_file(): + total += entry.stat().st_size + except OSError: + pass + return total + + +def read_bag_metadata(bag_dir: Path) -> Optional[dict]: + """Returns None if metadata.yaml is missing (e.g. bag was killed mid-write + before rosbag2 flushed it) or unparseable — a real, if incomplete, state + the operator should be able to see rather than a crash.""" + meta_path = bag_dir / "metadata.yaml" + if not meta_path.exists(): + return None + try: + with open(meta_path, encoding="utf-8") as f: + raw = yaml.safe_load(f) + info = raw["rosbag2_bagfile_information"] + except Exception: + return None + + duration_s = info.get("duration", {}).get("nanoseconds", 0) / 1e9 + message_count = info.get("message_count", 0) + topics = [] + for entry in info.get("topics_with_message_count", []): + tm = entry.get("topic_metadata", {}) + topics.append({ + "name": tm.get("name", "?"), + "type": tm.get("type", "?"), + "message_count": entry.get("message_count", 0), + }) + + return { + "duration_s": round(duration_s, 1), + "message_count": message_count, + "topics": topics, + "size_bytes": dir_size_bytes(bag_dir), + } diff --git a/backend/camera_params.py b/backend/camera_params.py new file mode 100644 index 0000000..36b2ef0 --- /dev/null +++ b/backend/camera_params.py @@ -0,0 +1,158 @@ +"""Camera exposure/gain parameter handling. + +Ported from scan_gui_triple.py's per-camera tab logic: +- load defaults by reading the base params YAML (ros__parameters block) +- "apply": live `ros2 param set ` per param, checked + (subprocess.run with captured output), reporting structured per-param + success/failure — the triple GUI's more robust approach, preferred here + over the single/dual GUIs' fire-and-forget version. +- "save": regex text-patch of the base YAML, NOT yaml.dump — the source + YAML files carry human-authored comments (e.g. timestamp-calibration + notes) that a full re-dump would destroy. +""" +from __future__ import annotations + +import re +import subprocess +from dataclasses import dataclass, asdict +from typing import Optional + +import yaml + +from config import ROS_SETUP, CAMERA2_WS_SETUP, CameraSpec + + +@dataclass +class CameraParamValues: + exposure_auto: bool + exposure_auto_target_brightness: int + exposure_auto_min: float + exposure_auto_max: float + exposure_time: int + gain: float + + def as_dict(self) -> dict: + return asdict(self) + + +def load_defaults(cam: CameraSpec) -> CameraParamValues: + p = {} + try: + with open(cam.base_params_path, encoding="utf-8") as f: + cfg = yaml.safe_load(f) + p = cfg[f"/{cam.node_name}"]["ros__parameters"] + except Exception: + pass + return CameraParamValues( + exposure_auto=bool(p.get("exposure_auto", False)), + exposure_auto_target_brightness=int(p.get("exposure_auto_target_brightness", 128)), + exposure_auto_min=float(p.get("exposure_auto_min", 100.0)), + exposure_auto_max=float(p.get("exposure_auto_max", 10000.0)), + exposure_time=int(p.get("exposure_time", 5000)), + gain=float(p.get("gain", 8.0)), + ) + + +def write_launch_params_yaml(cam: CameraSpec, values: CameraParamValues, tmp_prefix: str = "/tmp") -> str: + """Write a temp YAML (base + overridden values) for passing as a launch arg.""" + import tempfile + + try: + with open(cam.base_params_path, encoding="utf-8") as f: + cfg = yaml.safe_load(f) + except Exception: + cfg = {} + node_key = f"/{cam.node_name}" + cfg.setdefault(node_key, {}).setdefault("ros__parameters", {}) + cfg[node_key]["ros__parameters"].update(values.as_dict()) + + tf = tempfile.NamedTemporaryFile( + mode="w", suffix=".yaml", prefix=f"{tmp_prefix}/{cam.id}_params_", delete=False + ) + yaml.dump(cfg, tf, default_flow_style=False, allow_unicode=True) + tf.close() + return tf.name + + +def _format_param_value(value) -> str: + if isinstance(value, bool): + return "true" if value else "false" + if isinstance(value, float): + return f"{value:.4f}" + return str(value) + + +def apply_params(cam: CameraSpec, values: CameraParamValues) -> list[dict]: + """Run `ros2 param set` per param, checked. Returns [{name, ok, message}, ...].""" + node = f"/{cam.node_name}" + src = f"source {ROS_SETUP} && source {CAMERA2_WS_SETUP}" + + order = ["exposure_auto"] + if values.exposure_auto: + order += ["exposure_auto_target_brightness", "exposure_auto_min", "exposure_auto_max"] + else: + order += ["exposure_time"] + order += ["gain"] + + vals = values.as_dict() + results = [] + for name in order: + val_str = _format_param_value(vals[name]) + cmd = f"{src} && ros2 param set {node} {name} {val_str}" + try: + proc = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, timeout=10) + except subprocess.TimeoutExpired: + results.append({"name": name, "ok": False, "message": "timeout"}) + continue + if proc.returncode != 0: + reason = (proc.stdout.strip() or proc.stderr.strip() or "unknown error") + results.append({"name": name, "ok": False, "message": reason}) + else: + results.append({"name": name, "ok": True, "message": "ok"}) + return results + + +def _patch_yaml_value(text: str, key: str, new_value: str) -> tuple[str, bool]: + pattern = re.compile(rf"^([ \t]*){re.escape(key)}:([ \t]*)\S+", re.MULTILINE) + new_text, n = pattern.subn( + lambda m: f"{m.group(1)}{key}:{m.group(2)}{new_value}", text, count=1, + ) + return new_text, n > 0 + + +def _insert_missing_yaml_keys(text: str, missing: dict) -> str: + indent = " " + anchor_pos: Optional[int] = None + for anchor in ("exposure_time", "gain"): + m = re.search(rf"^([ \t]*){re.escape(anchor)}:", text, re.MULTILINE) + if m: + indent = m.group(1) + anchor_pos = m.start() + break + block = "".join(f"{indent}{k}: {v}\n" for k, v in missing.items()) + if anchor_pos is None: + return text.rstrip("\n") + "\n" + block + return text[:anchor_pos] + block + text[anchor_pos:] + + +def save_params(cam: CameraSpec, values: CameraParamValues) -> None: + """Persist values into the base YAML via comment-preserving text patch.""" + formatted = { + "exposure_auto": "true" if values.exposure_auto else "false", + "exposure_auto_target_brightness": str(values.exposure_auto_target_brightness), + "exposure_auto_min": f"{values.exposure_auto_min:.1f}", + "exposure_auto_max": f"{values.exposure_auto_max:.1f}", + "exposure_time": str(values.exposure_time), + "gain": f"{values.gain:.1f}", + } + with open(cam.base_params_path, encoding="utf-8") as f: + text = f.read() + missing = {} + for key, val in formatted.items(): + text, found = _patch_yaml_value(text, key, val) + if not found: + missing[key] = val + if missing: + text = _insert_missing_yaml_keys(text, missing) + with open(cam.base_params_path, "w", encoding="utf-8") as f: + f.write(text) diff --git a/backend/config.py b/backend/config.py new file mode 100644 index 0000000..e649c94 --- /dev/null +++ b/backend/config.py @@ -0,0 +1,235 @@ +"""Workspace paths, per-camera-count topic/launch config, and tunable thresholds. + +Values here are ported 1:1 from the source of truth in ~/fast_ws/scan_gui.py, +scan_gui_dual.py and scan_gui_triple.py — see docstrings in each CameraSpec +list for which file a given camera-count profile was copied from. +""" +from __future__ import annotations + +import os +from dataclasses import dataclass +from pathlib import Path + +HOME = Path.home() + +ROS_SETUP = "/opt/ros/humble/setup.bash" +# fast_ws is deprecated (2026-08-05, user decision) — it never had its own +# livox_ros_driver2 build, it only chained to lidar2_ws/install as an +# underlay (confirmed: COLCON_CURRENT_PREFIX in fast_ws/install/setup.bash +# points at lidar2_ws/install). Source lidar2_ws directly instead. +LIDAR2_WS_SETUP = str(HOME / "lidar2_ws/install/setup.bash") +CAMERA2_WS_SETUP = str(HOME / "camera2_ws/install/setup.bash") +FAST_DUAL_WS_SETUP = str(HOME / "fast_dual_ws/install/setup.bash") +RTK_WS_SETUP = str(HOME / "rtk_ws/install/setup.bash") +# hku-mars/FAST_LIO ROS2 port, LiDAR+IMU-only mapping (no camera) — confirmed +# working 2026-08-06. Its install/setup.bash was built with only +# /opt/ros/humble as an underlay (not chained to lidar2_ws), but it subscribes +# livox_ros_driver2::msg::CustomMsg on /livox/lidar, so lidar2_ws must still +# be sourced first at launch time for that message package to resolve. +FAST_LIO_WS_SETUP = str(HOME / "fast_lio/install/setup.bash") + +LIDAR_LAUNCH_CMD = ( + f"source {ROS_SETUP} && source {LIDAR2_WS_SETUP} && " + "stdbuf -oL -eL ros2 launch livox_ros_driver2 mid360s_fastlivo_launch.py" +) + +CAMERA_LAUNCH_SOURCE = f"source {ROS_SETUP} && source {CAMERA2_WS_SETUP}" + +# rviz:=false — this is launched headlessly as a recording-lifecycle +# subprocess, not an interactive session; mapping.launch.py defaults rviz to +# true which would otherwise pop an unwanted GUI window. +FASTLIO_LAUNCH_CMD = ( + f"source {ROS_SETUP} && source {LIDAR2_WS_SETUP} && source {FAST_LIO_WS_SETUP} && " + "stdbuf -oL -eL ros2 launch fast_lio mapping.launch.py rviz:=false" +) + + +def fastlio_env() -> dict: + """LD_LIBRARY_PATH override for the fastlio subprocess. + + run_scan_web.sh sources camera2_ws (for the Hikvision camera driver) + before exec'ing uvicorn, which puts the Hikvision SDK's lib dirs on + LD_LIBRARY_PATH for the whole backend process — and therefore, by + default, every ManagedProcess it spawns too (env = os.environ.copy()). + fastlio_mapping doesn't use the camera SDK at all, but its bundled + libusb-1.0.so.0 is old enough to be missing libusb_set_option, a symbol + PCL's libpcl_io needs — with that copy resolved first, fastlio_mapping + fails to even start (symbol lookup error, confirmed 2026-08-06). + Two separate paths carry this old libusb, not just one — /opt/MVS/lib/* + (the vendor SDK's own install) AND + camera2_ws/install/hik_camera_ros2_driver/lib (which symlinks straight + into the same vendored hikSDK/lib/amd64 copy) — both get filtered here. + """ + bad = ("mvs", "hik_camera", "hiksdk") + parts = os.environ.get("LD_LIBRARY_PATH", "").split(":") + cleaned = ":".join(p for p in parts if p and not any(b in p.lower() for b in bad)) + return {"LD_LIBRARY_PATH": cleaned} + +CAMERA_DELAY_SEC = 5 # let LiDAR init before starting cameras — ported from scan_gui*.py + +LOG_DIR = HOME / "scan_web_logs" +LIDAR_LOG_PATH = LOG_DIR / "lidar.log" +CAMERA_LOG_PATH = LOG_DIR / "camera.log" +RECORDING_LOG_PATH = LOG_DIR / "recording.log" +FASTLIO_LOG_PATH = LOG_DIR / "fastlio.log" +GNSS_LOG_PATH = LOG_DIR / "gnss.log" + +DEFAULT_SAVE_DIR = str(HOME / "bags") # confirmed default from scan_gui.py:460 + + +@dataclass(frozen=True) +class CameraSpec: + id: str + image_topic: str + info_topic: str + node_name: str # ROS node name, no leading slash + base_params_path: str + launch_arg: str # launch-file argument name this camera's params file is passed as + + +HIK_CFG_DIR = HOME / "camera2_ws/src/hik_camera_ros2_driver/config" + +# camera_count -> (launch_file, [CameraSpec, ...]) +CAMERA_PROFILES: dict[int, tuple[str, list[CameraSpec]]] = { + 1: ( + "hik_camera_launch.py", + [ + CameraSpec( + id="cam1", + image_topic="/camera/image", + info_topic="/camera/camera_info", + node_name="hik_camera_ros2_driver", + base_params_path=str(HIK_CFG_DIR / "camera_params.yaml"), + launch_arg="params_file", + ), + ], + ), + 2: ( + "hik_camera_dual_launch.py", + [ + CameraSpec( + id="cam1", + image_topic="/cam1/image", + info_topic="/cam1/camera_info", + node_name="hik_camera_cam1", + base_params_path=str(HIK_CFG_DIR / "camera_params_cam1.yaml"), + launch_arg="cam1_params_file", + ), + CameraSpec( + id="cam2", + image_topic="/cam2/image", + info_topic="/cam2/camera_info", + node_name="hik_camera_cam2", + base_params_path=str(HIK_CFG_DIR / "camera_params_cam2.yaml"), + launch_arg="cam2_params_file", + ), + ], + ), + 3: ( + "hik_camera_triple_launch.py", + [ + CameraSpec( + id="cam1", + image_topic="/cam1/image", + info_topic="/cam1/camera_info", + node_name="hik_camera_cam1", + base_params_path=str(HIK_CFG_DIR / "camera_params_cam1.yaml"), + launch_arg="cam1_params_file", + ), + CameraSpec( + id="cam2", + image_topic="/cam2/image", + info_topic="/cam2/camera_info", + node_name="hik_camera_cam2", + base_params_path=str(HIK_CFG_DIR / "camera_params_cam2.yaml"), + launch_arg="cam2_params_file", + ), + CameraSpec( + id="cam3", + image_topic="/cam3/image", + info_topic="/cam3/camera_info", + node_name="hik_camera_cam3", + base_params_path=str(HIK_CFG_DIR / "camera_params_cam3.yaml"), + launch_arg="cam3_params_file", + ), + ], + ), +} + +DEFAULT_CAMERA_COUNT = 3 # matches the physical rig described by the user (3 cams + MID360s) + + +def camera_specs(camera_count: int) -> list[CameraSpec]: + if camera_count not in CAMERA_PROFILES: + raise ValueError(f"unsupported camera_count={camera_count}, expected one of {sorted(CAMERA_PROFILES)}") + return CAMERA_PROFILES[camera_count][1] + + +def camera_launch_file(camera_count: int) -> str: + return CAMERA_PROFILES[camera_count][0] + + +def base_record_topics(camera_count: int) -> str: + parts = ["/livox/lidar", "/livox/imu"] + for cam in camera_specs(camera_count): + parts += [cam.image_topic, cam.info_topic] + return " ".join(parts) + + +ROSBAG_SOURCE = f"source {ROS_SETUP} && source {LIDAR2_WS_SETUP} && source {CAMERA2_WS_SETUP}" + +# GPS is out of scope for M1 (see plan milestone M2) — topic names kept here only +# as forward-reference documentation, not wired to any process/health logic yet. +GPS_TOPICS_LEGACY = "/gps/fix /gps/nmea" +GPS_TOPICS_RTK = "/ublox_driver/receiver_pvt" + +DISK_LOW_WARNING_GB = 10.0 # badge turns warning below this +DISK_LOW_DANGER_GB = 2.0 # badge turns danger below this — a long scan can lose a bag here + + +def disk_free_gb(path: str) -> float: + """Free space (GiB) on the filesystem backing `path` (walks up to an + existing ancestor dir first — the save dir itself may not exist yet).""" + import shutil as _shutil + p = Path(path) + while not p.exists() and p != p.parent: + p = p.parent + usage = _shutil.disk_usage(p) + return usage.free / (1024 ** 3) + + +# ── Health staleness thresholds (seconds) — proposed defaults, tune against +# real observed rates once M1-M3 are running (see plan §Health synthesis). ── +HEALTH_THRESHOLDS = { + "/livox/lidar": (1.0, 3.0), + # No "/livox/imu" entry — not subscribed at all anymore, see + # ros_bridge.py's module docstring. imu_health in /api/status mirrors + # lidar's own classification instead. + "camera_image": (1.5, 4.0), # applied per-camera image topic + # /Odometry, not /aft_mapped_to_init — that was FAST-LIVO2/LOAM-family + # naming, anticipated before the actual M3 backend (hku-mars FAST_LIO + # ROS2 port, confirmed 2026-08-06) was built. Confirmed via + # laserMapping.cpp: pubOdomAftMapped_ publishes nav_msgs/Odometry on + # "/Odometry". + "/Odometry": (1.0, 4.0), +} + +# ── M3 live point-cloud view ──────────────────────────────────────────── +# FAST-LIO's /cloud_registered is the per-scan (not cumulative) downsampled +# world-frame cloud — scan_web accumulates it server-side (see +# pointcloud_codec.py) so the operator sees full scan coverage, not a +# blinking per-scan cloud. +POINTCLOUD_TOPIC = "/cloud_registered" +ODOMETRY_TOPIC = "/Odometry" + +POINTCLOUD_VOXEL_SIZE_M = 0.05 # accumulation dedup grid +POINTCLOUD_MAX_ACCUM_POINTS = 200_000 # hard ceiling; oldest points evicted past this + +# Two output profiles selected at WS-connect time (?profile=desktop|phone) — +# phone gets fewer points at a lower rate, reasoned from three.js draw cost +# and WS bandwidth on field WiFi. Both sample from the same accumulated +# buffer, just decimated differently per frame. +POINTCLOUD_PROFILES = { + "desktop": {"max_points": 20_000, "hz": 5.0}, + "phone": {"max_points": 7_000, "hz": 3.5}, +} diff --git a/backend/health.py b/backend/health.py new file mode 100644 index 0000000..7f454be --- /dev/null +++ b/backend/health.py @@ -0,0 +1,85 @@ +"""Synthesized topic health (no diagnostics topics exist upstream — see plan). + +Classification per topic: + DOWN - backing process not running, OR no message ever received since + process start, OR staleness beyond down_threshold + STALE - running, >=1 message received, staleness in (stale, down] + OK - staleness <= stale_threshold + +Also tracks a rolling message rate (Hz) per topic — a measured number is a +more actionable status signal than a traffic light alone (e.g. "cam2 4.1Hz" +tells the operator it's alive but degraded, not just "not OK"). +""" +from __future__ import annotations + +import threading +import time +from collections import deque +from dataclasses import dataclass + +RATE_WINDOW = 150 # samples kept per topic — larger window trades responsiveness for a + # stable number: our own callback dispatch has scheduling jitter (see + # ros_bridge.py's MultiThreadedExecutor/GIL-contention notes), so a short + # window made the displayed Hz visibly jump around between updates even + # when the underlying topic rate was steady. 150 samples spans ~25s for a + # ~6Hz camera down to ~2s for ~80Hz IMU — smooths out that jitter without + # the number going stale-looking on genuinely slow topics. +RATE_STALE_S = 2.0 # if last message older than this, report rate as 0 regardless of window + + +@dataclass +class TopicThreshold: + stale_s: float + down_s: float + + +class TopicHealthTracker: + def __init__(self): + self._last_msg_time: dict[str, float] = {} + self._recent_times: dict[str, deque] = {} + self._lock = threading.Lock() + + def mark_received(self, topic: str) -> None: + now = time.monotonic() + with self._lock: + self._last_msg_time[topic] = now + dq = self._recent_times.setdefault(topic, deque(maxlen=RATE_WINDOW)) + dq.append(now) + + def reset(self, topic: str) -> None: + with self._lock: + self._last_msg_time.pop(topic, None) + self._recent_times.pop(topic, None) + + def classify(self, topic: str, process_running: bool, thresholds: TopicThreshold) -> str: + if not process_running: + return "DOWN" + with self._lock: + last = self._last_msg_time.get(topic) + if last is None: + return "DOWN" + staleness = time.monotonic() - last + if staleness <= thresholds.stale_s: + return "OK" + if staleness <= thresholds.down_s: + return "STALE" + return "DOWN" + + def staleness(self, topic: str) -> float | None: + with self._lock: + last = self._last_msg_time.get(topic) + return None if last is None else time.monotonic() - last + + def rate_hz(self, topic: str) -> float: + """Measured message rate over the last few samples. 0.0 if stale or unseen.""" + with self._lock: + last = self._last_msg_time.get(topic) + dq = self._recent_times.get(topic) + if last is None or dq is None or len(dq) < 2: + return 0.0 + if time.monotonic() - last > RATE_STALE_S: + return 0.0 + span = dq[-1] - dq[0] + if span <= 0: + return 0.0 + return round((len(dq) - 1) / span, 1) diff --git a/backend/log_tail.py b/backend/log_tail.py new file mode 100644 index 0000000..132170f --- /dev/null +++ b/backend/log_tail.py @@ -0,0 +1,54 @@ +"""Tails the lidar/camera/recording log files for the browser log panel. + +The original PyQt5 scan GUIs had a single unified QPlainTextEdit showing all +subprocess stdout with a [tag] prefix — this is the web equivalent. Each +ManagedProcess re-opens its log file in "w" mode on every start (see +process_manager.py), so a tailer must detect truncation (file shrank since +last read) and reset to the top rather than seeking past EOF forever. +""" +from __future__ import annotations + +from pathlib import Path +from typing import Iterable + + +class LogTailer: + def __init__(self, sources: dict[str, Path]): + self._sources = sources + self._offsets: dict[str, int] = {tag: 0 for tag in sources} + + def read_tail(self, tag: str, max_lines: int = 200) -> list[str]: + """Backlog for a fresh websocket connection — last N lines, and + advances this tag's offset to end-of-file so read_new() only + reports genuinely new lines afterwards.""" + path = self._sources.get(tag) + if path is None or not path.exists(): + return [] + try: + with open(path, "r", errors="replace") as f: + lines = f.readlines() + self._offsets[tag] = f.tell() + except OSError: + return [] + return [ln.rstrip("\n") for ln in lines[-max_lines:]] + + def read_new(self) -> Iterable[tuple[str, str]]: + """Yields (tag, line) for every line appended since the last call.""" + for tag, path in self._sources.items(): + if not path.exists(): + continue + try: + size = path.stat().st_size + if size < self._offsets.get(tag, 0): + self._offsets[tag] = 0 # file was truncated (process restarted) + with open(path, "r", errors="replace") as f: + f.seek(self._offsets[tag]) + new_text = f.read() + self._offsets[tag] = f.tell() + except OSError: + continue + if not new_text: + continue + for line in new_text.splitlines(): + if line: + yield tag, line diff --git a/backend/pointcloud_codec.py b/backend/pointcloud_codec.py new file mode 100644 index 0000000..f0c6d20 --- /dev/null +++ b/backend/pointcloud_codec.py @@ -0,0 +1,145 @@ +"""Accumulates FAST-LIO's per-scan /cloud_registered into a running +world-frame point cloud (voxel-deduped, capped at a hard point ceiling) and +encodes decimated binary frames for the /ws/pointcloud stream. + +Binary frame layout (all little-endian, no padding): + 4s magic b"PCF1" + I frame_seq + d timestamp (unix seconds) + I pose_flag 1 if a pose follows, else 0 (uint32, not uint8 — keeps + everything after it 4-byte aligned so the client can + construct a Float32Array view directly on the xyz + bytes below instead of copying) + 7f pose x, y, z, qx, qy, qz, qw (only present if pose_flag) + I point_count N + Nx3 f4 xyz, world frame, contiguous [x0,y0,z0, x1,y1,z1, ...] + Nx1 u1 intensity, normalized to 0-255 + +xyz and intensity are separate contiguous arrays (not an interleaved struct) +so both ends can hand them straight to a typed array / numpy view. +""" +from __future__ import annotations + +import struct +import threading +import time +from typing import Optional + +import numpy as np + +MAGIC = b"PCF1" + + +class PointCloudAccumulator: + def __init__(self, voxel_size: float, max_points: int): + self.voxel_size = voxel_size + self.max_points = max_points + self._lock = threading.Lock() + self._xyz = np.empty((0, 3), dtype=np.float32) + self._intensity = np.empty((0,), dtype=np.float32) + self._keys: list[int] = [] # packed voxel key per row, same order as _xyz + self._key_set: set[int] = set() # membership test for incoming-scan dedup + self._pose: Optional[tuple] = None # (x,y,z,qx,qy,qz,qw) + self._frame_seq = 0 + + # ── ingest (called from the ROS-side worker thread) ───────────────── + + def _pack_keys(self, xyz: np.ndarray) -> np.ndarray: + # Packs a voxel-quantized (ix,iy,iz) into one int64 so it's hashable + # for the Python set below. OFFSET/BASE give each axis +-2^19 voxels + # of range (with the default 5cm voxel that's +-26km), comfortably + # covering FAST-LIO's 100m det_range with headroom to spare, while + # staying well inside int64. + OFFSET = 1 << 19 + BASE = 1 << 20 + idx = np.floor(xyz / self.voxel_size).astype(np.int64) + OFFSET + return (idx[:, 0] * BASE + idx[:, 1]) * BASE + idx[:, 2] + + def add_scan(self, xyz: np.ndarray, intensity: np.ndarray) -> None: + """xyz: (K,3) float32 world-frame points from one /cloud_registered message.""" + if xyz.shape[0] == 0: + return + keys = self._pack_keys(xyz) + # Dedup within this scan first (a downsampled scan can still put + # multiple points in one voxel), then keep only voxels not already + # in the accumulated buffer. + _, first_idx = np.unique(keys, return_index=True) + keys = keys[first_idx] + xyz = xyz[first_idx] + intensity = intensity[first_idx] + + with self._lock: + novel = np.fromiter((k not in self._key_set for k in keys), dtype=bool, count=len(keys)) + if not novel.any(): + return + new_xyz = xyz[novel] + new_intensity = intensity[novel] + new_keys = keys[novel] + + self._xyz = np.concatenate([self._xyz, new_xyz]) + self._intensity = np.concatenate([self._intensity, new_intensity]) + self._keys.extend(int(k) for k in new_keys) + self._key_set.update(int(k) for k in new_keys) + + overflow = len(self._keys) - self.max_points + if overflow > 0: + # FIFO eviction — oldest points dropped first once over budget. + evicted, self._keys = self._keys[:overflow], self._keys[overflow:] + self._key_set.difference_update(evicted) + self._xyz = self._xyz[overflow:] + self._intensity = self._intensity[overflow:] + + def set_pose(self, x: float, y: float, z: float, qx: float, qy: float, qz: float, qw: float) -> None: + with self._lock: + self._pose = (x, y, z, qx, qy, qz, qw) + + def point_count(self) -> int: + with self._lock: + return self._xyz.shape[0] + + def reset(self) -> None: + """Clears the accumulated map — call when a new recording starts so + the previous scan's points don't linger into the next one.""" + with self._lock: + self._xyz = np.empty((0, 3), dtype=np.float32) + self._intensity = np.empty((0,), dtype=np.float32) + self._keys = [] + self._key_set = set() + self._pose = None + self._frame_seq = 0 + + # ── output (called from the asyncio WS loop) ───────────────────────── + + def encode_frame(self, max_points: int) -> bytes: + with self._lock: + n_total = self._xyz.shape[0] + if n_total <= max_points: + xyz, intensity = self._xyz, self._intensity + else: + # Stride sample, not random — keeps the decimated view stable + # frame-to-frame instead of sparkling with a fresh random + # subset every send. + idx = np.linspace(0, n_total - 1, max_points).astype(np.int64) + xyz, intensity = self._xyz[idx], self._intensity[idx] + pose = self._pose + self._frame_seq += 1 + seq = self._frame_seq + return _pack_frame(seq, time.time(), pose, xyz, intensity) + + +def _pack_frame(seq: int, timestamp: float, pose: Optional[tuple], xyz: np.ndarray, intensity: np.ndarray) -> bytes: + n = xyz.shape[0] + parts = [MAGIC, struct.pack(" None: + with self._lock: + if self.is_running(): + return + env = os.environ.copy() + env["PYTHONUNBUFFERED"] = "1" + if self.env: + env.update(self.env) + + stdout = subprocess.DEVNULL + stderr = subprocess.DEVNULL + if self.log_path is not None: + self.log_path.parent.mkdir(parents=True, exist_ok=True) + self._log_file = open(self.log_path, "w") + stdout = self._log_file + stderr = subprocess.STDOUT + + self._proc = subprocess.Popen( + ["bash", "-c", self.cmd], + stdout=stdout, stderr=stderr, + preexec_fn=os.setsid, env=env, + ) + self.started_at = time.monotonic() + logger.info("started %s pid=%s", self.name, self._proc.pid) + + def is_running(self) -> bool: + return self._proc is not None and self._proc.poll() is None + + def pid(self) -> Optional[int]: + return self._proc.pid if self._proc else None + + def stop(self, timeout: float = 3.0) -> None: + with self._lock: + proc = self._proc + if proc is None or proc.poll() is not None: + self._cleanup() + return + try: + parent = psutil.Process(proc.pid) + children = parent.children(recursive=True) + except psutil.NoSuchProcess: + self._cleanup() + return + for p in children + [parent]: + try: + p.send_signal(signal.SIGINT) + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + _, alive = psutil.wait_procs(children + [parent], timeout=timeout) + for p in alive: + try: + p.kill() + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + logger.info("stopped %s", self.name) + self._cleanup() + + def _cleanup(self) -> None: + self._proc = None + self.started_at = None + if self._log_file: + self._log_file.close() + self._log_file = None + + +class ProcessManager: + """App-lifetime singleton (stored on app.state) tracking all managed processes.""" + + def __init__(self): + self._procs: dict[str, ManagedProcess] = {} + self._lock = threading.Lock() + + def start(self, name: str, cmd: str, log_path: Optional[Path] = None, env: Optional[dict] = None) -> ManagedProcess: + with self._lock: + mp = self._procs.get(name) + if mp is None or not mp.is_running(): + mp = ManagedProcess(name, cmd, log_path=log_path, env=env) + self._procs[name] = mp + mp.start() + return mp + + def stop(self, name: str, timeout: float = 3.0) -> None: + with self._lock: + mp = self._procs.get(name) + if mp is not None: + mp.stop(timeout=timeout) + + def is_running(self, name: str) -> bool: + with self._lock: + mp = self._procs.get(name) + return mp is not None and mp.is_running() + + def status(self) -> dict[str, dict]: + with self._lock: + items = list(self._procs.items()) + return { + name: {"running": mp.is_running(), "pid": mp.pid()} + for name, mp in items + } + + def stop_all(self, timeout: float = 3.0) -> None: + with self._lock: + names = list(self._procs.keys()) + for name in names: + self.stop(name, timeout=timeout) diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..10903d9 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,5 @@ +fastapi>=0.110 +uvicorn[standard]>=0.29 +websockets>=12.0 +numpy>=1.22 +PyYAML>=6.0 diff --git a/backend/ros_bridge.py b/backend/ros_bridge.py new file mode 100644 index 0000000..dcb4854 --- /dev/null +++ b/backend/ros_bridge.py @@ -0,0 +1,282 @@ +"""Hosts rclpy inside the FastAPI process. + +Mirrors the isolation pattern used by RosSubscriber in scan_gui_triple.py: +a dedicated rclpy.Context + Node, spun in a daemon thread, kept out of the +ASGI event loop entirely. One node, one MultiThreadedExecutor, one shared +ReentrantCallbackGroup for every subscription. + +/livox/imu (~200Hz) is deliberately NOT subscribed here — it's ~20x every +other topic's rate, and every way of grouping it alongside slower topics +(shared default group, shared Reentrant group, its own dedicated +Context+Node+thread, split-by-cost with the other cheap raw subscriptions) +measurably starved the slower ones, cameras worst of all — confirmed +2026-08-06 across several configurations, right down to cameras measuring +~0Hz against a real ~10Hz feed even after every other topic's callback was +made cheap (raw=True). The operator decided camera accuracy matters more +than a live IMU Hz reading, so IMU health is now inferred from lidar's +health instead of measured directly (see routers/system.py) — they come off +the same livox_ros_driver2 process, so lidar being alive is already strong +evidence IMU is too. If IMU health/Hz needs to be real again later, give it +back its own dedicated Context+Node+executor+thread (not a shared one) and +expect to spend real GIL budget on it. + +Subscriptions cover: camera images (JPEG-encoded for MJPEG serving), the raw +lidar driver topic (health only), and (M3) FAST-LIO's point cloud + +odometry for the live scan view. GPS (M2) is not yet added. All +subscriptions register once at startup, never per start/stop call, so +health.py can tell "process down" apart from "never subscribed". + +Cross-thread handoff: ROS callbacks (background thread) write into a +lock-guarded "latest value" dict; HTTP/WS handlers (asyncio loop) read it. +No queues — a slow reader just sees a slightly stale value, never backs up +ROS callback processing. +""" +from __future__ import annotations + +import logging +import threading +import time +from typing import Optional + +import config +from config import camera_specs +from health import TopicHealthTracker +from pointcloud_codec import PointCloudAccumulator + +logger = logging.getLogger("scan_web.ros_bridge") + +try: + import numpy as np + import rclpy + from rclpy.callback_groups import ReentrantCallbackGroup + from rclpy.executors import MultiThreadedExecutor + from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSHistoryPolicy + from sensor_msgs.msg import Image, PointCloud2 + from nav_msgs.msg import Odometry + from livox_ros_driver2.msg import CustomMsg + from cv_bridge import CvBridge + import sensor_msgs_py.point_cloud2 as pc2 + import cv2 + ROS_AVAILABLE = True +except ImportError: + ROS_AVAILABLE = False + + +class ROSBridge: + def __init__(self, camera_count: int): + self.camera_count = camera_count + self.health = TopicHealthTracker() + self._running = False + self._thread: Optional[threading.Thread] = None + self._ctx = None # set in _spin(); stop() shuts it down to unblock executor.spin() + self._bridge = CvBridge() if ROS_AVAILABLE else None + self._frame_lock = threading.Lock() + self._latest_jpeg: dict[str, bytes] = {} + self._raw_lock = threading.Lock() + self._latest_raw: dict[str, object] = {} + self._frame_events: dict[str, threading.Event] = {} + self.pointcloud = PointCloudAccumulator( + config.POINTCLOUD_VOXEL_SIZE_M, config.POINTCLOUD_MAX_ACCUM_POINTS, + ) + + # ── lifecycle ────────────────────────────────────────────────────────── + + def start(self) -> None: + if not ROS_AVAILABLE: + logger.warning("rclpy not available (ROS overlays not sourced?) — camera preview disabled") + return + if self._running: + return + self._running = True + self._thread = threading.Thread(target=self._spin, daemon=True, name="ros-bridge") + self._thread.start() + + def stop(self) -> None: + self._running = False + if self._ctx is not None: + # Unblocks the executor.spin() call in _spin()'s thread from out + # here — spin() only checks context.ok()/is_shutdown, it doesn't + # know about self._running. + try: + self._ctx.shutdown() + except Exception: + pass + if self._thread: + self._thread.join(timeout=2.0) + self._thread = None + + def _spin(self) -> None: + ctx = rclpy.Context() + ctx.init() + self._ctx = ctx + try: + node = rclpy.create_node("scan_web_bridge", context=ctx) + # One shared ReentrantCallbackGroup for every subscription below + # — the node's default MutuallyExclusiveCallbackGroup would + # serialize dispatch across all of them regardless of executor + # type. No /livox/imu here (see module docstring) — with it + # removed, the remaining topics are all roughly the same order + # of magnitude (~10Hz), so they coexist fairly under one + # Reentrant group without needing to be split further. + cbg = ReentrantCallbackGroup() + img_qos = QoSProfile( + reliability=QoSReliabilityPolicy.BEST_EFFORT, + history=QoSHistoryPolicy.KEEP_LAST, + depth=1, + ) + for cam in camera_specs(self.camera_count): + node.create_subscription( + Image, cam.image_topic, + lambda msg, c=cam.id, t=cam.image_topic: self._on_image(c, t, msg), + img_qos, callback_group=cbg, + ) + # Raw (undeserialized) lidar driver topic — subscribed only for + # staleness/health tracking, not decoded/stored. This callback + # never touches the payload, so raw=True skips deserialization + # cost entirely (mirrors what `ros2 bag record` does + # internally). Distinct from the LIO mapping output + # (/aft_mapped_to_init etc.), which is M3 scope. + sensor_qos = QoSProfile( + reliability=QoSReliabilityPolicy.BEST_EFFORT, + history=QoSHistoryPolicy.KEEP_LAST, + depth=1, + ) + node.create_subscription( + CustomMsg, "/livox/lidar", + lambda msg: self.health.mark_received("/livox/lidar"), sensor_qos, callback_group=cbg, raw=True, + ) + # M3 — FAST-LIO's live mapping output. /cloud_registered is only + # published while the "fastlio" process is running (tied to the + # recording lifecycle, see routers/recording.py), same as lidar + # is gated on the "lidar" process. + node.create_subscription( + PointCloud2, config.POINTCLOUD_TOPIC, self._on_pointcloud, sensor_qos, callback_group=cbg, + ) + node.create_subscription( + Odometry, config.ODOMETRY_TOPIC, self._on_odometry, sensor_qos, callback_group=cbg, + ) + # MultiThreadedExecutor (not Single) — spin_once() on either type + # still only pulls ONE ready entity per call, but Single runs its + # callback synchronously inline before the next pull, whereas + # MultiThreaded submits it to a worker pool and returns + # immediately, so the poll loop keeps up with several ~10Hz + # topics instead of falling behind and starving each other. + # num_threads is deliberately just above the subscription count + # (6: 3 cameras + lidar + pointcloud + odometry) rather than + # generously oversized — Python's GIL means more threads doesn't + # mean more parallel throughput, only more context-switch + # overhead once you're past "enough to avoid queueing". + executor = MultiThreadedExecutor(context=ctx, num_threads=6) + executor.add_node(node) + executor.spin() + executor.remove_node(node) + node.destroy_node() + except Exception: + logger.exception("ros bridge spin loop crashed") + finally: + self._ctx = None + try: + ctx.shutdown() + except Exception: + pass + + # ── callbacks ────────────────────────────────────────────────────────── + + def _on_image(self, cam_id: str, topic: str, msg) -> None: + # Record arrival immediately and hand the deserialized msg off to a + # per-camera worker thread. cv_bridge/JPEG encoding is too slow to + # run inline here — running it directly in the ROS callback would + # tie up that callback's executor thread for the encode's duration, + # needlessly limiting how many camera frames can be in flight + # concurrently. + self.health.mark_received(topic) + with self._raw_lock: + self._latest_raw[cam_id] = msg + ev = self._frame_events.get(cam_id) + if ev is None: + ev = threading.Event() + self._frame_events[cam_id] = ev + threading.Thread( + target=self._encode_loop, args=(cam_id,), daemon=True, name=f"encode-{cam_id}", + ).start() + ev.set() + + def _encode_loop(self, cam_id: str) -> None: + ev = self._frame_events[cam_id] + while self._running: + if not ev.wait(timeout=0.5): + continue + ev.clear() + with self._raw_lock: + msg = self._latest_raw.get(cam_id) + if msg is None: + continue + try: + frame = self._bridge.imgmsg_to_cv2(msg, desired_encoding="bgr8") + ok, buf = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, 80]) + if not ok: + continue + with self._frame_lock: + self._latest_jpeg[cam_id] = buf.tobytes() + except Exception: + logger.exception("failed to decode frame for %s", cam_id) + + def _on_pointcloud(self, msg) -> None: + # Same reasoning as _on_image: read_points_numpy + voxel-dedup + # accumulation is too slow to run inline on the shared spin thread, + # so hand the raw msg off to a dedicated worker thread. + self.health.mark_received(config.POINTCLOUD_TOPIC) + with self._raw_lock: + self._latest_raw["pointcloud"] = msg + ev = self._frame_events.get("pointcloud") + if ev is None: + ev = threading.Event() + self._frame_events["pointcloud"] = ev + threading.Thread(target=self._pointcloud_loop, daemon=True, name="pointcloud-accum").start() + ev.set() + + def _pointcloud_loop(self) -> None: + ev = self._frame_events["pointcloud"] + while self._running: + if not ev.wait(timeout=0.5): + continue + ev.clear() + with self._raw_lock: + msg = self._latest_raw.get("pointcloud") + if msg is None: + continue + try: + arr = pc2.read_points_numpy(msg, field_names=("x", "y", "z", "intensity"), skip_nans=True) + if arr.size == 0: + continue + xyz = np.ascontiguousarray(arr[:, :3], dtype=np.float32) + intensity = np.ascontiguousarray(arr[:, 3], dtype=np.float32) + self.pointcloud.add_scan(xyz, intensity) + except Exception: + logger.exception("failed to accumulate point cloud") + + def _on_odometry(self, msg) -> None: + # Cheap (a few float unpacks) — fine to run inline on the spin thread. + self.health.mark_received(config.ODOMETRY_TOPIC) + p = msg.pose.pose.position + q = msg.pose.pose.orientation + self.pointcloud.set_pose(p.x, p.y, p.z, q.x, q.y, q.z, q.w) + + # ── readers (called from asyncio handlers) ─────────────────────────────── + + def latest_jpeg(self, cam_id: str) -> Optional[bytes]: + with self._frame_lock: + return self._latest_jpeg.get(cam_id) + + def mjpeg_generator(self, cam_id: str, target_hz: float = 12.0): + """Blocking generator (run in a threadpool) yielding multipart MJPEG chunks.""" + period = 1.0 / target_hz + boundary = b"--frame" + while self._running: + frame = self.latest_jpeg(cam_id) + if frame is not None: + yield ( + boundary + b"\r\nContent-Type: image/jpeg\r\nContent-Length: " + + str(len(frame)).encode() + b"\r\n\r\n" + frame + b"\r\n" + ) + time.sleep(period) diff --git a/backend/routers/__init__.py b/backend/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/routers/camera.py b/backend/routers/camera.py new file mode 100644 index 0000000..eb73cae --- /dev/null +++ b/backend/routers/camera.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from fastapi import APIRouter, HTTPException, Request +from fastapi.responses import StreamingResponse +from pydantic import BaseModel +from starlette.concurrency import run_in_threadpool + +import camera_params +import config + +router = APIRouter(prefix="/api/camera", tags=["camera"]) + + +class ParamValuesIn(BaseModel): + exposure_auto: bool + exposure_auto_target_brightness: int + exposure_auto_min: float + exposure_auto_max: float + exposure_time: int + gain: float + + +def _get_spec(request: Request, cam_id: str) -> config.CameraSpec: + session = request.app.state.session + specs = {c.id: c for c in config.camera_specs(session.camera_count)} + spec = specs.get(cam_id) + if spec is None: + raise HTTPException(404, f"unknown camera id '{cam_id}' for camera_count={session.camera_count}") + return spec + + +@router.get("/{cam_id}/params") +async def get_params(cam_id: str, request: Request): + spec = _get_spec(request, cam_id) + session = request.app.state.session + values = session.camera_values.get(cam_id) or camera_params.load_defaults(spec) + session.camera_values[cam_id] = values + return values.as_dict() + + +@router.post("/{cam_id}/params") +async def set_params(cam_id: str, body: ParamValuesIn, request: Request): + spec = _get_spec(request, cam_id) + session = request.app.state.session + values = camera_params.CameraParamValues(**body.model_dump()) + session.camera_values[cam_id] = values + return {"ok": True} + + +@router.post("/{cam_id}/params/apply") +async def apply_params(cam_id: str, request: Request): + spec = _get_spec(request, cam_id) + session = request.app.state.session + pm = request.app.state.pm + if not pm.is_running("camera"): + raise HTTPException(409, "camera is not running") + values = session.camera_values.get(cam_id) or camera_params.load_defaults(spec) + results = await run_in_threadpool(camera_params.apply_params, spec, values) + ok = all(r["ok"] for r in results) + return {"ok": ok, "results": results} + + +@router.post("/{cam_id}/params/save") +async def save_params(cam_id: str, request: Request): + spec = _get_spec(request, cam_id) + session = request.app.state.session + values = session.camera_values.get(cam_id) or camera_params.load_defaults(spec) + try: + await run_in_threadpool(camera_params.save_params, spec, values) + except Exception as e: + raise HTTPException(500, f"save failed: {e}") + return {"ok": True, "path": spec.base_params_path} + + +@router.get("/{cam_id}/stream.mjpg") +async def stream(cam_id: str, request: Request): + _get_spec(request, cam_id) # validate + ros = request.app.state.ros + return StreamingResponse( + ros.mjpeg_generator(cam_id), + media_type="multipart/x-mixed-replace; boundary=frame", + ) diff --git a/backend/routers/recording.py b/backend/routers/recording.py new file mode 100644 index 0000000..0cdb491 --- /dev/null +++ b/backend/routers/recording.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +import os +import time +from pathlib import Path + +from fastapi import APIRouter, HTTPException, Request +from pydantic import BaseModel + +import bag_metadata +import config + +router = APIRouter(tags=["recording"]) + +# Directory browsing/recording is restricted to the user's home tree — the +# browser has no server-filesystem access, so /api/fs/browse is a small +# custom endpoint standing in for a native folder picker. +BROWSE_ROOT = Path.home() + + +def _safe_path(raw: str) -> Path: + p = (BROWSE_ROOT / raw).resolve() if not os.path.isabs(raw) else Path(raw).resolve() + try: + p.relative_to(BROWSE_ROOT.resolve()) + except ValueError: + raise HTTPException(400, f"path must be under {BROWSE_ROOT}") + return p + + +@router.get("/fs/browse") +async def browse(path: str = str(BROWSE_ROOT)): + p = _safe_path(path) + if not p.exists() or not p.is_dir(): + raise HTTPException(404, "not a directory") + entries = [] + for child in sorted(p.iterdir()): + if child.is_dir(): + entries.append({"name": child.name, "path": str(child)}) + return {"path": str(p), "parent": str(p.parent) if p != BROWSE_ROOT.resolve() else None, "dirs": entries} + + +@router.get("/bags") +async def list_bags(save_dir: str = config.DEFAULT_SAVE_DIR, limit: int = 10): + d = Path(save_dir) + if not d.exists(): + return {"bags": []} + bags = [] + children = sorted(d.iterdir(), key=lambda p: p.stat().st_mtime, reverse=True) + for child in children: + if not child.is_dir(): + continue + if len(bags) >= limit: + break + meta = bag_metadata.read_bag_metadata(child) + bags.append({ + "name": child.name, + "path": str(child), + "mtime": child.stat().st_mtime, + "duration_s": meta["duration_s"] if meta else None, + "message_count": meta["message_count"] if meta else None, + "topics": meta["topics"] if meta else [], + "size_bytes": meta["size_bytes"] if meta else bag_metadata.dir_size_bytes(child), + "complete": meta is not None, # False = still recording, or was killed before flush + }) + return {"bags": bags} + + +@router.get("/fs/disk-usage") +async def disk_usage(path: str = config.DEFAULT_SAVE_DIR): + return {"free_gb": round(config.disk_free_gb(path), 2)} + + +class RecordingStartIn(BaseModel): + filename: str + save_dir: str = config.DEFAULT_SAVE_DIR + + +@router.post("/recording/start") +async def recording_start(body: RecordingStartIn, request: Request): + session = request.app.state.session + pm = request.app.state.pm + + if not pm.is_running("lidar") or not pm.is_running("camera"): + raise HTTPException(409, "start lidar+camera before recording") + if session.recording: + raise HTTPException(409, "already recording") + if not body.filename.strip(): + raise HTTPException(400, "filename required") + + save_dir = body.save_dir.strip() or config.DEFAULT_SAVE_DIR + os.makedirs(save_dir, exist_ok=True) + bag_path = os.path.join(save_dir, body.filename.strip()) + topics = config.base_record_topics(session.camera_count) + + # FAST-LIO tracks the scan (LiDAR+IMU-only mapping) for exactly the + # recording's lifetime — started alongside the bag, stopped alongside it. + # It only needs /livox/lidar + /livox/imu, both already live since + # lidar+camera are required (checked above) before recording can start. + request.app.state.ros.pointcloud.reset() # fresh map for this recording, not last one's leftovers + pm.start("fastlio", config.FASTLIO_LAUNCH_CMD, log_path=config.FASTLIO_LOG_PATH, env=config.fastlio_env()) + + cmd = f"{config.ROSBAG_SOURCE} && stdbuf -oL -eL ros2 bag record -o {bag_path} {topics}" + pm.start("recording", cmd, log_path=config.RECORDING_LOG_PATH) + session.recording = True + session.recording_path = bag_path + session.recording_started_at = time.monotonic() + return {"ok": True, "path": bag_path} + + +@router.post("/recording/stop") +async def recording_stop(request: Request): + session = request.app.state.session + pm = request.app.state.pm + pm.stop("recording") + pm.stop("fastlio") + session.recording = False + session.recording_path = None + session.recording_started_at = None + return {"ok": True} diff --git a/backend/routers/system.py b/backend/routers/system.py new file mode 100644 index 0000000..7388f4d --- /dev/null +++ b/backend/routers/system.py @@ -0,0 +1,212 @@ +from __future__ import annotations + +import asyncio +import time +from pathlib import Path + +from fastapi import APIRouter, HTTPException, Request +from pydantic import BaseModel + +import bag_metadata +import camera_params +import config +from ros_bridge import ROSBridge + +router = APIRouter() + + +class SessionConfig(BaseModel): + camera_count: int + + +@router.post("/session") +async def set_session(cfg: SessionConfig, request: Request): + if cfg.camera_count not in config.CAMERA_PROFILES: + raise HTTPException(400, f"unsupported camera_count, expected one of {sorted(config.CAMERA_PROFILES)}") + session = request.app.state.session + pm = request.app.state.pm + if pm.is_running("lidar") or pm.is_running("camera"): + raise HTTPException(409, "stop lidar/camera before changing camera_count") + + session.camera_count = cfg.camera_count + # ROSBridge subscriptions are fixed at construction (see ros_bridge.py) — + # changing camera_count means tearing down and recreating the bridge. + old_ros: ROSBridge = request.app.state.ros + old_ros.stop() + new_ros = ROSBridge(camera_count=cfg.camera_count) + new_ros.start() + request.app.state.ros = new_ros + return {"ok": True, "camera_count": session.camera_count} + + +def _camera_launch_cmd(app) -> str: + session = app.state.session + specs = config.camera_specs(session.camera_count) + launch_file = config.camera_launch_file(session.camera_count) + args = [] + for cam in specs: + values = session.camera_values.get(cam.id) or camera_params.load_defaults(cam) + session.camera_values[cam.id] = values + path = camera_params.write_launch_params_yaml(cam, values) + args.append(f" {cam.launch_arg}:={path}") + return ( + f"{config.CAMERA_LAUNCH_SOURCE} && " + f"stdbuf -oL -eL ros2 launch hik_camera_ros2_driver {launch_file}" + "".join(args) + ) + + +async def _auto_start_camera_after_delay(app): + await asyncio.sleep(config.CAMERA_DELAY_SEC) + pm = app.state.pm + if pm.is_running("lidar") and not pm.is_running("camera"): + pm.start("camera", _camera_launch_cmd(app), log_path=config.CAMERA_LOG_PATH) + + +@router.post("/lidar/start") +async def lidar_start(request: Request): + pm = request.app.state.pm + pm.start("lidar", config.LIDAR_LAUNCH_CMD, log_path=config.LIDAR_LOG_PATH) + asyncio.create_task(_auto_start_camera_after_delay(request.app)) + return {"ok": True} + + +@router.post("/lidar/stop") +async def lidar_stop(request: Request): + pm = request.app.state.pm + session = request.app.state.session + pm.stop("lidar") + pm.stop("camera") + # fastlio has no sensor data source once lidar stops — tear it down here + # too, whether it's running because a recording is active or because the + # operator started it standalone via the settings test panel. + pm.stop("fastlio") + if session.recording: + pm.stop("recording") + session.recording = False + session.recording_path = None + session.recording_started_at = None + return {"ok": True} + + +@router.post("/camera/start") +async def camera_start(request: Request): + pm = request.app.state.pm + if not pm.is_running("lidar"): + raise HTTPException(409, "start lidar first") + pm.start("camera", _camera_launch_cmd(request.app), log_path=config.CAMERA_LOG_PATH) + return {"ok": True} + + +@router.post("/camera/stop") +async def camera_stop(request: Request): + request.app.state.pm.stop("camera") + return {"ok": True} + + +@router.post("/fastlio/start") +async def fastlio_start(request: Request): + """Manual FAST-LIO start, independent of recording — lets the operator + preview lidar point-cloud quality (rig leveling, coverage) in Settings + before committing to an actual recording.""" + pm = request.app.state.pm + if not pm.is_running("lidar"): + raise HTTPException(409, "start lidar first") + request.app.state.ros.pointcloud.reset() + pm.start("fastlio", config.FASTLIO_LAUNCH_CMD, log_path=config.FASTLIO_LOG_PATH, env=config.fastlio_env()) + return {"ok": True} + + +@router.post("/fastlio/stop") +async def fastlio_stop(request: Request): + if request.app.state.session.recording: + raise HTTPException(409, "recording is using fastlio — stop the recording instead") + request.app.state.pm.stop("fastlio") + return {"ok": True} + + +@router.post("/system/estop") +async def estop(request: Request): + request.app.state.pm.stop_all() + request.app.state.session.recording = False + return {"ok": True} + + +def build_status(app) -> dict: + from health import TopicThreshold + + pm = app.state.pm + ros: ROSBridge = app.state.ros + session = app.state.session + specs = config.camera_specs(session.camera_count) + + lidar_running = pm.is_running("lidar") + camera_running = pm.is_running("camera") + fastlio_running = pm.is_running("fastlio") + + lidar_thr = config.HEALTH_THRESHOLDS["/livox/lidar"] + cam_thr = config.HEALTH_THRESHOLDS["camera_image"] + map_thr = config.HEALTH_THRESHOLDS["/Odometry"] + + cameras = [] + for cam in specs: + health = ros.health.classify( + cam.image_topic, camera_running, TopicThreshold(*cam_thr) + ) + cameras.append({ + "id": cam.id, "topic": cam.image_topic, "health": health, + "hz": ros.health.rate_hz(cam.image_topic), + }) + + recording_elapsed_s = None + recording_size_bytes = None + disk_check_dir = config.DEFAULT_SAVE_DIR + if session.recording and session.recording_path: + if session.recording_started_at is not None: + recording_elapsed_s = round(time.monotonic() - session.recording_started_at, 1) + recording_size_bytes = bag_metadata.dir_size_bytes(Path(session.recording_path)) + disk_check_dir = str(Path(session.recording_path).parent) + + lidar_health = ros.health.classify("/livox/lidar", lidar_running, TopicThreshold(*lidar_thr)) + + return { + "camera_count": session.camera_count, + "default_save_dir": config.DEFAULT_SAVE_DIR, + "lidar": { + "running": lidar_running, + "health": lidar_health, + # /livox/imu isn't subscribed at all anymore (see ros_bridge.py's + # module docstring — its ~200Hz starved the camera topics badly + # enough that the operator asked to drop it, 2026-08-06). It + # comes off the same livox_ros_driver2 process as the lidar + # topic, so lidar's own health is a reasonable stand-in; there's + # no real Hz number to report, so imu_hz stays 0 (the HUD badge + # just shows the health word with no Hz suffix in that case). + "imu_health": lidar_health, + "hz": ros.health.rate_hz("/livox/lidar"), + "imu_hz": 0.0, + }, + "cameras": cameras, + "map": { + "health": ros.health.classify(config.ODOMETRY_TOPIC, fastlio_running, TopicThreshold(*map_thr)), + "hz": ros.health.rate_hz(config.ODOMETRY_TOPIC), + "points": ros.pointcloud.point_count(), + }, + "recording": { + "active": session.recording, + "path": session.recording_path, + "elapsed_s": recording_elapsed_s, + "size_bytes": recording_size_bytes, + }, + "disk": { + "path": disk_check_dir, + "free_gb": round(config.disk_free_gb(disk_check_dir), 2), + "low_warning_gb": config.DISK_LOW_WARNING_GB, + "low_danger_gb": config.DISK_LOW_DANGER_GB, + }, + "process_status": pm.status(), + } + + +@router.get("/status") +async def status(request: Request): + return build_status(request.app) diff --git a/backend/routers/telemetry.py b/backend/routers/telemetry.py new file mode 100644 index 0000000..bb89951 --- /dev/null +++ b/backend/routers/telemetry.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +import asyncio + +from fastapi import APIRouter, WebSocket, WebSocketDisconnect + +import config +from log_tail import LogTailer +from routers.system import build_status + +router = APIRouter() + +STATUS_PUSH_HZ = 1.0 # slowed from 2.0 — paired with health.py's wider RATE_WINDOW, + # the operator asked for a calmer/more trustworthy number over a + # snappier one (2026-08-06). +LOG_POLL_HZ = 4.0 + + +@router.websocket("/ws/status") +async def ws_status(ws: WebSocket): + await ws.accept() + period = 1.0 / STATUS_PUSH_HZ + try: + while True: + await ws.send_json(build_status(ws.app)) + await asyncio.sleep(period) + except WebSocketDisconnect: + pass + + +@router.websocket("/ws/pointcloud") +async def ws_pointcloud(ws: WebSocket, profile: str = "desktop"): + """M3 live scan view — binary-framed decimated point cloud, see + pointcloud_codec.py for the wire format. `profile` picks desktop vs. + phone point/Hz budgets rather than trusting client-supplied numbers + directly (a client could otherwise ask for an unbounded frame size).""" + await ws.accept() + prof = config.POINTCLOUD_PROFILES.get(profile, config.POINTCLOUD_PROFILES["desktop"]) + period = 1.0 / prof["hz"] + ros = ws.app.state.ros + try: + while True: + await ws.send_bytes(ros.pointcloud.encode_frame(max_points=prof["max_points"])) + await asyncio.sleep(period) + except WebSocketDisconnect: + pass + + +@router.websocket("/ws/logs") +async def ws_logs(ws: WebSocket): + """Unified log tail — mirrors the old PyQt GUIs' single scrollback pane. + Sends recent backlog once on connect, then only newly-appended lines.""" + await ws.accept() + tailer = LogTailer({ + "lidar": config.LIDAR_LOG_PATH, + "camera": config.CAMERA_LOG_PATH, + "recording": config.RECORDING_LOG_PATH, + "fastlio": config.FASTLIO_LOG_PATH, + }) + backlog = [] + for tag in ("lidar", "camera", "recording", "fastlio"): + backlog += [{"tag": tag, "line": ln} for ln in tailer.read_tail(tag, max_lines=80)] + if backlog: + await ws.send_json({"backlog": backlog}) + + period = 1.0 / LOG_POLL_HZ + try: + while True: + new_lines = [{"tag": tag, "line": line} for tag, line in tailer.read_new()] + if new_lines: + await ws.send_json({"lines": new_lines}) + await asyncio.sleep(period) + except WebSocketDisconnect: + pass diff --git a/design.md b/design.md new file mode 100644 index 0000000..3092819 --- /dev/null +++ b/design.md @@ -0,0 +1,102 @@ +# Design — scan_web + +Locked design system for the unified scan/record control panel. Every future +view (the M3 Scanning/LIO view, any later addition) reads this file before +shipping new UI — extend it, don't invent a parallel system. + +## Genre +modern-minimal (Stripe / Linear / instrument-panel school — SaaS/dashboard trigger) + +## Macrostructure family +This is an **app**, not a marketing site — no macrostructure/nav-archetype/ +footer-archetype apparatus applies (those are page-shape concepts for +landing pages). The whole app is one page-type: + +- App pages (Setup/Control view, Scanning/Live view, any future view): + hairline-bordered panel grid, left-biased control column + right-biased + primary content, sticky toolbar with a segmented view switch. No + enrichment — function carries the page (never add hero imagery/CSS art). + +## Theme +Custom — cool-cobalt, referenced against the "Tally" modern-minimal SaaS +example the user pointed at, adapted toward Cobalt's instrument-panel +discipline (hairlines over shadow, mono status readouts) since the app is +an operational dashboard, not a marketing hero. + +- `--color-paper` oklch(97.8% 0.005 255) +- `--color-paper-1` oklch(99.3% 0.003 255) +- `--color-ink` oklch(22.0% 0.020 258) +- `--color-ink-2` oklch(38.0% 0.016 257) +- `--color-rule` oklch(85.0% 0.010 255) +- `--color-accent` oklch(52.0% 0.185 256) +- `--color-focus` oklch(48.0% 0.200 256) +- Functional state colours (not brand accent — status semantics): + `--color-success` (149°), `--color-warning` (68°), `--color-danger` (25°) + +Full token set: [`frontend/css/tokens.css`](frontend/css/tokens.css). + +## Typography +- Display: Geist, weight 650, normal style +- Body: Geist, weight 400 (single-family discipline — the modern-minimal signature) +- Mono/outlier: Geist Mono, weight 500 — carries exactly one role: status + badges, tab labels, meta/hint text, and numeric-readout fields (camera + params). Do not reach for it a third role. +- Korean UI copy falls back per-glyph to "Noto Sans CJK KR" / "Noto Sans KR" + automatically — Geist covers Latin only. +- Fonts are self-hosted at `frontend/vendor/fonts/` (woff2, variable) — + never a Google Fonts CDN link. The field PC/phone may have no internet. + +## Spacing +4-point named scale in `tokens.css` (`--space-3xs` … `--space-2xl`). Always +reference by name. + +## Motion +- Easings: `--ease-out` / `--ease-in` / `--ease-in-out`, no bounce/overshoot. +- Modern-minimal default: reveals are OFF — this is a repeatedly-used tool, + not a first-impression marketing page. The only motion is functional: + button press, badge colour transition, tab crossfade, REC pulse dot. +- `prefers-reduced-motion: reduce` collapses everything to ≤150ms opacity + (see `tokens.css`). + +## Microinteractions stance +- Silent success (no toast for visible state changes). +- Focus rings appear instantly, never animated in. +- No `transition: all` anywhere — properties are named explicitly. + +## Status/health vocabulary — must stay legible in direct sunlight +Every status signal is **icon + text + colour**, never colour alone +(outdoor/colour-blind requirement, not just a Hallmark default): +- `badge-ok` — ✓, success green +- `badge-stale` — !, warning amber +- `badge-down` — ✕, danger red +- `badge-rec` — pulsing dot, danger red +- The ● / state-line idiom (`● 실행 중` / `● 정지`) is carried over from the + original PyQt5 tools on purpose — operators already know it. + +## CTA voice +- Primary actions (시동/녹화 시작): pill radius, solid semantic fill + (`--color-success` / `--color-danger`), 44px min-height (touch target floor). +- Secondary/utility actions: `.btn-gray`, `.btn-blue` — same pill shape, + neutral or accent fill. +- E-STOP: outlined danger pill that fills solid on hover/press — visually + distinct from the recording Stop button, since it's a different severity + of action (kills every subsystem at once). + +## What pages MUST share +- The token set in `tokens.css` — never an inline OKLCH/hex value. +- The badge/status vocabulary above. +- 44px minimum touch target on every interactive control (phone access is + a hard requirement, not a nice-to-have). +- Hairline-bordered panels, whisper shadow at most — no drop-shadow cards. + +## What pages MAY differ on +- Panel layout within a view (the Scanning/LIO view is full-viewport canvas + + status strip, not the Setup view's panel grid — that's fine, it's a + different *content* need, not a different *system*). + +## Related tool: the calibration GUI (`~/dvlc_gui/dvlc_calib_gui.py`) +Native PyQt5, out of scope for a web port (Phase 2, if ever). Its Qt +stylesheet was updated to reuse this same palette (sRGB conversions of the +tokens above) purely for visual consistency across the two tools the +operator switches between — no functional change. See the `STYLESHEET` +constant and `C_*` colour constants near the top of that file. diff --git a/docs/운용가이드.md b/docs/운용가이드.md new file mode 100644 index 0000000..236be29 --- /dev/null +++ b/docs/운용가이드.md @@ -0,0 +1,217 @@ +# scan_web 운용 가이드 (Phase 1 / M1 — 시동·카메라설정·녹화 통합 웹앱) + +> `~/fast_ws` 의 `scan_gui.py` / `scan_gui_dual.py` / `scan_gui_triple.py` (PyQt5, 3개 중복 파일)를 +> 대체하는 **하나의 웹앱**이다. 브라우저(이 PC 또는 같은 네트워크의 폰)로 접속해서 쓴다. +> 기존 스캔 GUI 3종은 아직 그대로 두었다 — `~/Desktop/scan_gui*.desktop` 그대로 사용 가능(백업/롤백용). + +## 0. 지금 버전이 할 수 있는 것 / 못 하는 것 + +**Phase 1 - M1(이번 작업)에서 됨:** +- LiDAR + cam1/cam2/cam3 시동/종료 (기존과 동일한 5초 지연 순서) +- 카메라별 노출/게인 설정 (자동노출/밝기/노출상한하한/노출시간/게인) — 적용(`ros2 param set`) / 저장(YAML, 주석 보존) +- rosbag 녹화 시작/종료 (기존 트리플 GUI와 동일 토픽 목록) +- 브라우저 기반 카메라 미리보기 (MJPEG) +- 전체 정지(E-STOP) 버튼 +- 라이다/IMU/카메라 상태 배지 — OK/STALE/DOWN + **실측 Hz** (토픽 수신 여부/속도로 추정, 별도 진단 토픽 없음) +- **디스크 여유 공간 표시** (부족하면 경고/위험 색으로 바뀜 — §3 참고) +- **녹화 중 경과 시간 + 실시간 파일 크기** +- **최근 녹화 목록에 실제 길이/용량/메시지 수/토픽 수 표시** (`ros2 bag info`로 확인할 필요 없이 대시보드에서 바로 확인) +- **통합 로그 패널** — 기존 PyQt GUI의 로그창과 동일하게 lidar/camera/recording 프로세스 출력을 `[태그]`로 구분해 실시간으로 봄 +- **디자인** — hallmark 스킬로 리디자인 완료 (M4, 순서를 당겨서 먼저 진행) + +**아직 안 됨 (다음 단계):** +- GPS/RTK 연동 — **Phase 1 Milestone 2**. 지금 버전은 GPS 시작/정지/상태 표시가 아예 없다. +- 스캐닝 중 LIO(포인트클라우드+궤적) 라이브 뷰 — **Milestone 3**. + +**⚠️ 아직 실제 하드웨어로 테스트 안 됨**: 서버 기동/API/프로세스 종료 로직은 소프트웨어 레벨로 확인했지만, +실제로 브라우저에서 "시동 시작"을 눌러 LiDAR/카메라를 구동해본 적은 없다. 아래 §2 순서대로 처음 실행할 때 +평소 `scan_gui_triple.py` 켤 때처럼 하드웨어 연결 상태를 확인하고 진행할 것. + +--- + +## 1. 실행 방법 + +### 방법 A — 바탕화면 아이콘 +바탕화면 **"Scan Web (통합)"** 아이콘 실행 → 서버가 안 떠 있으면 자동으로 띄운 뒤, 준비되면 기본 브라우저로 +`http://localhost:8000` 이 열린다. + +### 방법 B — 수동 실행 (터미널에서 로그 보면서 디버깅할 때) +```bash +/home/gardentech/scan_web/run_scan_web.sh +``` +포그라운드로 뜨며 uvicorn 로그가 그대로 보인다. `Ctrl+C` 로 종료(종료 시 실행 중이던 lidar/camera/recording +프로세스도 함께 정리됨 — app.py의 lifespan shutdown 훅). + +### 폰에서 접속하려면 +같은 Wi-Fi에 연결된 폰 브라우저에서 `http://<이 PC의 LAN IP>:8000` 으로 접속. +```bash +hostname -I # 이 PC의 LAN IP 확인 +``` + +--- + +## 2. 사용법 (웹 UI) + +브라우저를 열면 좌측에 제어 패널, 우측에 카메라 미리보기가 보인다 (기존 PyQt5 GUI와 레이아웃 유사). + +1. **시동** — "시동 시작" 클릭 → LiDAR 즉시 실행, 5초 후 cam1/cam2/cam3 자동 실행. 상단 상태 배지에서 + `LiDAR OK 9.8Hz` / `IMU OK` / `cam1 OK 14.9Hz` 등으로 바뀌는지 확인. 우측 미리보기에 실시간 영상이 뜨는지 확인. +2. **카메라 설정** — 탭에서 카메라 선택 → 자동노출/밝기/노출상한하한/노출시간/게인 조절 → + - **적용**: 지금 켜진 카메라 노드에 바로 반영(`ros2 param set`), 재시동 전까지만 유효 + - **저장**: 카메라 config YAML 파일에 영구 저장 (기존 주석은 그대로 보존됨) +3. **녹화** — 저장 경로(기본 `~/bags`)와 파일 이름 입력 → "…" 버튼으로 폴더 찾아보기 가능 → + "녹화 시작" → 끝나면 "녹화 정지". 저장 위치는 `~/bags/<파일이름>/<파일이름>_0.db3` (기존과 동일). +4. **전체 정지(E-STOP)** — 라이다/카메라/녹화 프로세스를 한번에 즉시 정지. 확인 팝업 있음. + +--- + +## 3. 대시보드에 표시되는 추가 정보 + +이번에 녹화 화면에 추가된 것들 — 전부 터미널을 열지 않고 브라우저에서 바로 확인 가능: + +- **디스크 여유 공간** (녹화 패널, 저장 경로 입력 밑) — `여유 공간: 780.4 GB (/home/gardentech/bags)` + 식으로 표시. **10GB 미만이면 주황색(경고), 2GB 미만이면 빨간색(위험)** 으로 바뀐다 — 장시간 스캔 중 + 디스크가 꽉 차서 bag이 깨지는 상황을 미리 막기 위함. 저장 경로를 바꾸면 그 경로 기준으로 다시 계산됨. +- **녹화 경과 시간 + 실시간 용량** — 녹화 중일 때 "● 녹화 중" 옆에 `03:42 · 1.2 GB` 식으로 표시. +- **최근 녹화 목록** — 이름뿐 아니라 **길이 / 용량 / 총 메시지 수 / 토픽 개수** 까지 바로 보임. 방금 + 중지한 bag이 아직 `metadata.yaml`을 못 쓴 상태면(정지 직후 잠깐) `(녹화 중 / 미완료)`로 표시됐다가 + 1~2초 뒤 자동으로 정상 표시로 바뀐다. +- **상태 배지의 실측 Hz** — 예: `LiDAR OK 9.8Hz`. STALE/DOWN일 땐 Hz를 안 붙임(의미 없는 숫자라서). + 기준 임계값은 `backend/config.py`의 `HEALTH_THRESHOLDS`에서 조정 가능. +- **로그 패널** (카메라 미리보기 아래, 화면 전체 너비) — lidar/camera/recording 세 프로세스의 출력을 + `[lidar]`/`[camera]`/`[recording]` 태그로 구분해서 실시간으로 보여줌. 자동 스크롤 체크박스와 + 지우기 버튼 있음. 문제 생겼을 때(카메라 안 붙음, launch 실패 등) 원인 파악용 — 기존 PyQt GUI의 + 로그창과 동일한 역할. + +--- + +## 4. 실기(하드웨어) 테스트 체크리스트 + +소프트웨어 레벨 검증은 끝났고, 브라우저 화면도 확인됨. 다음은 **실제 LiDAR/카메라를 켜서** 확인해야 할 +것들 — 아래 순서대로 체크하면서 진행할 것을 권장. + +### 4.1 시동 전 +- [ ] LiDAR(MID360s), cam1/cam2/cam3 물리적으로 연결/전원 확인 (기존 `scan_gui_triple.py` 켤 때와 동일) +- [ ] 다른 곳에서 같은 토픽(`/livox/lidar`, `/cam1/image` 등)을 쓰는 프로세스가 남아있지 않은지 확인 + (`ps aux | grep -E "ros2 launch|hik_camera|livox"`) + +### 4.2 시동 +- [ ] "시동 시작" 클릭 → LiDAR 즉시 실행, **5초 후** 카메라 3대 자동 실행되는지(카운트다운 없이 그냥 5초 + 뒤 배지가 바뀌는지 확인 — 화면에 카운트다운 표시는 아직 없음, PyQt 버전엔 있었음) +- [ ] 상단 배지: `LiDAR OK ` / `IMU OK ` / `cam1 OK ` / `cam2 OK ` / `cam3 OK ` 로 전환 +- [ ] Hz 숫자가 말이 되는 값인지 (라이다는 보통 ~10Hz 대, 카메라는 설정된 fps 근처) — 기존 + `ros2 topic hz`로 알던 값과 비교 +- [ ] 우측 카메라 미리보기 3분할 화면에 실제 영상이 뜨는지, 세 대 다 정상인지 (cam3는 180도 회전 장착이라 + 화면도 거꾸로 나오는 게 정상 — SuperGlue 단계에서만 rotate_camera로 보정됨, 미리보기 자체는 원본) +- [ ] 로그 패널에서 `[lidar]`/`[camera]` 태그로 launch 로그가 흘러나오는지, 에러 없는지 + +### 4.3 카메라 설정 +- [ ] 카메라 탭에서 노출/게인 "적용" 눌렀을 때 실제 영상 밝기가 바뀌는지 (반영까지 약간의 지연 있을 수 있음) +- [ ] "저장" 눌렀을 때 해당 카메라 config YAML이 실제로 갱신되는지, 기존 주석이 안 지워졌는지 확인 + (`git diff` 있으면 diff로, 없으면 파일 직접 열어서 확인) + +### 4.4 녹화 +- [ ] 저장 경로/파일 이름 입력 후 "녹화 시작" → "● 녹화 중" + 경과시간/용량이 올라가는지 +- [ ] 녹화 중 디스크 여유 공간 숫자가 (아주 조금씩이라도) 줄어드는지 +- [ ] "녹화 정지" 후 "최근 녹화" 목록에 새 항목이 뜨는지, 잠깐의 "(미완료)" 표시 후 정상 정보로 바뀌는지 +- [ ] `ros2 bag info`로 직접 확인해서 대시보드에 보이는 길이/용량/메시지 수와 일치하는지 (§5 참고) + +### 4.5 종료 / 전체 정지 +- [ ] "시동 종료" 눌렀을 때 배지가 전부 DOWN으로, 미리보기가 다시 회색으로 돌아오는지 +- [ ] `ps aux | grep -E "ros2 launch|hik_camera|livox"` 로 프로세스가 하나도 안 남았는지 (§6) +- [ ] 녹화 중에 E-STOP을 눌러보고, bag이 깨지지 않고 (거의) 정상 종료되는지 — SIGINT로 정지하므로 + rosbag2가 db3를 flush할 시간을 주는지 확인하는 목적 + +### 4.6 문제 생기면 +로그 패널(§3) 또는 `~/scan_web_logs/*.log` 확인 → §8 트러블슈팅 표 참고 → 그래도 안 풀리면 필요한 로그 +내용 들고 알려주면 같이 봄. + +--- + +## 5. 녹화 검증 (기존 가이드와 동일한 방법) + +```bash +source /opt/ros/humble/setup.bash +ros2 bag info ~/bags/<파일이름>/ +``` +확인할 토픽: `/livox/lidar`, `/livox/imu`, `/cam1/image`, `/cam1/camera_info`, +`/cam2/image`, `/cam2/camera_info`, `/cam3/image`, `/cam3/camera_info` +(GPS는 M1에 없으므로 `/ublox_driver/receiver_pvt` 는 아직 안 담김 — M2에서 추가 예정). +**참고**: 이제 대시보드 "최근 녹화" 목록에서 같은 정보(길이/용량/토픽별 메시지 수)를 바로 볼 수 있어서, +터미널로 확인하는 건 더 자세히 파고들 때만 필요하다. + +실시간으로 토픽 수신 속도 확인하려면 시동 켠 상태에서: +```bash +ros2 topic hz /livox/lidar +ros2 topic hz /cam1/image +``` +(또는 상단 상태 배지의 실측 Hz 숫자를 바로 봐도 됨.) + +--- + +## 6. 종료 후 프로세스 정리 확인 + +시동 종료(또는 E-STOP) 후 `ros2 launch` 하위 프로세스가 안 남았는지 확인: +```bash +ps aux | grep -E "ros2 launch|fastlivo|hik_camera" | grep -v grep +``` +아무것도 안 나오면 정상 종료된 것. (내부적으로 기존 `_kill_proc` 로직과 동일하게 프로세스 그룹 전체에 +SIGINT → 3초 대기 → 안 죽으면 SIGKILL 하는 방식으로 정리한다.) + +--- + +## 7. 로그 위치 + +| 항목 | 경로 | +|---|---| +| LiDAR 로그 | `~/scan_web_logs/lidar.log` | +| 카메라 로그 | `~/scan_web_logs/camera.log` | +| 녹화(rosbag) 로그 | `~/scan_web_logs/recording.log` | +| 서버(uvicorn) 로그 — 바탕화면 아이콘으로 실행했을 때만 | `~/scan_web_logs/server.log` | + +위 세 개(lidar/camera/recording)는 이제 브라우저의 **로그 패널**(§3)에서 실시간으로도 볼 수 있다 — +터미널을 열 필요 없이 화면 하나에서 확인 가능. + +--- + +## 8. 트러블슈팅 + +| 증상 | 원인 | 조치 | +|---|---|---| +| 브라우저에서 "초기화 실패" 알림 | 백엔드 서버가 안 떠 있거나 방금 죽음 | 터미널에서 방법 B로 직접 실행해 로그 확인 | +| 시동 눌러도 미리보기 화면이 계속 회색 | 카메라 노드가 5초 지연 후 아직 안 붙었거나, 실제 카메라 미연결 | 브라우저 로그 패널(§3)에서 `[camera]` 태그 확인, `ros2 topic list` 로 `/cam1/image` 존재 확인 | +| 상태 배지가 계속 DOWN | 해당 토픽에 메시지가 안 들어옴(프로세스 자체는 떠 있어도) | 하드웨어 연결/드라이버 로그 확인 — 배지는 진단 토픽이 아니라 수신 여부/속도 추정치임 | +| 녹화 시작이 막힘(버튼 비활성) | 시동(LiDAR+카메라)이 먼저 켜져 있어야 함 | "시동 시작" 먼저 실행 | +| 디스크 여유 공간이 주황/빨강 | 저장 경로 볼륨에 공간이 얼마 안 남음 | 오래된 bag 정리하거나 저장 경로를 다른 볼륨으로 변경 | +| 최근 녹화 항목이 계속 "(녹화 중 / 미완료)"로 남음 | 녹화가 비정상 종료돼 `metadata.yaml`이 안 써짐(강제 kill 등) | `ros2 bag info` 로 직접 확인, 필요시 해당 bag 폴더 정리 | +| 포트 8000 이미 사용 중 | 이전 세션의 uvicorn 이 안 죽고 남아있음 | `pkill -f "uvicorn app:app"` 후 재실행 | + +--- + +## 9. 구버전(scan_gui\*, fast_ws)과의 관계 + +- `~/fast_ws/scan_gui.py` / `scan_gui_dual.py` / `scan_gui_triple.py` 및 해당 `.desktop` 파일은 + **삭제하지 않고 그대로 둠** — scan_web 이 실제 필드에서 최소 한 번 이상 문제없이 검증되기 전까지는 + 기존 GUI로 언제든 되돌아갈 수 있다. +- GPS/RTK(M2), LIO 라이브 뷰(M3)가 끝나고 필드 검증까지 마치면, 기존 `.desktop` 파일들을 + `~/Desktop/legacy/` 로 옮기는 것을 고려 (삭제는 그 이후 판단). +- **`fast_ws` 자체는 2026-08-05부로 폐기 방침** (`fast_dual_ws`로 3카메라 확장되기 이전의 구버전 + 저장소) — Jetson 이관 대상에서도 제외됨 (`~/fast_dual_ws/docs/Jetson-이관가이드.md` 참고). + 이에 맞춰 scan_web의 라이다 실행 경로도 `fast_ws`를 거치지 않고 **`lidar2_ws`를 직접 source** + 하도록 이미 고쳤다 (`backend/config.py`의 `LIDAR2_WS_SETUP`, `run_scan_web.sh`) — 알고 보니 + `fast_ws`는 자체 라이다 드라이버가 없이 `lidar2_ws`를 언더레이로 체이닝만 하던 것이라, 직접 + source해도 동작은 완전히 동일하다 (2026-08-05, 실제로 재기동해서 확인함). + +--- + +## 10. 코드 구조 (참고용) + +``` +~/scan_web/ + backend/ FastAPI 서버 (프로세스 관리, ROS2 브리지, 카메라 파라미터, REST/WebSocket API) + frontend/ 브라우저 UI (순수 HTML/CSS/JS, 빌드 과정 없음) + design.md hallmark 리디자인 시스템 (색/폰트/간격/모션 토큰 규칙) + run_scan_web.sh 실행 스크립트 (ROS 오버레이 source + uvicorn 실행) + open_scan_web.sh 바탕화면 아이콘용 — 서버 기동 대기 후 브라우저 자동 오픈 +``` +전체 설계 배경/마일스톤 계획은 `~/.claude/plans/federated-marinating-sunbeam.md` 참고. diff --git a/frontend/css/fonts.css b/frontend/css/fonts.css new file mode 100644 index 0000000..bd6e670 --- /dev/null +++ b/frontend/css/fonts.css @@ -0,0 +1,25 @@ +/* Self-hosted (vendored, not CDN) — the field PC/phone may have no internet. + Both files are variable fonts; one @font-face per family covers every + weight the design uses via the weight axis. */ + +@font-face { + font-family: "Geist"; + font-style: normal; + font-weight: 100 900; + font-display: swap; + src: url("/vendor/fonts/Geist-Variable.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, + U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} + +@font-face { + font-family: "Geist Mono"; + font-style: normal; + font-weight: 100 900; + font-display: swap; + src: url("/vendor/fonts/GeistMono-Variable.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, + U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} diff --git a/frontend/css/style.css b/frontend/css/style.css new file mode 100644 index 0000000..78fe33f --- /dev/null +++ b/frontend/css/style.css @@ -0,0 +1,699 @@ +/* Hallmark · pre-emit critique: P5 H4 E4 S4 R5 V4 + * genre: modern-minimal · theme: custom (Tally-referenced) · redesign + * scope: app (single-screen phone HUD over a live scan view) — no marketing + * macrostructure/nav/footer archetypes apply + * anchor hue: cool-cobalt 256 · outdoor-legibility constraint: status never colour-only + */ + +html, body { overflow: hidden; height: 100%; margin: 0; } + +* { box-sizing: border-box; } + +body { + background: var(--color-paper); + color: var(--color-ink-2); + font-family: var(--font-body); + font-weight: 400; + font-size: var(--text-base); + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +h1, h2, h3 { + font-family: var(--font-display); + font-weight: 650; + font-style: normal; + letter-spacing: -0.015em; + color: var(--color-ink); + margin: 0; +} + +.hidden { display: none !important; } + +a { color: var(--color-accent); } + +/* ── Focus — instant, never animated, always visible ────────────────────── */ +:focus { outline: none; } +:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} + +/* ── App shell — one fixed viewport, no page scroll ever. The scan view is + the permanent base layer; the HUD floats fixed on top of it; Settings/ + Recording/Log are overlays drawn above both, never a navigation away. ── */ + +#app { + position: fixed; + inset: 0; + overflow: hidden; +} + +.scan-view { + position: absolute; + inset: 0; + overflow: hidden; + background: var(--color-ink); +} + +#pointcloud-canvas { + position: absolute; + inset: 0; + display: block; + width: 100%; + height: 100%; + touch-action: none; /* OrbitControls owns touch gestures here, not the browser (no pull-to-refresh/pinch-zoom-page fighting it) */ +} + +.scan-placeholder { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + color: var(--color-ink-4); + font-family: var(--font-mono); + font-size: var(--text-sm); + text-align: center; + padding: var(--space-2xl); +} + +/* ── HUD — fixed, pointer-events pass through the gaps so the scan view + underneath stays interactive (pan/zoom, once M3 adds that). Top/bottom + scrims guarantee legible light text regardless of what's rendered in the + scan view beneath (placeholder today, a point cloud later). ── */ + +.hud { + position: absolute; + inset: 0; + z-index: var(--z-sticky); + display: flex; + flex-direction: column; + justify-content: space-between; + pointer-events: none; +} + +.hud-top { + pointer-events: auto; + display: flex; + align-items: flex-start; + justify-content: space-between; + flex-wrap: wrap; + gap: var(--space-2xs); + padding: max(var(--space-xs), env(safe-area-inset-top)) max(var(--space-sm), env(safe-area-inset-right)) + var(--space-lg) max(var(--space-sm), env(safe-area-inset-left)); + background: linear-gradient(to bottom, oklch(20% 0.02 258 / 0.65), transparent); +} + +.hud-bottom { + pointer-events: auto; + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: var(--space-sm); + padding: var(--space-lg) max(var(--space-sm), env(safe-area-inset-right)) + max(var(--space-xs), env(safe-area-inset-bottom)) max(var(--space-sm), env(safe-area-inset-left)); + background: linear-gradient(to top, oklch(20% 0.02 258 / 0.65), transparent); +} + +.hud-startup, .hud-rec, .hud-actions, .hud-top-right { + display: flex; + align-items: center; + gap: var(--space-xs); +} + +.status-badges { + display: flex; + gap: var(--space-2xs); + flex-wrap: wrap; +} + +/* ── Status badges — icon + text, never colour alone (outdoor/colour-blind safe) ── */ +.badge { + display: inline-flex; + align-items: center; + gap: 0.3em; + padding: 0.3em 0.7em; + border-radius: var(--radius-pill); + font-family: var(--font-mono); + font-size: var(--text-xs); + font-weight: 600; + letter-spacing: 0.02em; + line-height: 1.4; + white-space: nowrap; +} +/* Sensor badges (LiDAR/IMU/camN) get a hard fixed width, not shrink-to-fit + — icon glyphs (✓/!/✕) can render at slightly different widths than + monospace text depending on font fallback, so relying on content width + alone still let the box size drift. A fixed width removes that variable + entirely: label sits left, Hz sits right (space-between), and the box + itself never resizes as health/Hz change. Sized generously for the + longest realistic content ("✓ LiDAR STALE 199.9Hz"); flex-shrink:0 stops + the row from squeezing it under width pressure. */ +.badge-sensor { + width: 15em; + box-sizing: border-box; + flex-shrink: 0; + justify-content: space-between; +} +.badge-ok { background: var(--color-success); color: var(--color-success-ink); } +.badge-stale { background: var(--color-warning); color: var(--color-warning-ink); } +.badge-down { background: var(--color-danger); color: var(--color-danger-ink); } +.badge-rec { background: var(--color-danger); color: var(--color-danger-ink); } + +.badge-ok::before { content: "✓"; } +.badge-stale::before { content: "!"; } +.badge-down::before { content: "✕"; } +.badge-rec::before { + content: ""; + width: 7px; height: 7px; + border-radius: 50%; + background: currentColor; + animation: badge-rec-pulse 1.4s var(--ease-in-out) infinite; +} +@keyframes badge-rec-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.35; } +} +@media (prefers-reduced-motion: reduce) { + .badge-rec::before { animation: none; } +} + +/* Badge Hz suffix — a measured number, not just a traffic-light state. + status-panel.js NBSP-pads the health word and Hz digits to a fixed + character count so badge width never changes between updates (that's + the real fix); min-width here is just a safety margin against font + metric rounding. */ +.badge .badge-hz { + display: inline-block; + min-width: 7ch; + text-align: right; + opacity: 0.85; + font-weight: 500; +} + +.hint { + color: var(--color-ink-3); + font-size: var(--text-xs); + margin: var(--space-2xs) 0; + word-break: break-all; + font-family: var(--font-mono); +} + +/* ── Buttons — pill, semantic fills, full state coverage ───────────────── + default · hover · focus-visible · active · disabled all styled below. */ + +.btn-row { display: flex; gap: var(--space-xs); margin: var(--space-sm) 0; } + +.btn { + flex: 1; + min-height: 44px; /* touch target floor */ + padding: 0 var(--space-md); + border: 1px solid transparent; + border-radius: var(--radius-pill); + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-sm); + white-space: nowrap; + cursor: pointer; + transition: background-color var(--dur-mid) var(--ease-out), + border-color var(--dur-mid) var(--ease-out), + transform var(--dur-fast) var(--ease-out); +} +.btn:active { transform: translateY(1px); } +.btn:disabled { + background: var(--color-paper-2) !important; + color: var(--color-ink-4) !important; + border-color: var(--color-rule-soft) !important; + cursor: not-allowed; + transform: none; +} + +.btn-green { + background: var(--color-success); color: var(--color-success-ink); +} +.btn-green:hover:not(:disabled) { background: oklch(from var(--color-success) calc(l - 0.05) c h); } + +.btn-red { + background: var(--color-danger); color: var(--color-danger-ink); +} +.btn-red:hover:not(:disabled) { background: oklch(from var(--color-danger) calc(l - 0.05) c h); } + +.btn-blue { + background: var(--color-accent); color: var(--color-accent-ink); +} +.btn-blue:hover:not(:disabled) { background: oklch(from var(--color-accent) calc(l - 0.05) c h); } + +.btn-gray { + background: var(--color-paper-2); color: var(--color-ink-2); + border-color: var(--color-rule); +} +.btn-gray:hover:not(:disabled) { background: var(--color-paper-3); } + +.btn-small { + flex: 0 0 auto; + width: 44px; + min-height: 44px; + border-radius: var(--radius-sm); + background: var(--color-paper-2); + color: var(--color-ink-2); + border-color: var(--color-rule); +} +.btn-small:hover { background: var(--color-paper-3); } + +/* HUD buttons sit inline in a floating strip, not a stacked full-width + .btn-row, so they must not stretch (flex:1) — size to content instead. */ +.btn-hud { + flex: none; + min-width: 64px; + box-shadow: var(--shadow-whisper); +} +.btn-icon { + min-width: 44px; + width: 44px; + padding: 0; + font-size: var(--text-md); +} + +.btn-estop { + min-height: 52px; + background: var(--color-paper-1); + color: var(--color-danger); + border: 1.5px solid var(--color-danger); + border-radius: var(--radius-md); + font-family: var(--font-mono); + font-size: var(--text-sm); + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + cursor: pointer; + transition: background-color var(--dur-mid) var(--ease-out), color var(--dur-mid) var(--ease-out); +} +.btn-estop:hover { + background: var(--color-danger); + color: var(--color-danger-ink); +} +.btn-estop:active { transform: translateY(1px); } +.btn-estop-hud { + min-height: 44px; + padding: 0 var(--space-md); + font-size: var(--text-xs); + background: oklch(20% 0.02 258 / 0.55); + box-shadow: var(--shadow-whisper); +} + +/* ── State lines (● + label — carried over from the field-proven PyQt5 idiom) ── + Default colours assume a light panel; the HUD versions below override for + legibility over the dark top/bottom scrims. */ + +.state-line { + font-family: var(--font-mono); + font-size: var(--text-sm); + font-weight: 600; + margin-top: var(--space-2xs); +} +.state-line.on { color: var(--color-success); } +.state-line.off { color: var(--color-ink-4); } +.state-line.rec { color: var(--color-danger); } + +.hud .state-line { + font-size: var(--text-xs); + text-shadow: 0 1px 3px oklch(0% 0 0 / 0.5); +} +.hud .state-line.off { color: var(--color-paper-3); } + +/* ── Disk free space — safety info, not decoration ───────────────────── */ + +.disk-free { + display: flex; + align-items: center; + gap: 0.4em; + font-family: var(--font-mono); + font-size: var(--text-xs); + color: var(--color-ink-3); +} +.disk-free.warning { color: var(--color-warning); font-weight: 600; } +.disk-free.danger { color: var(--color-danger); font-weight: 600; } + +/* Small always-on chip in the HUD top strip — same idea as the sensor + badges, just neutral (free space isn't a health traffic light). */ +.hud-disk-free { + padding: 0.3em 0.7em; + border-radius: var(--radius-pill); + background: oklch(20% 0.02 258 / 0.55); + color: var(--color-paper-1); + font-weight: 600; +} +.hud-disk-free.warning { color: oklch(78% 0.16 68); } +.hud-disk-free.danger { color: oklch(78% 0.18 25); } + +/* ── Live recording meter (elapsed + size) ───────────────────────────── */ + +.rec-meter { + font-family: var(--font-mono); + font-size: var(--text-xs); + color: var(--color-ink-3); + font-variant-numeric: tabular-nums; +} +.hud .rec-meter { + color: var(--color-paper-1); + text-shadow: 0 1px 3px oklch(0% 0 0 / 0.5); +} + +/* ── Bag list — richer entries (duration / size / topic count) ──────── */ + +.bag-item { padding: var(--space-xs) 0; border-bottom: 1px solid var(--color-rule-soft); } +.bag-item:last-child { border-bottom: none; } +.bag-item-name { + color: var(--color-ink-2); + font-weight: 600; + display: flex; + align-items: center; + gap: 0.4em; +} +.bag-item-meta { + color: var(--color-ink-4); + font-size: var(--text-xs); + margin-top: 2px; +} +.bag-item-incomplete { + color: var(--color-warning); + font-weight: 600; +} + +/* ── Overlays — Settings / Recording / Log all float as a centred sheet + above the scan view + HUD, never a page navigation. Later overlays in + DOM order (log, opened from inside Settings) stack visually above + earlier ones at the same z-index. ── */ + +.overlay { + position: fixed; + inset: 0; + z-index: var(--z-modal); + display: flex; + align-items: center; + justify-content: center; + padding: var(--space-md); +} +.overlay-backdrop { + position: absolute; + inset: 0; + background: oklch(15% 0.02 258 / 0.55); +} +.overlay-sheet { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + max-width: 480px; + max-height: min(85dvh, 720px); + background: var(--color-paper-1); + border: 1px solid var(--color-rule); + border-radius: var(--radius-lg); + box-shadow: 0 8px 32px oklch(15% 0.02 258 / 0.28); + overflow: hidden; +} +.overlay-sheet-small { max-width: 380px; } + +.overlay-header { + flex: none; + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-md); + padding: var(--space-md) var(--space-lg); + border-bottom: 1px solid var(--color-rule-soft); +} +.overlay-header h2 { font-size: var(--text-md); } + +.btn-close { + flex: none; + width: 36px; + height: 36px; + border: none; + border-radius: var(--radius-sm); + background: var(--color-paper-2); + color: var(--color-ink-2); + font-size: var(--text-sm); + cursor: pointer; +} +.btn-close:hover { background: var(--color-paper-3); } + +.overlay-tabs { + flex: none; + display: flex; + gap: var(--space-lg); + padding: 0 var(--space-lg); + border-bottom: 1px solid var(--color-rule); + overflow-x: auto; +} +.overlay-body { + flex: 1; + overflow-y: auto; + padding: var(--space-lg); +} + +/* Outer overlay-level tabs (카메라 설정 / 미리보기 / 최근 녹화 / 로그). + Deliberately a different class from .tab-btn/.tab-panel below — those are + reset via a global querySelectorAll by camera-settings.js for the + per-camera tabs, which would otherwise clobber these too. */ +.pane-tab-btn { + padding: var(--space-xs) 0; + min-height: 40px; + border: none; + border-bottom: 2px solid transparent; + background: transparent; + color: var(--color-ink-3); + font-family: var(--font-mono); + font-size: var(--text-sm); + font-weight: 500; + cursor: pointer; + white-space: nowrap; + transition: color var(--dur-mid) var(--ease-out), border-color var(--dur-mid) var(--ease-out); +} +.pane-tab-btn:hover { color: var(--color-ink); } +.pane-tab-btn.active { color: var(--color-ink); border-bottom-color: var(--color-accent); font-weight: 600; } +.pane-tab-btn-log { margin-left: auto; color: var(--color-accent); } + +.pane-tab { display: none; } +.pane-tab.active { display: block; } + +/* ── Log overlay — the unified [tag] scrollback the PyQt tools had ──────── */ + +.log-header-actions { display: flex; align-items: center; gap: var(--space-md); } +.log-autoscroll { + display: flex; align-items: center; gap: var(--space-2xs); + font-family: var(--font-mono); + font-size: var(--text-xs); + color: var(--color-ink-3); + cursor: pointer; +} +.log-autoscroll input { accent-color: var(--color-accent); cursor: pointer; } + +.log-body { + flex: 1; + min-height: 0; + background: var(--color-ink); + padding: var(--space-sm) var(--space-md); + overflow-y: auto; + font-family: var(--font-mono); + font-size: var(--text-xs); + line-height: 1.6; + white-space: pre-wrap; + word-break: break-all; +} +.log-line { display: flex; gap: 0.6em; } +.log-line .log-tag { + flex: 0 0 auto; + color: var(--color-ink-4); + text-transform: uppercase; + font-weight: 600; +} +.log-line.tag-lidar .log-tag { color: oklch(70% 0.14 256); } +.log-line.tag-camera .log-tag { color: oklch(72% 0.14 149); } +.log-line.tag-recording .log-tag { color: oklch(72% 0.16 25); } +.log-line.tag-fastlio .log-tag { color: oklch(72% 0.15 320); } +.log-line .log-text { color: var(--color-paper-1); } + +/* ── Form fields ──────────────────────────────────────────────────────── */ + +.field-row { display: flex; align-items: center; gap: var(--space-xs); margin: var(--space-sm) 0; } +.field-row label { + width: 72px; flex: 0 0 72px; + font-family: var(--font-mono); + font-size: var(--text-xs); + color: var(--color-ink-3); +} +.field-row input[type=text] { + flex: 1; + min-height: 40px; + padding: 0 var(--space-sm); + background: var(--color-paper-2); + border: 1px solid var(--color-rule); + border-radius: var(--radius-sm); + color: var(--color-ink); + font-family: var(--font-body); + font-size: var(--text-sm); + transition: border-color var(--dur-mid) var(--ease-out), background-color var(--dur-mid) var(--ease-out); +} +.field-row input[type=text]:hover { border-color: var(--color-ink-4); } +.field-row input[type=text]:focus-visible { + background: var(--color-paper-1); + border-color: var(--color-accent); + outline: 2px solid var(--color-focus); + outline-offset: 1px; +} +.field-row input[type=text]::placeholder { color: var(--color-ink-4); } + +.browse-panel { + border: 1px solid var(--color-rule); + border-radius: var(--radius-sm); + background: var(--color-paper-2); + max-height: 180px; + overflow-y: auto; + padding: var(--space-2xs); + margin-bottom: var(--space-sm); + font-family: var(--font-mono); + font-size: var(--text-xs); +} +.browse-panel .dir-entry { + padding: var(--space-xs); + border-radius: var(--radius-sm); + cursor: pointer; + transition: background-color var(--dur-fast) var(--ease-out); +} +.browse-panel .dir-entry:hover { background: var(--color-paper-3); } + +.bag-list { + list-style: none; + padding: 0; + margin: 0; + font-family: var(--font-mono); + font-size: var(--text-xs); + color: var(--color-ink-2); +} +.bag-list li { + padding: var(--space-xs) 0; + border-bottom: 1px solid var(--color-rule-soft); +} +.bag-list li:last-child { border-bottom: none; } + +/* ── Tabs — underline indicator, crossfade content (never slide). + Per-camera tabs inside the "카메라 설정" pane. ── */ + +.tabs { display: flex; gap: var(--space-lg); border-bottom: 1px solid var(--color-rule); margin-bottom: var(--space-md); } +.tab-btn { + padding: var(--space-xs) 0; + min-height: 40px; + border: none; + border-bottom: 2px solid transparent; + background: transparent; + color: var(--color-ink-3); + font-family: var(--font-mono); + font-size: var(--text-sm); + font-weight: 500; + cursor: pointer; + transition: color var(--dur-mid) var(--ease-out), border-color var(--dur-mid) var(--ease-out); +} +.tab-btn:hover { color: var(--color-ink); } +.tab-btn.active { color: var(--color-ink); border-bottom-color: var(--color-accent); font-weight: 600; } + +.tab-panel { display: none; animation: tab-crossfade var(--dur-mid) var(--ease-out); } +.tab-panel.active { display: block; } +@keyframes tab-crossfade { from { opacity: 0; } to { opacity: 1; } } +@media (prefers-reduced-motion: reduce) { + .tab-panel { animation: none; } +} + +.tab-panel > label:first-child { + display: flex; + align-items: center; + gap: var(--space-xs); + font-size: var(--text-sm); + color: var(--color-ink-2); + cursor: pointer; +} +.tab-panel input[type=checkbox] { + width: 18px; height: 18px; + accent-color: var(--color-accent); + cursor: pointer; +} + +.cam-field-grid { + display: grid; + grid-template-columns: 140px minmax(0, 1fr); + gap: var(--space-xs) var(--space-sm); + align-items: center; + margin: var(--space-sm) 0; +} +.cam-field-grid label { + font-family: var(--font-mono); + font-size: var(--text-xs); + color: var(--color-ink-3); +} +.cam-field-grid input[type=number] { + min-height: 36px; + padding: 0 var(--space-xs); + border: 1px solid var(--color-rule); + border-radius: var(--radius-sm); + background: var(--color-paper-2); + color: var(--color-ink); + font-family: var(--font-mono); + font-variant-numeric: tabular-nums; + font-size: var(--text-sm); + width: 100%; + transition: border-color var(--dur-mid) var(--ease-out); +} +.cam-field-grid input[type=number]:hover { border-color: var(--color-ink-4); } +.cam-field-grid input[type=number]:focus-visible { + border-color: var(--color-accent); + outline: 2px solid var(--color-focus); + outline-offset: 1px; +} +.cam-field-grid input:disabled { + color: var(--color-ink-4); + background: var(--color-paper); + border-color: var(--color-rule-soft); +} + +.cam-result { + font-family: var(--font-mono); + font-size: var(--text-xs); + color: var(--color-ink-3); + min-height: 1.4em; + margin-top: var(--space-2xs); +} + +/* ── Camera preview grid ─────────────────────────────────────────────── */ + +.camera-grid { display: flex; gap: var(--space-md); flex-wrap: wrap; } +.camera-pane { flex: 1; min-width: 260px; } +.camera-pane .cam-title { + font-family: var(--font-mono); + font-size: var(--text-xs); + color: var(--color-ink-3); + text-transform: uppercase; + letter-spacing: 0.04em; + margin-bottom: var(--space-2xs); +} +.camera-pane img { + width: 100%; + aspect-ratio: 4 / 3; + object-fit: contain; + background: var(--color-ink); + border: 1px solid var(--color-rule); + border-radius: var(--radius-md); + display: block; +} + +/* ── Mobile / small overlay widths — verified at 320 / 375 / 414 / 768 px + (Hallmark floor). No horizontal scroll, no two-line button labels, tap + targets >= 44px. The app shell itself has no scroll at all (see #app). ── */ + +@media (max-width: 480px) { + .field-row { flex-wrap: wrap; } + .field-row label { width: 100%; flex-basis: 100%; } + .cam-field-grid { grid-template-columns: 1fr; } + .cam-field-grid label { margin-top: var(--space-2xs); } +} diff --git a/frontend/css/tokens.css b/frontend/css/tokens.css new file mode 100644 index 0000000..5f5945c --- /dev/null +++ b/frontend/css/tokens.css @@ -0,0 +1,96 @@ +/* Hallmark · genre: modern-minimal · theme: custom (Tally-referenced cool-indigo, + * instrument-panel discipline borrowed from Cobalt) · redesign · designed-as-app + * paper-band: light · display-style: grotesk-sans (Geist) · accent-hue: cool-cobalt (256) + */ + +:root { + color-scheme: light; + + /* ── Paper / ink — cool engineered near-white, never pure #fff/#000 ── */ + --color-paper: oklch(97.8% 0.005 255); /* page ground */ + --color-paper-1: oklch(99.3% 0.003 255); /* panel / card surface */ + --color-paper-2: oklch(95.0% 0.008 255); /* recessed surface (inputs, browse tree) */ + --color-paper-3: oklch(91.5% 0.010 255); /* hover/active recessed surface */ + + --color-ink: oklch(22.0% 0.020 258); /* headings, primary text */ + --color-ink-2: oklch(38.0% 0.016 257); /* body text */ + --color-ink-3: oklch(48.0% 0.013 257); /* secondary / hint text */ + --color-ink-4: oklch(66.0% 0.010 257); /* placeholder / disabled text */ + + --color-rule: oklch(85.0% 0.010 255); /* visible hairline border */ + --color-rule-soft: oklch(90.5% 0.008 255); /* quiet hairline */ + + /* ── Accent — one cobalt signal, used for focus / links / active state only ── */ + --color-accent: oklch(52.0% 0.185 256); + --color-accent-ink: oklch(98.5% 0.004 255); + --color-accent-tint: oklch(93.5% 0.030 256); + --color-focus: oklch(48.0% 0.200 256); + + /* ── Functional state colours — status/health semantics, not brand accent ── */ + --color-success: oklch(52.0% 0.145 149); + --color-success-ink: oklch(98.5% 0.004 149); + --color-warning: oklch(64.0% 0.165 68); + --color-warning-ink: oklch(20.0% 0.030 68); + --color-danger: oklch(53.0% 0.195 25); + --color-danger-ink: oklch(98.5% 0.004 25); + + /* ── Typography — Geist family throughout (single-family discipline is the + modern-minimal signature), Geist Mono as the one outlier for status/data. + Geist covers Latin only; "Noto Sans KR" carries the Korean UI copy — + the browser falls back to it per-glyph automatically. */ + --font-display: "Geist", "Noto Sans CJK KR", "Noto Sans KR", ui-sans-serif, system-ui, sans-serif; + --font-body: "Geist", "Noto Sans CJK KR", "Noto Sans KR", ui-sans-serif, system-ui, sans-serif; + --font-mono: "Geist Mono", "Noto Sans CJK KR", "Noto Sans KR", ui-monospace, "SF Mono", Menlo, monospace; + + --text-xs: 0.75rem; /* 12px */ + --text-sm: 0.8125rem; /* 13px */ + --text-base: 0.9375rem; /* 15px */ + --text-md: 1.0625rem; /* 17px */ + --text-lg: 1.3125rem; /* 21px */ + --text-xl: 1.75rem; /* 28px */ + --text-2xl: 2.25rem; /* 36px */ + + /* ── Spacing — 4pt scale ── */ + --space-3xs: 0.25rem; /* 4px */ + --space-2xs: 0.375rem; /* 6px */ + --space-xs: 0.5rem; /* 8px */ + --space-sm: 0.75rem; /* 12px */ + --space-md: 1rem; /* 16px */ + --space-lg: 1.5rem; /* 24px */ + --space-xl: 2rem; /* 32px */ + --space-2xl: 3rem; /* 48px */ + + /* ── Radius — tight/technical for surfaces, pill for actions & chips ── */ + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 16px; + --radius-pill: 999px; + + /* ── Elevation — hairline borders do the work; shadow is a whisper, one only ── */ + --shadow-whisper: 0 1px 2px oklch(20% 0.02 258 / 0.06); + + /* ── Motion — three named eases, no bounce/overshoot ── */ + --ease-out: cubic-bezier(0.16, 1, 0.3, 1); + --ease-in: cubic-bezier(0.7, 0, 0.84, 0); + --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); + + --dur-fast: 100ms; + --dur-mid: 180ms; + --dur-slow: 280ms; + + /* ── Z-index — named scale ── */ + --z-base: 1; + --z-raised: 10; + --z-dropdown: 100; + --z-sticky: 200; + --z-modal: 400; +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: var(--dur-fast) !important; + scroll-behavior: auto !important; + } +} diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..d24fd65 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,154 @@ + + + + + +scan_web + + + + + + +
+ + +
+ +
+

포인트 없음 — 시동 후 녹화를 시작하면 지도가 쌓입니다.

+
+
+ + +
+
+
+
+
+
+
+
+ +
+
+ + +
● 정지
+
+ +
+ + + +
+ +
+ + +
+
+
+ + + + + + + + + + +
+ + + + diff --git a/frontend/js/app.js b/frontend/js/app.js new file mode 100644 index 0000000..f9503a7 --- /dev/null +++ b/frontend/js/app.js @@ -0,0 +1,132 @@ +import { startStatusSocket } from "/js/status-panel.js"; +import { buildCameraGrid, setPreviewActive } from "/js/camera-preview.js"; +import { buildCameraTabs } from "/js/camera-settings.js"; +import { initRecordingPanel } from "/js/recording-panel.js"; +import { initLogPanel } from "/js/log-panel.js"; +import { initPointcloudView } from "/js/pointcloud-view.js"; + +async function getJSON(url, opts) { + const res = await fetch(url, opts); + if (!res.ok) throw new Error(`${url}: ${res.status} ${await res.text()}`); + return res.json(); +} + +const OVERLAY_IDS = ["overlay-settings", "overlay-recording", "overlay-log"]; +let pointcloudView = null; + +function anyOverlayOpen() { + return OVERLAY_IDS.some((id) => !document.getElementById(id).classList.contains("hidden")); +} +// The point-cloud render loop keeps running (cheap-ish) but there's no +// reason to keep painting frames the operator can't see behind a full-screen +// overlay — pause on open, resume once every overlay is closed. +function syncPointcloudPaused() { + if (pointcloudView) pointcloudView.setPaused(anyOverlayOpen()); +} + +function openOverlay(id) { + document.getElementById(id).classList.remove("hidden"); + syncPointcloudPaused(); +} +function closeOverlay(id) { + document.getElementById(id).classList.add("hidden"); + syncPointcloudPaused(); +} + +// Camera MJPEG only needs to stream while its tab is actually visible inside +// the open Settings overlay — otherwise it's wasted phone battery/bandwidth +// behind a closed overlay (same reasoning M3's point-cloud stream will need +// for the scan view itself). +function syncPreviewActive() { + const overlaySettings = document.getElementById("overlay-settings"); + const previewPane = document.getElementById("pane-camera-preview"); + const active = !overlaySettings.classList.contains("hidden") && previewPane.classList.contains("active"); + setPreviewActive(active); +} + +function setupOverlays() { + document.querySelectorAll("[data-close]").forEach((el) => { + el.onclick = () => { + closeOverlay(el.dataset.close); + syncPreviewActive(); + }; + }); + + document.getElementById("btn-settings").onclick = () => { + openOverlay("overlay-settings"); + syncPreviewActive(); + }; + document.getElementById("btn-rec-open").onclick = () => openOverlay("overlay-recording"); + document.getElementById("btn-open-log").onclick = () => openOverlay("overlay-log"); +} + +function setupPaneTabs() { + const btns = document.querySelectorAll(".pane-tab-btn[data-pane]"); + btns.forEach((btn) => { + btn.onclick = () => { + btns.forEach((b) => b.classList.remove("active")); + document.querySelectorAll(".pane-tab").forEach((p) => p.classList.remove("active")); + btn.classList.add("active"); + document.getElementById(btn.dataset.pane).classList.add("active"); + syncPreviewActive(); + }; + }); +} + +async function main() { + setupOverlays(); + setupPaneTabs(); + pointcloudView = initPointcloudView(); + + const status = await getJSON("/api/status"); + await buildCameraGrid(status.cameras); + await buildCameraTabs(status.cameras); + + initRecordingPanel(status.default_save_dir); + initLogPanel(); + + document.getElementById("btn-lidar-start").onclick = async () => { + await getJSON("/api/lidar/start", { method: "POST" }); + }; + document.getElementById("btn-lidar-stop").onclick = async () => { + await getJSON("/api/lidar/stop", { method: "POST" }); + }; + document.getElementById("btn-estop").onclick = async () => { + if (!confirm("모든 프로세스(LiDAR/카메라/녹화)를 즉시 정지합니다. 계속할까요?")) return; + await getJSON("/api/system/estop", { method: "POST" }); + }; + document.getElementById("btn-fastlio-test-start").onclick = async () => { + try { + await getJSON("/api/fastlio/start", { method: "POST" }); + } catch (e) { + alert(String(e)); + } + }; + document.getElementById("btn-fastlio-test-stop").onclick = async () => { + try { + await getJSON("/api/fastlio/stop", { method: "POST" }); + } catch (e) { + alert(String(e)); + } + }; + + const btnViewOrbit = document.getElementById("btn-view-orbit"); + const btnViewLidar = document.getElementById("btn-view-lidar"); + btnViewOrbit.onclick = () => { + pointcloudView.setViewMode("orbit"); + btnViewOrbit.className = "btn btn-blue"; + btnViewLidar.className = "btn btn-gray"; + }; + btnViewLidar.onclick = () => { + pointcloudView.setViewMode("lidar"); + btnViewLidar.className = "btn btn-blue"; + btnViewOrbit.className = "btn btn-gray"; + }; + + startStatusSocket(); +} + +main().catch((e) => { + console.error(e); + alert("초기화 실패: " + e); +}); diff --git a/frontend/js/camera-preview.js b/frontend/js/camera-preview.js new file mode 100644 index 0000000..45a233a --- /dev/null +++ b/frontend/js/camera-preview.js @@ -0,0 +1,33 @@ +// Builds the MJPEG camera preview grid. One per camera, no client JS +// decoding needed — the browser natively renders multipart MJPEG streams. + +export function buildCameraGrid(cameras) { + const grid = document.getElementById("camera-grid"); + grid.innerHTML = ""; + for (const cam of cameras) { + const pane = document.createElement("div"); + pane.className = "camera-pane"; + + const title = document.createElement("div"); + title.className = "cam-title"; + title.textContent = `${cam.id} (${cam.topic})`; + pane.appendChild(title); + + const img = document.createElement("img"); + img.dataset.camId = cam.id; + img.alt = cam.id; + pane.appendChild(img); + + grid.appendChild(pane); + } +} + +export function setPreviewActive(active) { + document.querySelectorAll("#camera-grid img").forEach((img) => { + if (active) { + if (!img.src) img.src = `/api/camera/${img.dataset.camId}/stream.mjpg?t=${Date.now()}`; + } else { + img.removeAttribute("src"); + } + }); +} diff --git a/frontend/js/camera-settings.js b/frontend/js/camera-settings.js new file mode 100644 index 0000000..ec780dd --- /dev/null +++ b/frontend/js/camera-settings.js @@ -0,0 +1,148 @@ +// Per-camera exposure/gain settings tabs — mirrors the QTabWidget layout in +// scan_gui_triple.py (auto-exposure toggle, brightness/exp_min/exp_max vs. +// exp_time mutually exclusive groups, gain; Apply = live ros2 param set, +// Save = persist into the base YAML). + +async function getJSON(url, opts) { + const res = await fetch(url, opts); + if (!res.ok) throw new Error(`${url}: ${res.status} ${await res.text()}`); + return res.json(); +} + +function field(labelText, id, type, step) { + const wrap = document.createDocumentFragment(); + const label = document.createElement("label"); + label.textContent = labelText; + label.htmlFor = id; + const input = document.createElement("input"); + input.type = type; + input.id = id; + if (step) input.step = step; + wrap.appendChild(label); + wrap.appendChild(input); + return { wrap, input }; +} + +export async function buildCameraTabs(cameras) { + const tabBar = document.getElementById("camera-tabs"); + const panelsRoot = document.getElementById("camera-tab-panels"); + tabBar.innerHTML = ""; + panelsRoot.innerHTML = ""; + + for (let i = 0; i < cameras.length; i++) { + const cam = cameras[i]; + const tabBtn = document.createElement("button"); + tabBtn.className = "tab-btn" + (i === 0 ? " active" : ""); + tabBtn.textContent = cam.id; + tabBtn.onclick = () => { + document.querySelectorAll(".tab-btn").forEach((b) => b.classList.remove("active")); + document.querySelectorAll(".tab-panel").forEach((p) => p.classList.remove("active")); + tabBtn.classList.add("active"); + document.getElementById(`cam-panel-${cam.id}`).classList.add("active"); + }; + tabBar.appendChild(tabBtn); + + const panel = document.createElement("div"); + panel.className = "tab-panel" + (i === 0 ? " active" : ""); + panel.id = `cam-panel-${cam.id}`; + panelsRoot.appendChild(panel); + + await buildCameraPanel(panel, cam.id); + } +} + +async function buildCameraPanel(panel, camId) { + const values = await getJSON(`/api/camera/${camId}/params`); + + const autoLabel = document.createElement("label"); + const autoChk = document.createElement("input"); + autoChk.type = "checkbox"; + autoChk.checked = values.exposure_auto; + autoLabel.appendChild(autoChk); + autoLabel.appendChild(document.createTextNode(" 자동 노출")); + panel.appendChild(autoLabel); + + const grid = document.createElement("div"); + grid.className = "cam-field-grid"; + panel.appendChild(grid); + + const fBrightness = field("목표 밝기:", `${camId}-brightness`, "number"); + fBrightness.input.min = 0; fBrightness.input.max = 255; fBrightness.input.value = values.exposure_auto_target_brightness; + const fExpMin = field("노출 하한 (us):", `${camId}-exp-min`, "number"); + fExpMin.input.min = 10; fExpMin.input.max = 1000000; fExpMin.input.step = 100; fExpMin.input.value = values.exposure_auto_min; + const fExpMax = field("노출 상한 (us):", `${camId}-exp-max`, "number"); + fExpMax.input.min = 10; fExpMax.input.max = 1000000; fExpMax.input.step = 1000; fExpMax.input.value = values.exposure_auto_max; + const fExpTime = field("노출 시간 (us):", `${camId}-exp-time`, "number"); + fExpTime.input.min = 10; fExpTime.input.max = 1000000; fExpTime.input.value = values.exposure_time; + const fGain = field("게인 (dB):", `${camId}-gain`, "number"); + fGain.input.min = 0; fGain.input.max = 16.9; fGain.input.step = 0.5; fGain.input.value = values.gain; + + for (const f of [fBrightness, fExpMin, fExpMax, fExpTime, fGain]) grid.appendChild(f.wrap); + + const autoWidgets = [fBrightness.input, fExpMin.input, fExpMax.input]; + const manualWidgets = [fExpTime.input]; + + function applyToggleState(checked) { + autoWidgets.forEach((w) => (w.disabled = !checked)); + manualWidgets.forEach((w) => (w.disabled = checked)); + } + autoChk.onchange = () => applyToggleState(autoChk.checked); + applyToggleState(values.exposure_auto); + + const btnRow = document.createElement("div"); + btnRow.className = "btn-row"; + const btnApply = document.createElement("button"); + btnApply.className = "btn btn-blue"; + btnApply.textContent = "적용"; + const btnSave = document.createElement("button"); + btnSave.className = "btn btn-green"; + btnSave.textContent = "저장"; + btnRow.appendChild(btnApply); + btnRow.appendChild(btnSave); + panel.appendChild(btnRow); + + const result = document.createElement("div"); + result.className = "cam-result"; + panel.appendChild(result); + + function collect() { + return { + exposure_auto: autoChk.checked, + exposure_auto_target_brightness: parseInt(fBrightness.input.value, 10), + exposure_auto_min: parseFloat(fExpMin.input.value), + exposure_auto_max: parseFloat(fExpMax.input.value), + exposure_time: parseInt(fExpTime.input.value, 10), + gain: parseFloat(fGain.input.value), + }; + } + + btnApply.onclick = async () => { + result.textContent = "적용 중…"; + try { + await getJSON(`/api/camera/${camId}/params`, { + method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(collect()), + }); + const r = await getJSON(`/api/camera/${camId}/params/apply`, { method: "POST" }); + result.textContent = r.ok ? "적용 완료" : r.results.filter(x => !x.ok).map(x => `${x.name}: ${x.message}`).join(" / "); + result.style.color = r.ok ? "var(--color-success)" : "var(--color-danger)"; + } catch (e) { + result.textContent = String(e); + result.style.color = "var(--color-danger)"; + } + }; + + btnSave.onclick = async () => { + result.textContent = "저장 중…"; + try { + await getJSON(`/api/camera/${camId}/params`, { + method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(collect()), + }); + const r = await getJSON(`/api/camera/${camId}/params/save`, { method: "POST" }); + result.textContent = `저장됨 → ${r.path}`; + result.style.color = "var(--color-success)"; + } catch (e) { + result.textContent = String(e); + result.style.color = "var(--color-danger)"; + } + }; +} diff --git a/frontend/js/format.js b/frontend/js/format.js new file mode 100644 index 0000000..6fdb842 --- /dev/null +++ b/frontend/js/format.js @@ -0,0 +1,21 @@ +export function formatBytes(bytes) { + if (bytes == null) return ""; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`; + if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; + return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; +} + +export function formatElapsed(seconds) { + if (seconds == null) return ""; + const s = Math.floor(seconds); + const m = Math.floor(s / 60); + const h = Math.floor(m / 60); + const pad = (n) => String(n).padStart(2, "0"); + return h > 0 ? `${h}:${pad(m % 60)}:${pad(s % 60)}` : `${pad(m)}:${pad(s % 60)}`; +} + +export function formatDuration(seconds) { + if (seconds == null) return "—"; + if (seconds < 60) return `${seconds.toFixed(1)}s`; + return formatElapsed(seconds); +} diff --git a/frontend/js/log-panel.js b/frontend/js/log-panel.js new file mode 100644 index 0000000..09f9025 --- /dev/null +++ b/frontend/js/log-panel.js @@ -0,0 +1,45 @@ +// Unified [tag] log scrollback — consumes /ws/logs (backlog once, then new +// lines as they're appended to lidar.log/camera.log/recording.log). + +const MAX_RENDERED_LINES = 2000; + +export function initLogPanel() { + const body = document.getElementById("log-body"); + const autoscrollChk = document.getElementById("log-autoscroll"); + const clearBtn = document.getElementById("btn-log-clear"); + + function appendLine({ tag, line }) { + const row = document.createElement("div"); + row.className = `log-line tag-${tag}`; + const tagSpan = document.createElement("span"); + tagSpan.className = "log-tag"; + tagSpan.textContent = `[${tag}]`; + const textSpan = document.createElement("span"); + textSpan.className = "log-text"; + textSpan.textContent = line; + row.appendChild(tagSpan); + row.appendChild(textSpan); + body.appendChild(row); + while (body.childElementCount > MAX_RENDERED_LINES) { + body.removeChild(body.firstChild); + } + if (autoscrollChk.checked) { + body.scrollTop = body.scrollHeight; + } + } + + clearBtn.onclick = () => { body.innerHTML = ""; }; + + const proto = location.protocol === "https:" ? "wss:" : "ws:"; + function connect() { + const ws = new WebSocket(`${proto}//${location.host}/ws/logs`); + ws.onmessage = (ev) => { + const msg = JSON.parse(ev.data); + if (msg.backlog) msg.backlog.forEach(appendLine); + if (msg.lines) msg.lines.forEach(appendLine); + }; + ws.onclose = () => setTimeout(connect, 1500); + ws.onerror = () => ws.close(); + } + connect(); +} diff --git a/frontend/js/pointcloud-view.js b/frontend/js/pointcloud-view.js new file mode 100644 index 0000000..0b3ee1a --- /dev/null +++ b/frontend/js/pointcloud-view.js @@ -0,0 +1,169 @@ +// M3 live scan view — consumes /ws/pointcloud (see backend/pointcloud_codec.py +// for the exact binary layout) and renders into #pointcloud-canvas with +// three.js. One reused BufferGeometry with pre-allocated typed arrays, +// overwritten in place per frame (needsUpdate=true) to avoid GC churn at +// the stream's 3-5Hz. +import * as THREE from "three"; +import { OrbitControls } from "/vendor/three/OrbitControls.js"; + +const MAGIC = "PCF1"; + +// Must match backend config.py POINTCLOUD_PROFILES max_points — sizes the +// pre-allocated buffers, so it can't just read the count off the wire. +const PROFILE_MAX_POINTS = { desktop: 20000, phone: 7000 }; + +function pickProfile() { + return window.innerWidth < 768 ? "phone" : "desktop"; +} + +export function initPointcloudView() { + const canvas = document.getElementById("pointcloud-canvas"); + const emptyState = document.getElementById("scan-empty"); + const profile = pickProfile(); + const maxPoints = PROFILE_MAX_POINTS[profile]; + + const renderer = new THREE.WebGLRenderer({ canvas, antialias: true }); + renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2)); + + const scene = new THREE.Scene(); + scene.background = new THREE.Color(0x0a0e14); + + const camera = new THREE.PerspectiveCamera(60, 1, 0.05, 500); + // FAST-LIO's "camera_init" world frame is z-up (LiDAR convention), not + // three.js's default y-up — set the camera's up vector to match so + // OrbitControls orbits around the right axis. + camera.up.set(0, 0, 1); + camera.position.set(0, -8, 5); + + const controls = new OrbitControls(camera, canvas); + controls.enableDamping = true; + controls.dampingFactor = 0.08; + + const grid = new THREE.GridHelper(40, 40, 0x2a3040, 0x1a1f28); + grid.rotation.x = Math.PI / 2; // GridHelper defaults to the XZ plane (y-up); rotate into XY (z-up) + scene.add(grid); + + const positions = new Float32Array(maxPoints * 3); + const colors = new Float32Array(maxPoints * 3); + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3)); + geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3)); + geometry.setDrawRange(0, 0); + const material = new THREE.PointsMaterial({ size: 0.035, vertexColors: true, sizeAttenuation: true }); + scene.add(new THREE.Points(geometry, material)); + + // Small marker at the current sensor pose (position + heading). + const poseGizmo = new THREE.Mesh( + new THREE.ConeGeometry(0.15, 0.4, 12), + new THREE.MeshBasicMaterial({ color: 0x5b8cff }), + ); + poseGizmo.rotation.x = Math.PI / 2; // ConeGeometry points +y by default; point it along +x (forward) instead + poseGizmo.visible = false; + scene.add(poseGizmo); + + let hasCentered = false; + let running = true; + let paused = false; + let viewMode = "orbit"; // or "lidar" + const latestPose = { position: new THREE.Vector3(), quaternion: new THREE.Quaternion(), valid: false }; + const lookTarget = new THREE.Vector3(); + const FORWARD = new THREE.Vector3(1, 0, 0); // body-frame +x — matches the poseGizmo's forward convention below + + function resize() { + const w = canvas.clientWidth, h = canvas.clientHeight; + if (w === 0 || h === 0) return; + renderer.setSize(w, h, false); + camera.aspect = w / h; + camera.updateProjectionMatrix(); + } + new ResizeObserver(resize).observe(canvas); + resize(); + + function updateCameraForLidarView() { + if (!latestPose.valid) return; + camera.position.copy(latestPose.position); + lookTarget.copy(FORWARD).applyQuaternion(latestPose.quaternion).add(latestPose.position); + camera.up.set(0, 0, 1); + camera.lookAt(lookTarget); + } + + function animate() { + if (!running) return; + requestAnimationFrame(animate); + if (paused) return; + if (viewMode === "lidar") { + updateCameraForLidarView(); + } else { + controls.update(); + } + renderer.render(scene, camera); + } + animate(); + + function applyFrame(buf) { + const view = new DataView(buf); + if (String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3)) !== MAGIC) return; + let off = 4; + off += 4; // frame_seq — unused client-side for now + off += 8; // timestamp + const poseFlag = view.getUint32(off, true); off += 4; + if (poseFlag) { + const x = view.getFloat32(off, true); const y = view.getFloat32(off + 4, true); const z = view.getFloat32(off + 8, true); + const qx = view.getFloat32(off + 12, true), qy = view.getFloat32(off + 16, true), + qz = view.getFloat32(off + 20, true), qw = view.getFloat32(off + 24, true); + off += 28; + poseGizmo.position.set(x, y, z); + poseGizmo.quaternion.set(qx, qy, qz, qw); + poseGizmo.visible = true; + latestPose.position.set(x, y, z); + latestPose.quaternion.set(qx, qy, qz, qw); + latestPose.valid = true; + if (!hasCentered) { + controls.target.set(x, y, z); + camera.position.set(x, y - 8, z + 5); + hasCentered = true; + } + } + const n = view.getUint32(off, true); off += 4; + + const count = Math.min(n, maxPoints); + positions.set(new Float32Array(buf, off, count * 3)); + const intensity = new Uint8Array(buf, off + n * 3 * 4, count); + for (let i = 0; i < count; i++) { + const t = intensity[i] / 255; + // Cool-to-warm ramp by intensity — no camera colour to draw from + // (LiDAR-only), so intensity is the only per-point signal worth encoding. + colors[i * 3] = 0.15 + 0.65 * t; + colors[i * 3 + 1] = 0.35 + 0.35 * (1 - Math.abs(t - 0.5) * 2); + colors[i * 3 + 2] = 0.75 - 0.55 * t; + } + geometry.attributes.position.needsUpdate = true; + geometry.attributes.color.needsUpdate = true; + geometry.setDrawRange(0, count); + + emptyState.classList.toggle("hidden", count > 0); + } + + function connect() { + const proto = location.protocol === "https:" ? "wss:" : "ws:"; + const ws = new WebSocket(`${proto}//${location.host}/ws/pointcloud?profile=${profile}`); + ws.binaryType = "arraybuffer"; + ws.onmessage = (ev) => applyFrame(ev.data); + ws.onclose = () => { if (running) setTimeout(connect, 1500); }; + ws.onerror = () => ws.close(); + } + connect(); + + return { + setPaused(p) { paused = p; }, + // "orbit" — free OrbitControls camera (default). "lidar" — camera locked + // to the sensor's live position/heading, first-person. Disabling + // OrbitControls in lidar mode (not just skipping controls.update()) stops + // it from silently accumulating drag input it never gets to apply, which + // would otherwise jump the view on switching back to orbit. + setViewMode(mode) { + viewMode = mode; + controls.enabled = mode === "orbit"; + }, + }; +} diff --git a/frontend/js/recording-panel.js b/frontend/js/recording-panel.js new file mode 100644 index 0000000..41f837d --- /dev/null +++ b/frontend/js/recording-panel.js @@ -0,0 +1,123 @@ +import { formatBytes, formatDuration } from "/js/format.js"; + +async function getJSON(url, opts) { + const res = await fetch(url, opts); + if (!res.ok) throw new Error(`${url}: ${res.status} ${await res.text()}`); + return res.json(); +} + +export function initRecordingPanel(defaultSaveDir) { + const saveDirInput = document.getElementById("rec-save-dir"); + saveDirInput.value = defaultSaveDir; + + const browseBtn = document.getElementById("btn-browse"); + const browsePanel = document.getElementById("browse-panel"); + const filenameInput = document.getElementById("rec-filename"); + const confirmBtn = document.getElementById("btn-rec-confirm"); + const stopBtn = document.getElementById("btn-rec-stop"); + const bagList = document.getElementById("bag-list"); + + browseBtn.onclick = async () => { + if (!browsePanel.classList.contains("hidden")) { + browsePanel.classList.add("hidden"); + return; + } + await renderBrowse(saveDirInput.value || defaultSaveDir); + browsePanel.classList.remove("hidden"); + }; + + async function renderBrowse(path) { + const data = await getJSON(`/api/fs/browse?path=${encodeURIComponent(path)}`); + browsePanel.innerHTML = ""; + const cur = document.createElement("div"); + cur.className = "dir-entry"; + cur.style.fontWeight = "bold"; + cur.textContent = `✓ 선택: ${data.path}`; + cur.onclick = () => { + saveDirInput.value = data.path; + browsePanel.classList.add("hidden"); + refreshBags(); + }; + browsePanel.appendChild(cur); + if (data.parent) { + const up = document.createElement("div"); + up.className = "dir-entry"; + up.textContent = ".."; + up.onclick = () => renderBrowse(data.parent); + browsePanel.appendChild(up); + } + for (const d of data.dirs) { + const el = document.createElement("div"); + el.className = "dir-entry"; + el.textContent = d.name; + el.onclick = () => renderBrowse(d.path); + browsePanel.appendChild(el); + } + } + + saveDirInput.addEventListener("change", refreshBags); + + confirmBtn.onclick = async () => { + try { + await getJSON("/api/recording/start", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ filename: filenameInput.value, save_dir: saveDirInput.value }), + }); + refreshBags(); + document.getElementById("overlay-recording").classList.add("hidden"); + } catch (e) { + alert(String(e)); + } + }; + + stopBtn.onclick = async () => { + await getJSON("/api/recording/stop", { method: "POST" }); + // rosbag2 flushes metadata.yaml on SIGINT — give it a moment before + // reading the bag back, otherwise it still shows as "incomplete". + setTimeout(refreshBags, 1200); + }; + + function renderBagItem(bag) { + const li = document.createElement("li"); + li.className = "bag-item"; + + const nameRow = document.createElement("div"); + nameRow.className = "bag-item-name"; + nameRow.textContent = bag.name; + if (!bag.complete) { + const badge = document.createElement("span"); + badge.className = "bag-item-incomplete"; + badge.textContent = "(녹화 중 / 미완료)"; + nameRow.appendChild(badge); + } + li.appendChild(nameRow); + + const meta = document.createElement("div"); + meta.className = "bag-item-meta"; + const parts = [formatDuration(bag.duration_s), formatBytes(bag.size_bytes)]; + if (bag.message_count != null) parts.push(`msg ${bag.message_count.toLocaleString()}`); + if (bag.topics && bag.topics.length) parts.push(`topic ${bag.topics.length}개`); + meta.textContent = parts.filter(Boolean).join(" · "); + li.appendChild(meta); + + return li; + } + + // Disk-free is owned entirely by status-panel.js now (HUD strip, driven by + // /ws/status) — this panel only needs the bag list. + async function refreshBags() { + const dir = saveDirInput.value || defaultSaveDir; + try { + const bagsData = await getJSON(`/api/bags?save_dir=${encodeURIComponent(dir)}`); + bagList.innerHTML = ""; + for (const bag of bagsData.bags) { + bagList.appendChild(renderBagItem(bag)); + } + } catch (e) { + // Directory may not exist yet (new save path) — not an error worth alarming over. + bagList.innerHTML = ""; + } + } + refreshBags(); + setInterval(refreshBags, 15000); +} diff --git a/frontend/js/status-panel.js b/frontend/js/status-panel.js new file mode 100644 index 0000000..d93a7c7 --- /dev/null +++ b/frontend/js/status-panel.js @@ -0,0 +1,138 @@ +// Renders the top-bar health badges + startup/recording state lines from /ws/status. +import { formatBytes, formatElapsed } from "/js/format.js"; + +function badgeClass(health) { + if (health === "OK") return "badge badge-ok"; + if (health === "STALE") return "badge badge-stale"; + return "badge badge-down"; +} + +// NBSP padding, not CSS min-width: regular spaces collapse in rendered +// text, so a guessed ch-width can still drift. Padding to a fixed character +// count (monospace font) makes each badge's pixel width identical on every +// tick regardless of health word or Hz digit count — nothing to reflow the +// row on mobile. +function padHealth(health) { + return health.padEnd(5, " "); +} + +function formatHz(hz) { + return hz.toFixed(1).padStart(5, " "); +} + +function makeBadge(label, health, hz) { + const b = document.createElement("span"); + // badge-sensor: fixed CSS width (see style.css) — the NBSP padding above + // keeps internal jitter down, but the box's own size being fixed is what + // actually guarantees the position never moves, regardless of icon glyph + // width or font-metric rounding. + b.className = `${badgeClass(health)} badge-sensor`; + b.textContent = `${label} ${padHealth(health)}`; + const hzSpan = document.createElement("span"); + hzSpan.className = "badge-hz"; + hzSpan.textContent = health !== "DOWN" && hz > 0 + ? `${formatHz(hz)}Hz` + : `${" ".repeat(5)}Hz`; + b.appendChild(hzSpan); + return b; +} + +export function startStatusSocket(onStatus) { + const proto = location.protocol === "https:" ? "wss:" : "ws:"; + let ws; + + function connect() { + ws = new WebSocket(`${proto}//${location.host}/ws/status`); + ws.onmessage = (ev) => { + const status = JSON.parse(ev.data); + render(status); + if (onStatus) onStatus(status); + }; + ws.onclose = () => setTimeout(connect, 1500); + ws.onerror = () => ws.close(); + } + connect(); +} + +function render(status) { + const badges = document.getElementById("status-badges"); + badges.innerHTML = ""; + + badges.appendChild(makeBadge("LiDAR", status.lidar.health, status.lidar.hz)); + badges.appendChild(makeBadge("IMU", status.lidar.imu_health, status.lidar.imu_hz)); + badges.appendChild(makeBadge("MAP", status.map.health, status.map.hz)); + for (const cam of status.cameras) { + badges.appendChild(makeBadge(cam.id, cam.health, cam.hz)); + } + + if (status.recording.active) { + const b = document.createElement("span"); + b.className = "badge badge-rec"; + b.textContent = "REC"; + badges.appendChild(b); + } + + const lidarState = document.getElementById("lidar-state"); + if (lidarState) { + lidarState.textContent = status.lidar.running ? "● 실행 중" : "● 정지"; + lidarState.className = "state-line " + (status.lidar.running ? "on" : "off"); + } + + const recPath = document.getElementById("rec-path"); + if (recPath) { + recPath.textContent = status.recording.path ? `저장: ${status.recording.path}` : ""; + } + const recMeter = document.getElementById("rec-meter"); + if (recMeter) { + if (status.recording.active) { + recMeter.textContent = `${formatElapsed(status.recording.elapsed_s)} · ${formatBytes(status.recording.size_bytes)}`; + recMeter.classList.remove("hidden"); + } else { + recMeter.classList.add("hidden"); + } + } + + // Always-on compact readout in the HUD — the backend already resolves the + // right path (DEFAULT_SAVE_DIR when idle, the live recording target while + // active), so this can just mirror status.disk unconditionally. + const diskEl = document.getElementById("disk-free"); + if (diskEl && status.disk) { + const freeGb = status.disk.free_gb; + diskEl.textContent = `여유 ${freeGb.toFixed(1)}GB`; + diskEl.classList.remove("warning", "danger"); + if (freeGb < status.disk.low_danger_gb) diskEl.classList.add("danger"); + else if (freeGb < status.disk.low_warning_gb) diskEl.classList.add("warning"); + } + + const mapPointsEl = document.getElementById("map-points"); + if (mapPointsEl && status.map) { + mapPointsEl.textContent = status.map.points > 0 ? `${status.map.points.toLocaleString()}pt` : ""; + mapPointsEl.classList.toggle("hidden", status.map.points === 0); + } + + document.getElementById("btn-lidar-start").disabled = status.lidar.running; + document.getElementById("btn-lidar-stop").disabled = !status.lidar.running; + + // The record button swaps for a stop button once recording is live, + // rather than just disabling both — HUD real estate is tight on a phone. + const recOpenBtn = document.getElementById("btn-rec-open"); + const recStopBtn = document.getElementById("btn-rec-stop"); + recOpenBtn.classList.toggle("hidden", status.recording.active); + recStopBtn.classList.toggle("hidden", !status.recording.active); + recOpenBtn.disabled = !status.lidar.running; + + const fastlioTestState = document.getElementById("fastlio-test-state"); + const fastlioTestStart = document.getElementById("btn-fastlio-test-start"); + const fastlioTestStop = document.getElementById("btn-fastlio-test-stop"); + if (fastlioTestState && status.map) { + const running = !!(status.process_status && status.process_status.fastlio && status.process_status.fastlio.running); + fastlioTestState.textContent = running + ? `● 실행 중 (${status.map.health} · ${status.map.hz.toFixed(1)}Hz · ${status.map.points.toLocaleString()}pt)` + : "● 정지"; + fastlioTestState.className = "state-line " + (running ? "on" : "off"); + fastlioTestStart.disabled = !status.lidar.running || running; + // Stopping while a real recording owns fastlio is rejected server-side + // (409) — disable client-side too so the test button can't fire that. + fastlioTestStop.disabled = !running || status.recording.active; + } +} diff --git a/frontend/vendor/fonts/Geist-Variable.woff2 b/frontend/vendor/fonts/Geist-Variable.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..991445d78ae619a7378ff219385588c7811c88ab GIT binary patch literal 29400 zcmV)2K+L~)Pew8T0RR910CLy>6aWAK0RadA0CHjg0RR9100000000000000000000 z0000Qh6NjepB@~COg~6gK~jfXKTTFaQalD=KT}jeRDn1Eg%B@(5eN$QXx<|WghBu? zfv9K!HUcCAi2wv31&A64i%ARz8zS))Y*%BTp1|g%HJ8sbD%h@O+zv$4`n_s4!iJ3l zAl@7k{eMzY8AB7;lNO*l-R=*yWFwRsIf8~9GL0FjwPHg^@2WWFJlFf012?ihsP#qZ z`;kyPGs$e>Hzyupcl@oUYGa#9SVz!vuHm}znb*V>SP;aAF)%QlYFJI3FdKLK;o9Vm zL%sj3B9mWq;uIuOmskyHi7k;>(vv|E#`_|7Mc32Pk68q`P5g+29AAWr)~F(~disD- z5k1a6Is!+ekc4EGfX3YEm(Rih&k@b}KYJIwgd~IzLI~mT03n#AG?)b~MN(7(MZA

6yP-h*_UmWm>FB3IQU-1<}Iu{JlGWR<|)Z;EicO6DLr(A24;o z|0kXHy+I8KZ5c3%1D~?{4;Z`axhXP|kBj!oYnsoT1z>N$abyxS0Js^?qnz4|p)g~W zFi>Jxa3m)(Pys-F-N{7^VhI2eotQ_fAzGgel1Xjype6J*v<*o;?cWPxwlc#qI>O`v zgQ$Q!;@VF6eLAaOUEQLV*fQ;S$S||JK1BOn5M3B{Nty$K2q;6~__F)ooPK{LOOE3} zfj~?3(+L@Y9 z3zQ-Wp?~D|Ac92lwycGr-1ZCLpMY3vCY{t7N+DL31CN1_-%Joanp-FhGO^FhP`~~B zuVpFicmI0BD2~#ul&7SYc9cT#6_S_^5Je;?b~C-Bxxam*ko!`eljN8$ug;l;NXN0=^j2RkmXrBN76Bkk-Mly*c9;2}Myf({r{APW?3Lq8@0KYy(h4zJD*em<^1lqnS~i>l zFI28wx{HflfY@CS(gGy!7OS)XdFO@V2SuMiQt}btMN*!TLrio^A0U16Nu8`^I3J{Q z2%%^~$eoMQO`ogUMWu03d9Elsmyw>woA|)2cboLBzyi!-r{Et;u~)_AQ}=L53m{f=Ce83*GPL_fsnx zGP*{x4+$y~WeEhSB_{RV*S!O?Vk)fzExU7}3*h6`-wWXi080U%0M25g2$^?|=TyZ$J4i&9|nVX;K^$AC&Hto|M1y%e%imq%G?y z{Tlsx{SN&;W6oGKmW^vnH%^*qbKZ*Eh#j}*>;>m0=T_%-=Wgd7=Lr|t5BFF1lzscb z>VAB`xL>N+$2%3d60D>ueU<(ycGx={9Bx&Gs<*0EwHh{gbS+SmYf3GBU^{x;LAd^9 zYubuioTD^I!?d5S*QGv`VW3|NvDW22GK+lDzQ5UCpftNVL1r}K0$RfOgJdA}TBbY|W zQ_vXc(k0C(Wfs$X>`(mEpZS?j#mz)3m<3rNgpaQfd&x#pPnt

4xJl4d-DN79mrX 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 Math.PI ) min -= _twoPI; + + if ( max < - Math.PI ) max += _twoPI; else if ( max > Math.PI ) max -= _twoPI; + + if ( min <= max ) { + + this._spherical.theta = Math.max( min, Math.min( max, this._spherical.theta ) ); + + } else { + + this._spherical.theta = ( this._spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, this._spherical.theta ) : + Math.min( max, this._spherical.theta ); + + } + + } + + // restrict phi to be between desired limits + this._spherical.phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, this._spherical.phi ) ); + + this._spherical.makeSafe(); + + + // move target to panned location + + if ( this.enableDamping === true ) { + + this.target.addScaledVector( this._panOffset, this.dampingFactor ); + + } else { + + this.target.add( this._panOffset ); + + } + + // Limit the target distance from the cursor to create a sphere around the center of interest + this.target.sub( this.cursor ); + this.target.clampLength( this.minTargetRadius, this.maxTargetRadius ); + this.target.add( this.cursor ); + + let zoomChanged = false; + // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera + // we adjust zoom later in these cases + if ( this.zoomToCursor && this._performCursorZoom || this.object.isOrthographicCamera ) { + + this._spherical.radius = this._clampDistance( this._spherical.radius ); + + } else { + + const prevRadius = this._spherical.radius; + this._spherical.radius = this._clampDistance( this._spherical.radius * this._scale ); + zoomChanged = prevRadius != this._spherical.radius; + + } + + _v.setFromSpherical( this._spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + _v.applyQuaternion( this._quatInverse ); + + position.copy( this.target ).add( _v ); + + this.object.lookAt( this.target ); + + if ( this.enableDamping === true ) { + + this._sphericalDelta.theta *= ( 1 - this.dampingFactor ); + this._sphericalDelta.phi *= ( 1 - this.dampingFactor ); + + this._panOffset.multiplyScalar( 1 - this.dampingFactor ); + + } else { + + this._sphericalDelta.set( 0, 0, 0 ); + + this._panOffset.set( 0, 0, 0 ); + + } + + // adjust camera position + if ( this.zoomToCursor && this._performCursorZoom ) { + + let newRadius = null; + if ( this.object.isPerspectiveCamera ) { + + // move the camera down the pointer ray + // this method avoids floating point error + const prevRadius = _v.length(); + newRadius = this._clampDistance( prevRadius * this._scale ); + + const radiusDelta = prevRadius - newRadius; + this.object.position.addScaledVector( this._dollyDirection, radiusDelta ); + this.object.updateMatrixWorld(); + + zoomChanged = !! radiusDelta; + + } else if ( this.object.isOrthographicCamera ) { + + // adjust the ortho camera position based on zoom changes + const mouseBefore = new Vector3( this._mouse.x, this._mouse.y, 0 ); + mouseBefore.unproject( this.object ); + + const prevZoom = this.object.zoom; + this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) ); + this.object.updateProjectionMatrix(); + + zoomChanged = prevZoom !== this.object.zoom; + + const mouseAfter = new Vector3( this._mouse.x, this._mouse.y, 0 ); + mouseAfter.unproject( this.object ); + + this.object.position.sub( mouseAfter ).add( mouseBefore ); + this.object.updateMatrixWorld(); + + newRadius = _v.length(); + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' ); + this.zoomToCursor = false; + + } + + // handle the placement of the target + if ( newRadius !== null ) { + + if ( this.screenSpacePanning ) { + + // position the orbit target in front of the new camera position + this.target.set( 0, 0, - 1 ) + .transformDirection( this.object.matrix ) + .multiplyScalar( newRadius ) + .add( this.object.position ); + + } else { + + // get the ray and translation plane to compute target + _ray.origin.copy( this.object.position ); + _ray.direction.set( 0, 0, - 1 ).transformDirection( this.object.matrix ); + + // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid + // extremely large values + if ( Math.abs( this.object.up.dot( _ray.direction ) ) < _TILT_LIMIT ) { + + this.object.lookAt( this.target ); + + } else { + + _plane.setFromNormalAndCoplanarPoint( this.object.up, this.target ); + _ray.intersectPlane( _plane, this.target ); + + } + + } + + } + + } else if ( this.object.isOrthographicCamera ) { + + const prevZoom = this.object.zoom; + this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) ); + + if ( prevZoom !== this.object.zoom ) { + + this.object.updateProjectionMatrix(); + zoomChanged = true; + + } + + } + + this._scale = 1; + this._performCursorZoom = false; + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + this._lastPosition.distanceToSquared( this.object.position ) > _EPS || + 8 * ( 1 - this._lastQuaternion.dot( this.object.quaternion ) ) > _EPS || + this._lastTargetPosition.distanceToSquared( this.target ) > _EPS ) { + + this.dispatchEvent( _changeEvent ); + + this._lastPosition.copy( this.object.position ); + this._lastQuaternion.copy( this.object.quaternion ); + this._lastTargetPosition.copy( this.target ); + + return true; + + } + + return false; + + } + + _getAutoRotationAngle( deltaTime ) { + + if ( deltaTime !== null ) { + + return ( _twoPI / 60 * this.autoRotateSpeed ) * deltaTime; + + } else { + + return _twoPI / 60 / 60 * this.autoRotateSpeed; + + } + + } + + _getZoomScale( delta ) { + + const normalizedDelta = Math.abs( delta * 0.01 ); + return Math.pow( 0.95, this.zoomSpeed * normalizedDelta ); + + } + + _rotateLeft( angle ) { + + this._sphericalDelta.theta -= angle; + + } + + _rotateUp( angle ) { + + this._sphericalDelta.phi -= angle; + + } + + _panLeft( distance, objectMatrix ) { + + _v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + _v.multiplyScalar( - distance ); + + this._panOffset.add( _v ); + + } + + _panUp( distance, objectMatrix ) { + + if ( this.screenSpacePanning === true ) { + + _v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + _v.setFromMatrixColumn( objectMatrix, 0 ); + _v.crossVectors( this.object.up, _v ); + + } + + _v.multiplyScalar( distance ); + + this._panOffset.add( _v ); + + } + + // deltaX and deltaY are in pixels; right and down are positive + _pan( deltaX, deltaY ) { + + const element = this.domElement; + + if ( this.object.isPerspectiveCamera ) { + + // perspective + const position = this.object.position; + _v.copy( position ).sub( this.target ); + let targetDistance = _v.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( this.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.object.matrix ); + this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.object.matrix ); + + } else if ( this.object.isOrthographicCamera ) { + + // orthographic + this._panLeft( deltaX * ( this.object.right - this.object.left ) / this.object.zoom / element.clientWidth, this.object.matrix ); + this._panUp( deltaY * ( this.object.top - this.object.bottom ) / this.object.zoom / element.clientHeight, this.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + this.enablePan = false; + + } + + } + + _dollyOut( dollyScale ) { + + if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) { + + this._scale /= dollyScale; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + this.enableZoom = false; + + } + + } + + _dollyIn( dollyScale ) { + + if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) { + + this._scale *= dollyScale; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + this.enableZoom = false; + + } + + } + + _updateZoomParameters( x, y ) { + + if ( ! this.zoomToCursor ) { + + return; + + } + + this._performCursorZoom = true; + + const rect = this.domElement.getBoundingClientRect(); + const dx = x - rect.left; + const dy = y - rect.top; + const w = rect.width; + const h = rect.height; + + this._mouse.x = ( dx / w ) * 2 - 1; + this._mouse.y = - ( dy / h ) * 2 + 1; + + this._dollyDirection.set( this._mouse.x, this._mouse.y, 1 ).unproject( this.object ).sub( this.object.position ).normalize(); + + } + + _clampDistance( dist ) { + + return Math.max( this.minDistance, Math.min( this.maxDistance, dist ) ); + + } + + // + // event callbacks - update the object state + // + + _handleMouseDownRotate( event ) { + + this._rotateStart.set( event.clientX, event.clientY ); + + } + + _handleMouseDownDolly( event ) { + + this._updateZoomParameters( event.clientX, event.clientX ); + this._dollyStart.set( event.clientX, event.clientY ); + + } + + _handleMouseDownPan( event ) { + + this._panStart.set( event.clientX, event.clientY ); + + } + + _handleMouseMoveRotate( event ) { + + this._rotateEnd.set( event.clientX, event.clientY ); + + this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed ); + + const element = this.domElement; + + this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height + + this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight ); + + this._rotateStart.copy( this._rotateEnd ); + + this.update(); + + } + + _handleMouseMoveDolly( event ) { + + this._dollyEnd.set( event.clientX, event.clientY ); + + this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart ); + + if ( this._dollyDelta.y > 0 ) { + + this._dollyOut( this._getZoomScale( this._dollyDelta.y ) ); + + } else if ( this._dollyDelta.y < 0 ) { + + this._dollyIn( this._getZoomScale( this._dollyDelta.y ) ); + + } + + this._dollyStart.copy( this._dollyEnd ); + + this.update(); + + } + + _handleMouseMovePan( event ) { + + this._panEnd.set( event.clientX, event.clientY ); + + this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed ); + + this._pan( this._panDelta.x, this._panDelta.y ); + + this._panStart.copy( this._panEnd ); + + this.update(); + + } + + _handleMouseWheel( event ) { + + this._updateZoomParameters( event.clientX, event.clientY ); + + if ( event.deltaY < 0 ) { + + this._dollyIn( this._getZoomScale( event.deltaY ) ); + + } else if ( event.deltaY > 0 ) { + + this._dollyOut( this._getZoomScale( event.deltaY ) ); + + } + + this.update(); + + } + + _handleKeyDown( event ) { + + let needsUpdate = false; + + switch ( event.code ) { + + case this.keys.UP: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + this._rotateUp( _twoPI * this.rotateSpeed / this.domElement.clientHeight ); + + } else { + + this._pan( 0, this.keyPanSpeed ); + + } + + needsUpdate = true; + break; + + case this.keys.BOTTOM: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + this._rotateUp( - _twoPI * this.rotateSpeed / this.domElement.clientHeight ); + + } else { + + this._pan( 0, - this.keyPanSpeed ); + + } + + needsUpdate = true; + break; + + case this.keys.LEFT: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + this._rotateLeft( _twoPI * this.rotateSpeed / this.domElement.clientHeight ); + + } else { + + this._pan( this.keyPanSpeed, 0 ); + + } + + needsUpdate = true; + break; + + case this.keys.RIGHT: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + this._rotateLeft( - _twoPI * this.rotateSpeed / this.domElement.clientHeight ); + + } else { + + this._pan( - this.keyPanSpeed, 0 ); + + } + + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + + this.update(); + + } + + + } + + _handleTouchStartRotate( event ) { + + if ( this._pointers.length === 1 ) { + + this._rotateStart.set( event.pageX, event.pageY ); + + } else { + + const position = this._getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + this._rotateStart.set( x, y ); + + } + + } + + _handleTouchStartPan( event ) { + + if ( this._pointers.length === 1 ) { + + this._panStart.set( event.pageX, event.pageY ); + + } else { + + const position = this._getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + this._panStart.set( x, y ); + + } + + } + + _handleTouchStartDolly( event ) { + + const position = this._getSecondPointerPosition( event ); + + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; + + const distance = Math.sqrt( dx * dx + dy * dy ); + + this._dollyStart.set( 0, distance ); + + } + + _handleTouchStartDollyPan( event ) { + + if ( this.enableZoom ) this._handleTouchStartDolly( event ); + + if ( this.enablePan ) this._handleTouchStartPan( event ); + + } + + _handleTouchStartDollyRotate( event ) { + + if ( this.enableZoom ) this._handleTouchStartDolly( event ); + + if ( this.enableRotate ) this._handleTouchStartRotate( event ); + + } + + _handleTouchMoveRotate( event ) { + + if ( this._pointers.length == 1 ) { + + this._rotateEnd.set( event.pageX, event.pageY ); + + } else { + + const position = this._getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + this._rotateEnd.set( x, y ); + + } + + this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed ); + + const element = this.domElement; + + this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height + + this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight ); + + this._rotateStart.copy( this._rotateEnd ); + + } + + _handleTouchMovePan( event ) { + + if ( this._pointers.length === 1 ) { + + this._panEnd.set( event.pageX, event.pageY ); + + } else { + + const position = this._getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + this._panEnd.set( x, y ); + + } + + this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed ); + + this._pan( this._panDelta.x, this._panDelta.y ); + + this._panStart.copy( this._panEnd ); + + } + + _handleTouchMoveDolly( event ) { + + const position = this._getSecondPointerPosition( event ); + + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; + + const distance = Math.sqrt( dx * dx + dy * dy ); + + this._dollyEnd.set( 0, distance ); + + this._dollyDelta.set( 0, Math.pow( this._dollyEnd.y / this._dollyStart.y, this.zoomSpeed ) ); + + this._dollyOut( this._dollyDelta.y ); + + this._dollyStart.copy( this._dollyEnd ); + + const centerX = ( event.pageX + position.x ) * 0.5; + const centerY = ( event.pageY + position.y ) * 0.5; + + this._updateZoomParameters( centerX, centerY ); + + } + + _handleTouchMoveDollyPan( event ) { + + if ( this.enableZoom ) this._handleTouchMoveDolly( event ); + + if ( this.enablePan ) this._handleTouchMovePan( event ); + + } + + _handleTouchMoveDollyRotate( event ) { + + if ( this.enableZoom ) this._handleTouchMoveDolly( event ); + + if ( this.enableRotate ) this._handleTouchMoveRotate( event ); + + } + + // pointers + + _addPointer( event ) { + + this._pointers.push( event.pointerId ); + + } + + _removePointer( event ) { + + delete this._pointerPositions[ event.pointerId ]; + + for ( let i = 0; i < this._pointers.length; i ++ ) { + + if ( this._pointers[ i ] == event.pointerId ) { + + this._pointers.splice( i, 1 ); + return; + + } + + } + + } + + _isTrackingPointer( event ) { + + for ( let i = 0; i < this._pointers.length; i ++ ) { + + if ( this._pointers[ i ] == event.pointerId ) return true; + + } + + return false; + + } + + _trackPointer( event ) { + + let position = this._pointerPositions[ event.pointerId ]; + + if ( position === undefined ) { + + position = new Vector2(); + this._pointerPositions[ event.pointerId ] = position; + + } + + position.set( event.pageX, event.pageY ); + + } + + _getSecondPointerPosition( event ) { + + const pointerId = ( event.pointerId === this._pointers[ 0 ] ) ? this._pointers[ 1 ] : this._pointers[ 0 ]; + + return this._pointerPositions[ pointerId ]; + + } + + // + + _customWheelEvent( event ) { + + const mode = event.deltaMode; + + // minimal wheel event altered to meet delta-zoom demand + const newEvent = { + clientX: event.clientX, + clientY: event.clientY, + deltaY: event.deltaY, + }; + + switch ( mode ) { + + case 1: // LINE_MODE + newEvent.deltaY *= 16; + break; + + case 2: // PAGE_MODE + newEvent.deltaY *= 100; + break; + + } + + // detect if event was triggered by pinching + if ( event.ctrlKey && ! this._controlActive ) { + + newEvent.deltaY *= 10; + + } + + return newEvent; + + } + +} + +function onPointerDown( event ) { + + if ( this.enabled === false ) return; + + if ( this._pointers.length === 0 ) { + + this.domElement.setPointerCapture( event.pointerId ); + + this.domElement.addEventListener( 'pointermove', this._onPointerMove ); + this.domElement.addEventListener( 'pointerup', this._onPointerUp ); + + } + + // + + if ( this._isTrackingPointer( event ) ) return; + + // + + this._addPointer( event ); + + if ( event.pointerType === 'touch' ) { + + this._onTouchStart( event ); + + } else { + + this._onMouseDown( event ); + + } + +} + +function onPointerMove( event ) { + + if ( this.enabled === false ) return; + + if ( event.pointerType === 'touch' ) { + + this._onTouchMove( event ); + + } else { + + this._onMouseMove( event ); + + } + +} + +function onPointerUp( event ) { + + this._removePointer( event ); + + switch ( this._pointers.length ) { + + case 0: + + this.domElement.releasePointerCapture( event.pointerId ); + + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); + + this.dispatchEvent( _endEvent ); + + this.state = _STATE.NONE; + + break; + + case 1: + + const pointerId = this._pointers[ 0 ]; + const position = this._pointerPositions[ pointerId ]; + + // minimal placeholder event - allows state correction on pointer-up + this._onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } ); + + break; + + } + +} + +function onMouseDown( event ) { + + let mouseAction; + + switch ( event.button ) { + + case 0: + + mouseAction = this.mouseButtons.LEFT; + break; + + case 1: + + mouseAction = this.mouseButtons.MIDDLE; + break; + + case 2: + + mouseAction = this.mouseButtons.RIGHT; + break; + + default: + + mouseAction = - 1; + + } + + switch ( mouseAction ) { + + case MOUSE.DOLLY: + + if ( this.enableZoom === false ) return; + + this._handleMouseDownDolly( event ); + + this.state = _STATE.DOLLY; + + break; + + case MOUSE.ROTATE: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( this.enablePan === false ) return; + + this._handleMouseDownPan( event ); + + this.state = _STATE.PAN; + + } else { + + if ( this.enableRotate === false ) return; + + this._handleMouseDownRotate( event ); + + this.state = _STATE.ROTATE; + + } + + break; + + case MOUSE.PAN: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( this.enableRotate === false ) return; + + this._handleMouseDownRotate( event ); + + this.state = _STATE.ROTATE; + + } else { + + if ( this.enablePan === false ) return; + + this._handleMouseDownPan( event ); + + this.state = _STATE.PAN; + + } + + break; + + default: + + this.state = _STATE.NONE; + + } + + if ( this.state !== _STATE.NONE ) { + + this.dispatchEvent( _startEvent ); + + } + +} + +function onMouseMove( event ) { + + switch ( this.state ) { + + case _STATE.ROTATE: + + if ( this.enableRotate === false ) return; + + this._handleMouseMoveRotate( event ); + + break; + + case _STATE.DOLLY: + + if ( this.enableZoom === false ) return; + + this._handleMouseMoveDolly( event ); + + break; + + case _STATE.PAN: + + if ( this.enablePan === false ) return; + + this._handleMouseMovePan( event ); + + break; + + } + +} + +function onMouseWheel( event ) { + + if ( this.enabled === false || this.enableZoom === false || this.state !== _STATE.NONE ) return; + + event.preventDefault(); + + this.dispatchEvent( _startEvent ); + + this._handleMouseWheel( this._customWheelEvent( event ) ); + + this.dispatchEvent( _endEvent ); + +} + +function onKeyDown( event ) { + + if ( this.enabled === false || this.enablePan === false ) return; + + this._handleKeyDown( event ); + +} + +function onTouchStart( event ) { + + this._trackPointer( event ); + + switch ( this._pointers.length ) { + + case 1: + + switch ( this.touches.ONE ) { + + case TOUCH.ROTATE: + + if ( this.enableRotate === false ) return; + + this._handleTouchStartRotate( event ); + + this.state = _STATE.TOUCH_ROTATE; + + break; + + case TOUCH.PAN: + + if ( this.enablePan === false ) return; + + this._handleTouchStartPan( event ); + + this.state = _STATE.TOUCH_PAN; + + break; + + default: + + this.state = _STATE.NONE; + + } + + break; + + case 2: + + switch ( this.touches.TWO ) { + + case TOUCH.DOLLY_PAN: + + if ( this.enableZoom === false && this.enablePan === false ) return; + + this._handleTouchStartDollyPan( event ); + + this.state = _STATE.TOUCH_DOLLY_PAN; + + break; + + case TOUCH.DOLLY_ROTATE: + + if ( this.enableZoom === false && this.enableRotate === false ) return; + + this._handleTouchStartDollyRotate( event ); + + this.state = _STATE.TOUCH_DOLLY_ROTATE; + + break; + + default: + + this.state = _STATE.NONE; + + } + + break; + + default: + + this.state = _STATE.NONE; + + } + + if ( this.state !== _STATE.NONE ) { + + this.dispatchEvent( _startEvent ); + + } + +} + +function onTouchMove( event ) { + + this._trackPointer( event ); + + switch ( this.state ) { + + case _STATE.TOUCH_ROTATE: + + if ( this.enableRotate === false ) return; + + this._handleTouchMoveRotate( event ); + + this.update(); + + break; + + case _STATE.TOUCH_PAN: + + if ( this.enablePan === false ) return; + + this._handleTouchMovePan( event ); + + this.update(); + + break; + + case _STATE.TOUCH_DOLLY_PAN: + + if ( this.enableZoom === false && this.enablePan === false ) return; + + this._handleTouchMoveDollyPan( event ); + + this.update(); + + break; + + case _STATE.TOUCH_DOLLY_ROTATE: + + if ( this.enableZoom === false && this.enableRotate === false ) return; + + this._handleTouchMoveDollyRotate( event ); + + this.update(); + + break; + + default: + + this.state = _STATE.NONE; + + } + +} + +function onContextMenu( event ) { + + if ( this.enabled === false ) return; + + event.preventDefault(); + +} + +function interceptControlDown( event ) { + + if ( event.key === 'Control' ) { + + this._controlActive = true; + + const document = this.domElement.getRootNode(); // offscreen canvas compatibility + + document.addEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } ); + + } + +} + +function interceptControlUp( event ) { + + if ( event.key === 'Control' ) { + + this._controlActive = false; + + const document = this.domElement.getRootNode(); // offscreen canvas compatibility + + document.removeEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } ); + + } + +} + +export { OrbitControls }; diff --git a/frontend/vendor/three/three.module.min.js b/frontend/vendor/three/three.module.min.js new file mode 100644 index 0000000..424ef1c --- /dev/null +++ b/frontend/vendor/three/three.module.min.js @@ -0,0 +1,6 @@ +/** + * @license + * Copyright 2010-2024 Three.js Authors + * SPDX-License-Identifier: MIT + */ +const t="169",e={LEFT:0,MIDDLE:1,RIGHT:2,ROTATE:0,DOLLY:1,PAN:2},n={ROTATE:0,PAN:1,DOLLY_PAN:2,DOLLY_ROTATE:3},i=0,r=1,s=2,a=3,o=0,l=1,c=2,h=3,u=0,d=1,p=2,m=0,f=1,g=2,v=3,_=4,x=5,y=100,M=101,S=102,b=103,w=104,T=200,E=201,A=202,R=203,C=204,P=205,I=206,L=207,U=208,N=209,D=210,O=211,F=212,B=213,z=214,k=0,V=1,H=2,G=3,W=4,X=5,j=6,q=7,Y=0,Z=1,J=2,K=0,$=1,Q=2,tt=3,et=4,nt=5,it=6,rt=7,st="attached",at="detached",ot=300,lt=301,ct=302,ht=303,ut=304,dt=306,pt=1e3,mt=1001,ft=1002,gt=1003,vt=1004,_t=1004,xt=1005,yt=1005,Mt=1006,St=1007,bt=1007,wt=1008,Tt=1008,Et=1009,At=1010,Rt=1011,Ct=1012,Pt=1013,It=1014,Lt=1015,Ut=1016,Nt=1017,Dt=1018,Ot=1020,Ft=35902,Bt=1021,zt=1022,kt=1023,Vt=1024,Ht=1025,Gt=1026,Wt=1027,Xt=1028,jt=1029,qt=1030,Yt=1031,Zt=1032,Jt=1033,Kt=33776,$t=33777,Qt=33778,te=33779,ee=35840,ne=35841,ie=35842,re=35843,se=36196,ae=37492,oe=37496,le=37808,ce=37809,he=37810,ue=37811,de=37812,pe=37813,me=37814,fe=37815,ge=37816,ve=37817,_e=37818,xe=37819,ye=37820,Me=37821,Se=36492,be=36494,we=36495,Te=36283,Ee=36284,Ae=36285,Re=36286,Ce=2200,Pe=2201,Ie=2202,Le=2300,Ue=2301,Ne=2302,De=2400,Oe=2401,Fe=2402,Be=2500,ze=2501,ke=0,Ve=1,He=2,Ge=3200,We=3201,Xe=3202,je=3203,qe=0,Ye=1,Ze="",Je="srgb",Ke="srgb-linear",$e="display-p3",Qe="display-p3-linear",tn="linear",en="srgb",nn="rec709",rn="p3",sn=0,an=7680,on=7681,ln=7682,cn=7683,hn=34055,un=34056,dn=5386,pn=512,mn=513,fn=514,gn=515,vn=516,_n=517,xn=518,yn=519,Mn=512,Sn=513,bn=514,wn=515,Tn=516,En=517,An=518,Rn=519,Cn=35044,Pn=35048,In=35040,Ln=35045,Un=35049,Nn=35041,Dn=35046,On=35050,Fn=35042,Bn="100",zn="300 es",kn=2e3,Vn=2001;class Hn{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[t]&&(n[t]=[]),-1===n[t].indexOf(e)&&n[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const n=this._listeners;return void 0!==n[t]&&-1!==n[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const n=this._listeners[t];if(void 0!==n){const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const n=e.slice(0);for(let e=0,i=n.length;e>8&255]+Gn[t>>16&255]+Gn[t>>24&255]+"-"+Gn[255&e]+Gn[e>>8&255]+"-"+Gn[e>>16&15|64]+Gn[e>>24&255]+"-"+Gn[63&n|128]+Gn[n>>8&255]+"-"+Gn[n>>16&255]+Gn[n>>24&255]+Gn[255&i]+Gn[i>>8&255]+Gn[i>>16&255]+Gn[i>>24&255]).toLowerCase()}function Yn(t,e,n){return Math.max(e,Math.min(n,t))}function Zn(t,e){return(t%e+e)%e}function Jn(t,e,n){return(1-n)*t+n*e}function Kn(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return t/4294967295;case Uint16Array:return t/65535;case Uint8Array:return t/255;case Int32Array:return Math.max(t/2147483647,-1);case Int16Array:return Math.max(t/32767,-1);case Int8Array:return Math.max(t/127,-1);default:throw new Error("Invalid component type.")}}function $n(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return Math.round(4294967295*t);case Uint16Array:return Math.round(65535*t);case Uint8Array:return Math.round(255*t);case Int32Array:return Math.round(2147483647*t);case Int16Array:return Math.round(32767*t);case Int8Array:return Math.round(127*t);default:throw new Error("Invalid component type.")}}const Qn={DEG2RAD:Xn,RAD2DEG:jn,generateUUID:qn,clamp:Yn,euclideanModulo:Zn,mapLinear:function(t,e,n,i,r){return i+(t-e)*(r-i)/(n-e)},inverseLerp:function(t,e,n){return t!==e?(n-t)/(e-t):0},lerp:Jn,damp:function(t,e,n,i){return Jn(t,e,1-Math.exp(-n*i))},pingpong:function(t,e=1){return e-Math.abs(Zn(t,2*e)-e)},smoothstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*(3-2*t)},smootherstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*t*(t*(6*t-15)+10)},randInt:function(t,e){return t+Math.floor(Math.random()*(e-t+1))},randFloat:function(t,e){return t+Math.random()*(e-t)},randFloatSpread:function(t){return t*(.5-Math.random())},seededRandom:function(t){void 0!==t&&(Wn=t);let e=Wn+=1831565813;return e=Math.imul(e^e>>>15,1|e),e^=e+Math.imul(e^e>>>7,61|e),((e^e>>>14)>>>0)/4294967296},degToRad:function(t){return t*Xn},radToDeg:function(t){return t*jn},isPowerOfTwo:function(t){return 0==(t&t-1)&&0!==t},ceilPowerOfTwo:function(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))},floorPowerOfTwo:function(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))},setQuaternionFromProperEuler:function(t,e,n,i,r){const s=Math.cos,a=Math.sin,o=s(n/2),l=a(n/2),c=s((e+i)/2),h=a((e+i)/2),u=s((e-i)/2),d=a((e-i)/2),p=s((i-e)/2),m=a((i-e)/2);switch(r){case"XYX":t.set(o*h,l*u,l*d,o*c);break;case"YZY":t.set(l*d,o*h,l*u,o*c);break;case"ZXZ":t.set(l*u,l*d,o*h,o*c);break;case"XZX":t.set(o*h,l*m,l*p,o*c);break;case"YXY":t.set(l*p,o*h,l*m,o*c);break;case"ZYZ":t.set(l*m,l*p,o*h,o*c);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}},normalize:$n,denormalize:Kn};class ti{constructor(t=0,e=0){ti.prototype.isVector2=!0,this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t){return this.x+=t.x,this.y+=t.y,this}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=this.x,n=this.y,i=t.elements;return this.x=i[0]*e+i[3]*n+i[6],this.y=i[1]*e+i[4]*n+i[7],this}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(Yn(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y;return e*e+n*n}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this}equals(t){return t.x===this.x&&t.y===this.y}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this}rotateAround(t,e){const n=Math.cos(e),i=Math.sin(e),r=this.x-t.x,s=this.y-t.y;return this.x=r*n-s*i+t.x,this.y=r*i+s*n+t.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}*[Symbol.iterator](){yield this.x,yield this.y}}class ei{constructor(t,e,n,i,r,s,a,o,l){ei.prototype.isMatrix3=!0,this.elements=[1,0,0,0,1,0,0,0,1],void 0!==t&&this.set(t,e,n,i,r,s,a,o,l)}set(t,e,n,i,r,s,a,o,l){const c=this.elements;return c[0]=t,c[1]=i,c[2]=a,c[3]=e,c[4]=r,c[5]=o,c[6]=n,c[7]=s,c[8]=l,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],this}extractBasis(t,e,n){return t.setFromMatrix3Column(this,0),e.setFromMatrix3Column(this,1),n.setFromMatrix3Column(this,2),this}setFromMatrix4(t){const e=t.elements;return this.set(e[0],e[4],e[8],e[1],e[5],e[9],e[2],e[6],e[10]),this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[3],o=n[6],l=n[1],c=n[4],h=n[7],u=n[2],d=n[5],p=n[8],m=i[0],f=i[3],g=i[6],v=i[1],_=i[4],x=i[7],y=i[2],M=i[5],S=i[8];return r[0]=s*m+a*v+o*y,r[3]=s*f+a*_+o*M,r[6]=s*g+a*x+o*S,r[1]=l*m+c*v+h*y,r[4]=l*f+c*_+h*M,r[7]=l*g+c*x+h*S,r[2]=u*m+d*v+p*y,r[5]=u*f+d*_+p*M,r[8]=u*g+d*x+p*S,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[3]*=t,e[6]*=t,e[1]*=t,e[4]*=t,e[7]*=t,e[2]*=t,e[5]*=t,e[8]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8];return e*s*c-e*a*l-n*r*c+n*a*o+i*r*l-i*s*o}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=c*s-a*l,u=a*o-c*r,d=l*r-s*o,p=e*h+n*u+i*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const m=1/p;return t[0]=h*m,t[1]=(i*l-c*n)*m,t[2]=(a*n-i*s)*m,t[3]=u*m,t[4]=(c*e-i*o)*m,t[5]=(i*r-a*e)*m,t[6]=d*m,t[7]=(n*o-l*e)*m,t[8]=(s*e-n*r)*m,this}transpose(){let t;const e=this.elements;return t=e[1],e[1]=e[3],e[3]=t,t=e[2],e[2]=e[6],e[6]=t,t=e[5],e[5]=e[7],e[7]=t,this}getNormalMatrix(t){return this.setFromMatrix4(t).invert().transpose()}transposeIntoArray(t){const e=this.elements;return t[0]=e[0],t[1]=e[3],t[2]=e[6],t[3]=e[1],t[4]=e[4],t[5]=e[7],t[6]=e[2],t[7]=e[5],t[8]=e[8],this}setUvTransform(t,e,n,i,r,s,a){const o=Math.cos(r),l=Math.sin(r);return this.set(n*o,n*l,-n*(o*s+l*a)+s+t,-i*l,i*o,-i*(-l*s+o*a)+a+e,0,0,1),this}scale(t,e){return this.premultiply(ni.makeScale(t,e)),this}rotate(t){return this.premultiply(ni.makeRotation(-t)),this}translate(t,e){return this.premultiply(ni.makeTranslation(t,e)),this}makeTranslation(t,e){return t.isVector2?this.set(1,0,t.x,0,1,t.y,0,0,1):this.set(1,0,t,0,1,e,0,0,1),this}makeRotation(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,n,e,0,0,0,1),this}makeScale(t,e){return this.set(t,0,0,0,e,0,0,0,1),this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<9;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<9;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t}clone(){return(new this.constructor).fromArray(this.elements)}}const ni=new ei;function ii(t){for(let e=t.length-1;e>=0;--e)if(t[e]>=65535)return!0;return!1}const ri={Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array};function si(t,e){return new ri[t](e)}function ai(t){return document.createElementNS("http://www.w3.org/1999/xhtml",t)}function oi(){const t=ai("canvas");return t.style.display="block",t}const li={};function ci(t){t in li||(li[t]=!0,console.warn(t))}const hi=(new ei).set(.8224621,.177538,0,.0331941,.9668058,0,.0170827,.0723974,.9105199),ui=(new ei).set(1.2249401,-.2249404,0,-.0420569,1.0420571,0,-.0196376,-.0786361,1.0982735),di={[Ke]:{transfer:tn,primaries:nn,luminanceCoefficients:[.2126,.7152,.0722],toReference:t=>t,fromReference:t=>t},[Je]:{transfer:en,primaries:nn,luminanceCoefficients:[.2126,.7152,.0722],toReference:t=>t.convertSRGBToLinear(),fromReference:t=>t.convertLinearToSRGB()},[Qe]:{transfer:tn,primaries:rn,luminanceCoefficients:[.2289,.6917,.0793],toReference:t=>t.applyMatrix3(ui),fromReference:t=>t.applyMatrix3(hi)},[$e]:{transfer:en,primaries:rn,luminanceCoefficients:[.2289,.6917,.0793],toReference:t=>t.convertSRGBToLinear().applyMatrix3(ui),fromReference:t=>t.applyMatrix3(hi).convertLinearToSRGB()}},pi=new Set([Ke,Qe]),mi={enabled:!0,_workingColorSpace:Ke,get workingColorSpace(){return this._workingColorSpace},set workingColorSpace(t){if(!pi.has(t))throw new Error(`Unsupported working color space, "${t}".`);this._workingColorSpace=t},convert:function(t,e,n){if(!1===this.enabled||e===n||!e||!n)return t;const i=di[e].toReference;return(0,di[n].fromReference)(i(t))},fromWorkingColorSpace:function(t,e){return this.convert(t,this._workingColorSpace,e)},toWorkingColorSpace:function(t,e){return this.convert(t,e,this._workingColorSpace)},getPrimaries:function(t){return di[t].primaries},getTransfer:function(t){return t===Ze?tn:di[t].transfer},getLuminanceCoefficients:function(t,e=this._workingColorSpace){return t.fromArray(di[e].luminanceCoefficients)}};function fi(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}function gi(t){return t<.0031308?12.92*t:1.055*Math.pow(t,.41666)-.055}let vi;class _i{static getDataURL(t){if(/^data:/i.test(t.src))return t.src;if("undefined"==typeof HTMLCanvasElement)return t.src;let e;if(t instanceof HTMLCanvasElement)e=t;else{void 0===vi&&(vi=ai("canvas")),vi.width=t.width,vi.height=t.height;const n=vi.getContext("2d");t instanceof ImageData?n.putImageData(t,0,0):n.drawImage(t,0,0,t.width,t.height),e=vi}return e.width>2048||e.height>2048?(console.warn("THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons",t),e.toDataURL("image/jpeg",.6)):e.toDataURL("image/png")}static sRGBToLinear(t){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const e=ai("canvas");e.width=t.width,e.height=t.height;const n=e.getContext("2d");n.drawImage(t,0,0,t.width,t.height);const i=n.getImageData(0,0,t.width,t.height),r=i.data;for(let t=0;t0&&(n.userData=this.userData),e||(t.textures[this.uuid]=n),n}dispose(){this.dispatchEvent({type:"dispose"})}transformUv(t){if(this.mapping!==ot)return t;if(t.applyMatrix3(this.matrix),t.x<0||t.x>1)switch(this.wrapS){case pt:t.x=t.x-Math.floor(t.x);break;case mt:t.x=t.x<0?0:1;break;case ft:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case pt:t.y=t.y-Math.floor(t.y);break;case mt:t.y=t.y<0?0:1;break;case ft:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flipY&&(t.y=1-t.y),t}set needsUpdate(t){!0===t&&(this.version++,this.source.needsUpdate=!0)}set needsPMREMUpdate(t){!0===t&&this.pmremVersion++}}bi.DEFAULT_IMAGE=null,bi.DEFAULT_MAPPING=ot,bi.DEFAULT_ANISOTROPY=1;class wi{constructor(t=0,e=0,n=0,i=1){wi.prototype.isVector4=!0,this.x=t,this.y=e,this.z=n,this.w=i}get width(){return this.z}set width(t){this.z=t}get height(){return this.w}set height(t){this.w=t}set(t,e,n,i){return this.x=t,this.y=e,this.z=n,this.w=i,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this.w=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setW(t){return this.w=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;case 3:this.w=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this.w=void 0!==t.w?t.w:1,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this.w+=t.w,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this.w+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this.w=t.w+e.w,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this.w+=t.w*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this.w-=t.w,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this.w-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this.w=t.w-e.w,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this.w*=t.w,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this.w*=t,this}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=this.w,s=t.elements;return this.x=s[0]*e+s[4]*n+s[8]*i+s[12]*r,this.y=s[1]*e+s[5]*n+s[9]*i+s[13]*r,this.z=s[2]*e+s[6]*n+s[10]*i+s[14]*r,this.w=s[3]*e+s[7]*n+s[11]*i+s[15]*r,this}divideScalar(t){return this.multiplyScalar(1/t)}setAxisAngleFromQuaternion(t){this.w=2*Math.acos(t.w);const e=Math.sqrt(1-t.w*t.w);return e<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=t.x/e,this.y=t.y/e,this.z=t.z/e),this}setAxisAngleFromRotationMatrix(t){let e,n,i,r;const s=.01,a=.1,o=t.elements,l=o[0],c=o[4],h=o[8],u=o[1],d=o[5],p=o[9],m=o[2],f=o[6],g=o[10];if(Math.abs(c-u)o&&t>v?tv?o=0?1:-1,i=1-e*e;if(i>Number.EPSILON){const r=Math.sqrt(i),s=Math.atan2(r,e*n);t=Math.sin(t*s)/r,a=Math.sin(a*s)/r}const r=a*n;if(o=o*t+u*r,l=l*t+d*r,c=c*t+p*r,h=h*t+m*r,t===1-a){const t=1/Math.sqrt(o*o+l*l+c*c+h*h);o*=t,l*=t,c*=t,h*=t}}t[e]=o,t[e+1]=l,t[e+2]=c,t[e+3]=h}static multiplyQuaternionsFlat(t,e,n,i,r,s){const a=n[i],o=n[i+1],l=n[i+2],c=n[i+3],h=r[s],u=r[s+1],d=r[s+2],p=r[s+3];return t[e]=a*p+c*h+o*d-l*u,t[e+1]=o*p+c*u+l*h-a*d,t[e+2]=l*p+c*d+a*u-o*h,t[e+3]=c*p-a*h-o*u-l*d,t}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get w(){return this._w}set w(t){this._w=t,this._onChangeCallback()}set(t,e,n,i){return this._x=t,this._y=e,this._z=n,this._w=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(t){return this._x=t.x,this._y=t.y,this._z=t.z,this._w=t.w,this._onChangeCallback(),this}setFromEuler(t,e=!0){const n=t._x,i=t._y,r=t._z,s=t._order,a=Math.cos,o=Math.sin,l=a(n/2),c=a(i/2),h=a(r/2),u=o(n/2),d=o(i/2),p=o(r/2);switch(s){case"XYZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"YXZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"ZXY":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"ZYX":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"YZX":this._x=u*c*h+l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h-u*d*p;break;case"XZY":this._x=u*c*h-l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h+u*d*p;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+s)}return!0===e&&this._onChangeCallback(),this}setFromAxisAngle(t,e){const n=e/2,i=Math.sin(n);return this._x=t.x*i,this._y=t.y*i,this._z=t.z*i,this._w=Math.cos(n),this._onChangeCallback(),this}setFromRotationMatrix(t){const e=t.elements,n=e[0],i=e[4],r=e[8],s=e[1],a=e[5],o=e[9],l=e[2],c=e[6],h=e[10],u=n+a+h;if(u>0){const t=.5/Math.sqrt(u+1);this._w=.25/t,this._x=(c-o)*t,this._y=(r-l)*t,this._z=(s-i)*t}else if(n>a&&n>h){const t=2*Math.sqrt(1+n-a-h);this._w=(c-o)/t,this._x=.25*t,this._y=(i+s)/t,this._z=(r+l)/t}else if(a>h){const t=2*Math.sqrt(1+a-n-h);this._w=(r-l)/t,this._x=(i+s)/t,this._y=.25*t,this._z=(o+c)/t}else{const t=2*Math.sqrt(1+h-n-a);this._w=(s-i)/t,this._x=(r+l)/t,this._y=(o+c)/t,this._z=.25*t}return this._onChangeCallback(),this}setFromUnitVectors(t,e){let n=t.dot(e)+1;return nMath.abs(t.z)?(this._x=-t.y,this._y=t.x,this._z=0,this._w=n):(this._x=0,this._y=-t.z,this._z=t.y,this._w=n)):(this._x=t.y*e.z-t.z*e.y,this._y=t.z*e.x-t.x*e.z,this._z=t.x*e.y-t.y*e.x,this._w=n),this.normalize()}angleTo(t){return 2*Math.acos(Math.abs(Yn(this.dot(t),-1,1)))}rotateTowards(t,e){const n=this.angleTo(t);if(0===n)return this;const i=Math.min(1,e/n);return this.slerp(t,i),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(t){return this._x*t._x+this._y*t._y+this._z*t._z+this._w*t._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let t=this.length();return 0===t?(this._x=0,this._y=0,this._z=0,this._w=1):(t=1/t,this._x=this._x*t,this._y=this._y*t,this._z=this._z*t,this._w=this._w*t),this._onChangeCallback(),this}multiply(t){return this.multiplyQuaternions(this,t)}premultiply(t){return this.multiplyQuaternions(t,this)}multiplyQuaternions(t,e){const n=t._x,i=t._y,r=t._z,s=t._w,a=e._x,o=e._y,l=e._z,c=e._w;return this._x=n*c+s*a+i*l-r*o,this._y=i*c+s*o+r*a-n*l,this._z=r*c+s*l+n*o-i*a,this._w=s*c-n*a-i*o-r*l,this._onChangeCallback(),this}slerp(t,e){if(0===e)return this;if(1===e)return this.copy(t);const n=this._x,i=this._y,r=this._z,s=this._w;let a=s*t._w+n*t._x+i*t._y+r*t._z;if(a<0?(this._w=-t._w,this._x=-t._x,this._y=-t._y,this._z=-t._z,a=-a):this.copy(t),a>=1)return this._w=s,this._x=n,this._y=i,this._z=r,this;const o=1-a*a;if(o<=Number.EPSILON){const t=1-e;return this._w=t*s+e*this._w,this._x=t*n+e*this._x,this._y=t*i+e*this._y,this._z=t*r+e*this._z,this.normalize(),this}const l=Math.sqrt(o),c=Math.atan2(l,a),h=Math.sin((1-e)*c)/l,u=Math.sin(e*c)/l;return this._w=s*h+this._w*u,this._x=n*h+this._x*u,this._y=i*h+this._y*u,this._z=r*h+this._z*u,this._onChangeCallback(),this}slerpQuaternions(t,e,n){return this.copy(t).slerp(e,n)}random(){const t=2*Math.PI*Math.random(),e=2*Math.PI*Math.random(),n=Math.random(),i=Math.sqrt(1-n),r=Math.sqrt(n);return this.set(i*Math.sin(t),i*Math.cos(t),r*Math.sin(e),r*Math.cos(e))}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._w===this._w}fromArray(t,e=0){return this._x=t[e],this._y=t[e+1],this._z=t[e+2],this._w=t[e+3],this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._w,t}fromBufferAttribute(t,e){return this._x=t.getX(e),this._y=t.getY(e),this._z=t.getZ(e),this._w=t.getW(e),this._onChangeCallback(),this}toJSON(){return this.toArray()}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._w}}class Li{constructor(t=0,e=0,n=0){Li.prototype.isVector3=!0,this.x=t,this.y=e,this.z=n}set(t,e,n){return void 0===n&&(n=this.z),this.x=t,this.y=e,this.z=n,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this}multiplyVectors(t,e){return this.x=t.x*e.x,this.y=t.y*e.y,this.z=t.z*e.z,this}applyEuler(t){return this.applyQuaternion(Ni.setFromEuler(t))}applyAxisAngle(t,e){return this.applyQuaternion(Ni.setFromAxisAngle(t,e))}applyMatrix3(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[3]*n+r[6]*i,this.y=r[1]*e+r[4]*n+r[7]*i,this.z=r[2]*e+r[5]*n+r[8]*i,this}applyNormalMatrix(t){return this.applyMatrix3(t).normalize()}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=t.elements,s=1/(r[3]*e+r[7]*n+r[11]*i+r[15]);return this.x=(r[0]*e+r[4]*n+r[8]*i+r[12])*s,this.y=(r[1]*e+r[5]*n+r[9]*i+r[13])*s,this.z=(r[2]*e+r[6]*n+r[10]*i+r[14])*s,this}applyQuaternion(t){const e=this.x,n=this.y,i=this.z,r=t.x,s=t.y,a=t.z,o=t.w,l=2*(s*i-a*n),c=2*(a*e-r*i),h=2*(r*n-s*e);return this.x=e+o*l+s*h-a*c,this.y=n+o*c+a*l-r*h,this.z=i+o*h+r*c-s*l,this}project(t){return this.applyMatrix4(t.matrixWorldInverse).applyMatrix4(t.projectionMatrix)}unproject(t){return this.applyMatrix4(t.projectionMatrixInverse).applyMatrix4(t.matrixWorld)}transformDirection(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[4]*n+r[8]*i,this.y=r[1]*e+r[5]*n+r[9]*i,this.z=r[2]*e+r[6]*n+r[10]*i,this.normalize()}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this}divideScalar(t){return this.multiplyScalar(1/t)}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this.z=Math.min(this.z,t.z),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this.z=Math.max(this.z,t.z),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this.z=Math.max(t.z,Math.min(e.z,this.z)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this.z=Math.max(t,Math.min(e,this.z)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this.z=Math.trunc(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(t){return this.x*t.x+this.y*t.y+this.z*t.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this.z+=(t.z-this.z)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this.z=t.z+(e.z-t.z)*n,this}cross(t){return this.crossVectors(this,t)}crossVectors(t,e){const n=t.x,i=t.y,r=t.z,s=e.x,a=e.y,o=e.z;return this.x=i*o-r*a,this.y=r*s-n*o,this.z=n*a-i*s,this}projectOnVector(t){const e=t.lengthSq();if(0===e)return this.set(0,0,0);const n=t.dot(this)/e;return this.copy(t).multiplyScalar(n)}projectOnPlane(t){return Ui.copy(this).projectOnVector(t),this.sub(Ui)}reflect(t){return this.sub(Ui.copy(t).multiplyScalar(2*this.dot(t)))}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(Yn(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y,i=this.z-t.z;return e*e+n*n+i*i}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)+Math.abs(this.z-t.z)}setFromSpherical(t){return this.setFromSphericalCoords(t.radius,t.phi,t.theta)}setFromSphericalCoords(t,e,n){const i=Math.sin(e)*t;return this.x=i*Math.sin(n),this.y=Math.cos(e)*t,this.z=i*Math.cos(n),this}setFromCylindrical(t){return this.setFromCylindricalCoords(t.radius,t.theta,t.y)}setFromCylindricalCoords(t,e,n){return this.x=t*Math.sin(e),this.y=n,this.z=t*Math.cos(e),this}setFromMatrixPosition(t){const e=t.elements;return this.x=e[12],this.y=e[13],this.z=e[14],this}setFromMatrixScale(t){const e=this.setFromMatrixColumn(t,0).length(),n=this.setFromMatrixColumn(t,1).length(),i=this.setFromMatrixColumn(t,2).length();return this.x=e,this.y=n,this.z=i,this}setFromMatrixColumn(t,e){return this.fromArray(t.elements,4*e)}setFromMatrix3Column(t,e){return this.fromArray(t.elements,3*e)}setFromEuler(t){return this.x=t._x,this.y=t._y,this.z=t._z,this}setFromColor(t){return this.x=t.r,this.y=t.g,this.z=t.b,this}equals(t){return t.x===this.x&&t.y===this.y&&t.z===this.z}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this.z=t[e+2],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t[e+2]=this.z,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this.z=t.getZ(e),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}randomDirection(){const t=Math.random()*Math.PI*2,e=2*Math.random()-1,n=Math.sqrt(1-e*e);return this.x=n*Math.cos(t),this.y=e,this.z=n*Math.sin(t),this}*[Symbol.iterator](){yield this.x,yield this.y,yield this.z}}const Ui=new Li,Ni=new Ii;class Di{constructor(t=new Li(1/0,1/0,1/0),e=new Li(-1/0,-1/0,-1/0)){this.isBox3=!0,this.min=t,this.max=e}set(t,e){return this.min.copy(t),this.max.copy(e),this}setFromArray(t){this.makeEmpty();for(let e=0,n=t.length;e=this.min.x&&t.x<=this.max.x&&t.y>=this.min.y&&t.y<=this.max.y&&t.z>=this.min.z&&t.z<=this.max.z}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y&&this.min.z<=t.min.z&&t.max.z<=this.max.z}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y),(t.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(t){return t.max.x>=this.min.x&&t.min.x<=this.max.x&&t.max.y>=this.min.y&&t.min.y<=this.max.y&&t.max.z>=this.min.z&&t.min.z<=this.max.z}intersectsSphere(t){return this.clampPoint(t.center,Fi),Fi.distanceToSquared(t.center)<=t.radius*t.radius}intersectsPlane(t){let e,n;return t.normal.x>0?(e=t.normal.x*this.min.x,n=t.normal.x*this.max.x):(e=t.normal.x*this.max.x,n=t.normal.x*this.min.x),t.normal.y>0?(e+=t.normal.y*this.min.y,n+=t.normal.y*this.max.y):(e+=t.normal.y*this.max.y,n+=t.normal.y*this.min.y),t.normal.z>0?(e+=t.normal.z*this.min.z,n+=t.normal.z*this.max.z):(e+=t.normal.z*this.max.z,n+=t.normal.z*this.min.z),e<=-t.constant&&n>=-t.constant}intersectsTriangle(t){if(this.isEmpty())return!1;this.getCenter(Xi),ji.subVectors(this.max,Xi),zi.subVectors(t.a,Xi),ki.subVectors(t.b,Xi),Vi.subVectors(t.c,Xi),Hi.subVectors(ki,zi),Gi.subVectors(Vi,ki),Wi.subVectors(zi,Vi);let e=[0,-Hi.z,Hi.y,0,-Gi.z,Gi.y,0,-Wi.z,Wi.y,Hi.z,0,-Hi.x,Gi.z,0,-Gi.x,Wi.z,0,-Wi.x,-Hi.y,Hi.x,0,-Gi.y,Gi.x,0,-Wi.y,Wi.x,0];return!!Zi(e,zi,ki,Vi,ji)&&(e=[1,0,0,0,1,0,0,0,1],!!Zi(e,zi,ki,Vi,ji)&&(qi.crossVectors(Hi,Gi),e=[qi.x,qi.y,qi.z],Zi(e,zi,ki,Vi,ji)))}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,Fi).distanceTo(t)}getBoundingSphere(t){return this.isEmpty()?t.makeEmpty():(this.getCenter(t.center),t.radius=.5*this.getSize(Fi).length()),t}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}applyMatrix4(t){return this.isEmpty()||(Oi[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(t),Oi[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(t),Oi[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(t),Oi[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(t),Oi[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(t),Oi[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(t),Oi[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(t),Oi[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(t),this.setFromPoints(Oi)),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const Oi=[new Li,new Li,new Li,new Li,new Li,new Li,new Li,new Li],Fi=new Li,Bi=new Di,zi=new Li,ki=new Li,Vi=new Li,Hi=new Li,Gi=new Li,Wi=new Li,Xi=new Li,ji=new Li,qi=new Li,Yi=new Li;function Zi(t,e,n,i,r){for(let s=0,a=t.length-3;s<=a;s+=3){Yi.fromArray(t,s);const a=r.x*Math.abs(Yi.x)+r.y*Math.abs(Yi.y)+r.z*Math.abs(Yi.z),o=e.dot(Yi),l=n.dot(Yi),c=i.dot(Yi);if(Math.max(-Math.max(o,l,c),Math.min(o,l,c))>a)return!1}return!0}const Ji=new Di,Ki=new Li,$i=new Li;class Qi{constructor(t=new Li,e=-1){this.isSphere=!0,this.center=t,this.radius=e}set(t,e){return this.center.copy(t),this.radius=e,this}setFromPoints(t,e){const n=this.center;void 0!==e?n.copy(e):Ji.setFromPoints(t).getCenter(n);let i=0;for(let e=0,r=t.length;ethis.radius*this.radius&&(e.sub(this.center).normalize(),e.multiplyScalar(this.radius).add(this.center)),e}getBoundingBox(t){return this.isEmpty()?(t.makeEmpty(),t):(t.set(this.center,this.center),t.expandByScalar(this.radius),t)}applyMatrix4(t){return this.center.applyMatrix4(t),this.radius=this.radius*t.getMaxScaleOnAxis(),this}translate(t){return this.center.add(t),this}expandByPoint(t){if(this.isEmpty())return this.center.copy(t),this.radius=0,this;Ki.subVectors(t,this.center);const e=Ki.lengthSq();if(e>this.radius*this.radius){const t=Math.sqrt(e),n=.5*(t-this.radius);this.center.addScaledVector(Ki,n/t),this.radius+=n}return this}union(t){return t.isEmpty()?this:this.isEmpty()?(this.copy(t),this):(!0===this.center.equals(t.center)?this.radius=Math.max(this.radius,t.radius):($i.subVectors(t.center,this.center).setLength(t.radius),this.expandByPoint(Ki.copy(t.center).add($i)),this.expandByPoint(Ki.copy(t.center).sub($i))),this)}equals(t){return t.center.equals(this.center)&&t.radius===this.radius}clone(){return(new this.constructor).copy(this)}}const tr=new Li,er=new Li,nr=new Li,ir=new Li,rr=new Li,sr=new Li,ar=new Li;class or{constructor(t=new Li,e=new Li(0,0,-1)){this.origin=t,this.direction=e}set(t,e){return this.origin.copy(t),this.direction.copy(e),this}copy(t){return this.origin.copy(t.origin),this.direction.copy(t.direction),this}at(t,e){return e.copy(this.origin).addScaledVector(this.direction,t)}lookAt(t){return this.direction.copy(t).sub(this.origin).normalize(),this}recast(t){return this.origin.copy(this.at(t,tr)),this}closestPointToPoint(t,e){e.subVectors(t,this.origin);const n=e.dot(this.direction);return n<0?e.copy(this.origin):e.copy(this.origin).addScaledVector(this.direction,n)}distanceToPoint(t){return Math.sqrt(this.distanceSqToPoint(t))}distanceSqToPoint(t){const e=tr.subVectors(t,this.origin).dot(this.direction);return e<0?this.origin.distanceToSquared(t):(tr.copy(this.origin).addScaledVector(this.direction,e),tr.distanceToSquared(t))}distanceSqToSegment(t,e,n,i){er.copy(t).add(e).multiplyScalar(.5),nr.copy(e).sub(t).normalize(),ir.copy(this.origin).sub(er);const r=.5*t.distanceTo(e),s=-this.direction.dot(nr),a=ir.dot(this.direction),o=-ir.dot(nr),l=ir.lengthSq(),c=Math.abs(1-s*s);let h,u,d,p;if(c>0)if(h=s*o-a,u=s*a-o,p=r*c,h>=0)if(u>=-p)if(u<=p){const t=1/c;h*=t,u*=t,d=h*(h+s*u+2*a)+u*(s*h+u+2*o)+l}else u=r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u=-r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u<=-p?(h=Math.max(0,-(-s*r+a)),u=h>0?-r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l):u<=p?(h=0,u=Math.min(Math.max(-r,-o),r),d=u*(u+2*o)+l):(h=Math.max(0,-(s*r+a)),u=h>0?r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l);else u=s>0?-r:r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;return n&&n.copy(this.origin).addScaledVector(this.direction,h),i&&i.copy(er).addScaledVector(nr,u),d}intersectSphere(t,e){tr.subVectors(t.center,this.origin);const n=tr.dot(this.direction),i=tr.dot(tr)-n*n,r=t.radius*t.radius;if(i>r)return null;const s=Math.sqrt(r-i),a=n-s,o=n+s;return o<0?null:a<0?this.at(o,e):this.at(a,e)}intersectsSphere(t){return this.distanceSqToPoint(t.center)<=t.radius*t.radius}distanceToPlane(t){const e=t.normal.dot(this.direction);if(0===e)return 0===t.distanceToPoint(this.origin)?0:null;const n=-(this.origin.dot(t.normal)+t.constant)/e;return n>=0?n:null}intersectPlane(t,e){const n=this.distanceToPlane(t);return null===n?null:this.at(n,e)}intersectsPlane(t){const e=t.distanceToPoint(this.origin);if(0===e)return!0;return t.normal.dot(this.direction)*e<0}intersectBox(t,e){let n,i,r,s,a,o;const l=1/this.direction.x,c=1/this.direction.y,h=1/this.direction.z,u=this.origin;return l>=0?(n=(t.min.x-u.x)*l,i=(t.max.x-u.x)*l):(n=(t.max.x-u.x)*l,i=(t.min.x-u.x)*l),c>=0?(r=(t.min.y-u.y)*c,s=(t.max.y-u.y)*c):(r=(t.max.y-u.y)*c,s=(t.min.y-u.y)*c),n>s||r>i?null:((r>n||isNaN(n))&&(n=r),(s=0?(a=(t.min.z-u.z)*h,o=(t.max.z-u.z)*h):(a=(t.max.z-u.z)*h,o=(t.min.z-u.z)*h),n>o||a>i?null:((a>n||n!=n)&&(n=a),(o=0?n:i,e)))}intersectsBox(t){return null!==this.intersectBox(t,tr)}intersectTriangle(t,e,n,i,r){rr.subVectors(e,t),sr.subVectors(n,t),ar.crossVectors(rr,sr);let s,a=this.direction.dot(ar);if(a>0){if(i)return null;s=1}else{if(!(a<0))return null;s=-1,a=-a}ir.subVectors(this.origin,t);const o=s*this.direction.dot(sr.crossVectors(ir,sr));if(o<0)return null;const l=s*this.direction.dot(rr.cross(ir));if(l<0)return null;if(o+l>a)return null;const c=-s*ir.dot(ar);return c<0?null:this.at(c/a,r)}applyMatrix4(t){return this.origin.applyMatrix4(t),this.direction.transformDirection(t),this}equals(t){return t.origin.equals(this.origin)&&t.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}class lr{constructor(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f){lr.prototype.isMatrix4=!0,this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],void 0!==t&&this.set(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f)}set(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f){const g=this.elements;return g[0]=t,g[4]=e,g[8]=n,g[12]=i,g[1]=r,g[5]=s,g[9]=a,g[13]=o,g[2]=l,g[6]=c,g[10]=h,g[14]=u,g[3]=d,g[7]=p,g[11]=m,g[15]=f,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return(new lr).fromArray(this.elements)}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],e[9]=n[9],e[10]=n[10],e[11]=n[11],e[12]=n[12],e[13]=n[13],e[14]=n[14],e[15]=n[15],this}copyPosition(t){const e=this.elements,n=t.elements;return e[12]=n[12],e[13]=n[13],e[14]=n[14],this}setFromMatrix3(t){const e=t.elements;return this.set(e[0],e[3],e[6],0,e[1],e[4],e[7],0,e[2],e[5],e[8],0,0,0,0,1),this}extractBasis(t,e,n){return t.setFromMatrixColumn(this,0),e.setFromMatrixColumn(this,1),n.setFromMatrixColumn(this,2),this}makeBasis(t,e,n){return this.set(t.x,e.x,n.x,0,t.y,e.y,n.y,0,t.z,e.z,n.z,0,0,0,0,1),this}extractRotation(t){const e=this.elements,n=t.elements,i=1/cr.setFromMatrixColumn(t,0).length(),r=1/cr.setFromMatrixColumn(t,1).length(),s=1/cr.setFromMatrixColumn(t,2).length();return e[0]=n[0]*i,e[1]=n[1]*i,e[2]=n[2]*i,e[3]=0,e[4]=n[4]*r,e[5]=n[5]*r,e[6]=n[6]*r,e[7]=0,e[8]=n[8]*s,e[9]=n[9]*s,e[10]=n[10]*s,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromEuler(t){const e=this.elements,n=t.x,i=t.y,r=t.z,s=Math.cos(n),a=Math.sin(n),o=Math.cos(i),l=Math.sin(i),c=Math.cos(r),h=Math.sin(r);if("XYZ"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=-o*h,e[8]=l,e[1]=n+i*l,e[5]=t-r*l,e[9]=-a*o,e[2]=r-t*l,e[6]=i+n*l,e[10]=s*o}else if("YXZ"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t+r*a,e[4]=i*a-n,e[8]=s*l,e[1]=s*h,e[5]=s*c,e[9]=-a,e[2]=n*a-i,e[6]=r+t*a,e[10]=s*o}else if("ZXY"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t-r*a,e[4]=-s*h,e[8]=i+n*a,e[1]=n+i*a,e[5]=s*c,e[9]=r-t*a,e[2]=-s*l,e[6]=a,e[10]=s*o}else if("ZYX"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=i*l-n,e[8]=t*l+r,e[1]=o*h,e[5]=r*l+t,e[9]=n*l-i,e[2]=-l,e[6]=a*o,e[10]=s*o}else if("YZX"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=r-t*h,e[8]=i*h+n,e[1]=h,e[5]=s*c,e[9]=-a*c,e[2]=-l*c,e[6]=n*h+i,e[10]=t-r*h}else if("XZY"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=-h,e[8]=l*c,e[1]=t*h+r,e[5]=s*c,e[9]=n*h-i,e[2]=i*h-n,e[6]=a*c,e[10]=r*h+t}return e[3]=0,e[7]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromQuaternion(t){return this.compose(ur,t,dr)}lookAt(t,e,n){const i=this.elements;return fr.subVectors(t,e),0===fr.lengthSq()&&(fr.z=1),fr.normalize(),pr.crossVectors(n,fr),0===pr.lengthSq()&&(1===Math.abs(n.z)?fr.x+=1e-4:fr.z+=1e-4,fr.normalize(),pr.crossVectors(n,fr)),pr.normalize(),mr.crossVectors(fr,pr),i[0]=pr.x,i[4]=mr.x,i[8]=fr.x,i[1]=pr.y,i[5]=mr.y,i[9]=fr.y,i[2]=pr.z,i[6]=mr.z,i[10]=fr.z,this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[4],o=n[8],l=n[12],c=n[1],h=n[5],u=n[9],d=n[13],p=n[2],m=n[6],f=n[10],g=n[14],v=n[3],_=n[7],x=n[11],y=n[15],M=i[0],S=i[4],b=i[8],w=i[12],T=i[1],E=i[5],A=i[9],R=i[13],C=i[2],P=i[6],I=i[10],L=i[14],U=i[3],N=i[7],D=i[11],O=i[15];return r[0]=s*M+a*T+o*C+l*U,r[4]=s*S+a*E+o*P+l*N,r[8]=s*b+a*A+o*I+l*D,r[12]=s*w+a*R+o*L+l*O,r[1]=c*M+h*T+u*C+d*U,r[5]=c*S+h*E+u*P+d*N,r[9]=c*b+h*A+u*I+d*D,r[13]=c*w+h*R+u*L+d*O,r[2]=p*M+m*T+f*C+g*U,r[6]=p*S+m*E+f*P+g*N,r[10]=p*b+m*A+f*I+g*D,r[14]=p*w+m*R+f*L+g*O,r[3]=v*M+_*T+x*C+y*U,r[7]=v*S+_*E+x*P+y*N,r[11]=v*b+_*A+x*I+y*D,r[15]=v*w+_*R+x*L+y*O,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[4]*=t,e[8]*=t,e[12]*=t,e[1]*=t,e[5]*=t,e[9]*=t,e[13]*=t,e[2]*=t,e[6]*=t,e[10]*=t,e[14]*=t,e[3]*=t,e[7]*=t,e[11]*=t,e[15]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[4],i=t[8],r=t[12],s=t[1],a=t[5],o=t[9],l=t[13],c=t[2],h=t[6],u=t[10],d=t[14];return t[3]*(+r*o*h-i*l*h-r*a*u+n*l*u+i*a*d-n*o*d)+t[7]*(+e*o*d-e*l*u+r*s*u-i*s*d+i*l*c-r*o*c)+t[11]*(+e*l*h-e*a*d-r*s*h+n*s*d+r*a*c-n*l*c)+t[15]*(-i*a*c-e*o*h+e*a*u+i*s*h-n*s*u+n*o*c)}transpose(){const t=this.elements;let e;return e=t[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this}setPosition(t,e,n){const i=this.elements;return t.isVector3?(i[12]=t.x,i[13]=t.y,i[14]=t.z):(i[12]=t,i[13]=e,i[14]=n),this}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=t[9],u=t[10],d=t[11],p=t[12],m=t[13],f=t[14],g=t[15],v=h*f*l-m*u*l+m*o*d-a*f*d-h*o*g+a*u*g,_=p*u*l-c*f*l-p*o*d+s*f*d+c*o*g-s*u*g,x=c*m*l-p*h*l+p*a*d-s*m*d-c*a*g+s*h*g,y=p*h*o-c*m*o-p*a*u+s*m*u+c*a*f-s*h*f,M=e*v+n*_+i*x+r*y;if(0===M)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const S=1/M;return t[0]=v*S,t[1]=(m*u*r-h*f*r-m*i*d+n*f*d+h*i*g-n*u*g)*S,t[2]=(a*f*r-m*o*r+m*i*l-n*f*l-a*i*g+n*o*g)*S,t[3]=(h*o*r-a*u*r-h*i*l+n*u*l+a*i*d-n*o*d)*S,t[4]=_*S,t[5]=(c*f*r-p*u*r+p*i*d-e*f*d-c*i*g+e*u*g)*S,t[6]=(p*o*r-s*f*r-p*i*l+e*f*l+s*i*g-e*o*g)*S,t[7]=(s*u*r-c*o*r+c*i*l-e*u*l-s*i*d+e*o*d)*S,t[8]=x*S,t[9]=(p*h*r-c*m*r-p*n*d+e*m*d+c*n*g-e*h*g)*S,t[10]=(s*m*r-p*a*r+p*n*l-e*m*l-s*n*g+e*a*g)*S,t[11]=(c*a*r-s*h*r-c*n*l+e*h*l+s*n*d-e*a*d)*S,t[12]=y*S,t[13]=(c*m*i-p*h*i+p*n*u-e*m*u-c*n*f+e*h*f)*S,t[14]=(p*a*i-s*m*i-p*n*o+e*m*o+s*n*f-e*a*f)*S,t[15]=(s*h*i-c*a*i+c*n*o-e*h*o-s*n*u+e*a*u)*S,this}scale(t){const e=this.elements,n=t.x,i=t.y,r=t.z;return e[0]*=n,e[4]*=i,e[8]*=r,e[1]*=n,e[5]*=i,e[9]*=r,e[2]*=n,e[6]*=i,e[10]*=r,e[3]*=n,e[7]*=i,e[11]*=r,this}getMaxScaleOnAxis(){const t=this.elements,e=t[0]*t[0]+t[1]*t[1]+t[2]*t[2],n=t[4]*t[4]+t[5]*t[5]+t[6]*t[6],i=t[8]*t[8]+t[9]*t[9]+t[10]*t[10];return Math.sqrt(Math.max(e,n,i))}makeTranslation(t,e,n){return t.isVector3?this.set(1,0,0,t.x,0,1,0,t.y,0,0,1,t.z,0,0,0,1):this.set(1,0,0,t,0,1,0,e,0,0,1,n,0,0,0,1),this}makeRotationX(t){const e=Math.cos(t),n=Math.sin(t);return this.set(1,0,0,0,0,e,-n,0,0,n,e,0,0,0,0,1),this}makeRotationY(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,0,n,0,0,1,0,0,-n,0,e,0,0,0,0,1),this}makeRotationZ(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,0,n,e,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(t,e){const n=Math.cos(e),i=Math.sin(e),r=1-n,s=t.x,a=t.y,o=t.z,l=r*s,c=r*a;return this.set(l*s+n,l*a-i*o,l*o+i*a,0,l*a+i*o,c*a+n,c*o-i*s,0,l*o-i*a,c*o+i*s,r*o*o+n,0,0,0,0,1),this}makeScale(t,e,n){return this.set(t,0,0,0,0,e,0,0,0,0,n,0,0,0,0,1),this}makeShear(t,e,n,i,r,s){return this.set(1,n,r,0,t,1,s,0,e,i,1,0,0,0,0,1),this}compose(t,e,n){const i=this.elements,r=e._x,s=e._y,a=e._z,o=e._w,l=r+r,c=s+s,h=a+a,u=r*l,d=r*c,p=r*h,m=s*c,f=s*h,g=a*h,v=o*l,_=o*c,x=o*h,y=n.x,M=n.y,S=n.z;return i[0]=(1-(m+g))*y,i[1]=(d+x)*y,i[2]=(p-_)*y,i[3]=0,i[4]=(d-x)*M,i[5]=(1-(u+g))*M,i[6]=(f+v)*M,i[7]=0,i[8]=(p+_)*S,i[9]=(f-v)*S,i[10]=(1-(u+m))*S,i[11]=0,i[12]=t.x,i[13]=t.y,i[14]=t.z,i[15]=1,this}decompose(t,e,n){const i=this.elements;let r=cr.set(i[0],i[1],i[2]).length();const s=cr.set(i[4],i[5],i[6]).length(),a=cr.set(i[8],i[9],i[10]).length();this.determinant()<0&&(r=-r),t.x=i[12],t.y=i[13],t.z=i[14],hr.copy(this);const o=1/r,l=1/s,c=1/a;return hr.elements[0]*=o,hr.elements[1]*=o,hr.elements[2]*=o,hr.elements[4]*=l,hr.elements[5]*=l,hr.elements[6]*=l,hr.elements[8]*=c,hr.elements[9]*=c,hr.elements[10]*=c,e.setFromRotationMatrix(hr),n.x=r,n.y=s,n.z=a,this}makePerspective(t,e,n,i,r,s,a=2e3){const o=this.elements,l=2*r/(e-t),c=2*r/(n-i),h=(e+t)/(e-t),u=(n+i)/(n-i);let d,p;if(a===kn)d=-(s+r)/(s-r),p=-2*s*r/(s-r);else{if(a!==Vn)throw new Error("THREE.Matrix4.makePerspective(): Invalid coordinate system: "+a);d=-s/(s-r),p=-s*r/(s-r)}return o[0]=l,o[4]=0,o[8]=h,o[12]=0,o[1]=0,o[5]=c,o[9]=u,o[13]=0,o[2]=0,o[6]=0,o[10]=d,o[14]=p,o[3]=0,o[7]=0,o[11]=-1,o[15]=0,this}makeOrthographic(t,e,n,i,r,s,a=2e3){const o=this.elements,l=1/(e-t),c=1/(n-i),h=1/(s-r),u=(e+t)*l,d=(n+i)*c;let p,m;if(a===kn)p=(s+r)*h,m=-2*h;else{if(a!==Vn)throw new Error("THREE.Matrix4.makeOrthographic(): Invalid coordinate system: "+a);p=r*h,m=-1*h}return o[0]=2*l,o[4]=0,o[8]=0,o[12]=-u,o[1]=0,o[5]=2*c,o[9]=0,o[13]=-d,o[2]=0,o[6]=0,o[10]=m,o[14]=-p,o[3]=0,o[7]=0,o[11]=0,o[15]=1,this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<16;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<16;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t[e+9]=n[9],t[e+10]=n[10],t[e+11]=n[11],t[e+12]=n[12],t[e+13]=n[13],t[e+14]=n[14],t[e+15]=n[15],t}}const cr=new Li,hr=new lr,ur=new Li(0,0,0),dr=new Li(1,1,1),pr=new Li,mr=new Li,fr=new Li,gr=new lr,vr=new Ii;class _r{constructor(t=0,e=0,n=0,i=_r.DEFAULT_ORDER){this.isEuler=!0,this._x=t,this._y=e,this._z=n,this._order=i}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get order(){return this._order}set order(t){this._order=t,this._onChangeCallback()}set(t,e,n,i=this._order){return this._x=t,this._y=e,this._z=n,this._order=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._order)}copy(t){return this._x=t._x,this._y=t._y,this._z=t._z,this._order=t._order,this._onChangeCallback(),this}setFromRotationMatrix(t,e=this._order,n=!0){const i=t.elements,r=i[0],s=i[4],a=i[8],o=i[1],l=i[5],c=i[9],h=i[2],u=i[6],d=i[10];switch(e){case"XYZ":this._y=Math.asin(Yn(a,-1,1)),Math.abs(a)<.9999999?(this._x=Math.atan2(-c,d),this._z=Math.atan2(-s,r)):(this._x=Math.atan2(u,l),this._z=0);break;case"YXZ":this._x=Math.asin(-Yn(c,-1,1)),Math.abs(c)<.9999999?(this._y=Math.atan2(a,d),this._z=Math.atan2(o,l)):(this._y=Math.atan2(-h,r),this._z=0);break;case"ZXY":this._x=Math.asin(Yn(u,-1,1)),Math.abs(u)<.9999999?(this._y=Math.atan2(-h,d),this._z=Math.atan2(-s,l)):(this._y=0,this._z=Math.atan2(o,r));break;case"ZYX":this._y=Math.asin(-Yn(h,-1,1)),Math.abs(h)<.9999999?(this._x=Math.atan2(u,d),this._z=Math.atan2(o,r)):(this._x=0,this._z=Math.atan2(-s,l));break;case"YZX":this._z=Math.asin(Yn(o,-1,1)),Math.abs(o)<.9999999?(this._x=Math.atan2(-c,l),this._y=Math.atan2(-h,r)):(this._x=0,this._y=Math.atan2(a,d));break;case"XZY":this._z=Math.asin(-Yn(s,-1,1)),Math.abs(s)<.9999999?(this._x=Math.atan2(u,l),this._y=Math.atan2(a,r)):(this._x=Math.atan2(-c,d),this._y=0);break;default:console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: "+e)}return this._order=e,!0===n&&this._onChangeCallback(),this}setFromQuaternion(t,e,n){return gr.makeRotationFromQuaternion(t),this.setFromRotationMatrix(gr,e,n)}setFromVector3(t,e=this._order){return this.set(t.x,t.y,t.z,e)}reorder(t){return vr.setFromEuler(this),this.setFromQuaternion(vr,t)}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._order===this._order}fromArray(t){return this._x=t[0],this._y=t[1],this._z=t[2],void 0!==t[3]&&(this._order=t[3]),this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._order,t}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._order}}_r.DEFAULT_ORDER="XYZ";class xr{constructor(){this.mask=1}set(t){this.mask=(1<>>0}enable(t){this.mask|=1<1){for(let t=0;t1){for(let t=0;t0&&(i.userData=this.userData),i.layers=this.layers.mask,i.matrix=this.matrix.toArray(),i.up=this.up.toArray(),!1===this.matrixAutoUpdate&&(i.matrixAutoUpdate=!1),this.isInstancedMesh&&(i.type="InstancedMesh",i.count=this.count,i.instanceMatrix=this.instanceMatrix.toJSON(),null!==this.instanceColor&&(i.instanceColor=this.instanceColor.toJSON())),this.isBatchedMesh&&(i.type="BatchedMesh",i.perObjectFrustumCulled=this.perObjectFrustumCulled,i.sortObjects=this.sortObjects,i.drawRanges=this._drawRanges,i.reservedRanges=this._reservedRanges,i.visibility=this._visibility,i.active=this._active,i.bounds=this._bounds.map((t=>({boxInitialized:t.boxInitialized,boxMin:t.box.min.toArray(),boxMax:t.box.max.toArray(),sphereInitialized:t.sphereInitialized,sphereRadius:t.sphere.radius,sphereCenter:t.sphere.center.toArray()}))),i.maxInstanceCount=this._maxInstanceCount,i.maxVertexCount=this._maxVertexCount,i.maxIndexCount=this._maxIndexCount,i.geometryInitialized=this._geometryInitialized,i.geometryCount=this._geometryCount,i.matricesTexture=this._matricesTexture.toJSON(t),null!==this._colorsTexture&&(i.colorsTexture=this._colorsTexture.toJSON(t)),null!==this.boundingSphere&&(i.boundingSphere={center:i.boundingSphere.center.toArray(),radius:i.boundingSphere.radius}),null!==this.boundingBox&&(i.boundingBox={min:i.boundingBox.min.toArray(),max:i.boundingBox.max.toArray()})),this.isScene)this.background&&(this.background.isColor?i.background=this.background.toJSON():this.background.isTexture&&(i.background=this.background.toJSON(t).uuid)),this.environment&&this.environment.isTexture&&!0!==this.environment.isRenderTargetTexture&&(i.environment=this.environment.toJSON(t).uuid);else if(this.isMesh||this.isLine||this.isPoints){i.geometry=r(t.geometries,this.geometry);const e=this.geometry.parameters;if(void 0!==e&&void 0!==e.shapes){const n=e.shapes;if(Array.isArray(n))for(let e=0,i=n.length;e0){i.children=[];for(let e=0;e0){i.animations=[];for(let e=0;e0&&(n.geometries=e),i.length>0&&(n.materials=i),r.length>0&&(n.textures=r),a.length>0&&(n.images=a),o.length>0&&(n.shapes=o),l.length>0&&(n.skeletons=l),c.length>0&&(n.animations=c),h.length>0&&(n.nodes=h)}return n.object=i,n;function s(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}}clone(t){return(new this.constructor).copy(this,t)}copy(t,e=!0){if(this.name=t.name,this.up.copy(t.up),this.position.copy(t.position),this.rotation.order=t.rotation.order,this.quaternion.copy(t.quaternion),this.scale.copy(t.scale),this.matrix.copy(t.matrix),this.matrixWorld.copy(t.matrixWorld),this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrixWorldAutoUpdate=t.matrixWorldAutoUpdate,this.matrixWorldNeedsUpdate=t.matrixWorldNeedsUpdate,this.layers.mask=t.layers.mask,this.visible=t.visible,this.castShadow=t.castShadow,this.receiveShadow=t.receiveShadow,this.frustumCulled=t.frustumCulled,this.renderOrder=t.renderOrder,this.animations=t.animations.slice(),this.userData=JSON.parse(JSON.stringify(t.userData)),!0===e)for(let e=0;e0?i.multiplyScalar(1/Math.sqrt(r)):i.set(0,0,0)}static getBarycoord(t,e,n,i,r){Or.subVectors(i,e),Fr.subVectors(n,e),Br.subVectors(t,e);const s=Or.dot(Or),a=Or.dot(Fr),o=Or.dot(Br),l=Fr.dot(Fr),c=Fr.dot(Br),h=s*l-a*a;if(0===h)return r.set(0,0,0),null;const u=1/h,d=(l*o-a*c)*u,p=(s*c-a*o)*u;return r.set(1-d-p,p,d)}static containsPoint(t,e,n,i){return null!==this.getBarycoord(t,e,n,i,zr)&&(zr.x>=0&&zr.y>=0&&zr.x+zr.y<=1)}static getInterpolation(t,e,n,i,r,s,a,o){return null===this.getBarycoord(t,e,n,i,zr)?(o.x=0,o.y=0,"z"in o&&(o.z=0),"w"in o&&(o.w=0),null):(o.setScalar(0),o.addScaledVector(r,zr.x),o.addScaledVector(s,zr.y),o.addScaledVector(a,zr.z),o)}static getInterpolatedAttribute(t,e,n,i,r,s){return jr.setScalar(0),qr.setScalar(0),Yr.setScalar(0),jr.fromBufferAttribute(t,e),qr.fromBufferAttribute(t,n),Yr.fromBufferAttribute(t,i),s.setScalar(0),s.addScaledVector(jr,r.x),s.addScaledVector(qr,r.y),s.addScaledVector(Yr,r.z),s}static isFrontFacing(t,e,n,i){return Or.subVectors(n,e),Fr.subVectors(t,e),Or.cross(Fr).dot(i)<0}set(t,e,n){return this.a.copy(t),this.b.copy(e),this.c.copy(n),this}setFromPointsAndIndices(t,e,n,i){return this.a.copy(t[e]),this.b.copy(t[n]),this.c.copy(t[i]),this}setFromAttributeAndIndices(t,e,n,i){return this.a.fromBufferAttribute(t,e),this.b.fromBufferAttribute(t,n),this.c.fromBufferAttribute(t,i),this}clone(){return(new this.constructor).copy(this)}copy(t){return this.a.copy(t.a),this.b.copy(t.b),this.c.copy(t.c),this}getArea(){return Or.subVectors(this.c,this.b),Fr.subVectors(this.a,this.b),.5*Or.cross(Fr).length()}getMidpoint(t){return t.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(t){return Zr.getNormal(this.a,this.b,this.c,t)}getPlane(t){return t.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(t,e){return Zr.getBarycoord(t,this.a,this.b,this.c,e)}getInterpolation(t,e,n,i,r){return Zr.getInterpolation(t,this.a,this.b,this.c,e,n,i,r)}containsPoint(t){return Zr.containsPoint(t,this.a,this.b,this.c)}isFrontFacing(t){return Zr.isFrontFacing(this.a,this.b,this.c,t)}intersectsBox(t){return t.intersectsTriangle(this)}closestPointToPoint(t,e){const n=this.a,i=this.b,r=this.c;let s,a;kr.subVectors(i,n),Vr.subVectors(r,n),Gr.subVectors(t,n);const o=kr.dot(Gr),l=Vr.dot(Gr);if(o<=0&&l<=0)return e.copy(n);Wr.subVectors(t,i);const c=kr.dot(Wr),h=Vr.dot(Wr);if(c>=0&&h<=c)return e.copy(i);const u=o*h-c*l;if(u<=0&&o>=0&&c<=0)return s=o/(o-c),e.copy(n).addScaledVector(kr,s);Xr.subVectors(t,r);const d=kr.dot(Xr),p=Vr.dot(Xr);if(p>=0&&d<=p)return e.copy(r);const m=d*l-o*p;if(m<=0&&l>=0&&p<=0)return a=l/(l-p),e.copy(n).addScaledVector(Vr,a);const f=c*p-d*h;if(f<=0&&h-c>=0&&d-p>=0)return Hr.subVectors(r,i),a=(h-c)/(h-c+(d-p)),e.copy(i).addScaledVector(Hr,a);const g=1/(f+m+u);return s=m*g,a=u*g,e.copy(n).addScaledVector(kr,s).addScaledVector(Vr,a)}equals(t){return t.a.equals(this.a)&&t.b.equals(this.b)&&t.c.equals(this.c)}}const Jr={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},Kr={h:0,s:0,l:0},$r={h:0,s:0,l:0};function Qr(t,e,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+6*(e-t)*(2/3-n):t}class ts{constructor(t,e,n){return this.isColor=!0,this.r=1,this.g=1,this.b=1,this.set(t,e,n)}set(t,e,n){if(void 0===e&&void 0===n){const e=t;e&&e.isColor?this.copy(e):"number"==typeof e?this.setHex(e):"string"==typeof e&&this.setStyle(e)}else this.setRGB(t,e,n);return this}setScalar(t){return this.r=t,this.g=t,this.b=t,this}setHex(t,e=Je){return t=Math.floor(t),this.r=(t>>16&255)/255,this.g=(t>>8&255)/255,this.b=(255&t)/255,mi.toWorkingColorSpace(this,e),this}setRGB(t,e,n,i=mi.workingColorSpace){return this.r=t,this.g=e,this.b=n,mi.toWorkingColorSpace(this,i),this}setHSL(t,e,n,i=mi.workingColorSpace){if(t=Zn(t,1),e=Yn(e,0,1),n=Yn(n,0,1),0===e)this.r=this.g=this.b=n;else{const i=n<=.5?n*(1+e):n+e-n*e,r=2*n-i;this.r=Qr(r,i,t+1/3),this.g=Qr(r,i,t),this.b=Qr(r,i,t-1/3)}return mi.toWorkingColorSpace(this,i),this}setStyle(t,e=Je){function n(e){void 0!==e&&parseFloat(e)<1&&console.warn("THREE.Color: Alpha component of "+t+" will be ignored.")}let i;if(i=/^(\w+)\(([^\)]*)\)/.exec(t)){let r;const s=i[1],a=i[2];switch(s){case"rgb":case"rgba":if(r=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setRGB(Math.min(255,parseInt(r[1],10))/255,Math.min(255,parseInt(r[2],10))/255,Math.min(255,parseInt(r[3],10))/255,e);if(r=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setRGB(Math.min(100,parseInt(r[1],10))/100,Math.min(100,parseInt(r[2],10))/100,Math.min(100,parseInt(r[3],10))/100,e);break;case"hsl":case"hsla":if(r=/^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setHSL(parseFloat(r[1])/360,parseFloat(r[2])/100,parseFloat(r[3])/100,e);break;default:console.warn("THREE.Color: Unknown color model "+t)}}else if(i=/^\#([A-Fa-f\d]+)$/.exec(t)){const n=i[1],r=n.length;if(3===r)return this.setRGB(parseInt(n.charAt(0),16)/15,parseInt(n.charAt(1),16)/15,parseInt(n.charAt(2),16)/15,e);if(6===r)return this.setHex(parseInt(n,16),e);console.warn("THREE.Color: Invalid hex color "+t)}else if(t&&t.length>0)return this.setColorName(t,e);return this}setColorName(t,e=Je){const n=Jr[t.toLowerCase()];return void 0!==n?this.setHex(n,e):console.warn("THREE.Color: Unknown color "+t),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(t){return this.r=t.r,this.g=t.g,this.b=t.b,this}copySRGBToLinear(t){return this.r=fi(t.r),this.g=fi(t.g),this.b=fi(t.b),this}copyLinearToSRGB(t){return this.r=gi(t.r),this.g=gi(t.g),this.b=gi(t.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(t=Je){return mi.fromWorkingColorSpace(es.copy(this),t),65536*Math.round(Yn(255*es.r,0,255))+256*Math.round(Yn(255*es.g,0,255))+Math.round(Yn(255*es.b,0,255))}getHexString(t=Je){return("000000"+this.getHex(t).toString(16)).slice(-6)}getHSL(t,e=mi.workingColorSpace){mi.fromWorkingColorSpace(es.copy(this),e);const n=es.r,i=es.g,r=es.b,s=Math.max(n,i,r),a=Math.min(n,i,r);let o,l;const c=(a+s)/2;if(a===s)o=0,l=0;else{const t=s-a;switch(l=c<=.5?t/(s+a):t/(2-s-a),s){case n:o=(i-r)/t+(i0!=t>0&&this.version++,this._alphaTest=t}onBeforeRender(){}onBeforeCompile(){}customProgramCacheKey(){return this.onBeforeCompile.toString()}setValues(t){if(void 0!==t)for(const e in t){const n=t[e];if(void 0===n){console.warn(`THREE.Material: parameter '${e}' has value of undefined.`);continue}const i=this[e];void 0!==i?i&&i.isColor?i.set(n):i&&i.isVector3&&n&&n.isVector3?i.copy(n):this[e]=n:console.warn(`THREE.Material: '${e}' is not a property of THREE.${this.type}.`)}}toJSON(t){const e=void 0===t||"string"==typeof t;e&&(t={textures:{},images:{}});const n={metadata:{version:4.6,type:"Material",generator:"Material.toJSON"}};function i(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}if(n.uuid=this.uuid,n.type=this.type,""!==this.name&&(n.name=this.name),this.color&&this.color.isColor&&(n.color=this.color.getHex()),void 0!==this.roughness&&(n.roughness=this.roughness),void 0!==this.metalness&&(n.metalness=this.metalness),void 0!==this.sheen&&(n.sheen=this.sheen),this.sheenColor&&this.sheenColor.isColor&&(n.sheenColor=this.sheenColor.getHex()),void 0!==this.sheenRoughness&&(n.sheenRoughness=this.sheenRoughness),this.emissive&&this.emissive.isColor&&(n.emissive=this.emissive.getHex()),void 0!==this.emissiveIntensity&&1!==this.emissiveIntensity&&(n.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(n.specular=this.specular.getHex()),void 0!==this.specularIntensity&&(n.specularIntensity=this.specularIntensity),this.specularColor&&this.specularColor.isColor&&(n.specularColor=this.specularColor.getHex()),void 0!==this.shininess&&(n.shininess=this.shininess),void 0!==this.clearcoat&&(n.clearcoat=this.clearcoat),void 0!==this.clearcoatRoughness&&(n.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(n.clearcoatMap=this.clearcoatMap.toJSON(t).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(n.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(t).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(n.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(t).uuid,n.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),void 0!==this.dispersion&&(n.dispersion=this.dispersion),void 0!==this.iridescence&&(n.iridescence=this.iridescence),void 0!==this.iridescenceIOR&&(n.iridescenceIOR=this.iridescenceIOR),void 0!==this.iridescenceThicknessRange&&(n.iridescenceThicknessRange=this.iridescenceThicknessRange),this.iridescenceMap&&this.iridescenceMap.isTexture&&(n.iridescenceMap=this.iridescenceMap.toJSON(t).uuid),this.iridescenceThicknessMap&&this.iridescenceThicknessMap.isTexture&&(n.iridescenceThicknessMap=this.iridescenceThicknessMap.toJSON(t).uuid),void 0!==this.anisotropy&&(n.anisotropy=this.anisotropy),void 0!==this.anisotropyRotation&&(n.anisotropyRotation=this.anisotropyRotation),this.anisotropyMap&&this.anisotropyMap.isTexture&&(n.anisotropyMap=this.anisotropyMap.toJSON(t).uuid),this.map&&this.map.isTexture&&(n.map=this.map.toJSON(t).uuid),this.matcap&&this.matcap.isTexture&&(n.matcap=this.matcap.toJSON(t).uuid),this.alphaMap&&this.alphaMap.isTexture&&(n.alphaMap=this.alphaMap.toJSON(t).uuid),this.lightMap&&this.lightMap.isTexture&&(n.lightMap=this.lightMap.toJSON(t).uuid,n.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(n.aoMap=this.aoMap.toJSON(t).uuid,n.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(n.bumpMap=this.bumpMap.toJSON(t).uuid,n.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(n.normalMap=this.normalMap.toJSON(t).uuid,n.normalMapType=this.normalMapType,n.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(n.displacementMap=this.displacementMap.toJSON(t).uuid,n.displacementScale=this.displacementScale,n.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(n.roughnessMap=this.roughnessMap.toJSON(t).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(n.metalnessMap=this.metalnessMap.toJSON(t).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(n.emissiveMap=this.emissiveMap.toJSON(t).uuid),this.specularMap&&this.specularMap.isTexture&&(n.specularMap=this.specularMap.toJSON(t).uuid),this.specularIntensityMap&&this.specularIntensityMap.isTexture&&(n.specularIntensityMap=this.specularIntensityMap.toJSON(t).uuid),this.specularColorMap&&this.specularColorMap.isTexture&&(n.specularColorMap=this.specularColorMap.toJSON(t).uuid),this.envMap&&this.envMap.isTexture&&(n.envMap=this.envMap.toJSON(t).uuid,void 0!==this.combine&&(n.combine=this.combine)),void 0!==this.envMapRotation&&(n.envMapRotation=this.envMapRotation.toArray()),void 0!==this.envMapIntensity&&(n.envMapIntensity=this.envMapIntensity),void 0!==this.reflectivity&&(n.reflectivity=this.reflectivity),void 0!==this.refractionRatio&&(n.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(n.gradientMap=this.gradientMap.toJSON(t).uuid),void 0!==this.transmission&&(n.transmission=this.transmission),this.transmissionMap&&this.transmissionMap.isTexture&&(n.transmissionMap=this.transmissionMap.toJSON(t).uuid),void 0!==this.thickness&&(n.thickness=this.thickness),this.thicknessMap&&this.thicknessMap.isTexture&&(n.thicknessMap=this.thicknessMap.toJSON(t).uuid),void 0!==this.attenuationDistance&&this.attenuationDistance!==1/0&&(n.attenuationDistance=this.attenuationDistance),void 0!==this.attenuationColor&&(n.attenuationColor=this.attenuationColor.getHex()),void 0!==this.size&&(n.size=this.size),null!==this.shadowSide&&(n.shadowSide=this.shadowSide),void 0!==this.sizeAttenuation&&(n.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(n.blending=this.blending),this.side!==u&&(n.side=this.side),!0===this.vertexColors&&(n.vertexColors=!0),this.opacity<1&&(n.opacity=this.opacity),!0===this.transparent&&(n.transparent=!0),this.blendSrc!==C&&(n.blendSrc=this.blendSrc),this.blendDst!==P&&(n.blendDst=this.blendDst),this.blendEquation!==y&&(n.blendEquation=this.blendEquation),null!==this.blendSrcAlpha&&(n.blendSrcAlpha=this.blendSrcAlpha),null!==this.blendDstAlpha&&(n.blendDstAlpha=this.blendDstAlpha),null!==this.blendEquationAlpha&&(n.blendEquationAlpha=this.blendEquationAlpha),this.blendColor&&this.blendColor.isColor&&(n.blendColor=this.blendColor.getHex()),0!==this.blendAlpha&&(n.blendAlpha=this.blendAlpha),3!==this.depthFunc&&(n.depthFunc=this.depthFunc),!1===this.depthTest&&(n.depthTest=this.depthTest),!1===this.depthWrite&&(n.depthWrite=this.depthWrite),!1===this.colorWrite&&(n.colorWrite=this.colorWrite),255!==this.stencilWriteMask&&(n.stencilWriteMask=this.stencilWriteMask),519!==this.stencilFunc&&(n.stencilFunc=this.stencilFunc),0!==this.stencilRef&&(n.stencilRef=this.stencilRef),255!==this.stencilFuncMask&&(n.stencilFuncMask=this.stencilFuncMask),this.stencilFail!==an&&(n.stencilFail=this.stencilFail),this.stencilZFail!==an&&(n.stencilZFail=this.stencilZFail),this.stencilZPass!==an&&(n.stencilZPass=this.stencilZPass),!0===this.stencilWrite&&(n.stencilWrite=this.stencilWrite),void 0!==this.rotation&&0!==this.rotation&&(n.rotation=this.rotation),!0===this.polygonOffset&&(n.polygonOffset=!0),0!==this.polygonOffsetFactor&&(n.polygonOffsetFactor=this.polygonOffsetFactor),0!==this.polygonOffsetUnits&&(n.polygonOffsetUnits=this.polygonOffsetUnits),void 0!==this.linewidth&&1!==this.linewidth&&(n.linewidth=this.linewidth),void 0!==this.dashSize&&(n.dashSize=this.dashSize),void 0!==this.gapSize&&(n.gapSize=this.gapSize),void 0!==this.scale&&(n.scale=this.scale),!0===this.dithering&&(n.dithering=!0),this.alphaTest>0&&(n.alphaTest=this.alphaTest),!0===this.alphaHash&&(n.alphaHash=!0),!0===this.alphaToCoverage&&(n.alphaToCoverage=!0),!0===this.premultipliedAlpha&&(n.premultipliedAlpha=!0),!0===this.forceSinglePass&&(n.forceSinglePass=!0),!0===this.wireframe&&(n.wireframe=!0),this.wireframeLinewidth>1&&(n.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(n.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(n.wireframeLinejoin=this.wireframeLinejoin),!0===this.flatShading&&(n.flatShading=!0),!1===this.visible&&(n.visible=!1),!1===this.toneMapped&&(n.toneMapped=!1),!1===this.fog&&(n.fog=!1),Object.keys(this.userData).length>0&&(n.userData=this.userData),e){const e=i(t.textures),r=i(t.images);e.length>0&&(n.textures=e),r.length>0&&(n.images=r)}return n}clone(){return(new this.constructor).copy(this)}copy(t){this.name=t.name,this.blending=t.blending,this.side=t.side,this.vertexColors=t.vertexColors,this.opacity=t.opacity,this.transparent=t.transparent,this.blendSrc=t.blendSrc,this.blendDst=t.blendDst,this.blendEquation=t.blendEquation,this.blendSrcAlpha=t.blendSrcAlpha,this.blendDstAlpha=t.blendDstAlpha,this.blendEquationAlpha=t.blendEquationAlpha,this.blendColor.copy(t.blendColor),this.blendAlpha=t.blendAlpha,this.depthFunc=t.depthFunc,this.depthTest=t.depthTest,this.depthWrite=t.depthWrite,this.stencilWriteMask=t.stencilWriteMask,this.stencilFunc=t.stencilFunc,this.stencilRef=t.stencilRef,this.stencilFuncMask=t.stencilFuncMask,this.stencilFail=t.stencilFail,this.stencilZFail=t.stencilZFail,this.stencilZPass=t.stencilZPass,this.stencilWrite=t.stencilWrite;const e=t.clippingPlanes;let n=null;if(null!==e){const t=e.length;n=new Array(t);for(let i=0;i!==t;++i)n[i]=e[i].clone()}return this.clippingPlanes=n,this.clipIntersection=t.clipIntersection,this.clipShadows=t.clipShadows,this.shadowSide=t.shadowSide,this.colorWrite=t.colorWrite,this.precision=t.precision,this.polygonOffset=t.polygonOffset,this.polygonOffsetFactor=t.polygonOffsetFactor,this.polygonOffsetUnits=t.polygonOffsetUnits,this.dithering=t.dithering,this.alphaTest=t.alphaTest,this.alphaHash=t.alphaHash,this.alphaToCoverage=t.alphaToCoverage,this.premultipliedAlpha=t.premultipliedAlpha,this.forceSinglePass=t.forceSinglePass,this.visible=t.visible,this.toneMapped=t.toneMapped,this.userData=JSON.parse(JSON.stringify(t.userData)),this}dispose(){this.dispatchEvent({type:"dispose"})}set needsUpdate(t){!0===t&&this.version++}onBuild(){console.warn("Material: onBuild() has been removed.")}}class rs extends is{constructor(t){super(),this.isMeshBasicMaterial=!0,this.type="MeshBasicMaterial",this.color=new ts(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new _r,this.combine=Y,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}const ss=as();function as(){const t=new ArrayBuffer(4),e=new Float32Array(t),n=new Uint32Array(t),i=new Uint32Array(512),r=new Uint32Array(512);for(let t=0;t<256;++t){const e=t-127;e<-27?(i[t]=0,i[256|t]=32768,r[t]=24,r[256|t]=24):e<-14?(i[t]=1024>>-e-14,i[256|t]=1024>>-e-14|32768,r[t]=-e-1,r[256|t]=-e-1):e<=15?(i[t]=e+15<<10,i[256|t]=e+15<<10|32768,r[t]=13,r[256|t]=13):e<128?(i[t]=31744,i[256|t]=64512,r[t]=24,r[256|t]=24):(i[t]=31744,i[256|t]=64512,r[t]=13,r[256|t]=13)}const s=new Uint32Array(2048),a=new Uint32Array(64),o=new Uint32Array(64);for(let t=1;t<1024;++t){let e=t<<13,n=0;for(;0==(8388608&e);)e<<=1,n-=8388608;e&=-8388609,n+=947912704,s[t]=e|n}for(let t=1024;t<2048;++t)s[t]=939524096+(t-1024<<13);for(let t=1;t<31;++t)a[t]=t<<23;a[31]=1199570944,a[32]=2147483648;for(let t=33;t<63;++t)a[t]=2147483648+(t-32<<23);a[63]=3347054592;for(let t=1;t<64;++t)32!==t&&(o[t]=1024);return{floatView:e,uint32View:n,baseTable:i,shiftTable:r,mantissaTable:s,exponentTable:a,offsetTable:o}}function os(t){Math.abs(t)>65504&&console.warn("THREE.DataUtils.toHalfFloat(): Value out of range."),t=Yn(t,-65504,65504),ss.floatView[0]=t;const e=ss.uint32View[0],n=e>>23&511;return ss.baseTable[n]+((8388607&e)>>ss.shiftTable[n])}function ls(t){const e=t>>10;return ss.uint32View[0]=ss.mantissaTable[ss.offsetTable[e]+(1023&t)]+ss.exponentTable[e],ss.floatView[0]}const cs={toHalfFloat:os,fromHalfFloat:ls},hs=new Li,us=new ti;class ds{constructor(t,e,n=!1){if(Array.isArray(t))throw new TypeError("THREE.BufferAttribute: array should be a Typed Array.");this.isBufferAttribute=!0,this.name="",this.array=t,this.itemSize=e,this.count=void 0!==t?t.length/e:0,this.normalized=n,this.usage=Cn,this.updateRanges=[],this.gpuType=Lt,this.version=0}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}addUpdateRange(t,e){this.updateRanges.push({start:t,count:e})}clearUpdateRanges(){this.updateRanges.length=0}copy(t){return this.name=t.name,this.array=new t.array.constructor(t.array),this.itemSize=t.itemSize,this.count=t.count,this.normalized=t.normalized,this.usage=t.usage,this.gpuType=t.gpuType,this}copyAt(t,e,n){t*=this.itemSize,n*=e.itemSize;for(let i=0,r=this.itemSize;i0&&(t.userData=this.userData),void 0!==this.parameters){const e=this.parameters;for(const n in e)void 0!==e[n]&&(t[n]=e[n]);return t}t.data={attributes:{}};const e=this.index;null!==e&&(t.data.index={type:e.array.constructor.name,array:Array.prototype.slice.call(e.array)});const n=this.attributes;for(const e in n){const i=n[e];t.data.attributes[e]=i.toJSON(t.data)}const i={};let r=!1;for(const e in this.morphAttributes){const n=this.morphAttributes[e],s=[];for(let e=0,i=n.length;e0&&(i[e]=s,r=!0)}r&&(t.data.morphAttributes=i,t.data.morphTargetsRelative=this.morphTargetsRelative);const s=this.groups;s.length>0&&(t.data.groups=JSON.parse(JSON.stringify(s)));const a=this.boundingSphere;return null!==a&&(t.data.boundingSphere={center:a.center.toArray(),radius:a.radius}),t}clone(){return(new this.constructor).copy(this)}copy(t){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const e={};this.name=t.name;const n=t.index;null!==n&&this.setIndex(n.clone(e));const i=t.attributes;for(const t in i){const n=i[t];this.setAttribute(t,n.clone(e))}const r=t.morphAttributes;for(const t in r){const n=[],i=r[t];for(let t=0,r=i.length;t0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;t(t.far-t.near)**2)return}Ps.copy(r).invert(),Is.copy(t.ray).applyMatrix4(Ps),null!==n.boundingBox&&!1===Is.intersectsBox(n.boundingBox)||this._computeIntersections(t,e,Is)}}_computeIntersections(t,e,n){let i;const r=this.geometry,s=this.material,a=r.index,o=r.attributes.position,l=r.attributes.uv,c=r.attributes.uv1,h=r.attributes.normal,u=r.groups,d=r.drawRange;if(null!==a)if(Array.isArray(s))for(let r=0,o=u.length;rn.far?null:{distance:c,point:ks.clone(),object:t}}(t,e,n,i,Ns,Ds,Os,zs);if(h){const t=new Li;Zr.getBarycoord(zs,Ns,Ds,Os,t),r&&(h.uv=Zr.getInterpolatedAttribute(r,o,l,c,t,new ti)),s&&(h.uv1=Zr.getInterpolatedAttribute(s,o,l,c,t,new ti)),a&&(h.normal=Zr.getInterpolatedAttribute(a,o,l,c,t,new Li),h.normal.dot(i.direction)>0&&h.normal.multiplyScalar(-1));const e={a:o,b:l,c:c,normal:new Li,materialIndex:0};Zr.getNormal(Ns,Ds,Os,e.normal),h.face=e,h.barycoord=t}return h}class Gs extends Cs{constructor(t=1,e=1,n=1,i=1,r=1,s=1){super(),this.type="BoxGeometry",this.parameters={width:t,height:e,depth:n,widthSegments:i,heightSegments:r,depthSegments:s};const a=this;i=Math.floor(i),r=Math.floor(r),s=Math.floor(s);const o=[],l=[],c=[],h=[];let u=0,d=0;function p(t,e,n,i,r,s,p,m,f,g,v){const _=s/f,x=p/g,y=s/2,M=p/2,S=m/2,b=f+1,w=g+1;let T=0,E=0;const A=new Li;for(let s=0;s0?1:-1,c.push(A.x,A.y,A.z),h.push(o/f),h.push(1-s/g),T+=1}}for(let t=0;t0&&(e.defines=this.defines),e.vertexShader=this.vertexShader,e.fragmentShader=this.fragmentShader,e.lights=this.lights,e.clipping=this.clipping;const n={};for(const t in this.extensions)!0===this.extensions[t]&&(n[t]=!0);return Object.keys(n).length>0&&(e.extensions=n),e}}class Zs extends Dr{constructor(){super(),this.isCamera=!0,this.type="Camera",this.matrixWorldInverse=new lr,this.projectionMatrix=new lr,this.projectionMatrixInverse=new lr,this.coordinateSystem=kn}copy(t,e){return super.copy(t,e),this.matrixWorldInverse.copy(t.matrixWorldInverse),this.projectionMatrix.copy(t.projectionMatrix),this.projectionMatrixInverse.copy(t.projectionMatrixInverse),this.coordinateSystem=t.coordinateSystem,this}getWorldDirection(t){return super.getWorldDirection(t).negate()}updateMatrixWorld(t){super.updateMatrixWorld(t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(t,e){super.updateWorldMatrix(t,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return(new this.constructor).copy(this)}}const Js=new Li,Ks=new ti,$s=new ti;class Qs extends Zs{constructor(t=50,e=1,n=.1,i=2e3){super(),this.isPerspectiveCamera=!0,this.type="PerspectiveCamera",this.fov=t,this.zoom=1,this.near=n,this.far=i,this.focus=10,this.aspect=e,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.fov=t.fov,this.zoom=t.zoom,this.near=t.near,this.far=t.far,this.focus=t.focus,this.aspect=t.aspect,this.view=null===t.view?null:Object.assign({},t.view),this.filmGauge=t.filmGauge,this.filmOffset=t.filmOffset,this}setFocalLength(t){const e=.5*this.getFilmHeight()/t;this.fov=2*jn*Math.atan(e),this.updateProjectionMatrix()}getFocalLength(){const t=Math.tan(.5*Xn*this.fov);return.5*this.getFilmHeight()/t}getEffectiveFOV(){return 2*jn*Math.atan(Math.tan(.5*Xn*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}getViewBounds(t,e,n){Js.set(-1,-1,.5).applyMatrix4(this.projectionMatrixInverse),e.set(Js.x,Js.y).multiplyScalar(-t/Js.z),Js.set(1,1,.5).applyMatrix4(this.projectionMatrixInverse),n.set(Js.x,Js.y).multiplyScalar(-t/Js.z)}getViewSize(t,e){return this.getViewBounds(t,Ks,$s),e.subVectors($s,Ks)}setViewOffset(t,e,n,i,r,s){this.aspect=t/e,null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=this.near;let e=t*Math.tan(.5*Xn*this.fov)/this.zoom,n=2*e,i=this.aspect*n,r=-.5*i;const s=this.view;if(null!==this.view&&this.view.enabled){const t=s.fullWidth,a=s.fullHeight;r+=s.offsetX*i/t,e-=s.offsetY*n/a,i*=s.width/t,n*=s.height/a}const a=this.filmOffset;0!==a&&(r+=t*a/this.getFilmWidth()),this.projectionMatrix.makePerspective(r,r+i,e,e-n,t,this.far,this.coordinateSystem),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.fov=this.fov,e.object.zoom=this.zoom,e.object.near=this.near,e.object.far=this.far,e.object.focus=this.focus,e.object.aspect=this.aspect,null!==this.view&&(e.object.view=Object.assign({},this.view)),e.object.filmGauge=this.filmGauge,e.object.filmOffset=this.filmOffset,e}}const ta=-90;class ea extends Dr{constructor(t,e,n){super(),this.type="CubeCamera",this.renderTarget=n,this.coordinateSystem=null,this.activeMipmapLevel=0;const i=new Qs(ta,1,t,e);i.layers=this.layers,this.add(i);const r=new Qs(ta,1,t,e);r.layers=this.layers,this.add(r);const s=new Qs(ta,1,t,e);s.layers=this.layers,this.add(s);const a=new Qs(ta,1,t,e);a.layers=this.layers,this.add(a);const o=new Qs(ta,1,t,e);o.layers=this.layers,this.add(o);const l=new Qs(ta,1,t,e);l.layers=this.layers,this.add(l)}updateCoordinateSystem(){const t=this.coordinateSystem,e=this.children.concat(),[n,i,r,s,a,o]=e;for(const t of e)this.remove(t);if(t===kn)n.up.set(0,1,0),n.lookAt(1,0,0),i.up.set(0,1,0),i.lookAt(-1,0,0),r.up.set(0,0,-1),r.lookAt(0,1,0),s.up.set(0,0,1),s.lookAt(0,-1,0),a.up.set(0,1,0),a.lookAt(0,0,1),o.up.set(0,1,0),o.lookAt(0,0,-1);else{if(t!==Vn)throw new Error("THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinate system: "+t);n.up.set(0,-1,0),n.lookAt(-1,0,0),i.up.set(0,-1,0),i.lookAt(1,0,0),r.up.set(0,0,1),r.lookAt(0,1,0),s.up.set(0,0,-1),s.lookAt(0,-1,0),a.up.set(0,-1,0),a.lookAt(0,0,1),o.up.set(0,-1,0),o.lookAt(0,0,-1)}for(const t of e)this.add(t),t.updateMatrixWorld()}update(t,e){null===this.parent&&this.updateMatrixWorld();const{renderTarget:n,activeMipmapLevel:i}=this;this.coordinateSystem!==t.coordinateSystem&&(this.coordinateSystem=t.coordinateSystem,this.updateCoordinateSystem());const[r,s,a,o,l,c]=this.children,h=t.getRenderTarget(),u=t.getActiveCubeFace(),d=t.getActiveMipmapLevel(),p=t.xr.enabled;t.xr.enabled=!1;const m=n.texture.generateMipmaps;n.texture.generateMipmaps=!1,t.setRenderTarget(n,0,i),t.render(e,r),t.setRenderTarget(n,1,i),t.render(e,s),t.setRenderTarget(n,2,i),t.render(e,a),t.setRenderTarget(n,3,i),t.render(e,o),t.setRenderTarget(n,4,i),t.render(e,l),n.texture.generateMipmaps=m,t.setRenderTarget(n,5,i),t.render(e,c),t.setRenderTarget(h,u,d),t.xr.enabled=p,n.texture.needsPMREMUpdate=!0}}class na extends bi{constructor(t,e,n,i,r,s,a,o,l,c){super(t=void 0!==t?t:[],e=void 0!==e?e:lt,n,i,r,s,a,o,l,c),this.isCubeTexture=!0,this.flipY=!1}get images(){return this.image}set images(t){this.image=t}}class ia extends Ei{constructor(t=1,e={}){super(t,t,e),this.isWebGLCubeRenderTarget=!0;const n={width:t,height:t,depth:1},i=[n,n,n,n,n,n];this.texture=new na(i,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.colorSpace),this.texture.isRenderTargetTexture=!0,this.texture.generateMipmaps=void 0!==e.generateMipmaps&&e.generateMipmaps,this.texture.minFilter=void 0!==e.minFilter?e.minFilter:Mt}fromEquirectangularTexture(t,e){this.texture.type=e.type,this.texture.colorSpace=e.colorSpace,this.texture.generateMipmaps=e.generateMipmaps,this.texture.minFilter=e.minFilter,this.texture.magFilter=e.magFilter;const n={uniforms:{tEquirect:{value:null}},vertexShader:"\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\tvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\n\t\t\t\t\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvWorldDirection = transformDirection( position, modelMatrix );\n\n\t\t\t\t\t#include \n\t\t\t\t\t#include \n\n\t\t\t\t}\n\t\t\t",fragmentShader:"\n\n\t\t\t\tuniform sampler2D tEquirect;\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\t#include \n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvec3 direction = normalize( vWorldDirection );\n\n\t\t\t\t\tvec2 sampleUV = equirectUv( direction );\n\n\t\t\t\t\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\n\t\t\t\t}\n\t\t\t"},i=new Gs(5,5,5),r=new Ys({name:"CubemapFromEquirect",uniforms:Ws(n.uniforms),vertexShader:n.vertexShader,fragmentShader:n.fragmentShader,side:d,blending:0});r.uniforms.tEquirect.value=e;const s=new Vs(i,r),a=e.minFilter;e.minFilter===wt&&(e.minFilter=Mt);return new ea(1,10,this).update(t,s),e.minFilter=a,s.geometry.dispose(),s.material.dispose(),this}clear(t,e,n,i){const r=t.getRenderTarget();for(let r=0;r<6;r++)t.setRenderTarget(this,r),t.clear(e,n,i);t.setRenderTarget(r)}}const ra=new Li,sa=new Li,aa=new ei;class oa{constructor(t=new Li(1,0,0),e=0){this.isPlane=!0,this.normal=t,this.constant=e}set(t,e){return this.normal.copy(t),this.constant=e,this}setComponents(t,e,n,i){return this.normal.set(t,e,n),this.constant=i,this}setFromNormalAndCoplanarPoint(t,e){return this.normal.copy(t),this.constant=-e.dot(this.normal),this}setFromCoplanarPoints(t,e,n){const i=ra.subVectors(n,e).cross(sa.subVectors(t,e)).normalize();return this.setFromNormalAndCoplanarPoint(i,t),this}copy(t){return this.normal.copy(t.normal),this.constant=t.constant,this}normalize(){const t=1/this.normal.length();return this.normal.multiplyScalar(t),this.constant*=t,this}negate(){return this.constant*=-1,this.normal.negate(),this}distanceToPoint(t){return this.normal.dot(t)+this.constant}distanceToSphere(t){return this.distanceToPoint(t.center)-t.radius}projectPoint(t,e){return e.copy(t).addScaledVector(this.normal,-this.distanceToPoint(t))}intersectLine(t,e){const n=t.delta(ra),i=this.normal.dot(n);if(0===i)return 0===this.distanceToPoint(t.start)?e.copy(t.start):null;const r=-(t.start.dot(this.normal)+this.constant)/i;return r<0||r>1?null:e.copy(t.start).addScaledVector(n,r)}intersectsLine(t){const e=this.distanceToPoint(t.start),n=this.distanceToPoint(t.end);return e<0&&n>0||n<0&&e>0}intersectsBox(t){return t.intersectsPlane(this)}intersectsSphere(t){return t.intersectsPlane(this)}coplanarPoint(t){return t.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(t,e){const n=e||aa.getNormalMatrix(t),i=this.coplanarPoint(ra).applyMatrix4(t),r=this.normal.applyMatrix3(n).normalize();return this.constant=-i.dot(r),this}translate(t){return this.constant-=t.dot(this.normal),this}equals(t){return t.normal.equals(this.normal)&&t.constant===this.constant}clone(){return(new this.constructor).copy(this)}}const la=new Qi,ca=new Li;class ha{constructor(t=new oa,e=new oa,n=new oa,i=new oa,r=new oa,s=new oa){this.planes=[t,e,n,i,r,s]}set(t,e,n,i,r,s){const a=this.planes;return a[0].copy(t),a[1].copy(e),a[2].copy(n),a[3].copy(i),a[4].copy(r),a[5].copy(s),this}copy(t){const e=this.planes;for(let n=0;n<6;n++)e[n].copy(t.planes[n]);return this}setFromProjectionMatrix(t,e=2e3){const n=this.planes,i=t.elements,r=i[0],s=i[1],a=i[2],o=i[3],l=i[4],c=i[5],h=i[6],u=i[7],d=i[8],p=i[9],m=i[10],f=i[11],g=i[12],v=i[13],_=i[14],x=i[15];if(n[0].setComponents(o-r,u-l,f-d,x-g).normalize(),n[1].setComponents(o+r,u+l,f+d,x+g).normalize(),n[2].setComponents(o+s,u+c,f+p,x+v).normalize(),n[3].setComponents(o-s,u-c,f-p,x-v).normalize(),n[4].setComponents(o-a,u-h,f-m,x-_).normalize(),e===kn)n[5].setComponents(o+a,u+h,f+m,x+_).normalize();else{if(e!==Vn)throw new Error("THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: "+e);n[5].setComponents(a,h,m,_).normalize()}return this}intersectsObject(t){if(void 0!==t.boundingSphere)null===t.boundingSphere&&t.computeBoundingSphere(),la.copy(t.boundingSphere).applyMatrix4(t.matrixWorld);else{const e=t.geometry;null===e.boundingSphere&&e.computeBoundingSphere(),la.copy(e.boundingSphere).applyMatrix4(t.matrixWorld)}return this.intersectsSphere(la)}intersectsSprite(t){return la.center.set(0,0,0),la.radius=.7071067811865476,la.applyMatrix4(t.matrixWorld),this.intersectsSphere(la)}intersectsSphere(t){const e=this.planes,n=t.center,i=-t.radius;for(let t=0;t<6;t++){if(e[t].distanceToPoint(n)0?t.max.x:t.min.x,ca.y=i.normal.y>0?t.max.y:t.min.y,ca.z=i.normal.z>0?t.max.z:t.min.z,i.distanceToPoint(ca)<0)return!1}return!0}containsPoint(t){const e=this.planes;for(let n=0;n<6;n++)if(e[n].distanceToPoint(t)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}function ua(){let t=null,e=!1,n=null,i=null;function r(e,s){n(e,s),i=t.requestAnimationFrame(r)}return{start:function(){!0!==e&&null!==n&&(i=t.requestAnimationFrame(r),e=!0)},stop:function(){t.cancelAnimationFrame(i),e=!1},setAnimationLoop:function(t){n=t},setContext:function(e){t=e}}}function da(t){const e=new WeakMap;return{get:function(t){return t.isInterleavedBufferAttribute&&(t=t.data),e.get(t)},remove:function(n){n.isInterleavedBufferAttribute&&(n=n.data);const i=e.get(n);i&&(t.deleteBuffer(i.buffer),e.delete(n))},update:function(n,i){if(n.isInterleavedBufferAttribute&&(n=n.data),n.isGLBufferAttribute){const t=e.get(n);return void((!t||t.versiont.start-e.start));let e=0;for(let t=1;t 0\n\tvec4 plane;\n\t#ifdef ALPHA_TO_COVERAGE\n\t\tfloat distanceToPlane, distanceGradient;\n\t\tfloat clipOpacity = 1.0;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\tclipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\tif ( clipOpacity == 0.0 ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tfloat unionClipOpacity = 1.0;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\t\tunionClipOpacity *= 1.0 - smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tclipOpacity *= 1.0 - unionClipOpacity;\n\t\t#endif\n\t\tdiffuseColor.a *= clipOpacity;\n\t\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tbool clipped = true;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tif ( clipped ) discard;\n\t\t#endif\n\t#endif\n#endif",clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif",color_fragment:"#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvarying vec3 vColor;\n#endif",color_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif\n#ifdef USE_BATCHING_COLOR\n\tvec3 batchingColor = getBatchingColor( getIndirectIndex( gl_DrawID ) );\n\tvColor.xyz *= batchingColor.xyz;\n#endif",common:"#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated",cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif",defaultnormal_vertex:"vec3 transformedNormal = objectNormal;\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = objectTangent;\n#endif\n#ifdef USE_BATCHING\n\tmat3 bm = mat3( batchingMatrix );\n\ttransformedNormal /= vec3( dot( bm[ 0 ], bm[ 0 ] ), dot( bm[ 1 ], bm[ 1 ] ), dot( bm[ 2 ], bm[ 2 ] ) );\n\ttransformedNormal = bm * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = bm * transformedTangent;\n\t#endif\n#endif\n#ifdef USE_INSTANCING\n\tmat3 im = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( im[ 0 ], im[ 0 ] ), dot( im[ 1 ], im[ 1 ] ), dot( im[ 2 ], im[ 2 ] ) );\n\ttransformedNormal = im * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = im * transformedTangent;\n\t#endif\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\ttransformedTangent = ( modelViewMatrix * vec4( transformedTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif",emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",colorspace_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",colorspace_pars_fragment:"\nconst mat3 LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = mat3(\n\tvec3( 0.8224621, 0.177538, 0.0 ),\n\tvec3( 0.0331941, 0.9668058, 0.0 ),\n\tvec3( 0.0170827, 0.0723974, 0.9105199 )\n);\nconst mat3 LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.2249401, - 0.2249404, 0.0 ),\n\tvec3( - 0.0420569, 1.0420571, 0.0 ),\n\tvec3( - 0.0196376, - 0.0786361, 1.0982735 )\n);\nvec4 LinearSRGBToLinearDisplayP3( in vec4 value ) {\n\treturn vec4( value.rgb * LINEAR_SRGB_TO_LINEAR_DISPLAY_P3, value.a );\n}\nvec4 LinearDisplayP3ToLinearSRGB( in vec4 value ) {\n\treturn vec4( value.rgb * LINEAR_DISPLAY_P3_TO_LINEAR_SRGB, value.a );\n}\nvec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}",envmap_fragment:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, envMapRotation * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",envmap_common_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform mat3 envMapRotation;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",fog_vertex:"#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",gradientmap_pars_fragment:"#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_fragment:"LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;",lights_lambert_pars_fragment:"varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert",lights_pars_begin:"uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif ( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif",lights_toon_fragment:"ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;",lights_toon_pars_fragment:"varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometryNormal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon",lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong",lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_DISPERSION\n\tmaterial.dispersion = dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif",lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\tfloat dispersion;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",lights_fragment_begin:"\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowIntensity, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif",lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometryNormal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif",lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGDEPTHBUF )\n\tgl_FragDepth = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGDEPTHBUF )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_pars_vertex:"#ifdef USE_LOGDEPTHBUF\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGDEPTHBUF\n\tvFragDepth = 1.0 + gl_Position.w;\n\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n#endif",map_fragment:"#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = vec4( mix( pow( sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), sampledDiffuseColor.rgb * 0.0773993808, vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) ) ), sampledDiffuseColor.w );\n\t\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif",map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif",map_particle_pars_fragment:"#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphinstance_vertex:"#ifdef USE_INSTANCING_MORPH\n\tfloat morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\tfloat morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tmorphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;\n\t}\n#endif",morphcolor_vertex:"#if defined( USE_MORPHCOLORS )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\t#ifndef USE_INSTANCING_MORPH\n\t\tuniform float morphTargetBaseInfluence;\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t#endif\n\tuniform sampler2DArray morphTargetsTexture;\n\tuniform ivec2 morphTargetsTextureSize;\n\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t}\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif",normal_fragment_begin:"float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal,\n\t\t#if defined( USE_NORMALMAP )\n\t\t\tvNormalMapUv\n\t\t#elif defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tvClearcoatNormalMapUv\n\t\t#else\n\t\t\tvUv\n\t\t#endif\n\t\t);\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 nonPerturbedNormal = normal;",normal_fragment_maps:"#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif",normal_pars_fragment:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_pars_vertex:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_vertex:"#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif",normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif",clearcoat_normal_fragment_begin:"#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = nonPerturbedNormal;\n#endif",clearcoat_normal_fragment_maps:"#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif",clearcoat_pars_fragment:"#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif",iridescence_pars_fragment:"#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif",opaque_fragment:"#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );",packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;const float ShiftRight8 = 1. / 256.;\nconst float Inv255 = 1. / 255.;\nconst vec4 PackFactors = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 );\nconst vec2 UnpackFactors2 = vec2( UnpackDownscale, 1.0 / PackFactors.g );\nconst vec3 UnpackFactors3 = vec3( UnpackDownscale / PackFactors.rg, 1.0 / PackFactors.b );\nconst vec4 UnpackFactors4 = vec4( UnpackDownscale / PackFactors.rgb, 1.0 / PackFactors.a );\nvec4 packDepthToRGBA( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec4( 0., 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec4( 1., 1., 1., 1. );\n\tfloat vuf;\n\tfloat af = modf( v * PackFactors.a, vuf );\n\tfloat bf = modf( vuf * ShiftRight8, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec4( vuf * Inv255, gf * PackUpscale, bf * PackUpscale, af );\n}\nvec3 packDepthToRGB( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec3( 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec3( 1., 1., 1. );\n\tfloat vuf;\n\tfloat bf = modf( v * PackFactors.b, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec3( vuf * Inv255, gf * PackUpscale, bf );\n}\nvec2 packDepthToRG( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec2( 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec2( 1., 1. );\n\tfloat vuf;\n\tfloat gf = modf( v * 256., vuf );\n\treturn vec2( vuf * Inv255, gf );\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors4 );\n}\nfloat unpackRGBToDepth( const in vec3 v ) {\n\treturn dot( v, UnpackFactors3 );\n}\nfloat unpackRGToDepth( const in vec2 v ) {\n\treturn v.r * UnpackFactors2.r + v.g * UnpackFactors2.g;\n}\nvec4 pack2HalfToRGBA( const in vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( const in vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}",premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_BATCHING\n\tmvPosition = batchingMatrix * mvPosition;\n#endif\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tfloat shadow = 1.0;\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\t\n\t\tfloat lightToPositionLength = length( lightToPosition );\n\t\tif ( lightToPositionLength - shadowCameraFar <= 0.0 && lightToPositionLength - shadowCameraNear >= 0.0 ) {\n\t\t\tfloat dp = ( lightToPositionLength - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\t\tdp += shadowBias;\n\t\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\t\tshadow = (\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t\t) * ( 1.0 / 9.0 );\n\t\t\t#else\n\t\t\t\tshadow = texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n#endif",shadowmap_pars_vertex:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif",shadowmap_vertex:"#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif",shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowIntensity, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowIntensity, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowIntensity, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}",skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tint size = textureSize( boneTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 CineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor *= toneMappingExposure;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\tcolor = clamp( color, 0.0, 1.0 );\n\treturn color;\n}\nvec3 NeutralToneMapping( vec3 color ) {\n\tconst float StartCompression = 0.8 - 0.04;\n\tconst float Desaturation = 0.15;\n\tcolor *= toneMappingExposure;\n\tfloat x = min( color.r, min( color.g, color.b ) );\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max( color.r, max( color.g, color.b ) );\n\tif ( peak < StartCompression ) return color;\n\tfloat d = 1. - StartCompression;\n\tfloat newPeak = 1. - d * d / ( peak + d - StartCompression );\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / ( Desaturation * ( peak - newPeak ) + 1. );\n\treturn mix( color, vec3( newPeak ), g );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }",transmission_fragment:"#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.dispersion, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif",transmission_pars_fragment:"#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float dispersion, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec4 transmittedLight;\n\t\tvec3 transmittance;\n\t\t#ifdef USE_DISPERSION\n\t\t\tfloat halfSpread = ( ior - 1.0 ) * 0.025 * dispersion;\n\t\t\tvec3 iors = vec3( ior - halfSpread, ior, ior + halfSpread );\n\t\t\tfor ( int i = 0; i < 3; i ++ ) {\n\t\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, iors[ i ], modelMatrix );\n\t\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\n\t\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\t\trefractionCoords += 1.0;\n\t\t\t\trefractionCoords /= 2.0;\n\t\t\n\t\t\t\tvec4 transmissionSample = getTransmissionSample( refractionCoords, roughness, iors[ i ] );\n\t\t\t\ttransmittedLight[ i ] = transmissionSample[ i ];\n\t\t\t\ttransmittedLight.a += transmissionSample.a;\n\t\t\t\ttransmittance[ i ] = diffuseColor[ i ] * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance )[ i ];\n\t\t\t}\n\t\t\ttransmittedLight.a /= 3.0;\n\t\t\n\t\t#else\n\t\t\n\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\trefractionCoords += 1.0;\n\t\t\trefractionCoords /= 2.0;\n\t\t\ttransmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\t\ttransmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\t\n\t\t#endif\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif",uv_pars_fragment:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_pars_vertex:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tuniform mat3 anisotropyMapTransform;\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_vertex:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_BATCHING\n\t\tworldPosition = batchingMatrix * worldPosition;\n\t#endif\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",background_frag:"uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",backgroundCube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",backgroundCube_frag:"#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nuniform mat3 backgroundRotation;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, backgroundRotation * vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, backgroundRotation * vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",cube_frag:"uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#elif DEPTH_PACKING == 3202\n\t\tgl_FragColor = vec4( packDepthToRGB( fragCoordZ ), 1.0 );\n\t#elif DEPTH_PACKING == 3203\n\t\tgl_FragColor = vec4( packDepthToRG( fragCoordZ ), 0.0, 1.0 );\n\t#endif\n}",distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}",linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_vert:"#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_frag:"#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_vert:"#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}",meshmatcap_frag:"#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshnormal_vert:"#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}",meshnormal_frag:"#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( 0.0, 0.0, 0.0, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), diffuseColor.a );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}",meshphong_vert:"#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphong_frag:"#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_vert:"#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}",meshphysical_frag:"#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_DISPERSION\n\tuniform float dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshtoon_vert:"#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}",meshtoon_frag:"#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",points_vert:"uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}",points_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_frag:"uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}",sprite_vert:"uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix[ 3 ];\n\tvec2 scale = vec2( length( modelMatrix[ 0 ].xyz ), length( modelMatrix[ 1 ].xyz ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}",sprite_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"},fa={common:{diffuse:{value:new ts(16777215)},opacity:{value:1},map:{value:null},mapTransform:{value:new ei},alphaMap:{value:null},alphaMapTransform:{value:new ei},alphaTest:{value:0}},specularmap:{specularMap:{value:null},specularMapTransform:{value:new ei}},envmap:{envMap:{value:null},envMapRotation:{value:new ei},flipEnvMap:{value:-1},reflectivity:{value:1},ior:{value:1.5},refractionRatio:{value:.98}},aomap:{aoMap:{value:null},aoMapIntensity:{value:1},aoMapTransform:{value:new ei}},lightmap:{lightMap:{value:null},lightMapIntensity:{value:1},lightMapTransform:{value:new ei}},bumpmap:{bumpMap:{value:null},bumpMapTransform:{value:new ei},bumpScale:{value:1}},normalmap:{normalMap:{value:null},normalMapTransform:{value:new ei},normalScale:{value:new ti(1,1)}},displacementmap:{displacementMap:{value:null},displacementMapTransform:{value:new ei},displacementScale:{value:1},displacementBias:{value:0}},emissivemap:{emissiveMap:{value:null},emissiveMapTransform:{value:new ei}},metalnessmap:{metalnessMap:{value:null},metalnessMapTransform:{value:new ei}},roughnessmap:{roughnessMap:{value:null},roughnessMapTransform:{value:new ei}},gradientmap:{gradientMap:{value:null}},fog:{fogDensity:{value:25e-5},fogNear:{value:1},fogFar:{value:2e3},fogColor:{value:new ts(16777215)}},lights:{ambientLightColor:{value:[]},lightProbe:{value:[]},directionalLights:{value:[],properties:{direction:{},color:{}}},directionalLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},directionalShadowMap:{value:[]},directionalShadowMatrix:{value:[]},spotLights:{value:[],properties:{color:{},position:{},direction:{},distance:{},coneCos:{},penumbraCos:{},decay:{}}},spotLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},spotLightMap:{value:[]},spotShadowMap:{value:[]},spotLightMatrix:{value:[]},pointLights:{value:[],properties:{color:{},position:{},decay:{},distance:{}}},pointLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{},shadowCameraNear:{},shadowCameraFar:{}}},pointShadowMap:{value:[]},pointShadowMatrix:{value:[]},hemisphereLights:{value:[],properties:{direction:{},skyColor:{},groundColor:{}}},rectAreaLights:{value:[],properties:{color:{},position:{},width:{},height:{}}},ltc_1:{value:null},ltc_2:{value:null}},points:{diffuse:{value:new ts(16777215)},opacity:{value:1},size:{value:1},scale:{value:1},map:{value:null},alphaMap:{value:null},alphaMapTransform:{value:new ei},alphaTest:{value:0},uvTransform:{value:new ei}},sprite:{diffuse:{value:new ts(16777215)},opacity:{value:1},center:{value:new ti(.5,.5)},rotation:{value:0},map:{value:null},mapTransform:{value:new ei},alphaMap:{value:null},alphaMapTransform:{value:new ei},alphaTest:{value:0}}},ga={basic:{uniforms:Xs([fa.common,fa.specularmap,fa.envmap,fa.aomap,fa.lightmap,fa.fog]),vertexShader:ma.meshbasic_vert,fragmentShader:ma.meshbasic_frag},lambert:{uniforms:Xs([fa.common,fa.specularmap,fa.envmap,fa.aomap,fa.lightmap,fa.emissivemap,fa.bumpmap,fa.normalmap,fa.displacementmap,fa.fog,fa.lights,{emissive:{value:new ts(0)}}]),vertexShader:ma.meshlambert_vert,fragmentShader:ma.meshlambert_frag},phong:{uniforms:Xs([fa.common,fa.specularmap,fa.envmap,fa.aomap,fa.lightmap,fa.emissivemap,fa.bumpmap,fa.normalmap,fa.displacementmap,fa.fog,fa.lights,{emissive:{value:new ts(0)},specular:{value:new ts(1118481)},shininess:{value:30}}]),vertexShader:ma.meshphong_vert,fragmentShader:ma.meshphong_frag},standard:{uniforms:Xs([fa.common,fa.envmap,fa.aomap,fa.lightmap,fa.emissivemap,fa.bumpmap,fa.normalmap,fa.displacementmap,fa.roughnessmap,fa.metalnessmap,fa.fog,fa.lights,{emissive:{value:new ts(0)},roughness:{value:1},metalness:{value:0},envMapIntensity:{value:1}}]),vertexShader:ma.meshphysical_vert,fragmentShader:ma.meshphysical_frag},toon:{uniforms:Xs([fa.common,fa.aomap,fa.lightmap,fa.emissivemap,fa.bumpmap,fa.normalmap,fa.displacementmap,fa.gradientmap,fa.fog,fa.lights,{emissive:{value:new ts(0)}}]),vertexShader:ma.meshtoon_vert,fragmentShader:ma.meshtoon_frag},matcap:{uniforms:Xs([fa.common,fa.bumpmap,fa.normalmap,fa.displacementmap,fa.fog,{matcap:{value:null}}]),vertexShader:ma.meshmatcap_vert,fragmentShader:ma.meshmatcap_frag},points:{uniforms:Xs([fa.points,fa.fog]),vertexShader:ma.points_vert,fragmentShader:ma.points_frag},dashed:{uniforms:Xs([fa.common,fa.fog,{scale:{value:1},dashSize:{value:1},totalSize:{value:2}}]),vertexShader:ma.linedashed_vert,fragmentShader:ma.linedashed_frag},depth:{uniforms:Xs([fa.common,fa.displacementmap]),vertexShader:ma.depth_vert,fragmentShader:ma.depth_frag},normal:{uniforms:Xs([fa.common,fa.bumpmap,fa.normalmap,fa.displacementmap,{opacity:{value:1}}]),vertexShader:ma.meshnormal_vert,fragmentShader:ma.meshnormal_frag},sprite:{uniforms:Xs([fa.sprite,fa.fog]),vertexShader:ma.sprite_vert,fragmentShader:ma.sprite_frag},background:{uniforms:{uvTransform:{value:new ei},t2D:{value:null},backgroundIntensity:{value:1}},vertexShader:ma.background_vert,fragmentShader:ma.background_frag},backgroundCube:{uniforms:{envMap:{value:null},flipEnvMap:{value:-1},backgroundBlurriness:{value:0},backgroundIntensity:{value:1},backgroundRotation:{value:new ei}},vertexShader:ma.backgroundCube_vert,fragmentShader:ma.backgroundCube_frag},cube:{uniforms:{tCube:{value:null},tFlip:{value:-1},opacity:{value:1}},vertexShader:ma.cube_vert,fragmentShader:ma.cube_frag},equirect:{uniforms:{tEquirect:{value:null}},vertexShader:ma.equirect_vert,fragmentShader:ma.equirect_frag},distanceRGBA:{uniforms:Xs([fa.common,fa.displacementmap,{referencePosition:{value:new Li},nearDistance:{value:1},farDistance:{value:1e3}}]),vertexShader:ma.distanceRGBA_vert,fragmentShader:ma.distanceRGBA_frag},shadow:{uniforms:Xs([fa.lights,fa.fog,{color:{value:new ts(0)},opacity:{value:1}}]),vertexShader:ma.shadow_vert,fragmentShader:ma.shadow_frag}};ga.physical={uniforms:Xs([ga.standard.uniforms,{clearcoat:{value:0},clearcoatMap:{value:null},clearcoatMapTransform:{value:new ei},clearcoatNormalMap:{value:null},clearcoatNormalMapTransform:{value:new ei},clearcoatNormalScale:{value:new ti(1,1)},clearcoatRoughness:{value:0},clearcoatRoughnessMap:{value:null},clearcoatRoughnessMapTransform:{value:new ei},dispersion:{value:0},iridescence:{value:0},iridescenceMap:{value:null},iridescenceMapTransform:{value:new ei},iridescenceIOR:{value:1.3},iridescenceThicknessMinimum:{value:100},iridescenceThicknessMaximum:{value:400},iridescenceThicknessMap:{value:null},iridescenceThicknessMapTransform:{value:new ei},sheen:{value:0},sheenColor:{value:new ts(0)},sheenColorMap:{value:null},sheenColorMapTransform:{value:new ei},sheenRoughness:{value:1},sheenRoughnessMap:{value:null},sheenRoughnessMapTransform:{value:new ei},transmission:{value:0},transmissionMap:{value:null},transmissionMapTransform:{value:new ei},transmissionSamplerSize:{value:new ti},transmissionSamplerMap:{value:null},thickness:{value:0},thicknessMap:{value:null},thicknessMapTransform:{value:new ei},attenuationDistance:{value:0},attenuationColor:{value:new ts(0)},specularColor:{value:new ts(1,1,1)},specularColorMap:{value:null},specularColorMapTransform:{value:new ei},specularIntensity:{value:1},specularIntensityMap:{value:null},specularIntensityMapTransform:{value:new ei},anisotropyVector:{value:new ti},anisotropyMap:{value:null},anisotropyMapTransform:{value:new ei}}]),vertexShader:ma.meshphysical_vert,fragmentShader:ma.meshphysical_frag};const va={r:0,b:0,g:0},_a=new _r,xa=new lr;function ya(t,e,n,i,r,s,a){const o=new ts(0);let l,c,h=!0===s?0:1,p=null,m=0,f=null;function g(t){let i=!0===t.isScene?t.background:null;if(i&&i.isTexture){i=(t.backgroundBlurriness>0?n:e).get(i)}return i}function v(e,n){e.getRGB(va,js(t)),i.buffers.color.setClear(va.r,va.g,va.b,n,a)}return{getClearColor:function(){return o},setClearColor:function(t,e=1){o.set(t),h=e,v(o,h)},getClearAlpha:function(){return h},setClearAlpha:function(t){h=t,v(o,h)},render:function(e){let n=!1;const r=g(e);null===r?v(o,h):r&&r.isColor&&(v(r,1),n=!0);const s=t.xr.getEnvironmentBlendMode();"additive"===s?i.buffers.color.setClear(0,0,0,1,a):"alpha-blend"===s&&i.buffers.color.setClear(0,0,0,0,a),(t.autoClear||n)&&(i.buffers.depth.setTest(!0),i.buffers.depth.setMask(!0),i.buffers.color.setMask(!0),t.clear(t.autoClearColor,t.autoClearDepth,t.autoClearStencil))},addToRenderList:function(e,n){const i=g(n);i&&(i.isCubeTexture||i.mapping===dt)?(void 0===c&&(c=new Vs(new Gs(1,1,1),new Ys({name:"BackgroundCubeMaterial",uniforms:Ws(ga.backgroundCube.uniforms),vertexShader:ga.backgroundCube.vertexShader,fragmentShader:ga.backgroundCube.fragmentShader,side:d,depthTest:!1,depthWrite:!1,fog:!1})),c.geometry.deleteAttribute("normal"),c.geometry.deleteAttribute("uv"),c.onBeforeRender=function(t,e,n){this.matrixWorld.copyPosition(n.matrixWorld)},Object.defineProperty(c.material,"envMap",{get:function(){return this.uniforms.envMap.value}}),r.update(c)),_a.copy(n.backgroundRotation),_a.x*=-1,_a.y*=-1,_a.z*=-1,i.isCubeTexture&&!1===i.isRenderTargetTexture&&(_a.y*=-1,_a.z*=-1),c.material.uniforms.envMap.value=i,c.material.uniforms.flipEnvMap.value=i.isCubeTexture&&!1===i.isRenderTargetTexture?-1:1,c.material.uniforms.backgroundBlurriness.value=n.backgroundBlurriness,c.material.uniforms.backgroundIntensity.value=n.backgroundIntensity,c.material.uniforms.backgroundRotation.value.setFromMatrix4(xa.makeRotationFromEuler(_a)),c.material.toneMapped=mi.getTransfer(i.colorSpace)!==en,p===i&&m===i.version&&f===t.toneMapping||(c.material.needsUpdate=!0,p=i,m=i.version,f=t.toneMapping),c.layers.enableAll(),e.unshift(c,c.geometry,c.material,0,0,null)):i&&i.isTexture&&(void 0===l&&(l=new Vs(new pa(2,2),new Ys({name:"BackgroundMaterial",uniforms:Ws(ga.background.uniforms),vertexShader:ga.background.vertexShader,fragmentShader:ga.background.fragmentShader,side:u,depthTest:!1,depthWrite:!1,fog:!1})),l.geometry.deleteAttribute("normal"),Object.defineProperty(l.material,"map",{get:function(){return this.uniforms.t2D.value}}),r.update(l)),l.material.uniforms.t2D.value=i,l.material.uniforms.backgroundIntensity.value=n.backgroundIntensity,l.material.toneMapped=mi.getTransfer(i.colorSpace)!==en,!0===i.matrixAutoUpdate&&i.updateMatrix(),l.material.uniforms.uvTransform.value.copy(i.matrix),p===i&&m===i.version&&f===t.toneMapping||(l.material.needsUpdate=!0,p=i,m=i.version,f=t.toneMapping),l.layers.enableAll(),e.unshift(l,l.geometry,l.material,0,0,null))}}}function Ma(t,e){const n=t.getParameter(t.MAX_VERTEX_ATTRIBS),i={},r=c(null);let s=r,a=!1;function o(e){return t.bindVertexArray(e)}function l(e){return t.deleteVertexArray(e)}function c(t){const e=[],i=[],r=[];for(let t=0;t=0){const n=r[e];let i=a[e];if(void 0===i&&("instanceMatrix"===e&&t.instanceMatrix&&(i=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(i=t.instanceColor)),void 0===n)return!0;if(n.attribute!==i)return!0;if(i&&n.data!==i.data)return!0;o++}}return s.attributesNum!==o||s.index!==i}(n,f,l,g),v&&function(t,e,n,i){const r={},a=e.attributes;let o=0;const l=n.getAttributes();for(const e in l){if(l[e].location>=0){let n=a[e];void 0===n&&("instanceMatrix"===e&&t.instanceMatrix&&(n=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(n=t.instanceColor));const i={};i.attribute=n,n&&n.data&&(i.data=n.data),r[e]=i,o++}}s.attributes=r,s.attributesNum=o,s.index=i}(n,f,l,g),null!==g&&e.update(g,t.ELEMENT_ARRAY_BUFFER),(v||a)&&(a=!1,function(n,i,r,s){h();const a=s.attributes,o=r.getAttributes(),l=i.defaultAttributeValues;for(const i in o){const r=o[i];if(r.location>=0){let o=a[i];if(void 0===o&&("instanceMatrix"===i&&n.instanceMatrix&&(o=n.instanceMatrix),"instanceColor"===i&&n.instanceColor&&(o=n.instanceColor)),void 0!==o){const i=o.normalized,a=o.itemSize,l=e.get(o);if(void 0===l)continue;const c=l.buffer,h=l.type,p=l.bytesPerElement,f=h===t.INT||h===t.UNSIGNED_INT||o.gpuType===Pt;if(o.isInterleavedBufferAttribute){const e=o.data,l=e.stride,g=o.offset;if(e.isInstancedInterleavedBuffer){for(let t=0;t0&&t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.HIGH_FLOAT).precision>0)return"highp";e="mediump"}return"mediump"===e&&t.getShaderPrecisionFormat(t.VERTEX_SHADER,t.MEDIUM_FLOAT).precision>0&&t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.MEDIUM_FLOAT).precision>0?"mediump":"lowp"}let a=void 0!==n.precision?n.precision:"highp";const o=s(a);o!==a&&(console.warn("THREE.WebGLRenderer:",a,"not supported, using",o,"instead."),a=o);const l=!0===n.logarithmicDepthBuffer,c=!0===n.reverseDepthBuffer&&e.has("EXT_clip_control");if(!0===c){const t=e.get("EXT_clip_control");t.clipControlEXT(t.LOWER_LEFT_EXT,t.ZERO_TO_ONE_EXT)}const h=t.getParameter(t.MAX_TEXTURE_IMAGE_UNITS),u=t.getParameter(t.MAX_VERTEX_TEXTURE_IMAGE_UNITS);return{isWebGL2:!0,getMaxAnisotropy:function(){if(void 0!==r)return r;if(!0===e.has("EXT_texture_filter_anisotropic")){const n=e.get("EXT_texture_filter_anisotropic");r=t.getParameter(n.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else r=0;return r},getMaxPrecision:s,textureFormatReadable:function(e){return e===kt||i.convert(e)===t.getParameter(t.IMPLEMENTATION_COLOR_READ_FORMAT)},textureTypeReadable:function(n){const r=n===Ut&&(e.has("EXT_color_buffer_half_float")||e.has("EXT_color_buffer_float"));return!(n!==Et&&i.convert(n)!==t.getParameter(t.IMPLEMENTATION_COLOR_READ_TYPE)&&n!==Lt&&!r)},precision:a,logarithmicDepthBuffer:l,reverseDepthBuffer:c,maxTextures:h,maxVertexTextures:u,maxTextureSize:t.getParameter(t.MAX_TEXTURE_SIZE),maxCubemapSize:t.getParameter(t.MAX_CUBE_MAP_TEXTURE_SIZE),maxAttributes:t.getParameter(t.MAX_VERTEX_ATTRIBS),maxVertexUniforms:t.getParameter(t.MAX_VERTEX_UNIFORM_VECTORS),maxVaryings:t.getParameter(t.MAX_VARYING_VECTORS),maxFragmentUniforms:t.getParameter(t.MAX_FRAGMENT_UNIFORM_VECTORS),vertexTextures:u>0,maxSamples:t.getParameter(t.MAX_SAMPLES)}}function wa(t){const e=this;let n=null,i=0,r=!1,s=!1;const a=new oa,o=new ei,l={value:null,needsUpdate:!1};function c(t,n,i,r){const s=null!==t?t.length:0;let c=null;if(0!==s){if(c=l.value,!0!==r||null===c){const e=i+4*s,r=n.matrixWorldInverse;o.getNormalMatrix(r),(null===c||c.length0);e.numPlanes=i,e.numIntersection=0}();else{const t=s?0:i,e=4*t;let r=m.clippingState||null;l.value=r,r=c(u,o,e,h);for(let t=0;t!==e;++t)r[t]=n[t];m.clippingState=r,this.numIntersection=d?this.numPlanes:0,this.numPlanes+=t}}}function Ta(t){let e=new WeakMap;function n(t,e){return e===ht?t.mapping=lt:e===ut&&(t.mapping=ct),t}function i(t){const n=t.target;n.removeEventListener("dispose",i);const r=e.get(n);void 0!==r&&(e.delete(n),r.dispose())}return{get:function(r){if(r&&r.isTexture){const s=r.mapping;if(s===ht||s===ut){if(e.has(r)){return n(e.get(r).texture,r.mapping)}{const s=r.image;if(s&&s.height>0){const a=new ia(s.height);return a.fromEquirectangularTexture(t,r),e.set(r,a),r.addEventListener("dispose",i),n(a.texture,r.mapping)}return null}}}return r},dispose:function(){e=new WeakMap}}}class Ea extends Zs{constructor(t=-1,e=1,n=1,i=-1,r=.1,s=2e3){super(),this.isOrthographicCamera=!0,this.type="OrthographicCamera",this.zoom=1,this.view=null,this.left=t,this.right=e,this.top=n,this.bottom=i,this.near=r,this.far=s,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.left=t.left,this.right=t.right,this.top=t.top,this.bottom=t.bottom,this.near=t.near,this.far=t.far,this.zoom=t.zoom,this.view=null===t.view?null:Object.assign({},t.view),this}setViewOffset(t,e,n,i,r,s){null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=(this.right-this.left)/(2*this.zoom),e=(this.top-this.bottom)/(2*this.zoom),n=(this.right+this.left)/2,i=(this.top+this.bottom)/2;let r=n-t,s=n+t,a=i+e,o=i-e;if(null!==this.view&&this.view.enabled){const t=(this.right-this.left)/this.view.fullWidth/this.zoom,e=(this.top-this.bottom)/this.view.fullHeight/this.zoom;r+=t*this.view.offsetX,s=r+t*this.view.width,a-=e*this.view.offsetY,o=a-e*this.view.height}this.projectionMatrix.makeOrthographic(r,s,a,o,this.near,this.far,this.coordinateSystem),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.zoom=this.zoom,e.object.left=this.left,e.object.right=this.right,e.object.top=this.top,e.object.bottom=this.bottom,e.object.near=this.near,e.object.far=this.far,null!==this.view&&(e.object.view=Object.assign({},this.view)),e}}const Aa=[.125,.215,.35,.446,.526,.582],Ra=20,Ca=new Ea,Pa=new ts;let Ia=null,La=0,Ua=0,Na=!1;const Da=(1+Math.sqrt(5))/2,Oa=1/Da,Fa=[new Li(-Da,Oa,0),new Li(Da,Oa,0),new Li(-Oa,0,Da),new Li(Oa,0,Da),new Li(0,Da,-Oa),new Li(0,Da,Oa),new Li(-1,1,-1),new Li(1,1,-1),new Li(-1,1,1),new Li(1,1,1)];class Ba{constructor(t){this._renderer=t,this._pingPongRenderTarget=null,this._lodMax=0,this._cubeSize=0,this._lodPlanes=[],this._sizeLods=[],this._sigmas=[],this._blurMaterial=null,this._cubemapMaterial=null,this._equirectMaterial=null,this._compileMaterial(this._blurMaterial)}fromScene(t,e=0,n=.1,i=100){Ia=this._renderer.getRenderTarget(),La=this._renderer.getActiveCubeFace(),Ua=this._renderer.getActiveMipmapLevel(),Na=this._renderer.xr.enabled,this._renderer.xr.enabled=!1,this._setSize(256);const r=this._allocateTargets();return r.depthBuffer=!0,this._sceneToCubeUV(t,n,i,r),e>0&&this._blur(r,0,0,e),this._applyPMREM(r),this._cleanup(r),r}fromEquirectangular(t,e=null){return this._fromTexture(t,e)}fromCubemap(t,e=null){return this._fromTexture(t,e)}compileCubemapShader(){null===this._cubemapMaterial&&(this._cubemapMaterial=Ha(),this._compileMaterial(this._cubemapMaterial))}compileEquirectangularShader(){null===this._equirectMaterial&&(this._equirectMaterial=Va(),this._compileMaterial(this._equirectMaterial))}dispose(){this._dispose(),null!==this._cubemapMaterial&&this._cubemapMaterial.dispose(),null!==this._equirectMaterial&&this._equirectMaterial.dispose()}_setSize(t){this._lodMax=Math.floor(Math.log2(t)),this._cubeSize=Math.pow(2,this._lodMax)}_dispose(){null!==this._blurMaterial&&this._blurMaterial.dispose(),null!==this._pingPongRenderTarget&&this._pingPongRenderTarget.dispose();for(let t=0;tt-4?o=Aa[a-t+4-1]:0===a&&(o=0),i.push(o);const l=1/(s-2),c=-l,h=1+l,u=[c,c,h,c,h,h,c,c,h,h,c,h],d=6,p=6,m=3,f=2,g=1,v=new Float32Array(m*p*d),_=new Float32Array(f*p*d),x=new Float32Array(g*p*d);for(let t=0;t2?0:-1,i=[e,n,0,e+2/3,n,0,e+2/3,n+1,0,e,n,0,e+2/3,n+1,0,e,n+1,0];v.set(i,m*p*t),_.set(u,f*p*t);const r=[t,t,t,t,t,t];x.set(r,g*p*t)}const y=new Cs;y.setAttribute("position",new ds(v,m)),y.setAttribute("uv",new ds(_,f)),y.setAttribute("faceIndex",new ds(x,g)),e.push(y),r>4&&r--}return{lodPlanes:e,sizeLods:n,sigmas:i}}(i)),this._blurMaterial=function(t,e,n){const i=new Float32Array(Ra),r=new Li(0,1,0),s=new Ys({name:"SphericalGaussianBlur",defines:{n:Ra,CUBEUV_TEXEL_WIDTH:1/e,CUBEUV_TEXEL_HEIGHT:1/n,CUBEUV_MAX_MIP:`${t}.0`},uniforms:{envMap:{value:null},samples:{value:1},weights:{value:i},latitudinal:{value:!1},dTheta:{value:0},mipInt:{value:0},poleAxis:{value:r}},vertexShader:Ga(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\t\t\tuniform int samples;\n\t\t\tuniform float weights[ n ];\n\t\t\tuniform bool latitudinal;\n\t\t\tuniform float dTheta;\n\t\t\tuniform float mipInt;\n\t\t\tuniform vec3 poleAxis;\n\n\t\t\t#define ENVMAP_TYPE_CUBE_UV\n\t\t\t#include \n\n\t\t\tvec3 getSample( float theta, vec3 axis ) {\n\n\t\t\t\tfloat cosTheta = cos( theta );\n\t\t\t\t// Rodrigues' axis-angle rotation\n\t\t\t\tvec3 sampleDirection = vOutputDirection * cosTheta\n\t\t\t\t\t+ cross( axis, vOutputDirection ) * sin( theta )\n\t\t\t\t\t+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );\n\n\t\t\t\treturn bilinearCubeUV( envMap, sampleDirection, mipInt );\n\n\t\t\t}\n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );\n\n\t\t\t\tif ( all( equal( axis, vec3( 0.0 ) ) ) ) {\n\n\t\t\t\t\taxis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );\n\n\t\t\t\t}\n\n\t\t\t\taxis = normalize( axis );\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );\n\n\t\t\t\tfor ( int i = 1; i < n; i++ ) {\n\n\t\t\t\t\tif ( i >= samples ) {\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat theta = dTheta * float( i );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( theta, axis );\n\n\t\t\t\t}\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1});return s}(i,t,e)}return i}_compileMaterial(t){const e=new Vs(this._lodPlanes[0],t);this._renderer.compile(e,Ca)}_sceneToCubeUV(t,e,n,i){const r=new Qs(90,1,e,n),s=[1,-1,1,1,1,1],a=[1,1,1,-1,-1,-1],o=this._renderer,l=o.autoClear,c=o.toneMapping;o.getClearColor(Pa),o.toneMapping=K,o.autoClear=!1;const h=new rs({name:"PMREM.Background",side:d,depthWrite:!1,depthTest:!1}),u=new Vs(new Gs,h);let p=!1;const m=t.background;m?m.isColor&&(h.color.copy(m),t.background=null,p=!0):(h.color.copy(Pa),p=!0);for(let e=0;e<6;e++){const n=e%3;0===n?(r.up.set(0,s[e],0),r.lookAt(a[e],0,0)):1===n?(r.up.set(0,0,s[e]),r.lookAt(0,a[e],0)):(r.up.set(0,s[e],0),r.lookAt(0,0,a[e]));const l=this._cubeSize;ka(i,n*l,e>2?l:0,l,l),o.setRenderTarget(i),p&&o.render(u,r),o.render(t,r)}u.geometry.dispose(),u.material.dispose(),o.toneMapping=c,o.autoClear=l,t.background=m}_textureToCubeUV(t,e){const n=this._renderer,i=t.mapping===lt||t.mapping===ct;i?(null===this._cubemapMaterial&&(this._cubemapMaterial=Ha()),this._cubemapMaterial.uniforms.flipEnvMap.value=!1===t.isRenderTargetTexture?-1:1):null===this._equirectMaterial&&(this._equirectMaterial=Va());const r=i?this._cubemapMaterial:this._equirectMaterial,s=new Vs(this._lodPlanes[0],r);r.uniforms.envMap.value=t;const a=this._cubeSize;ka(e,0,0,3*a,2*a),n.setRenderTarget(e),n.render(s,Ca)}_applyPMREM(t){const e=this._renderer,n=e.autoClear;e.autoClear=!1;const i=this._lodPlanes.length;for(let e=1;eRa&&console.warn(`sigmaRadians, ${r}, is too large and will clip, as it requested ${m} samples when the maximum is set to 20`);const f=[];let g=0;for(let t=0;tv-4?i-v+4:0),4*(this._cubeSize-_),3*_,2*_),o.setRenderTarget(e),o.render(c,Ca)}}function za(t,e,n){const i=new Ei(t,e,n);return i.texture.mapping=dt,i.texture.name="PMREM.cubeUv",i.scissorTest=!0,i}function ka(t,e,n,i,r){t.viewport.set(e,n,i,r),t.scissor.set(e,n,i,r)}function Va(){return new Ys({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null}},vertexShader:Ga(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\n\t\t\t#include \n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 outputDirection = normalize( vOutputDirection );\n\t\t\t\tvec2 uv = equirectUv( outputDirection );\n\n\t\t\t\tgl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function Ha(){return new Ys({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},flipEnvMap:{value:-1}},vertexShader:Ga(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tuniform float flipEnvMap;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform samplerCube envMap;\n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function Ga(){return"\n\n\t\tprecision mediump float;\n\t\tprecision mediump int;\n\n\t\tattribute float faceIndex;\n\n\t\tvarying vec3 vOutputDirection;\n\n\t\t// RH coordinate system; PMREM face-indexing convention\n\t\tvec3 getDirection( vec2 uv, float face ) {\n\n\t\t\tuv = 2.0 * uv - 1.0;\n\n\t\t\tvec3 direction = vec3( uv, 1.0 );\n\n\t\t\tif ( face == 0.0 ) {\n\n\t\t\t\tdirection = direction.zyx; // ( 1, v, u ) pos x\n\n\t\t\t} else if ( face == 1.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xz *= -1.0; // ( -u, 1, -v ) pos y\n\n\t\t\t} else if ( face == 2.0 ) {\n\n\t\t\t\tdirection.x *= -1.0; // ( -u, v, 1 ) pos z\n\n\t\t\t} else if ( face == 3.0 ) {\n\n\t\t\t\tdirection = direction.zyx;\n\t\t\t\tdirection.xz *= -1.0; // ( -1, v, -u ) neg x\n\n\t\t\t} else if ( face == 4.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xy *= -1.0; // ( -u, -1, v ) neg y\n\n\t\t\t} else if ( face == 5.0 ) {\n\n\t\t\t\tdirection.z *= -1.0; // ( u, v, -1 ) neg z\n\n\t\t\t}\n\n\t\t\treturn direction;\n\n\t\t}\n\n\t\tvoid main() {\n\n\t\t\tvOutputDirection = getDirection( uv, faceIndex );\n\t\t\tgl_Position = vec4( position, 1.0 );\n\n\t\t}\n\t"}function Wa(t){let e=new WeakMap,n=null;function i(t){const n=t.target;n.removeEventListener("dispose",i);const r=e.get(n);void 0!==r&&(e.delete(n),r.dispose())}return{get:function(r){if(r&&r.isTexture){const s=r.mapping,a=s===ht||s===ut,o=s===lt||s===ct;if(a||o){let s=e.get(r);const l=void 0!==s?s.texture.pmremVersion:0;if(r.isRenderTargetTexture&&r.pmremVersion!==l)return null===n&&(n=new Ba(t)),s=a?n.fromEquirectangular(r,s):n.fromCubemap(r,s),s.texture.pmremVersion=r.pmremVersion,e.set(r,s),s.texture;if(void 0!==s)return s.texture;{const l=r.image;return a&&l&&l.height>0||o&&l&&function(t){let e=0;const n=6;for(let i=0;ie.maxTextureSize&&(y=Math.ceil(x/e.maxTextureSize),x=e.maxTextureSize);const M=new Float32Array(x*y*4*h),S=new Ai(M,x,y,h);S.type=Lt,S.needsUpdate=!0;const b=4*_;for(let T=0;T0)return t;const r=e*n;let s=io[r];if(void 0===s&&(s=new Float32Array(r),io[r]=s),0!==e){i.toArray(s,0);for(let i=1,r=0;i!==e;++i)r+=n,t[i].toArray(s,r)}return s}function co(t,e){if(t.length!==e.length)return!1;for(let n=0,i=t.length;n":" "} ${r}: ${n[t]}`)}return i.join("\n")}(t.getShaderSource(e),i)}return r}function ll(t,e){const n=function(t){const e=mi.getPrimaries(mi.workingColorSpace),n=mi.getPrimaries(t);let i;switch(e===n?i="":e===rn&&n===nn?i="LinearDisplayP3ToLinearSRGB":e===nn&&n===rn&&(i="LinearSRGBToLinearDisplayP3"),t){case Ke:case Qe:return[i,"LinearTransferOETF"];case Je:case $e:return[i,"sRGBTransferOETF"];default:return console.warn("THREE.WebGLProgram: Unsupported color space:",t),[i,"LinearTransferOETF"]}}(e);return`vec4 ${t}( vec4 value ) { return ${n[0]}( ${n[1]}( value ) ); }`}function cl(t,e){let n;switch(e){case $:n="Linear";break;case Q:n="Reinhard";break;case tt:n="Cineon";break;case et:n="ACESFilmic";break;case it:n="AgX";break;case rt:n="Neutral";break;case nt:n="Custom";break;default:console.warn("THREE.WebGLProgram: Unsupported toneMapping:",e),n="Linear"}return"vec3 "+t+"( vec3 color ) { return "+n+"ToneMapping( color ); }"}const hl=new Li;function ul(){mi.getLuminanceCoefficients(hl);return["float luminance( const in vec3 rgb ) {",`\tconst vec3 weights = vec3( ${hl.x.toFixed(4)}, ${hl.y.toFixed(4)}, ${hl.z.toFixed(4)} );`,"\treturn dot( weights, rgb );","}"].join("\n")}function dl(t){return""!==t}function pl(t,e){const n=e.numSpotLightShadows+e.numSpotLightMaps-e.numSpotLightShadowsWithMaps;return t.replace(/NUM_DIR_LIGHTS/g,e.numDirLights).replace(/NUM_SPOT_LIGHTS/g,e.numSpotLights).replace(/NUM_SPOT_LIGHT_MAPS/g,e.numSpotLightMaps).replace(/NUM_SPOT_LIGHT_COORDS/g,n).replace(/NUM_RECT_AREA_LIGHTS/g,e.numRectAreaLights).replace(/NUM_POINT_LIGHTS/g,e.numPointLights).replace(/NUM_HEMI_LIGHTS/g,e.numHemiLights).replace(/NUM_DIR_LIGHT_SHADOWS/g,e.numDirLightShadows).replace(/NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g,e.numSpotLightShadowsWithMaps).replace(/NUM_SPOT_LIGHT_SHADOWS/g,e.numSpotLightShadows).replace(/NUM_POINT_LIGHT_SHADOWS/g,e.numPointLightShadows)}function ml(t,e){return t.replace(/NUM_CLIPPING_PLANES/g,e.numClippingPlanes).replace(/UNION_CLIPPING_PLANES/g,e.numClippingPlanes-e.numClipIntersection)}const fl=/^[ \t]*#include +<([\w\d./]+)>/gm;function gl(t){return t.replace(fl,_l)}const vl=new Map;function _l(t,e){let n=ma[e];if(void 0===n){const t=vl.get(e);if(void 0===t)throw new Error("Can not resolve #include <"+e+">");n=ma[t],console.warn('THREE.WebGLRenderer: Shader chunk "%s" has been deprecated. Use "%s" instead.',e,t)}return gl(n)}const xl=/#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;function yl(t){return t.replace(xl,Ml)}function Ml(t,e,n,i){let r="";for(let t=parseInt(e);t0&&(x+="\n"),y=["#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,v].filter(dl).join("\n"),y.length>0&&(y+="\n")):(x=[Sl(n),"#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,v,n.extensionClipCullDistance?"#define USE_CLIP_DISTANCE":"",n.batching?"#define USE_BATCHING":"",n.batchingColor?"#define USE_BATCHING_COLOR":"",n.instancing?"#define USE_INSTANCING":"",n.instancingColor?"#define USE_INSTANCING_COLOR":"",n.instancingMorph?"#define USE_INSTANCING_MORPH":"",n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+p:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.displacementMap?"#define USE_DISPLACEMENTMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropy?"#define USE_ANISOTROPY":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaHash?"#define USE_ALPHAHASH":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.mapUv?"#define MAP_UV "+n.mapUv:"",n.alphaMapUv?"#define ALPHAMAP_UV "+n.alphaMapUv:"",n.lightMapUv?"#define LIGHTMAP_UV "+n.lightMapUv:"",n.aoMapUv?"#define AOMAP_UV "+n.aoMapUv:"",n.emissiveMapUv?"#define EMISSIVEMAP_UV "+n.emissiveMapUv:"",n.bumpMapUv?"#define BUMPMAP_UV "+n.bumpMapUv:"",n.normalMapUv?"#define NORMALMAP_UV "+n.normalMapUv:"",n.displacementMapUv?"#define DISPLACEMENTMAP_UV "+n.displacementMapUv:"",n.metalnessMapUv?"#define METALNESSMAP_UV "+n.metalnessMapUv:"",n.roughnessMapUv?"#define ROUGHNESSMAP_UV "+n.roughnessMapUv:"",n.anisotropyMapUv?"#define ANISOTROPYMAP_UV "+n.anisotropyMapUv:"",n.clearcoatMapUv?"#define CLEARCOATMAP_UV "+n.clearcoatMapUv:"",n.clearcoatNormalMapUv?"#define CLEARCOAT_NORMALMAP_UV "+n.clearcoatNormalMapUv:"",n.clearcoatRoughnessMapUv?"#define CLEARCOAT_ROUGHNESSMAP_UV "+n.clearcoatRoughnessMapUv:"",n.iridescenceMapUv?"#define IRIDESCENCEMAP_UV "+n.iridescenceMapUv:"",n.iridescenceThicknessMapUv?"#define IRIDESCENCE_THICKNESSMAP_UV "+n.iridescenceThicknessMapUv:"",n.sheenColorMapUv?"#define SHEEN_COLORMAP_UV "+n.sheenColorMapUv:"",n.sheenRoughnessMapUv?"#define SHEEN_ROUGHNESSMAP_UV "+n.sheenRoughnessMapUv:"",n.specularMapUv?"#define SPECULARMAP_UV "+n.specularMapUv:"",n.specularColorMapUv?"#define SPECULAR_COLORMAP_UV "+n.specularColorMapUv:"",n.specularIntensityMapUv?"#define SPECULAR_INTENSITYMAP_UV "+n.specularIntensityMapUv:"",n.transmissionMapUv?"#define TRANSMISSIONMAP_UV "+n.transmissionMapUv:"",n.thicknessMapUv?"#define THICKNESSMAP_UV "+n.thicknessMapUv:"",n.vertexTangents&&!1===n.flatShading?"#define USE_TANGENT":"",n.vertexColors?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.flatShading?"#define FLAT_SHADED":"",n.skinning?"#define USE_SKINNING":"",n.morphTargets?"#define USE_MORPHTARGETS":"",n.morphNormals&&!1===n.flatShading?"#define USE_MORPHNORMALS":"",n.morphColors?"#define USE_MORPHCOLORS":"",n.morphTargetsCount>0?"#define MORPHTARGETS_TEXTURE_STRIDE "+n.morphTextureStride:"",n.morphTargetsCount>0?"#define MORPHTARGETS_COUNT "+n.morphTargetsCount:"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+u:"",n.sizeAttenuation?"#define USE_SIZEATTENUATION":"",n.numLightProbes>0?"#define USE_LIGHT_PROBES":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.reverseDepthBuffer?"#define USE_REVERSEDEPTHBUF":"","uniform mat4 modelMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform mat4 viewMatrix;","uniform mat3 normalMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;","#ifdef USE_INSTANCING","\tattribute mat4 instanceMatrix;","#endif","#ifdef USE_INSTANCING_COLOR","\tattribute vec3 instanceColor;","#endif","#ifdef USE_INSTANCING_MORPH","\tuniform sampler2D morphTexture;","#endif","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_UV1","\tattribute vec2 uv1;","#endif","#ifdef USE_UV2","\tattribute vec2 uv2;","#endif","#ifdef USE_UV3","\tattribute vec2 uv3;","#endif","#ifdef USE_TANGENT","\tattribute vec4 tangent;","#endif","#if defined( USE_COLOR_ALPHA )","\tattribute vec4 color;","#elif defined( USE_COLOR )","\tattribute vec3 color;","#endif","#ifdef USE_SKINNING","\tattribute vec4 skinIndex;","\tattribute vec4 skinWeight;","#endif","\n"].filter(dl).join("\n"),y=[Sl(n),"#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,v,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.alphaToCoverage?"#define ALPHA_TO_COVERAGE":"",n.map?"#define USE_MAP":"",n.matcap?"#define USE_MATCAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+d:"",n.envMap?"#define "+p:"",n.envMap?"#define "+m:"",f?"#define CUBEUV_TEXEL_WIDTH "+f.texelWidth:"",f?"#define CUBEUV_TEXEL_HEIGHT "+f.texelHeight:"",f?"#define CUBEUV_MAX_MIP "+f.maxMip+".0":"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropy?"#define USE_ANISOTROPY":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoat?"#define USE_CLEARCOAT":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.dispersion?"#define USE_DISPERSION":"",n.iridescence?"#define USE_IRIDESCENCE":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaTest?"#define USE_ALPHATEST":"",n.alphaHash?"#define USE_ALPHAHASH":"",n.sheen?"#define USE_SHEEN":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.vertexTangents&&!1===n.flatShading?"#define USE_TANGENT":"",n.vertexColors||n.instancingColor||n.batchingColor?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.gradientMap?"#define USE_GRADIENTMAP":"",n.flatShading?"#define FLAT_SHADED":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+u:"",n.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",n.numLightProbes>0?"#define USE_LIGHT_PROBES":"",n.decodeVideoTexture?"#define DECODE_VIDEO_TEXTURE":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.reverseDepthBuffer?"#define USE_REVERSEDEPTHBUF":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",n.toneMapping!==K?"#define TONE_MAPPING":"",n.toneMapping!==K?ma.tonemapping_pars_fragment:"",n.toneMapping!==K?cl("toneMapping",n.toneMapping):"",n.dithering?"#define DITHERING":"",n.opaque?"#define OPAQUE":"",ma.colorspace_pars_fragment,ll("linearToOutputTexel",n.outputColorSpace),ul(),n.useDepthPacking?"#define DEPTH_PACKING "+n.depthPacking:"","\n"].filter(dl).join("\n")),a=gl(a),a=pl(a,n),a=ml(a,n),o=gl(o),o=pl(o,n),o=ml(o,n),a=yl(a),o=yl(o),!0!==n.isRawShaderMaterial&&(M="#version 300 es\n",x=[g,"#define attribute in","#define varying out","#define texture2D texture"].join("\n")+"\n"+x,y=["#define varying in",n.glslVersion===zn?"":"layout(location = 0) out highp vec4 pc_fragColor;",n.glslVersion===zn?"":"#define gl_FragColor pc_fragColor","#define gl_FragDepthEXT gl_FragDepth","#define texture2D texture","#define textureCube texture","#define texture2DProj textureProj","#define texture2DLodEXT textureLod","#define texture2DProjLodEXT textureProjLod","#define textureCubeLodEXT textureLod","#define texture2DGradEXT textureGrad","#define texture2DProjGradEXT textureProjGrad","#define textureCubeGradEXT textureGrad"].join("\n")+"\n"+y);const S=M+x+a,b=M+y+o,w=rl(r,r.VERTEX_SHADER,S),T=rl(r,r.FRAGMENT_SHADER,b);function E(e){if(t.debug.checkShaderErrors){const n=r.getProgramInfoLog(_).trim(),i=r.getShaderInfoLog(w).trim(),s=r.getShaderInfoLog(T).trim();let a=!0,o=!0;if(!1===r.getProgramParameter(_,r.LINK_STATUS))if(a=!1,"function"==typeof t.debug.onShaderError)t.debug.onShaderError(r,_,w,T);else{const t=ol(r,w,"vertex"),i=ol(r,T,"fragment");console.error("THREE.WebGLProgram: Shader Error "+r.getError()+" - VALIDATE_STATUS "+r.getProgramParameter(_,r.VALIDATE_STATUS)+"\n\nMaterial Name: "+e.name+"\nMaterial Type: "+e.type+"\n\nProgram Info Log: "+n+"\n"+t+"\n"+i)}else""!==n?console.warn("THREE.WebGLProgram: Program Info Log:",n):""!==i&&""!==s||(o=!1);o&&(e.diagnostics={runnable:a,programLog:n,vertexShader:{log:i,prefix:x},fragmentShader:{log:s,prefix:y}})}r.deleteShader(w),r.deleteShader(T),A=new il(r,_),R=function(t,e){const n={},i=t.getProgramParameter(e,t.ACTIVE_ATTRIBUTES);for(let r=0;r0,Y=s.clearcoat>0,Z=s.dispersion>0,J=s.iridescence>0,$=s.sheen>0,Q=s.transmission>0,tt=q&&!!s.anisotropyMap,et=Y&&!!s.clearcoatMap,nt=Y&&!!s.clearcoatNormalMap,it=Y&&!!s.clearcoatRoughnessMap,rt=J&&!!s.iridescenceMap,st=J&&!!s.iridescenceThicknessMap,at=$&&!!s.sheenColorMap,ot=$&&!!s.sheenRoughnessMap,lt=!!s.specularMap,ct=!!s.specularColorMap,ht=!!s.specularIntensityMap,ut=Q&&!!s.transmissionMap,pt=Q&&!!s.thicknessMap,mt=!!s.gradientMap,ft=!!s.alphaMap,gt=s.alphaTest>0,vt=!!s.alphaHash,_t=!!s.extensions;let xt=K;s.toneMapped&&(null!==U&&!0!==U.isXRRenderTarget||(xt=t.toneMapping));const yt={shaderID:T,shaderType:s.type,shaderName:s.name,vertexShader:R,fragmentShader:C,defines:s.defines,customVertexShaderID:P,customFragmentShaderID:I,isRawShaderMaterial:!0===s.isRawShaderMaterial,glslVersion:s.glslVersion,precision:f,batching:D,batchingColor:D&&null!==x._colorsTexture,instancing:N,instancingColor:N&&null!==x.instanceColor,instancingMorph:N&&null!==x.morphTexture,supportsVertexTextures:m,outputColorSpace:null===U?t.outputColorSpace:!0===U.isXRRenderTarget?U.texture.colorSpace:Ke,alphaToCoverage:!!s.alphaToCoverage,map:O,matcap:F,envMap:B,envMapMode:B&&b.mapping,envMapCubeUVHeight:w,aoMap:z,lightMap:k,bumpMap:V,normalMap:H,displacementMap:m&&G,emissiveMap:W,normalMapObjectSpace:H&&1===s.normalMapType,normalMapTangentSpace:H&&0===s.normalMapType,metalnessMap:X,roughnessMap:j,anisotropy:q,anisotropyMap:tt,clearcoat:Y,clearcoatMap:et,clearcoatNormalMap:nt,clearcoatRoughnessMap:it,dispersion:Z,iridescence:J,iridescenceMap:rt,iridescenceThicknessMap:st,sheen:$,sheenColorMap:at,sheenRoughnessMap:ot,specularMap:lt,specularColorMap:ct,specularIntensityMap:ht,transmission:Q,transmissionMap:ut,thicknessMap:pt,gradientMap:mt,opaque:!1===s.transparent&&1===s.blending&&!1===s.alphaToCoverage,alphaMap:ft,alphaTest:gt,alphaHash:vt,combine:s.combine,mapUv:O&&v(s.map.channel),aoMapUv:z&&v(s.aoMap.channel),lightMapUv:k&&v(s.lightMap.channel),bumpMapUv:V&&v(s.bumpMap.channel),normalMapUv:H&&v(s.normalMap.channel),displacementMapUv:G&&v(s.displacementMap.channel),emissiveMapUv:W&&v(s.emissiveMap.channel),metalnessMapUv:X&&v(s.metalnessMap.channel),roughnessMapUv:j&&v(s.roughnessMap.channel),anisotropyMapUv:tt&&v(s.anisotropyMap.channel),clearcoatMapUv:et&&v(s.clearcoatMap.channel),clearcoatNormalMapUv:nt&&v(s.clearcoatNormalMap.channel),clearcoatRoughnessMapUv:it&&v(s.clearcoatRoughnessMap.channel),iridescenceMapUv:rt&&v(s.iridescenceMap.channel),iridescenceThicknessMapUv:st&&v(s.iridescenceThicknessMap.channel),sheenColorMapUv:at&&v(s.sheenColorMap.channel),sheenRoughnessMapUv:ot&&v(s.sheenRoughnessMap.channel),specularMapUv:lt&&v(s.specularMap.channel),specularColorMapUv:ct&&v(s.specularColorMap.channel),specularIntensityMapUv:ht&&v(s.specularIntensityMap.channel),transmissionMapUv:ut&&v(s.transmissionMap.channel),thicknessMapUv:pt&&v(s.thicknessMap.channel),alphaMapUv:ft&&v(s.alphaMap.channel),vertexTangents:!!M.attributes.tangent&&(H||q),vertexColors:s.vertexColors,vertexAlphas:!0===s.vertexColors&&!!M.attributes.color&&4===M.attributes.color.itemSize,pointsUvs:!0===x.isPoints&&!!M.attributes.uv&&(O||ft),fog:!!y,useFog:!0===s.fog,fogExp2:!!y&&y.isFogExp2,flatShading:!0===s.flatShading,sizeAttenuation:!0===s.sizeAttenuation,logarithmicDepthBuffer:u,reverseDepthBuffer:p,skinning:!0===x.isSkinnedMesh,morphTargets:void 0!==M.morphAttributes.position,morphNormals:void 0!==M.morphAttributes.normal,morphColors:void 0!==M.morphAttributes.color,morphTargetsCount:A,morphTextureStride:L,numDirLights:o.directional.length,numPointLights:o.point.length,numSpotLights:o.spot.length,numSpotLightMaps:o.spotLightMap.length,numRectAreaLights:o.rectArea.length,numHemiLights:o.hemi.length,numDirLightShadows:o.directionalShadowMap.length,numPointLightShadows:o.pointShadowMap.length,numSpotLightShadows:o.spotShadowMap.length,numSpotLightShadowsWithMaps:o.numSpotLightShadowsWithMaps,numLightProbes:o.numLightProbes,numClippingPlanes:a.numPlanes,numClipIntersection:a.numIntersection,dithering:s.dithering,shadowMapEnabled:t.shadowMap.enabled&&h.length>0,shadowMapType:t.shadowMap.type,toneMapping:xt,decodeVideoTexture:O&&!0===s.map.isVideoTexture&&mi.getTransfer(s.map.colorSpace)===en,premultipliedAlpha:s.premultipliedAlpha,doubleSided:2===s.side,flipSided:s.side===d,useDepthPacking:s.depthPacking>=0,depthPacking:s.depthPacking||0,index0AttributeName:s.index0AttributeName,extensionClipCullDistance:_t&&!0===s.extensions.clipCullDistance&&i.has("WEBGL_clip_cull_distance"),extensionMultiDraw:(_t&&!0===s.extensions.multiDraw||D)&&i.has("WEBGL_multi_draw"),rendererExtensionParallelShaderCompile:i.has("KHR_parallel_shader_compile"),customProgramCacheKey:s.customProgramCacheKey()};return yt.vertexUv1s=c.has(1),yt.vertexUv2s=c.has(2),yt.vertexUv3s=c.has(3),c.clear(),yt},getProgramCacheKey:function(e){const n=[];if(e.shaderID?n.push(e.shaderID):(n.push(e.customVertexShaderID),n.push(e.customFragmentShaderID)),void 0!==e.defines)for(const t in e.defines)n.push(t),n.push(e.defines[t]);return!1===e.isRawShaderMaterial&&(!function(t,e){t.push(e.precision),t.push(e.outputColorSpace),t.push(e.envMapMode),t.push(e.envMapCubeUVHeight),t.push(e.mapUv),t.push(e.alphaMapUv),t.push(e.lightMapUv),t.push(e.aoMapUv),t.push(e.bumpMapUv),t.push(e.normalMapUv),t.push(e.displacementMapUv),t.push(e.emissiveMapUv),t.push(e.metalnessMapUv),t.push(e.roughnessMapUv),t.push(e.anisotropyMapUv),t.push(e.clearcoatMapUv),t.push(e.clearcoatNormalMapUv),t.push(e.clearcoatRoughnessMapUv),t.push(e.iridescenceMapUv),t.push(e.iridescenceThicknessMapUv),t.push(e.sheenColorMapUv),t.push(e.sheenRoughnessMapUv),t.push(e.specularMapUv),t.push(e.specularColorMapUv),t.push(e.specularIntensityMapUv),t.push(e.transmissionMapUv),t.push(e.thicknessMapUv),t.push(e.combine),t.push(e.fogExp2),t.push(e.sizeAttenuation),t.push(e.morphTargetsCount),t.push(e.morphAttributeCount),t.push(e.numDirLights),t.push(e.numPointLights),t.push(e.numSpotLights),t.push(e.numSpotLightMaps),t.push(e.numHemiLights),t.push(e.numRectAreaLights),t.push(e.numDirLightShadows),t.push(e.numPointLightShadows),t.push(e.numSpotLightShadows),t.push(e.numSpotLightShadowsWithMaps),t.push(e.numLightProbes),t.push(e.shadowMapType),t.push(e.toneMapping),t.push(e.numClippingPlanes),t.push(e.numClipIntersection),t.push(e.depthPacking)}(n,e),function(t,e){o.disableAll(),e.supportsVertexTextures&&o.enable(0);e.instancing&&o.enable(1);e.instancingColor&&o.enable(2);e.instancingMorph&&o.enable(3);e.matcap&&o.enable(4);e.envMap&&o.enable(5);e.normalMapObjectSpace&&o.enable(6);e.normalMapTangentSpace&&o.enable(7);e.clearcoat&&o.enable(8);e.iridescence&&o.enable(9);e.alphaTest&&o.enable(10);e.vertexColors&&o.enable(11);e.vertexAlphas&&o.enable(12);e.vertexUv1s&&o.enable(13);e.vertexUv2s&&o.enable(14);e.vertexUv3s&&o.enable(15);e.vertexTangents&&o.enable(16);e.anisotropy&&o.enable(17);e.alphaHash&&o.enable(18);e.batching&&o.enable(19);e.dispersion&&o.enable(20);e.batchingColor&&o.enable(21);t.push(o.mask),o.disableAll(),e.fog&&o.enable(0);e.useFog&&o.enable(1);e.flatShading&&o.enable(2);e.logarithmicDepthBuffer&&o.enable(3);e.reverseDepthBuffer&&o.enable(4);e.skinning&&o.enable(5);e.morphTargets&&o.enable(6);e.morphNormals&&o.enable(7);e.morphColors&&o.enable(8);e.premultipliedAlpha&&o.enable(9);e.shadowMapEnabled&&o.enable(10);e.doubleSided&&o.enable(11);e.flipSided&&o.enable(12);e.useDepthPacking&&o.enable(13);e.dithering&&o.enable(14);e.transmission&&o.enable(15);e.sheen&&o.enable(16);e.opaque&&o.enable(17);e.pointsUvs&&o.enable(18);e.decodeVideoTexture&&o.enable(19);e.alphaToCoverage&&o.enable(20);t.push(o.mask)}(n,e),n.push(t.outputColorSpace)),n.push(e.customProgramCacheKey),n.join()},getUniforms:function(t){const e=g[t.type];let n;if(e){const t=ga[e];n=qs.clone(t.uniforms)}else n=t.uniforms;return n},acquireProgram:function(e,n){let i;for(let t=0,e=h.length;t0?i.push(h):!0===a.transparent?r.push(h):n.push(h)},unshift:function(t,e,a,o,l,c){const h=s(t,e,a,o,l,c);a.transmission>0?i.unshift(h):!0===a.transparent?r.unshift(h):n.unshift(h)},finish:function(){for(let n=e,i=t.length;n1&&n.sort(t||Cl),i.length>1&&i.sort(e||Pl),r.length>1&&r.sort(e||Pl)}}}function Ll(){let t=new WeakMap;return{get:function(e,n){const i=t.get(e);let r;return void 0===i?(r=new Il,t.set(e,[r])):n>=i.length?(r=new Il,i.push(r)):r=i[n],r},dispose:function(){t=new WeakMap}}}function Ul(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":n={direction:new Li,color:new ts};break;case"SpotLight":n={position:new Li,direction:new Li,color:new ts,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":n={position:new Li,color:new ts,distance:0,decay:0};break;case"HemisphereLight":n={direction:new Li,skyColor:new ts,groundColor:new ts};break;case"RectAreaLight":n={color:new ts,position:new Li,halfWidth:new Li,halfHeight:new Li}}return t[e.id]=n,n}}}let Nl=0;function Dl(t,e){return(e.castShadow?2:0)-(t.castShadow?2:0)+(e.map?1:0)-(t.map?1:0)}function Ol(t){const e=new Ul,n=function(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":case"SpotLight":n={shadowIntensity:1,shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new ti};break;case"PointLight":n={shadowIntensity:1,shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new ti,shadowCameraNear:1,shadowCameraFar:1e3}}return t[e.id]=n,n}}}(),i={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1,numSpotMaps:-1,numLightProbes:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotLightMap:[],spotShadow:[],spotShadowMap:[],spotLightMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[],numSpotLightShadowsWithMaps:0,numLightProbes:0};for(let t=0;t<9;t++)i.probe.push(new Li);const r=new Li,s=new lr,a=new lr;return{setup:function(r){let s=0,a=0,o=0;for(let t=0;t<9;t++)i.probe[t].set(0,0,0);let l=0,c=0,h=0,u=0,d=0,p=0,m=0,f=0,g=0,v=0,_=0;r.sort(Dl);for(let t=0,x=r.length;t0&&(!0===t.has("OES_texture_float_linear")?(i.rectAreaLTC1=fa.LTC_FLOAT_1,i.rectAreaLTC2=fa.LTC_FLOAT_2):(i.rectAreaLTC1=fa.LTC_HALF_1,i.rectAreaLTC2=fa.LTC_HALF_2)),i.ambient[0]=s,i.ambient[1]=a,i.ambient[2]=o;const x=i.hash;x.directionalLength===l&&x.pointLength===c&&x.spotLength===h&&x.rectAreaLength===u&&x.hemiLength===d&&x.numDirectionalShadows===p&&x.numPointShadows===m&&x.numSpotShadows===f&&x.numSpotMaps===g&&x.numLightProbes===_||(i.directional.length=l,i.spot.length=h,i.rectArea.length=u,i.point.length=c,i.hemi.length=d,i.directionalShadow.length=p,i.directionalShadowMap.length=p,i.pointShadow.length=m,i.pointShadowMap.length=m,i.spotShadow.length=f,i.spotShadowMap.length=f,i.directionalShadowMatrix.length=p,i.pointShadowMatrix.length=m,i.spotLightMatrix.length=f+g-v,i.spotLightMap.length=g,i.numSpotLightShadowsWithMaps=v,i.numLightProbes=_,x.directionalLength=l,x.pointLength=c,x.spotLength=h,x.rectAreaLength=u,x.hemiLength=d,x.numDirectionalShadows=p,x.numPointShadows=m,x.numSpotShadows=f,x.numSpotMaps=g,x.numLightProbes=_,i.version=Nl++)},setupView:function(t,e){let n=0,o=0,l=0,c=0,h=0;const u=e.matrixWorldInverse;for(let e=0,d=t.length;e=r.length?(s=new Fl(t),r.push(s)):s=r[i],s},dispose:function(){e=new WeakMap}}}class zl extends is{constructor(t){super(),this.isMeshDepthMaterial=!0,this.type="MeshDepthMaterial",this.depthPacking=3200,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.setValues(t)}copy(t){return super.copy(t),this.depthPacking=t.depthPacking,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this}}class kl extends is{constructor(t){super(),this.isMeshDistanceMaterial=!0,this.type="MeshDistanceMaterial",this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.setValues(t)}copy(t){return super.copy(t),this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this}}function Vl(t,e,n){let i=new ha;const r=new ti,s=new ti,a=new wi,o=new zl({depthPacking:3201}),c=new kl,p={},m=n.maxTextureSize,f={[u]:d,[d]:u,2:2},g=new Ys({defines:{VSM_SAMPLES:8},uniforms:{shadow_pass:{value:null},resolution:{value:new ti},radius:{value:4}},vertexShader:"void main() {\n\tgl_Position = vec4( position, 1.0 );\n}",fragmentShader:"uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"}),v=g.clone();v.defines.HORIZONTAL_PASS=1;const _=new Cs;_.setAttribute("position",new ds(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const x=new Vs(_,g),y=this;this.enabled=!1,this.autoUpdate=!0,this.needsUpdate=!1,this.type=l;let M=this.type;function S(n,i){const s=e.update(x);g.defines.VSM_SAMPLES!==n.blurSamples&&(g.defines.VSM_SAMPLES=n.blurSamples,v.defines.VSM_SAMPLES=n.blurSamples,g.needsUpdate=!0,v.needsUpdate=!0),null===n.mapPass&&(n.mapPass=new Ei(r.x,r.y)),g.uniforms.shadow_pass.value=n.map.texture,g.uniforms.resolution.value=n.mapSize,g.uniforms.radius.value=n.radius,t.setRenderTarget(n.mapPass),t.clear(),t.renderBufferDirect(i,null,s,g,x,null),v.uniforms.shadow_pass.value=n.mapPass.texture,v.uniforms.resolution.value=n.mapSize,v.uniforms.radius.value=n.radius,t.setRenderTarget(n.map),t.clear(),t.renderBufferDirect(i,null,s,v,x,null)}function b(e,n,i,r){let s=null;const a=!0===i.isPointLight?e.customDistanceMaterial:e.customDepthMaterial;if(void 0!==a)s=a;else if(s=!0===i.isPointLight?c:o,t.localClippingEnabled&&!0===n.clipShadows&&Array.isArray(n.clippingPlanes)&&0!==n.clippingPlanes.length||n.displacementMap&&0!==n.displacementScale||n.alphaMap&&n.alphaTest>0||n.map&&n.alphaTest>0){const t=s.uuid,e=n.uuid;let i=p[t];void 0===i&&(i={},p[t]=i);let r=i[e];void 0===r&&(r=s.clone(),i[e]=r,n.addEventListener("dispose",T)),s=r}if(s.visible=n.visible,s.wireframe=n.wireframe,s.side=r===h?null!==n.shadowSide?n.shadowSide:n.side:null!==n.shadowSide?n.shadowSide:f[n.side],s.alphaMap=n.alphaMap,s.alphaTest=n.alphaTest,s.map=n.map,s.clipShadows=n.clipShadows,s.clippingPlanes=n.clippingPlanes,s.clipIntersection=n.clipIntersection,s.displacementMap=n.displacementMap,s.displacementScale=n.displacementScale,s.displacementBias=n.displacementBias,s.wireframeLinewidth=n.wireframeLinewidth,s.linewidth=n.linewidth,!0===i.isPointLight&&!0===s.isMeshDistanceMaterial){t.properties.get(s).light=i}return s}function w(n,r,s,a,o){if(!1===n.visible)return;if(n.layers.test(r.layers)&&(n.isMesh||n.isLine||n.isPoints)&&(n.castShadow||n.receiveShadow&&o===h)&&(!n.frustumCulled||i.intersectsObject(n))){n.modelViewMatrix.multiplyMatrices(s.matrixWorldInverse,n.matrixWorld);const i=e.update(n),l=n.material;if(Array.isArray(l)){const e=i.groups;for(let c=0,h=e.length;cm||r.y>m)&&(r.x>m&&(s.x=Math.floor(m/g.x),r.x=s.x*g.x,u.mapSize.x=s.x),r.y>m&&(s.y=Math.floor(m/g.y),r.y=s.y*g.y,u.mapSize.y=s.y)),null===u.map||!0===p||!0===f){const t=this.type!==h?{minFilter:gt,magFilter:gt}:{};null!==u.map&&u.map.dispose(),u.map=new Ei(r.x,r.y,t),u.map.texture.name=c.name+".shadowMap",u.camera.updateProjectionMatrix()}t.setRenderTarget(u.map),t.clear();const v=u.getViewportCount();for(let t=0;t=1):-1!==N.indexOf("OpenGL ES")&&(U=parseFloat(/^OpenGL ES (\d)/.exec(N)[1]),L=U>=2);let D=null,O={};const F=t.getParameter(t.SCISSOR_BOX),B=t.getParameter(t.VIEWPORT),z=(new wi).fromArray(F),k=(new wi).fromArray(B);function V(e,n,i,r){const s=new Uint8Array(4),a=t.createTexture();t.bindTexture(e,a),t.texParameteri(e,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(e,t.TEXTURE_MAG_FILTER,t.NEAREST);for(let a=0;ae?(t.repeat.x=1,t.repeat.y=n/e,t.offset.x=0,t.offset.y=(1-t.repeat.y)/2):(t.repeat.x=e/n,t.repeat.y=1,t.offset.x=(1-t.repeat.x)/2,t.offset.y=0),t},cover:function(t,e){const n=t.image&&t.image.width?t.image.width/t.image.height:1;return n>e?(t.repeat.x=e/n,t.repeat.y=1,t.offset.x=(1-t.repeat.x)/2,t.offset.y=0):(t.repeat.x=1,t.repeat.y=n/e,t.offset.x=0,t.offset.y=(1-t.repeat.y)/2),t},fill:function(t){return t.repeat.x=1,t.repeat.y=1,t.offset.x=0,t.offset.y=0,t},getByteLength:Wl};function jl(t,e,n,i,r,s,a){const o=e.has("WEBGL_multisampled_render_to_texture")?e.get("WEBGL_multisampled_render_to_texture"):null,l="undefined"!=typeof navigator&&/OculusBrowser/g.test(navigator.userAgent),c=new ti,h=new WeakMap;let u;const d=new WeakMap;let p=!1;try{p="undefined"!=typeof OffscreenCanvas&&null!==new OffscreenCanvas(1,1).getContext("2d")}catch(t){}function m(t,e){return p?new OffscreenCanvas(t,e):ai("canvas")}function f(t,e,n){let i=1;const r=k(t);if((r.width>n||r.height>n)&&(i=n/Math.max(r.width,r.height)),i<1){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap||"undefined"!=typeof VideoFrame&&t instanceof VideoFrame){const n=Math.floor(i*r.width),s=Math.floor(i*r.height);void 0===u&&(u=m(n,s));const a=e?m(n,s):u;a.width=n,a.height=s;return a.getContext("2d").drawImage(t,0,0,n,s),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+r.width+"x"+r.height+") to ("+n+"x"+s+")."),a}return"data"in t&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+r.width+"x"+r.height+")."),t}return t}function g(t){return t.generateMipmaps&&t.minFilter!==gt&&t.minFilter!==Mt}function v(e){t.generateMipmap(e)}function _(n,i,r,s,a=!1){if(null!==n){if(void 0!==t[n])return t[n];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+n+"'")}let o=i;if(i===t.RED&&(r===t.FLOAT&&(o=t.R32F),r===t.HALF_FLOAT&&(o=t.R16F),r===t.UNSIGNED_BYTE&&(o=t.R8)),i===t.RED_INTEGER&&(r===t.UNSIGNED_BYTE&&(o=t.R8UI),r===t.UNSIGNED_SHORT&&(o=t.R16UI),r===t.UNSIGNED_INT&&(o=t.R32UI),r===t.BYTE&&(o=t.R8I),r===t.SHORT&&(o=t.R16I),r===t.INT&&(o=t.R32I)),i===t.RG&&(r===t.FLOAT&&(o=t.RG32F),r===t.HALF_FLOAT&&(o=t.RG16F),r===t.UNSIGNED_BYTE&&(o=t.RG8)),i===t.RG_INTEGER&&(r===t.UNSIGNED_BYTE&&(o=t.RG8UI),r===t.UNSIGNED_SHORT&&(o=t.RG16UI),r===t.UNSIGNED_INT&&(o=t.RG32UI),r===t.BYTE&&(o=t.RG8I),r===t.SHORT&&(o=t.RG16I),r===t.INT&&(o=t.RG32I)),i===t.RGB_INTEGER&&(r===t.UNSIGNED_BYTE&&(o=t.RGB8UI),r===t.UNSIGNED_SHORT&&(o=t.RGB16UI),r===t.UNSIGNED_INT&&(o=t.RGB32UI),r===t.BYTE&&(o=t.RGB8I),r===t.SHORT&&(o=t.RGB16I),r===t.INT&&(o=t.RGB32I)),i===t.RGBA_INTEGER&&(r===t.UNSIGNED_BYTE&&(o=t.RGBA8UI),r===t.UNSIGNED_SHORT&&(o=t.RGBA16UI),r===t.UNSIGNED_INT&&(o=t.RGBA32UI),r===t.BYTE&&(o=t.RGBA8I),r===t.SHORT&&(o=t.RGBA16I),r===t.INT&&(o=t.RGBA32I)),i===t.RGB&&r===t.UNSIGNED_INT_5_9_9_9_REV&&(o=t.RGB9_E5),i===t.RGBA){const e=a?tn:mi.getTransfer(s);r===t.FLOAT&&(o=t.RGBA32F),r===t.HALF_FLOAT&&(o=t.RGBA16F),r===t.UNSIGNED_BYTE&&(o=e===en?t.SRGB8_ALPHA8:t.RGBA8),r===t.UNSIGNED_SHORT_4_4_4_4&&(o=t.RGBA4),r===t.UNSIGNED_SHORT_5_5_5_1&&(o=t.RGB5_A1)}return o!==t.R16F&&o!==t.R32F&&o!==t.RG16F&&o!==t.RG32F&&o!==t.RGBA16F&&o!==t.RGBA32F||e.get("EXT_color_buffer_float"),o}function x(e,n){let i;return e?null===n||n===It||n===Ot?i=t.DEPTH24_STENCIL8:n===Lt?i=t.DEPTH32F_STENCIL8:n===Ct&&(i=t.DEPTH24_STENCIL8,console.warn("DepthTexture: 16 bit depth attachment is not supported with stencil. Using 24-bit attachment.")):null===n||n===It||n===Ot?i=t.DEPTH_COMPONENT24:n===Lt?i=t.DEPTH_COMPONENT32F:n===Ct&&(i=t.DEPTH_COMPONENT16),i}function y(t,e){return!0===g(t)||t.isFramebufferTexture&&t.minFilter!==gt&&t.minFilter!==Mt?Math.log2(Math.max(e.width,e.height))+1:void 0!==t.mipmaps&&t.mipmaps.length>0?t.mipmaps.length:t.isCompressedTexture&&Array.isArray(t.image)?e.mipmaps.length:1}function M(t){const e=t.target;e.removeEventListener("dispose",M),function(t){const e=i.get(t);if(void 0===e.__webglInit)return;const n=t.source,r=d.get(n);if(r){const i=r[e.__cacheKey];i.usedTimes--,0===i.usedTimes&&b(t),0===Object.keys(r).length&&d.delete(n)}i.remove(t)}(e),e.isVideoTexture&&h.delete(e)}function S(e){const n=e.target;n.removeEventListener("dispose",S),function(e){const n=i.get(e);e.depthTexture&&e.depthTexture.dispose();if(e.isWebGLCubeRenderTarget)for(let e=0;e<6;e++){if(Array.isArray(n.__webglFramebuffer[e]))for(let i=0;i0&&s.__version!==e.version){const t=e.image;if(null===t)console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");else{if(!1!==t.complete)return void I(s,e,r);console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete")}}n.bindTexture(t.TEXTURE_2D,s.__webglTexture,t.TEXTURE0+r)}const E={[pt]:t.REPEAT,[mt]:t.CLAMP_TO_EDGE,[ft]:t.MIRRORED_REPEAT},A={[gt]:t.NEAREST,[vt]:t.NEAREST_MIPMAP_NEAREST,[xt]:t.NEAREST_MIPMAP_LINEAR,[Mt]:t.LINEAR,[St]:t.LINEAR_MIPMAP_NEAREST,[wt]:t.LINEAR_MIPMAP_LINEAR},R={512:t.NEVER,519:t.ALWAYS,513:t.LESS,[wn]:t.LEQUAL,514:t.EQUAL,518:t.GEQUAL,516:t.GREATER,517:t.NOTEQUAL};function C(n,s){if(s.type!==Lt||!1!==e.has("OES_texture_float_linear")||s.magFilter!==Mt&&s.magFilter!==St&&s.magFilter!==xt&&s.magFilter!==wt&&s.minFilter!==Mt&&s.minFilter!==St&&s.minFilter!==xt&&s.minFilter!==wt||console.warn("THREE.WebGLRenderer: Unable to use linear filtering with floating point textures. OES_texture_float_linear not supported on this device."),t.texParameteri(n,t.TEXTURE_WRAP_S,E[s.wrapS]),t.texParameteri(n,t.TEXTURE_WRAP_T,E[s.wrapT]),n!==t.TEXTURE_3D&&n!==t.TEXTURE_2D_ARRAY||t.texParameteri(n,t.TEXTURE_WRAP_R,E[s.wrapR]),t.texParameteri(n,t.TEXTURE_MAG_FILTER,A[s.magFilter]),t.texParameteri(n,t.TEXTURE_MIN_FILTER,A[s.minFilter]),s.compareFunction&&(t.texParameteri(n,t.TEXTURE_COMPARE_MODE,t.COMPARE_REF_TO_TEXTURE),t.texParameteri(n,t.TEXTURE_COMPARE_FUNC,R[s.compareFunction])),!0===e.has("EXT_texture_filter_anisotropic")){if(s.magFilter===gt)return;if(s.minFilter!==xt&&s.minFilter!==wt)return;if(s.type===Lt&&!1===e.has("OES_texture_float_linear"))return;if(s.anisotropy>1||i.get(s).__currentAnisotropy){const a=e.get("EXT_texture_filter_anisotropic");t.texParameterf(n,a.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(s.anisotropy,r.getMaxAnisotropy())),i.get(s).__currentAnisotropy=s.anisotropy}}}function P(e,n){let i=!1;void 0===e.__webglInit&&(e.__webglInit=!0,n.addEventListener("dispose",M));const r=n.source;let s=d.get(r);void 0===s&&(s={},d.set(r,s));const o=function(t){const e=[];return e.push(t.wrapS),e.push(t.wrapT),e.push(t.wrapR||0),e.push(t.magFilter),e.push(t.minFilter),e.push(t.anisotropy),e.push(t.internalFormat),e.push(t.format),e.push(t.type),e.push(t.generateMipmaps),e.push(t.premultiplyAlpha),e.push(t.flipY),e.push(t.unpackAlignment),e.push(t.colorSpace),e.join()}(n);if(o!==e.__cacheKey){void 0===s[o]&&(s[o]={texture:t.createTexture(),usedTimes:0},a.memory.textures++,i=!0),s[o].usedTimes++;const r=s[e.__cacheKey];void 0!==r&&(s[e.__cacheKey].usedTimes--,0===r.usedTimes&&b(n)),e.__cacheKey=o,e.__webglTexture=s[o].texture}return i}function I(e,a,o){let l=t.TEXTURE_2D;(a.isDataArrayTexture||a.isCompressedArrayTexture)&&(l=t.TEXTURE_2D_ARRAY),a.isData3DTexture&&(l=t.TEXTURE_3D);const c=P(e,a),h=a.source;n.bindTexture(l,e.__webglTexture,t.TEXTURE0+o);const u=i.get(h);if(h.version!==u.__version||!0===c){n.activeTexture(t.TEXTURE0+o);const e=mi.getPrimaries(mi.workingColorSpace),i=a.colorSpace===Ze?null:mi.getPrimaries(a.colorSpace),d=a.colorSpace===Ze||e===i?t.NONE:t.BROWSER_DEFAULT_WEBGL;t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,a.flipY),t.pixelStorei(t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,a.premultiplyAlpha),t.pixelStorei(t.UNPACK_ALIGNMENT,a.unpackAlignment),t.pixelStorei(t.UNPACK_COLORSPACE_CONVERSION_WEBGL,d);let p=f(a.image,!1,r.maxTextureSize);p=z(a,p);const m=s.convert(a.format,a.colorSpace),M=s.convert(a.type);let S,b=_(a.internalFormat,m,M,a.colorSpace,a.isVideoTexture);C(l,a);const w=a.mipmaps,T=!0!==a.isVideoTexture,E=void 0===u.__version||!0===c,A=h.dataReady,R=y(a,p);if(a.isDepthTexture)b=x(a.format===Wt,a.type),E&&(T?n.texStorage2D(t.TEXTURE_2D,1,b,p.width,p.height):n.texImage2D(t.TEXTURE_2D,0,b,p.width,p.height,0,m,M,null));else if(a.isDataTexture)if(w.length>0){T&&E&&n.texStorage2D(t.TEXTURE_2D,R,b,w[0].width,w[0].height);for(let e=0,i=w.length;e0){const i=Wl(S.width,S.height,a.format,a.type);for(const r of a.layerUpdates){const s=S.data.subarray(r*i/S.data.BYTES_PER_ELEMENT,(r+1)*i/S.data.BYTES_PER_ELEMENT);n.compressedTexSubImage3D(t.TEXTURE_2D_ARRAY,e,0,0,r,S.width,S.height,1,m,s,0,0)}a.clearLayerUpdates()}else n.compressedTexSubImage3D(t.TEXTURE_2D_ARRAY,e,0,0,0,S.width,S.height,p.depth,m,S.data,0,0)}else n.compressedTexImage3D(t.TEXTURE_2D_ARRAY,e,b,S.width,S.height,p.depth,0,S.data,0,0);else console.warn("THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()");else T?A&&n.texSubImage3D(t.TEXTURE_2D_ARRAY,e,0,0,0,S.width,S.height,p.depth,m,M,S.data):n.texImage3D(t.TEXTURE_2D_ARRAY,e,b,S.width,S.height,p.depth,0,m,M,S.data)}else{T&&E&&n.texStorage2D(t.TEXTURE_2D,R,b,w[0].width,w[0].height);for(let e=0,i=w.length;e0){const e=Wl(p.width,p.height,a.format,a.type);for(const i of a.layerUpdates){const r=p.data.subarray(i*e/p.data.BYTES_PER_ELEMENT,(i+1)*e/p.data.BYTES_PER_ELEMENT);n.texSubImage3D(t.TEXTURE_2D_ARRAY,0,0,0,i,p.width,p.height,1,m,M,r)}a.clearLayerUpdates()}else n.texSubImage3D(t.TEXTURE_2D_ARRAY,0,0,0,0,p.width,p.height,p.depth,m,M,p.data)}else n.texImage3D(t.TEXTURE_2D_ARRAY,0,b,p.width,p.height,p.depth,0,m,M,p.data);else if(a.isData3DTexture)T?(E&&n.texStorage3D(t.TEXTURE_3D,R,b,p.width,p.height,p.depth),A&&n.texSubImage3D(t.TEXTURE_3D,0,0,0,0,p.width,p.height,p.depth,m,M,p.data)):n.texImage3D(t.TEXTURE_3D,0,b,p.width,p.height,p.depth,0,m,M,p.data);else if(a.isFramebufferTexture){if(E)if(T)n.texStorage2D(t.TEXTURE_2D,R,b,p.width,p.height);else{let e=p.width,i=p.height;for(let r=0;r>=1,i>>=1}}else if(w.length>0){if(T&&E){const e=k(w[0]);n.texStorage2D(t.TEXTURE_2D,R,b,e.width,e.height)}for(let e=0,i=w.length;e>h),i=Math.max(1,r.height>>h);c===t.TEXTURE_3D||c===t.TEXTURE_2D_ARRAY?n.texImage3D(c,h,p,e,i,r.depth,0,u,d,null):n.texImage2D(c,h,p,e,i,0,u,d,null)}n.bindFramebuffer(t.FRAMEBUFFER,e),B(r)?o.framebufferTexture2DMultisampleEXT(t.FRAMEBUFFER,l,c,i.get(a).__webglTexture,0,F(r)):(c===t.TEXTURE_2D||c>=t.TEXTURE_CUBE_MAP_POSITIVE_X&&c<=t.TEXTURE_CUBE_MAP_NEGATIVE_Z)&&t.framebufferTexture2D(t.FRAMEBUFFER,l,c,i.get(a).__webglTexture,h),n.bindFramebuffer(t.FRAMEBUFFER,null)}function U(e,n,i){if(t.bindRenderbuffer(t.RENDERBUFFER,e),n.depthBuffer){const r=n.depthTexture,s=r&&r.isDepthTexture?r.type:null,a=x(n.stencilBuffer,s),l=n.stencilBuffer?t.DEPTH_STENCIL_ATTACHMENT:t.DEPTH_ATTACHMENT,c=F(n);B(n)?o.renderbufferStorageMultisampleEXT(t.RENDERBUFFER,c,a,n.width,n.height):i?t.renderbufferStorageMultisample(t.RENDERBUFFER,c,a,n.width,n.height):t.renderbufferStorage(t.RENDERBUFFER,a,n.width,n.height),t.framebufferRenderbuffer(t.FRAMEBUFFER,l,t.RENDERBUFFER,e)}else{const e=n.textures;for(let r=0;r{delete r.__boundDepthTexture,delete r.__depthDisposeCallback,t.removeEventListener("dispose",e)};t.addEventListener("dispose",e),r.__depthDisposeCallback=e}r.__boundDepthTexture=t}if(e.depthTexture&&!r.__autoAllocateDepthBuffer){if(s)throw new Error("target.depthTexture not supported in Cube render targets");!function(e,r){if(r&&r.isWebGLCubeRenderTarget)throw new Error("Depth Texture with cube render targets is not supported");if(n.bindFramebuffer(t.FRAMEBUFFER,e),!r.depthTexture||!r.depthTexture.isDepthTexture)throw new Error("renderTarget.depthTexture must be an instance of THREE.DepthTexture");i.get(r.depthTexture).__webglTexture&&r.depthTexture.image.width===r.width&&r.depthTexture.image.height===r.height||(r.depthTexture.image.width=r.width,r.depthTexture.image.height=r.height,r.depthTexture.needsUpdate=!0),T(r.depthTexture,0);const s=i.get(r.depthTexture).__webglTexture,a=F(r);if(r.depthTexture.format===Gt)B(r)?o.framebufferTexture2DMultisampleEXT(t.FRAMEBUFFER,t.DEPTH_ATTACHMENT,t.TEXTURE_2D,s,0,a):t.framebufferTexture2D(t.FRAMEBUFFER,t.DEPTH_ATTACHMENT,t.TEXTURE_2D,s,0);else{if(r.depthTexture.format!==Wt)throw new Error("Unknown depthTexture format");B(r)?o.framebufferTexture2DMultisampleEXT(t.FRAMEBUFFER,t.DEPTH_STENCIL_ATTACHMENT,t.TEXTURE_2D,s,0,a):t.framebufferTexture2D(t.FRAMEBUFFER,t.DEPTH_STENCIL_ATTACHMENT,t.TEXTURE_2D,s,0)}}(r.__webglFramebuffer,e)}else if(s){r.__webglDepthbuffer=[];for(let i=0;i<6;i++)if(n.bindFramebuffer(t.FRAMEBUFFER,r.__webglFramebuffer[i]),void 0===r.__webglDepthbuffer[i])r.__webglDepthbuffer[i]=t.createRenderbuffer(),U(r.__webglDepthbuffer[i],e,!1);else{const n=e.stencilBuffer?t.DEPTH_STENCIL_ATTACHMENT:t.DEPTH_ATTACHMENT,s=r.__webglDepthbuffer[i];t.bindRenderbuffer(t.RENDERBUFFER,s),t.framebufferRenderbuffer(t.FRAMEBUFFER,n,t.RENDERBUFFER,s)}}else if(n.bindFramebuffer(t.FRAMEBUFFER,r.__webglFramebuffer),void 0===r.__webglDepthbuffer)r.__webglDepthbuffer=t.createRenderbuffer(),U(r.__webglDepthbuffer,e,!1);else{const n=e.stencilBuffer?t.DEPTH_STENCIL_ATTACHMENT:t.DEPTH_ATTACHMENT,i=r.__webglDepthbuffer;t.bindRenderbuffer(t.RENDERBUFFER,i),t.framebufferRenderbuffer(t.FRAMEBUFFER,n,t.RENDERBUFFER,i)}n.bindFramebuffer(t.FRAMEBUFFER,null)}const D=[],O=[];function F(t){return Math.min(r.maxSamples,t.samples)}function B(t){const n=i.get(t);return t.samples>0&&!0===e.has("WEBGL_multisampled_render_to_texture")&&!1!==n.__useRenderToTexture}function z(t,e){const n=t.colorSpace,i=t.format,r=t.type;return!0===t.isCompressedTexture||!0===t.isVideoTexture||n!==Ke&&n!==Ze&&(mi.getTransfer(n)===en?i===kt&&r===Et||console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType."):console.error("THREE.WebGLTextures: Unsupported texture color space:",n)),e}function k(t){return"undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement?(c.width=t.naturalWidth||t.width,c.height=t.naturalHeight||t.height):"undefined"!=typeof VideoFrame&&t instanceof VideoFrame?(c.width=t.displayWidth,c.height=t.displayHeight):(c.width=t.width,c.height=t.height),c}this.allocateTextureUnit=function(){const t=w;return t>=r.maxTextures&&console.warn("THREE.WebGLTextures: Trying to use "+t+" texture units while this GPU supports only "+r.maxTextures),w+=1,t},this.resetTextureUnits=function(){w=0},this.setTexture2D=T,this.setTexture2DArray=function(e,r){const s=i.get(e);e.version>0&&s.__version!==e.version?I(s,e,r):n.bindTexture(t.TEXTURE_2D_ARRAY,s.__webglTexture,t.TEXTURE0+r)},this.setTexture3D=function(e,r){const s=i.get(e);e.version>0&&s.__version!==e.version?I(s,e,r):n.bindTexture(t.TEXTURE_3D,s.__webglTexture,t.TEXTURE0+r)},this.setTextureCube=function(e,a){const o=i.get(e);e.version>0&&o.__version!==e.version?function(e,a,o){if(6!==a.image.length)return;const l=P(e,a),c=a.source;n.bindTexture(t.TEXTURE_CUBE_MAP,e.__webglTexture,t.TEXTURE0+o);const h=i.get(c);if(c.version!==h.__version||!0===l){n.activeTexture(t.TEXTURE0+o);const e=mi.getPrimaries(mi.workingColorSpace),i=a.colorSpace===Ze?null:mi.getPrimaries(a.colorSpace),u=a.colorSpace===Ze||e===i?t.NONE:t.BROWSER_DEFAULT_WEBGL;t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,a.flipY),t.pixelStorei(t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,a.premultiplyAlpha),t.pixelStorei(t.UNPACK_ALIGNMENT,a.unpackAlignment),t.pixelStorei(t.UNPACK_COLORSPACE_CONVERSION_WEBGL,u);const d=a.isCompressedTexture||a.image[0].isCompressedTexture,p=a.image[0]&&a.image[0].isDataTexture,m=[];for(let t=0;t<6;t++)m[t]=d||p?p?a.image[t].image:a.image[t]:f(a.image[t],!0,r.maxCubemapSize),m[t]=z(a,m[t]);const x=m[0],M=s.convert(a.format,a.colorSpace),S=s.convert(a.type),b=_(a.internalFormat,M,S,a.colorSpace),w=!0!==a.isVideoTexture,T=void 0===h.__version||!0===l,E=c.dataReady;let A,R=y(a,x);if(C(t.TEXTURE_CUBE_MAP,a),d){w&&T&&n.texStorage2D(t.TEXTURE_CUBE_MAP,R,b,x.width,x.height);for(let e=0;e<6;e++){A=m[e].mipmaps;for(let i=0;i0&&R++;const e=k(m[0]);n.texStorage2D(t.TEXTURE_CUBE_MAP,R,b,e.width,e.height)}for(let e=0;e<6;e++)if(p){w?E&&n.texSubImage2D(t.TEXTURE_CUBE_MAP_POSITIVE_X+e,0,0,0,m[e].width,m[e].height,M,S,m[e].data):n.texImage2D(t.TEXTURE_CUBE_MAP_POSITIVE_X+e,0,b,m[e].width,m[e].height,0,M,S,m[e].data);for(let i=0;i1;if(u||(void 0===l.__webglTexture&&(l.__webglTexture=t.createTexture()),l.__version=r.version,a.memory.textures++),h){o.__webglFramebuffer=[];for(let e=0;e<6;e++)if(r.mipmaps&&r.mipmaps.length>0){o.__webglFramebuffer[e]=[];for(let n=0;n0){o.__webglFramebuffer=[];for(let e=0;e0&&!1===B(e)){o.__webglMultisampledFramebuffer=t.createFramebuffer(),o.__webglColorRenderbuffer=[],n.bindFramebuffer(t.FRAMEBUFFER,o.__webglMultisampledFramebuffer);for(let n=0;n0)for(let i=0;i0)for(let n=0;n0)if(!1===B(e)){const r=e.textures,s=e.width,a=e.height;let o=t.COLOR_BUFFER_BIT;const c=e.stencilBuffer?t.DEPTH_STENCIL_ATTACHMENT:t.DEPTH_ATTACHMENT,h=i.get(e),u=r.length>1;if(u)for(let e=0;eo+c?(l.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:t.handedness,target:this})):!l.inputState.pinching&&a<=o-c&&(l.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:t.handedness,target:this}))}else null!==o&&t.gripSpace&&(r=e.getPose(t.gripSpace,n),null!==r&&(o.matrix.fromArray(r.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale),o.matrixWorldNeedsUpdate=!0,r.linearVelocity?(o.hasLinearVelocity=!0,o.linearVelocity.copy(r.linearVelocity)):o.hasLinearVelocity=!1,r.angularVelocity?(o.hasAngularVelocity=!0,o.angularVelocity.copy(r.angularVelocity)):o.hasAngularVelocity=!1));null!==a&&(i=e.getPose(t.targetRaySpace,n),null===i&&null!==r&&(i=r),null!==i&&(a.matrix.fromArray(i.transform.matrix),a.matrix.decompose(a.position,a.rotation,a.scale),a.matrixWorldNeedsUpdate=!0,i.linearVelocity?(a.hasLinearVelocity=!0,a.linearVelocity.copy(i.linearVelocity)):a.hasLinearVelocity=!1,i.angularVelocity?(a.hasAngularVelocity=!0,a.angularVelocity.copy(i.angularVelocity)):a.hasAngularVelocity=!1,this.dispatchEvent(Jl)))}return null!==a&&(a.visible=null!==i),null!==o&&(o.visible=null!==r),null!==l&&(l.visible=null!==s),this}_getHandJoint(t,e){if(void 0===t.joints[e.jointName]){const n=new Zl;n.matrixAutoUpdate=!1,n.visible=!1,t.joints[e.jointName]=n,t.add(n)}return t.joints[e.jointName]}}class $l{constructor(){this.texture=null,this.mesh=null,this.depthNear=0,this.depthFar=0}init(t,e,n){if(null===this.texture){const i=new bi;t.properties.get(i).__webglTexture=e.texture,e.depthNear==n.depthNear&&e.depthFar==n.depthFar||(this.depthNear=e.depthNear,this.depthFar=e.depthFar),this.texture=i}}getMesh(t){if(null!==this.texture&&null===this.mesh){const e=t.cameras[0].viewport,n=new Ys({vertexShader:"\nvoid main() {\n\n\tgl_Position = vec4( position, 1.0 );\n\n}",fragmentShader:"\nuniform sampler2DArray depthColor;\nuniform float depthWidth;\nuniform float depthHeight;\n\nvoid main() {\n\n\tvec2 coord = vec2( gl_FragCoord.x / depthWidth, gl_FragCoord.y / depthHeight );\n\n\tif ( coord.x >= 1.0 ) {\n\n\t\tgl_FragDepth = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r;\n\n\t} else {\n\n\t\tgl_FragDepth = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r;\n\n\t}\n\n}",uniforms:{depthColor:{value:this.texture},depthWidth:{value:e.z},depthHeight:{value:e.w}}});this.mesh=new Vs(new pa(20,20),n)}return this.mesh}reset(){this.texture=null,this.mesh=null}getDepthTexture(){return this.texture}}class Ql extends Hn{constructor(t,e){super();const n=this;let i=null,r=1,s=null,a="local-floor",o=1,l=null,c=null,h=null,u=null,d=null,p=null;const m=new $l,f=e.getContextAttributes();let g=null,v=null;const _=[],x=[],y=new ti;let M=null;const S=new Qs;S.layers.enable(1),S.viewport=new wi;const b=new Qs;b.layers.enable(2),b.viewport=new wi;const w=[S,b],T=new Yl;T.layers.enable(1),T.layers.enable(2);let E=null,A=null;function R(t){const e=x.indexOf(t.inputSource);if(-1===e)return;const n=_[e];void 0!==n&&(n.update(t.inputSource,t.frame,l||s),n.dispatchEvent({type:t.type,data:t.inputSource}))}function C(){i.removeEventListener("select",R),i.removeEventListener("selectstart",R),i.removeEventListener("selectend",R),i.removeEventListener("squeeze",R),i.removeEventListener("squeezestart",R),i.removeEventListener("squeezeend",R),i.removeEventListener("end",C),i.removeEventListener("inputsourceschange",P);for(let t=0;t<_.length;t++){const e=x[t];null!==e&&(x[t]=null,_[t].disconnect(e))}E=null,A=null,m.reset(),t.setRenderTarget(g),d=null,u=null,h=null,i=null,v=null,D.stop(),n.isPresenting=!1,t.setPixelRatio(M),t.setSize(y.width,y.height,!1),n.dispatchEvent({type:"sessionend"})}function P(t){for(let e=0;e=0&&(x[i]=null,_[i].disconnect(n))}for(let e=0;e=x.length){x.push(n),i=t;break}if(null===x[t]){x[t]=n,i=t;break}}if(-1===i)break}const r=_[i];r&&r.connect(n)}}this.cameraAutoUpdate=!0,this.enabled=!1,this.isPresenting=!1,this.getController=function(t){let e=_[t];return void 0===e&&(e=new Kl,_[t]=e),e.getTargetRaySpace()},this.getControllerGrip=function(t){let e=_[t];return void 0===e&&(e=new Kl,_[t]=e),e.getGripSpace()},this.getHand=function(t){let e=_[t];return void 0===e&&(e=new Kl,_[t]=e),e.getHandSpace()},this.setFramebufferScaleFactor=function(t){r=t,!0===n.isPresenting&&console.warn("THREE.WebXRManager: Cannot change framebuffer scale while presenting.")},this.setReferenceSpaceType=function(t){a=t,!0===n.isPresenting&&console.warn("THREE.WebXRManager: Cannot change reference space type while presenting.")},this.getReferenceSpace=function(){return l||s},this.setReferenceSpace=function(t){l=t},this.getBaseLayer=function(){return null!==u?u:d},this.getBinding=function(){return h},this.getFrame=function(){return p},this.getSession=function(){return i},this.setSession=async function(c){if(i=c,null!==i){if(g=t.getRenderTarget(),i.addEventListener("select",R),i.addEventListener("selectstart",R),i.addEventListener("selectend",R),i.addEventListener("squeeze",R),i.addEventListener("squeezestart",R),i.addEventListener("squeezeend",R),i.addEventListener("end",C),i.addEventListener("inputsourceschange",P),!0!==f.xrCompatible&&await e.makeXRCompatible(),M=t.getPixelRatio(),t.getSize(y),void 0===i.renderState.layers){const n={antialias:f.antialias,alpha:!0,depth:f.depth,stencil:f.stencil,framebufferScaleFactor:r};d=new XRWebGLLayer(i,e,n),i.updateRenderState({baseLayer:d}),t.setPixelRatio(1),t.setSize(d.framebufferWidth,d.framebufferHeight,!1),v=new Ei(d.framebufferWidth,d.framebufferHeight,{format:kt,type:Et,colorSpace:t.outputColorSpace,stencilBuffer:f.stencil})}else{let n=null,s=null,a=null;f.depth&&(a=f.stencil?e.DEPTH24_STENCIL8:e.DEPTH_COMPONENT24,n=f.stencil?Wt:Gt,s=f.stencil?Ot:It);const o={colorFormat:e.RGBA8,depthFormat:a,scaleFactor:r};h=new XRWebGLBinding(i,e),u=h.createProjectionLayer(o),i.updateRenderState({layers:[u]}),t.setPixelRatio(1),t.setSize(u.textureWidth,u.textureHeight,!1),v=new Ei(u.textureWidth,u.textureHeight,{format:kt,type:Et,depthTexture:new Ka(u.textureWidth,u.textureHeight,s,void 0,void 0,void 0,void 0,void 0,void 0,n),stencilBuffer:f.stencil,colorSpace:t.outputColorSpace,samples:f.antialias?4:0,resolveDepthBuffer:!1===u.ignoreDepthValues})}v.isXRRenderTarget=!0,this.setFoveation(o),l=null,s=await i.requestReferenceSpace(a),D.setContext(i),D.start(),n.isPresenting=!0,n.dispatchEvent({type:"sessionstart"})}},this.getEnvironmentBlendMode=function(){if(null!==i)return i.environmentBlendMode},this.getDepthTexture=function(){return m.getDepthTexture()};const I=new Li,L=new Li;function U(t,e){null===e?t.matrixWorld.copy(t.matrix):t.matrixWorld.multiplyMatrices(e.matrixWorld,t.matrix),t.matrixWorldInverse.copy(t.matrixWorld).invert()}this.updateCamera=function(t){if(null===i)return;let e=t.near,n=t.far;null!==m.texture&&(m.depthNear>0&&(e=m.depthNear),m.depthFar>0&&(n=m.depthFar)),T.near=b.near=S.near=e,T.far=b.far=S.far=n,E===T.near&&A===T.far||(i.updateRenderState({depthNear:T.near,depthFar:T.far}),E=T.near,A=T.far);const r=t.parent,s=T.cameras;U(T,r);for(let t=0;t0&&(t.alphaTest.value=i.alphaTest);const r=e.get(i),s=r.envMap,a=r.envMapRotation;s&&(t.envMap.value=s,tc.copy(a),tc.x*=-1,tc.y*=-1,tc.z*=-1,s.isCubeTexture&&!1===s.isRenderTargetTexture&&(tc.y*=-1,tc.z*=-1),t.envMapRotation.value.setFromMatrix4(ec.makeRotationFromEuler(tc)),t.flipEnvMap.value=s.isCubeTexture&&!1===s.isRenderTargetTexture?-1:1,t.reflectivity.value=i.reflectivity,t.ior.value=i.ior,t.refractionRatio.value=i.refractionRatio),i.lightMap&&(t.lightMap.value=i.lightMap,t.lightMapIntensity.value=i.lightMapIntensity,n(i.lightMap,t.lightMapTransform)),i.aoMap&&(t.aoMap.value=i.aoMap,t.aoMapIntensity.value=i.aoMapIntensity,n(i.aoMap,t.aoMapTransform))}return{refreshFogUniforms:function(e,n){n.color.getRGB(e.fogColor.value,js(t)),n.isFog?(e.fogNear.value=n.near,e.fogFar.value=n.far):n.isFogExp2&&(e.fogDensity.value=n.density)},refreshMaterialUniforms:function(t,r,s,a,o){r.isMeshBasicMaterial||r.isMeshLambertMaterial?i(t,r):r.isMeshToonMaterial?(i(t,r),function(t,e){e.gradientMap&&(t.gradientMap.value=e.gradientMap)}(t,r)):r.isMeshPhongMaterial?(i(t,r),function(t,e){t.specular.value.copy(e.specular),t.shininess.value=Math.max(e.shininess,1e-4)}(t,r)):r.isMeshStandardMaterial?(i(t,r),function(t,e){t.metalness.value=e.metalness,e.metalnessMap&&(t.metalnessMap.value=e.metalnessMap,n(e.metalnessMap,t.metalnessMapTransform));t.roughness.value=e.roughness,e.roughnessMap&&(t.roughnessMap.value=e.roughnessMap,n(e.roughnessMap,t.roughnessMapTransform));e.envMap&&(t.envMapIntensity.value=e.envMapIntensity)}(t,r),r.isMeshPhysicalMaterial&&function(t,e,i){t.ior.value=e.ior,e.sheen>0&&(t.sheenColor.value.copy(e.sheenColor).multiplyScalar(e.sheen),t.sheenRoughness.value=e.sheenRoughness,e.sheenColorMap&&(t.sheenColorMap.value=e.sheenColorMap,n(e.sheenColorMap,t.sheenColorMapTransform)),e.sheenRoughnessMap&&(t.sheenRoughnessMap.value=e.sheenRoughnessMap,n(e.sheenRoughnessMap,t.sheenRoughnessMapTransform)));e.clearcoat>0&&(t.clearcoat.value=e.clearcoat,t.clearcoatRoughness.value=e.clearcoatRoughness,e.clearcoatMap&&(t.clearcoatMap.value=e.clearcoatMap,n(e.clearcoatMap,t.clearcoatMapTransform)),e.clearcoatRoughnessMap&&(t.clearcoatRoughnessMap.value=e.clearcoatRoughnessMap,n(e.clearcoatRoughnessMap,t.clearcoatRoughnessMapTransform)),e.clearcoatNormalMap&&(t.clearcoatNormalMap.value=e.clearcoatNormalMap,n(e.clearcoatNormalMap,t.clearcoatNormalMapTransform),t.clearcoatNormalScale.value.copy(e.clearcoatNormalScale),e.side===d&&t.clearcoatNormalScale.value.negate()));e.dispersion>0&&(t.dispersion.value=e.dispersion);e.iridescence>0&&(t.iridescence.value=e.iridescence,t.iridescenceIOR.value=e.iridescenceIOR,t.iridescenceThicknessMinimum.value=e.iridescenceThicknessRange[0],t.iridescenceThicknessMaximum.value=e.iridescenceThicknessRange[1],e.iridescenceMap&&(t.iridescenceMap.value=e.iridescenceMap,n(e.iridescenceMap,t.iridescenceMapTransform)),e.iridescenceThicknessMap&&(t.iridescenceThicknessMap.value=e.iridescenceThicknessMap,n(e.iridescenceThicknessMap,t.iridescenceThicknessMapTransform)));e.transmission>0&&(t.transmission.value=e.transmission,t.transmissionSamplerMap.value=i.texture,t.transmissionSamplerSize.value.set(i.width,i.height),e.transmissionMap&&(t.transmissionMap.value=e.transmissionMap,n(e.transmissionMap,t.transmissionMapTransform)),t.thickness.value=e.thickness,e.thicknessMap&&(t.thicknessMap.value=e.thicknessMap,n(e.thicknessMap,t.thicknessMapTransform)),t.attenuationDistance.value=e.attenuationDistance,t.attenuationColor.value.copy(e.attenuationColor));e.anisotropy>0&&(t.anisotropyVector.value.set(e.anisotropy*Math.cos(e.anisotropyRotation),e.anisotropy*Math.sin(e.anisotropyRotation)),e.anisotropyMap&&(t.anisotropyMap.value=e.anisotropyMap,n(e.anisotropyMap,t.anisotropyMapTransform)));t.specularIntensity.value=e.specularIntensity,t.specularColor.value.copy(e.specularColor),e.specularColorMap&&(t.specularColorMap.value=e.specularColorMap,n(e.specularColorMap,t.specularColorMapTransform));e.specularIntensityMap&&(t.specularIntensityMap.value=e.specularIntensityMap,n(e.specularIntensityMap,t.specularIntensityMapTransform))}(t,r,o)):r.isMeshMatcapMaterial?(i(t,r),function(t,e){e.matcap&&(t.matcap.value=e.matcap)}(t,r)):r.isMeshDepthMaterial?i(t,r):r.isMeshDistanceMaterial?(i(t,r),function(t,n){const i=e.get(n).light;t.referencePosition.value.setFromMatrixPosition(i.matrixWorld),t.nearDistance.value=i.shadow.camera.near,t.farDistance.value=i.shadow.camera.far}(t,r)):r.isMeshNormalMaterial?i(t,r):r.isLineBasicMaterial?(function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,e.map&&(t.map.value=e.map,n(e.map,t.mapTransform))}(t,r),r.isLineDashedMaterial&&function(t,e){t.dashSize.value=e.dashSize,t.totalSize.value=e.dashSize+e.gapSize,t.scale.value=e.scale}(t,r)):r.isPointsMaterial?function(t,e,i,r){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.size.value=e.size*i,t.scale.value=.5*r,e.map&&(t.map.value=e.map,n(e.map,t.uvTransform));e.alphaMap&&(t.alphaMap.value=e.alphaMap,n(e.alphaMap,t.alphaMapTransform));e.alphaTest>0&&(t.alphaTest.value=e.alphaTest)}(t,r,s,a):r.isSpriteMaterial?function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.rotation.value=e.rotation,e.map&&(t.map.value=e.map,n(e.map,t.mapTransform));e.alphaMap&&(t.alphaMap.value=e.alphaMap,n(e.alphaMap,t.alphaMapTransform));e.alphaTest>0&&(t.alphaTest.value=e.alphaTest)}(t,r):r.isShadowMaterial?(t.color.value.copy(r.color),t.opacity.value=r.opacity):r.isShaderMaterial&&(r.uniformsNeedUpdate=!1)}}}function ic(t,e,n,i){let r={},s={},a=[];const o=t.getParameter(t.MAX_UNIFORM_BUFFER_BINDINGS);function l(t,e,n,i){const r=t.value,s=e+"_"+n;if(void 0===i[s])return i[s]="number"==typeof r||"boolean"==typeof r?r:r.clone(),!0;{const t=i[s];if("number"==typeof r||"boolean"==typeof r){if(t!==r)return i[s]=r,!0}else if(!1===t.equals(r))return t.copy(r),!0}return!1}function c(t){const e={boundary:0,storage:0};return"number"==typeof t||"boolean"==typeof t?(e.boundary=4,e.storage=4):t.isVector2?(e.boundary=8,e.storage=8):t.isVector3||t.isColor?(e.boundary=16,e.storage=12):t.isVector4?(e.boundary=16,e.storage=16):t.isMatrix3?(e.boundary=48,e.storage=48):t.isMatrix4?(e.boundary=64,e.storage=64):t.isTexture?console.warn("THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group."):console.warn("THREE.WebGLRenderer: Unsupported uniform value type.",t),e}function h(e){const n=e.target;n.removeEventListener("dispose",h);const i=a.indexOf(n.__bindingPointIndex);a.splice(i,1),t.deleteBuffer(r[n.id]),delete r[n.id],delete s[n.id]}return{bind:function(t,e){const n=e.program;i.uniformBlockBinding(t,n)},update:function(n,u){let d=r[n.id];void 0===d&&(!function(t){const e=t.uniforms;let n=0;const i=16;for(let t=0,r=e.length;t0&&(n+=i-r);t.__size=n,t.__cache={}}(n),d=function(e){const n=function(){for(let t=0;t0),u=!!n.morphAttributes.position,d=!!n.morphAttributes.normal,p=!!n.morphAttributes.color;let m=K;i.toneMapped&&(null!==T&&!0!==T.isXRRenderTarget||(m=M.toneMapping));const f=n.morphAttributes.position||n.morphAttributes.normal||n.morphAttributes.color,g=void 0!==f?f.length:0,v=nt.get(i),x=_.state.lights;if(!0===H&&(!0===G||t!==A)){const e=t===A&&i.id===E;pt.setState(i,t,e)}let y=!1;i.version===v.__version?v.needsLights&&v.lightsStateVersion!==x.state.version||v.outputColorSpace!==o||r.isBatchedMesh&&!1===v.batching?y=!0:r.isBatchedMesh||!0!==v.batching?r.isBatchedMesh&&!0===v.batchingColor&&null===r.colorTexture||r.isBatchedMesh&&!1===v.batchingColor&&null!==r.colorTexture||r.isInstancedMesh&&!1===v.instancing?y=!0:r.isInstancedMesh||!0!==v.instancing?r.isSkinnedMesh&&!1===v.skinning?y=!0:r.isSkinnedMesh||!0!==v.skinning?r.isInstancedMesh&&!0===v.instancingColor&&null===r.instanceColor||r.isInstancedMesh&&!1===v.instancingColor&&null!==r.instanceColor||r.isInstancedMesh&&!0===v.instancingMorph&&null===r.morphTexture||r.isInstancedMesh&&!1===v.instancingMorph&&null!==r.morphTexture||v.envMap!==l||!0===i.fog&&v.fog!==s?y=!0:void 0===v.numClippingPlanes||v.numClippingPlanes===pt.numPlanes&&v.numIntersection===pt.numIntersection?(v.vertexAlphas!==c||v.vertexTangents!==h||v.morphTargets!==u||v.morphNormals!==d||v.morphColors!==p||v.toneMapping!==m||v.morphTargetsCount!==g)&&(y=!0):y=!0:y=!0:y=!0:y=!0:(y=!0,v.__version=i.version);let S=v.currentProgram;!0===y&&(S=Kt(i,e,r));let b=!1,w=!1,R=!1;const C=S.getUniforms(),P=v.uniforms;tt.useProgram(S.program)&&(b=!0,w=!0,R=!0);i.id!==E&&(E=i.id,w=!0);if(b||A!==t){Q.reverseDepthBuffer?(W.copy(t.projectionMatrix),function(t){const e=t.elements;e[2]=.5*e[2]+.5*e[3],e[6]=.5*e[6]+.5*e[7],e[10]=.5*e[10]+.5*e[11],e[14]=.5*e[14]+.5*e[15]}(W),function(t){const e=t.elements;-1===e[11]?(e[10]=-e[10]-1,e[14]=-e[14]):(e[10]=-e[10],e[14]=1-e[14])}(W),C.setValue(St,"projectionMatrix",W)):C.setValue(St,"projectionMatrix",t.projectionMatrix),C.setValue(St,"viewMatrix",t.matrixWorldInverse);const e=C.map.cameraPosition;void 0!==e&&e.setValue(St,j.setFromMatrixPosition(t.matrixWorld)),Q.logarithmicDepthBuffer&&C.setValue(St,"logDepthBufFC",2/(Math.log(t.far+1)/Math.LN2)),(i.isMeshPhongMaterial||i.isMeshToonMaterial||i.isMeshLambertMaterial||i.isMeshBasicMaterial||i.isMeshStandardMaterial||i.isShaderMaterial)&&C.setValue(St,"isOrthographic",!0===t.isOrthographicCamera),A!==t&&(A=t,w=!0,R=!0)}if(r.isSkinnedMesh){C.setOptional(St,r,"bindMatrix"),C.setOptional(St,r,"bindMatrixInverse");const t=r.skeleton;t&&(null===t.boneTexture&&t.computeBoneTexture(),C.setValue(St,"boneTexture",t.boneTexture,it))}r.isBatchedMesh&&(C.setOptional(St,r,"batchingTexture"),C.setValue(St,"batchingTexture",r._matricesTexture,it),C.setOptional(St,r,"batchingIdTexture"),C.setValue(St,"batchingIdTexture",r._indirectTexture,it),C.setOptional(St,r,"batchingColorTexture"),null!==r._colorsTexture&&C.setValue(St,"batchingColorTexture",r._colorsTexture,it));const I=n.morphAttributes;void 0===I.position&&void 0===I.normal&&void 0===I.color||gt.update(r,n,S);(w||v.receiveShadow!==r.receiveShadow)&&(v.receiveShadow=r.receiveShadow,C.setValue(St,"receiveShadow",r.receiveShadow));i.isMeshGouraudMaterial&&null!==i.envMap&&(P.envMap.value=l,P.flipEnvMap.value=l.isCubeTexture&&!1===l.isRenderTargetTexture?-1:1);i.isMeshStandardMaterial&&null===i.envMap&&null!==e.environment&&(P.envMapIntensity.value=e.environmentIntensity);w&&(C.setValue(St,"toneMappingExposure",M.toneMappingExposure),v.needsLights&&(U=R,(L=P).ambientLightColor.needsUpdate=U,L.lightProbe.needsUpdate=U,L.directionalLights.needsUpdate=U,L.directionalLightShadows.needsUpdate=U,L.pointLights.needsUpdate=U,L.pointLightShadows.needsUpdate=U,L.spotLights.needsUpdate=U,L.spotLightShadows.needsUpdate=U,L.rectAreaLights.needsUpdate=U,L.hemisphereLights.needsUpdate=U),s&&!0===i.fog&&ht.refreshFogUniforms(P,s),ht.refreshMaterialUniforms(P,i,D,N,_.state.transmissionRenderTarget[t.id]),il.upload(St,$t(v),P,it));var L,U;i.isShaderMaterial&&!0===i.uniformsNeedUpdate&&(il.upload(St,$t(v),P,it),i.uniformsNeedUpdate=!1);i.isSpriteMaterial&&C.setValue(St,"center",r.center);if(C.setValue(St,"modelViewMatrix",r.modelViewMatrix),C.setValue(St,"normalMatrix",r.normalMatrix),C.setValue(St,"modelMatrix",r.matrixWorld),i.isShaderMaterial||i.isRawShaderMaterial){const t=i.uniformsGroups;for(let e=0,n=t.length;e{function n(){i.forEach((function(t){nt.get(t).currentProgram.isReady()&&i.delete(t)})),0!==i.size?setTimeout(n,10):e(t)}null!==$.get("KHR_parallel_shader_compile")?n():setTimeout(n,10)}))};let zt=null;function kt(){Ht.stop()}function Vt(){Ht.start()}const Ht=new ua;function Gt(t,e,n,i){if(!1===t.visible)return;if(t.layers.test(e.layers))if(t.isGroup)n=t.renderOrder;else if(t.isLOD)!0===t.autoUpdate&&t.update(e);else if(t.isLight)_.pushLight(t),t.castShadow&&_.pushShadow(t);else if(t.isSprite){if(!t.frustumCulled||V.intersectsSprite(t)){i&&q.setFromMatrixPosition(t.matrixWorld).applyMatrix4(X);const e=lt.update(t),r=t.material;r.visible&&v.push(t,e,r,n,q.z,null)}}else if((t.isMesh||t.isLine||t.isPoints)&&(!t.frustumCulled||V.intersectsObject(t))){const e=lt.update(t),r=t.material;if(i&&(void 0!==t.boundingSphere?(null===t.boundingSphere&&t.computeBoundingSphere(),q.copy(t.boundingSphere.center)):(null===e.boundingSphere&&e.computeBoundingSphere(),q.copy(e.boundingSphere.center)),q.applyMatrix4(t.matrixWorld).applyMatrix4(X)),Array.isArray(r)){const i=e.groups;for(let s=0,a=i.length;s0&&qt(r,e,n),s.length>0&&qt(s,e,n),a.length>0&&qt(a,e,n),tt.buffers.depth.setTest(!0),tt.buffers.depth.setMask(!0),tt.buffers.color.setMask(!0),tt.setPolygonOffset(!1)}function Xt(t,e,n,i){if(null!==(!0===n.isScene?n.overrideMaterial:null))return;void 0===_.state.transmissionRenderTarget[i.id]&&(_.state.transmissionRenderTarget[i.id]=new Ei(1,1,{generateMipmaps:!0,type:$.has("EXT_color_buffer_half_float")||$.has("EXT_color_buffer_float")?Ut:Et,minFilter:wt,samples:4,stencilBuffer:s,resolveDepthBuffer:!1,resolveStencilBuffer:!1,colorSpace:mi.workingColorSpace}));const r=_.state.transmissionRenderTarget[i.id],a=i.viewport||R;r.setSize(a.z,a.w);const o=M.getRenderTarget();M.setRenderTarget(r),M.getClearColor(I),L=M.getClearAlpha(),L<1&&M.setClearColor(16777215,.5),M.clear(),Z&&ft.render(n);const l=M.toneMapping;M.toneMapping=K;const c=i.viewport;if(void 0!==i.viewport&&(i.viewport=void 0),_.setupLightsView(i),!0===H&&pt.setGlobalState(M.clippingPlanes,i),qt(t,n,i),it.updateMultisampleRenderTarget(r),it.updateRenderTargetMipmap(r),!1===$.has("WEBGL_multisampled_render_to_texture")){let t=!1;for(let r=0,s=e.length;r0)for(let e=0,s=n.length;e0&&Xt(i,r,t,e),Z&&ft.render(t),Wt(v,t,e);null!==T&&(it.updateMultisampleRenderTarget(T),it.updateRenderTargetMipmap(T)),!0===t.isScene&&t.onAfterRender(M,t,e),yt.resetDefaultState(),E=-1,A=null,y.pop(),y.length>0?(_=y[y.length-1],!0===H&&pt.setGlobalState(M.clippingPlanes,_.state.camera)):_=null,x.pop(),v=x.length>0?x[x.length-1]:null},this.getActiveCubeFace=function(){return b},this.getActiveMipmapLevel=function(){return w},this.getRenderTarget=function(){return T},this.setRenderTargetTextures=function(t,e,n){nt.get(t.texture).__webglTexture=e,nt.get(t.depthTexture).__webglTexture=n;const i=nt.get(t);i.__hasExternalTextures=!0,i.__autoAllocateDepthBuffer=void 0===n,i.__autoAllocateDepthBuffer||!0===$.has("WEBGL_multisampled_render_to_texture")&&(console.warn("THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided"),i.__useRenderToTexture=!1)},this.setRenderTargetFramebuffer=function(t,e){const n=nt.get(t);n.__webglFramebuffer=e,n.__useDefaultFramebuffer=void 0===e},this.setRenderTarget=function(t,e=0,n=0){T=t,b=e,w=n;let i=!0,r=null,s=!1,a=!1;if(t){const o=nt.get(t);if(void 0!==o.__useDefaultFramebuffer)tt.bindFramebuffer(St.FRAMEBUFFER,null),i=!1;else if(void 0===o.__webglFramebuffer)it.setupRenderTarget(t);else if(o.__hasExternalTextures)it.rebindTextures(t,nt.get(t.texture).__webglTexture,nt.get(t.depthTexture).__webglTexture);else if(t.depthBuffer){const e=t.depthTexture;if(o.__boundDepthTexture!==e){if(null!==e&&nt.has(e)&&(t.width!==e.image.width||t.height!==e.image.height))throw new Error("WebGLRenderTarget: Attached DepthTexture is initialized to the incorrect size.");it.setupDepthRenderbuffer(t)}}const l=t.texture;(l.isData3DTexture||l.isDataArrayTexture||l.isCompressedArrayTexture)&&(a=!0);const c=nt.get(t).__webglFramebuffer;t.isWebGLCubeRenderTarget?(r=Array.isArray(c[e])?c[e][n]:c[e],s=!0):r=t.samples>0&&!1===it.useMultisampledRTT(t)?nt.get(t).__webglMultisampledFramebuffer:Array.isArray(c)?c[n]:c,R.copy(t.viewport),C.copy(t.scissor),P=t.scissorTest}else R.copy(B).multiplyScalar(D).floor(),C.copy(z).multiplyScalar(D).floor(),P=k;if(tt.bindFramebuffer(St.FRAMEBUFFER,r)&&i&&tt.drawBuffers(t,r),tt.viewport(R),tt.scissor(C),tt.setScissorTest(P),s){const i=nt.get(t.texture);St.framebufferTexture2D(St.FRAMEBUFFER,St.COLOR_ATTACHMENT0,St.TEXTURE_CUBE_MAP_POSITIVE_X+e,i.__webglTexture,n)}else if(a){const i=nt.get(t.texture),r=e||0;St.framebufferTextureLayer(St.FRAMEBUFFER,St.COLOR_ATTACHMENT0,i.__webglTexture,n||0,r)}E=-1},this.readRenderTargetPixels=function(t,e,n,i,r,s,a){if(!t||!t.isWebGLRenderTarget)return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let o=nt.get(t).__webglFramebuffer;if(t.isWebGLCubeRenderTarget&&void 0!==a&&(o=o[a]),o){tt.bindFramebuffer(St.FRAMEBUFFER,o);try{const a=t.texture,o=a.format,l=a.type;if(!Q.textureFormatReadable(o))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");if(!Q.textureTypeReadable(l))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");e>=0&&e<=t.width-i&&n>=0&&n<=t.height-r&&St.readPixels(e,n,i,r,xt.convert(o),xt.convert(l),s)}finally{const t=null!==T?nt.get(T).__webglFramebuffer:null;tt.bindFramebuffer(St.FRAMEBUFFER,t)}}},this.readRenderTargetPixelsAsync=async function(t,e,n,i,r,s,a){if(!t||!t.isWebGLRenderTarget)throw new Error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let o=nt.get(t).__webglFramebuffer;if(t.isWebGLCubeRenderTarget&&void 0!==a&&(o=o[a]),o){const a=t.texture,l=a.format,c=a.type;if(!Q.textureFormatReadable(l))throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in RGBA or implementation defined format.");if(!Q.textureTypeReadable(c))throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in UnsignedByteType or implementation defined type.");if(e>=0&&e<=t.width-i&&n>=0&&n<=t.height-r){tt.bindFramebuffer(St.FRAMEBUFFER,o);const t=St.createBuffer();St.bindBuffer(St.PIXEL_PACK_BUFFER,t),St.bufferData(St.PIXEL_PACK_BUFFER,s.byteLength,St.STREAM_READ),St.readPixels(e,n,i,r,xt.convert(l),xt.convert(c),0);const a=null!==T?nt.get(T).__webglFramebuffer:null;tt.bindFramebuffer(St.FRAMEBUFFER,a);const h=St.fenceSync(St.SYNC_GPU_COMMANDS_COMPLETE,0);return St.flush(),await function(t,e,n){return new Promise((function(i,r){setTimeout((function s(){switch(t.clientWaitSync(e,t.SYNC_FLUSH_COMMANDS_BIT,0)){case t.WAIT_FAILED:r();break;case t.TIMEOUT_EXPIRED:setTimeout(s,n);break;default:i()}}),n)}))}(St,h,4),St.bindBuffer(St.PIXEL_PACK_BUFFER,t),St.getBufferSubData(St.PIXEL_PACK_BUFFER,0,s),St.deleteBuffer(t),St.deleteSync(h),s}throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested read bounds are out of range.")}},this.copyFramebufferToTexture=function(t,e=null,n=0){!0!==t.isTexture&&(ci("WebGLRenderer: copyFramebufferToTexture function signature has changed."),e=arguments[0]||null,t=arguments[1]);const i=Math.pow(2,-n),r=Math.floor(t.image.width*i),s=Math.floor(t.image.height*i),a=null!==e?e.x:0,o=null!==e?e.y:0;it.setTexture2D(t,0),St.copyTexSubImage2D(St.TEXTURE_2D,n,0,0,a,o,r,s),tt.unbindTexture()},this.copyTextureToTexture=function(t,e,n=null,i=null,r=0){let s,a,o,l,c,h;!0!==t.isTexture&&(ci("WebGLRenderer: copyTextureToTexture function signature has changed."),i=arguments[0]||null,t=arguments[1],e=arguments[2],r=arguments[3]||0,n=null),null!==n?(s=n.max.x-n.min.x,a=n.max.y-n.min.y,o=n.min.x,l=n.min.y):(s=t.image.width,a=t.image.height,o=0,l=0),null!==i?(c=i.x,h=i.y):(c=0,h=0);const u=xt.convert(e.format),d=xt.convert(e.type);it.setTexture2D(e,0),St.pixelStorei(St.UNPACK_FLIP_Y_WEBGL,e.flipY),St.pixelStorei(St.UNPACK_PREMULTIPLY_ALPHA_WEBGL,e.premultiplyAlpha),St.pixelStorei(St.UNPACK_ALIGNMENT,e.unpackAlignment);const p=St.getParameter(St.UNPACK_ROW_LENGTH),m=St.getParameter(St.UNPACK_IMAGE_HEIGHT),f=St.getParameter(St.UNPACK_SKIP_PIXELS),g=St.getParameter(St.UNPACK_SKIP_ROWS),v=St.getParameter(St.UNPACK_SKIP_IMAGES),_=t.isCompressedTexture?t.mipmaps[r]:t.image;St.pixelStorei(St.UNPACK_ROW_LENGTH,_.width),St.pixelStorei(St.UNPACK_IMAGE_HEIGHT,_.height),St.pixelStorei(St.UNPACK_SKIP_PIXELS,o),St.pixelStorei(St.UNPACK_SKIP_ROWS,l),t.isDataTexture?St.texSubImage2D(St.TEXTURE_2D,r,c,h,s,a,u,d,_.data):t.isCompressedTexture?St.compressedTexSubImage2D(St.TEXTURE_2D,r,c,h,_.width,_.height,u,_.data):St.texSubImage2D(St.TEXTURE_2D,r,c,h,s,a,u,d,_),St.pixelStorei(St.UNPACK_ROW_LENGTH,p),St.pixelStorei(St.UNPACK_IMAGE_HEIGHT,m),St.pixelStorei(St.UNPACK_SKIP_PIXELS,f),St.pixelStorei(St.UNPACK_SKIP_ROWS,g),St.pixelStorei(St.UNPACK_SKIP_IMAGES,v),0===r&&e.generateMipmaps&&St.generateMipmap(St.TEXTURE_2D),tt.unbindTexture()},this.copyTextureToTexture3D=function(t,e,n=null,i=null,r=0){let s,a,o,l,c,h,u,d,p;!0!==t.isTexture&&(ci("WebGLRenderer: copyTextureToTexture3D function signature has changed."),n=arguments[0]||null,i=arguments[1]||null,t=arguments[2],e=arguments[3],r=arguments[4]||0);const m=t.isCompressedTexture?t.mipmaps[r]:t.image;null!==n?(s=n.max.x-n.min.x,a=n.max.y-n.min.y,o=n.max.z-n.min.z,l=n.min.x,c=n.min.y,h=n.min.z):(s=m.width,a=m.height,o=m.depth,l=0,c=0,h=0),null!==i?(u=i.x,d=i.y,p=i.z):(u=0,d=0,p=0);const f=xt.convert(e.format),g=xt.convert(e.type);let v;if(e.isData3DTexture)it.setTexture3D(e,0),v=St.TEXTURE_3D;else{if(!e.isDataArrayTexture&&!e.isCompressedArrayTexture)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.");it.setTexture2DArray(e,0),v=St.TEXTURE_2D_ARRAY}St.pixelStorei(St.UNPACK_FLIP_Y_WEBGL,e.flipY),St.pixelStorei(St.UNPACK_PREMULTIPLY_ALPHA_WEBGL,e.premultiplyAlpha),St.pixelStorei(St.UNPACK_ALIGNMENT,e.unpackAlignment);const _=St.getParameter(St.UNPACK_ROW_LENGTH),x=St.getParameter(St.UNPACK_IMAGE_HEIGHT),y=St.getParameter(St.UNPACK_SKIP_PIXELS),M=St.getParameter(St.UNPACK_SKIP_ROWS),S=St.getParameter(St.UNPACK_SKIP_IMAGES);St.pixelStorei(St.UNPACK_ROW_LENGTH,m.width),St.pixelStorei(St.UNPACK_IMAGE_HEIGHT,m.height),St.pixelStorei(St.UNPACK_SKIP_PIXELS,l),St.pixelStorei(St.UNPACK_SKIP_ROWS,c),St.pixelStorei(St.UNPACK_SKIP_IMAGES,h),t.isDataTexture||t.isData3DTexture?St.texSubImage3D(v,r,u,d,p,s,a,o,f,g,m.data):e.isCompressedArrayTexture?St.compressedTexSubImage3D(v,r,u,d,p,s,a,o,f,m.data):St.texSubImage3D(v,r,u,d,p,s,a,o,f,g,m),St.pixelStorei(St.UNPACK_ROW_LENGTH,_),St.pixelStorei(St.UNPACK_IMAGE_HEIGHT,x),St.pixelStorei(St.UNPACK_SKIP_PIXELS,y),St.pixelStorei(St.UNPACK_SKIP_ROWS,M),St.pixelStorei(St.UNPACK_SKIP_IMAGES,S),0===r&&e.generateMipmaps&&St.generateMipmap(v),tt.unbindTexture()},this.initRenderTarget=function(t){void 0===nt.get(t).__webglFramebuffer&&it.setupRenderTarget(t)},this.initTexture=function(t){t.isCubeTexture?it.setTextureCube(t,0):t.isData3DTexture?it.setTexture3D(t,0):t.isDataArrayTexture||t.isCompressedArrayTexture?it.setTexture2DArray(t,0):it.setTexture2D(t,0),tt.unbindTexture()},this.resetState=function(){b=0,w=0,T=null,tt.reset(),yt.reset()},"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}get coordinateSystem(){return kn}get outputColorSpace(){return this._outputColorSpace}set outputColorSpace(t){this._outputColorSpace=t;const e=this.getContext();e.drawingBufferColorSpace=t===$e?"display-p3":"srgb",e.unpackColorSpace=mi.workingColorSpace===Qe?"display-p3":"srgb"}}class sc{constructor(t,e=25e-5){this.isFogExp2=!0,this.name="",this.color=new ts(t),this.density=e}clone(){return new sc(this.color,this.density)}toJSON(){return{type:"FogExp2",name:this.name,color:this.color.getHex(),density:this.density}}}class ac{constructor(t,e=1,n=1e3){this.isFog=!0,this.name="",this.color=new ts(t),this.near=e,this.far=n}clone(){return new ac(this.color,this.near,this.far)}toJSON(){return{type:"Fog",name:this.name,color:this.color.getHex(),near:this.near,far:this.far}}}class oc extends Dr{constructor(){super(),this.isScene=!0,this.type="Scene",this.background=null,this.environment=null,this.fog=null,this.backgroundBlurriness=0,this.backgroundIntensity=1,this.backgroundRotation=new _r,this.environmentIntensity=1,this.environmentRotation=new _r,this.overrideMaterial=null,"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}copy(t,e){return super.copy(t,e),null!==t.background&&(this.background=t.background.clone()),null!==t.environment&&(this.environment=t.environment.clone()),null!==t.fog&&(this.fog=t.fog.clone()),this.backgroundBlurriness=t.backgroundBlurriness,this.backgroundIntensity=t.backgroundIntensity,this.backgroundRotation.copy(t.backgroundRotation),this.environmentIntensity=t.environmentIntensity,this.environmentRotation.copy(t.environmentRotation),null!==t.overrideMaterial&&(this.overrideMaterial=t.overrideMaterial.clone()),this.matrixAutoUpdate=t.matrixAutoUpdate,this}toJSON(t){const e=super.toJSON(t);return null!==this.fog&&(e.object.fog=this.fog.toJSON()),this.backgroundBlurriness>0&&(e.object.backgroundBlurriness=this.backgroundBlurriness),1!==this.backgroundIntensity&&(e.object.backgroundIntensity=this.backgroundIntensity),e.object.backgroundRotation=this.backgroundRotation.toArray(),1!==this.environmentIntensity&&(e.object.environmentIntensity=this.environmentIntensity),e.object.environmentRotation=this.environmentRotation.toArray(),e}}class lc{constructor(t,e){this.isInterleavedBuffer=!0,this.array=t,this.stride=e,this.count=void 0!==t?t.length/e:0,this.usage=Cn,this.updateRanges=[],this.version=0,this.uuid=qn()}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}addUpdateRange(t,e){this.updateRanges.push({start:t,count:e})}clearUpdateRanges(){this.updateRanges.length=0}copy(t){return this.array=new t.array.constructor(t.array),this.count=t.count,this.stride=t.stride,this.usage=t.usage,this}copyAt(t,e,n){t*=this.stride,n*=e.stride;for(let i=0,r=this.stride;it.far||e.push({distance:o,point:pc.clone(),uv:Zr.getInterpolation(pc,xc,yc,Mc,Sc,bc,wc,new ti),face:null,object:this})}copy(t,e){return super.copy(t,e),void 0!==t.center&&this.center.copy(t.center),this.material=t.material,this}}function Ec(t,e,n,i,r,s){gc.subVectors(t,n).addScalar(.5).multiply(i),void 0!==r?(vc.x=s*gc.x-r*gc.y,vc.y=r*gc.x+s*gc.y):vc.copy(gc),t.copy(e),t.x+=vc.x,t.y+=vc.y,t.applyMatrix4(_c)}const Ac=new Li,Rc=new Li;class Cc extends Dr{constructor(){super(),this._currentLevel=0,this.type="LOD",Object.defineProperties(this,{levels:{enumerable:!0,value:[]},isLOD:{value:!0}}),this.autoUpdate=!0}copy(t){super.copy(t,!1);const e=t.levels;for(let t=0,n=e.length;t0){let n,i;for(n=1,i=e.length;n0){Ac.setFromMatrixPosition(this.matrixWorld);const n=t.ray.origin.distanceTo(Ac);this.getObjectForDistance(n).raycast(t,e)}}update(t){const e=this.levels;if(e.length>1){Ac.setFromMatrixPosition(t.matrixWorld),Rc.setFromMatrixPosition(this.matrixWorld);const n=Ac.distanceTo(Rc)/t.zoom;let i,r;for(e[0].object.visible=!0,i=1,r=e.length;i=t))break;e[i-1].object.visible=!1,e[i].object.visible=!0}for(this._currentLevel=i-1;i=i.length&&i.push({start:-1,count:-1,z:-1,index:-1});const s=i[this.index];r.push(s),this.index++,s.start=t.start,s.count=t.count,s.z=e,s.index=n}reset(){this.list.length=0,this.index=0}}const ih=new lr,rh=new lr,sh=new lr,ah=new ts(1,1,1),oh=new lr,lh=new ha,ch=new Di,hh=new Qi,uh=new Li,dh=new Li,ph=new Li,mh=new nh,fh=new Vs,gh=[];function vh(t,e,n=0){const i=e.itemSize;if(t.isInterleavedBufferAttribute||t.array.constructor!==e.array.constructor){const r=t.count;for(let s=0;s65535?new Uint32Array(i):new Uint16Array(i);e.setIndex(new ds(t,1))}this._geometryInitialized=!0}}_validateGeometry(t){const e=this.geometry;if(Boolean(t.getIndex())!==Boolean(e.getIndex()))throw new Error('BatchedMesh: All geometries must consistently have "index".');for(const n in e.attributes){if(!t.hasAttribute(n))throw new Error(`BatchedMesh: Added geometry missing "${n}". All geometries must have consistent attributes.`);const i=t.getAttribute(n),r=e.getAttribute(n);if(i.itemSize!==r.itemSize||i.normalized!==r.normalized)throw new Error("BatchedMesh: All attributes must have a consistent itemSize and normalized value.")}}setCustomSort(t){return this.customSort=t,this}computeBoundingBox(){null===this.boundingBox&&(this.boundingBox=new Di);const t=this.boundingBox,e=this._drawInfo;t.makeEmpty();for(let n=0,i=e.length;n=this.maxInstanceCount&&0===this._availableInstanceIds.length)throw new Error("BatchedMesh: Maximum item count reached.");const e={visible:!0,active:!0,geometryIndex:t};let n=null;this._availableInstanceIds.length>0?(n=this._availableInstanceIds.pop(),this._drawInfo[n]=e):(n=this._drawInfo.length,this._drawInfo.push(e));const i=this._matricesTexture,r=i.image.data;sh.toArray(r,16*n),i.needsUpdate=!0;const s=this._colorsTexture;return s&&(ah.toArray(s.image.data,4*n),s.needsUpdate=!0),n}addGeometry(t,e=-1,n=-1){if(this._initializeGeometry(t),this._validateGeometry(t),this._drawInfo.length>=this._maxInstanceCount)throw new Error("BatchedMesh: Maximum item count reached.");const i={vertexStart:-1,vertexCount:-1,indexStart:-1,indexCount:-1};let r=null;const s=this._reservedRanges,a=this._drawRanges,o=this._bounds;0!==this._geometryCount&&(r=s[s.length-1]),i.vertexCount=-1===e?t.getAttribute("position").count:e,i.vertexStart=null===r?0:r.vertexStart+r.vertexCount;const l=t.getIndex(),c=null!==l;if(c&&(i.indexCount=-1===n?l.count:n,i.indexStart=null===r?0:r.indexStart+r.indexCount),-1!==i.indexStart&&i.indexStart+i.indexCount>this._maxIndexCount||i.vertexStart+i.vertexCount>this._maxVertexCount)throw new Error("BatchedMesh: Reserved space request exceeds the maximum buffer size.");const h=this._geometryCount;return this._geometryCount++,s.push(i),a.push({start:c?i.indexStart:i.vertexStart,count:-1}),o.push({boxInitialized:!1,box:new Di,sphereInitialized:!1,sphere:new Qi}),this.setGeometryAt(h,t),h}setGeometryAt(t,e){if(t>=this._geometryCount)throw new Error("BatchedMesh: Maximum geometry count reached.");this._validateGeometry(e);const n=this.geometry,i=null!==n.getIndex(),r=n.getIndex(),s=e.getIndex(),a=this._reservedRanges[t];if(i&&s.count>a.indexCount||e.attributes.position.count>a.vertexCount)throw new Error("BatchedMesh: Reserved space not large enough for provided geometry.");const o=a.vertexStart,l=a.vertexCount;for(const t in n.attributes){const i=e.getAttribute(t),r=n.getAttribute(t);vh(i,r,o);const s=i.itemSize;for(let t=i.count,e=l;t=e.length||!1===e[t].active||(e[t].active=!1,this._availableInstanceIds.push(t),this._visibilityChanged=!0),this}getBoundingBoxAt(t,e){if(t>=this._geometryCount)return null;const n=this._bounds[t],i=n.box,r=this.geometry;if(!1===n.boxInitialized){i.makeEmpty();const e=r.index,s=r.attributes.position,a=this._drawRanges[t];for(let t=a.start,n=a.start+a.count;t=this._geometryCount)return null;const n=this._bounds[t],i=n.sphere,r=this.geometry;if(!1===n.sphereInitialized){i.makeEmpty(),this.getBoundingBoxAt(t,ch),ch.getCenter(i.center);const e=r.index,s=r.attributes.position,a=this._drawRanges[t];let o=0;for(let t=a.start,n=a.start+a.count;t=n.length||!1===n[t].active||(e.toArray(r,16*t),i.needsUpdate=!0),this}getMatrixAt(t,e){const n=this._drawInfo,i=this._matricesTexture.image.data;return t>=n.length||!1===n[t].active?null:e.fromArray(i,16*t)}setColorAt(t,e){null===this._colorsTexture&&this._initColorsTexture();const n=this._colorsTexture,i=this._colorsTexture.image.data,r=this._drawInfo;return t>=r.length||!1===r[t].active||(e.toArray(i,4*t),n.needsUpdate=!0),this}getColorAt(t,e){const n=this._colorsTexture.image.data,i=this._drawInfo;return t>=i.length||!1===i[t].active?null:e.fromArray(n,4*t)}setVisibleAt(t,e){const n=this._drawInfo;return t>=n.length||!1===n[t].active||n[t].visible===e||(n[t].visible=e,this._visibilityChanged=!0),this}getVisibleAt(t){const e=this._drawInfo;return!(t>=e.length||!1===e[t].active)&&e[t].visible}setGeometryIdAt(t,e){const n=this._drawInfo;return t>=n.length||!1===n[t].active||e<0||e>=this._geometryCount?null:(n[t].geometryIndex=e,this)}getGeometryIdAt(t){const e=this._drawInfo;return t>=e.length||!1===e[t].active?-1:e[t].geometryIndex}getGeometryRangeAt(t,e={}){if(t<0||t>=this._geometryCount)return null;const n=this._drawRanges[t];return e.start=n.start,e.count=n.count,e}raycast(t,e){const n=this._drawInfo,i=this._drawRanges,r=this.matrixWorld,s=this.geometry;fh.material=this.material,fh.geometry.index=s.index,fh.geometry.attributes=s.attributes,null===fh.geometry.boundingBox&&(fh.geometry.boundingBox=new Di),null===fh.geometry.boundingSphere&&(fh.geometry.boundingSphere=new Qi);for(let s=0,a=n.length;s({...t}))),this._reservedRanges=t._reservedRanges.map((t=>({...t}))),this._drawInfo=t._drawInfo.map((t=>({...t}))),this._bounds=t._bounds.map((t=>({boxInitialized:t.boxInitialized,box:t.box.clone(),sphereInitialized:t.sphereInitialized,sphere:t.sphere.clone()}))),this._maxInstanceCount=t._maxInstanceCount,this._maxVertexCount=t._maxVertexCount,this._maxIndexCount=t._maxIndexCount,this._geometryInitialized=t._geometryInitialized,this._geometryCount=t._geometryCount,this._multiDrawCounts=t._multiDrawCounts.slice(),this._multiDrawStarts=t._multiDrawStarts.slice(),this._matricesTexture=t._matricesTexture.clone(),this._matricesTexture.image.data=this._matricesTexture.image.data.slice(),null!==this._colorsTexture&&(this._colorsTexture=t._colorsTexture.clone(),this._colorsTexture.image.data=this._colorsTexture.image.data.slice()),this}dispose(){return this.geometry.dispose(),this._matricesTexture.dispose(),this._matricesTexture=null,this._indirectTexture.dispose(),this._indirectTexture=null,null!==this._colorsTexture&&(this._colorsTexture.dispose(),this._colorsTexture=null),this}onBeforeRender(t,e,n,i,r){if(!this._visibilityChanged&&!this.perObjectFrustumCulled&&!this.sortObjects)return;const s=i.getIndex(),a=null===s?1:s.array.BYTES_PER_ELEMENT,o=this._drawInfo,l=this._multiDrawStarts,c=this._multiDrawCounts,h=this._drawRanges,u=this.perObjectFrustumCulled,d=this._indirectTexture,p=d.image.data;u&&(oh.multiplyMatrices(n.projectionMatrix,n.matrixWorldInverse).multiply(this.matrixWorld),lh.setFromProjectionMatrix(oh,t.coordinateSystem));let m=0;if(this.sortObjects){rh.copy(this.matrixWorld).invert(),uh.setFromMatrixPosition(n.matrixWorld).applyMatrix4(rh),dh.set(0,0,-1).transformDirection(n.matrixWorld).transformDirection(rh);for(let t=0,e=o.length;t0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;ti)return;Th.applyMatrix4(t.matrixWorld);const o=e.ray.origin.distanceTo(Th);return oe.far?void 0:{distance:o,point:Eh.clone().applyMatrix4(t.matrixWorld),index:r,face:null,faceIndex:null,barycoord:null,object:t}}const Ch=new Li,Ph=new Li;class Ih extends Ah{constructor(t,e){super(t,e),this.isLineSegments=!0,this.type="LineSegments"}computeLineDistances(){const t=this.geometry;if(null===t.index){const e=t.attributes.position,n=[];for(let t=0,i=e.count;t0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;tr.far)return;s.push({distance:l,distanceToRay:Math.sqrt(o),point:n,index:e,face:null,faceIndex:null,barycoord:null,object:a})}}class kh extends bi{constructor(t,e,n,i,r,s,a,o,l){super(t,e,n,i,r,s,a,o,l),this.isVideoTexture=!0,this.minFilter=void 0!==s?s:Mt,this.magFilter=void 0!==r?r:Mt,this.generateMipmaps=!1;const c=this;"requestVideoFrameCallback"in t&&t.requestVideoFrameCallback((function e(){c.needsUpdate=!0,t.requestVideoFrameCallback(e)}))}clone(){return new this.constructor(this.image).copy(this)}update(){const t=this.image;!1==="requestVideoFrameCallback"in t&&t.readyState>=t.HAVE_CURRENT_DATA&&(this.needsUpdate=!0)}}class Vh extends bi{constructor(t,e){super({width:t,height:e}),this.isFramebufferTexture=!0,this.magFilter=gt,this.minFilter=gt,this.generateMipmaps=!1,this.needsUpdate=!0}}class Hh extends bi{constructor(t,e,n,i,r,s,a,o,l,c,h,u){super(null,s,a,o,l,c,i,r,h,u),this.isCompressedTexture=!0,this.image={width:e,height:n},this.mipmaps=t,this.flipY=!1,this.generateMipmaps=!1}}class Gh extends Hh{constructor(t,e,n,i,r,s){super(t,e,n,r,s),this.isCompressedArrayTexture=!0,this.image.depth=i,this.wrapR=mt,this.layerUpdates=new Set}addLayerUpdate(t){this.layerUpdates.add(t)}clearLayerUpdates(){this.layerUpdates.clear()}}class Wh extends Hh{constructor(t,e,n){super(void 0,t[0].width,t[0].height,e,n,lt),this.isCompressedCubeTexture=!0,this.isCubeTexture=!0,this.image=t}}class Xh extends bi{constructor(t,e,n,i,r,s,a,o,l){super(t,e,n,i,r,s,a,o,l),this.isCanvasTexture=!0,this.needsUpdate=!0}}class jh{constructor(){this.type="Curve",this.arcLengthDivisions=200}getPoint(){return console.warn("THREE.Curve: .getPoint() not implemented."),null}getPointAt(t,e){const n=this.getUtoTmapping(t);return this.getPoint(n,e)}getPoints(t=5){const e=[];for(let n=0;n<=t;n++)e.push(this.getPoint(n/t));return e}getSpacedPoints(t=5){const e=[];for(let n=0;n<=t;n++)e.push(this.getPointAt(n/t));return e}getLength(){const t=this.getLengths();return t[t.length-1]}getLengths(t=this.arcLengthDivisions){if(this.cacheArcLengths&&this.cacheArcLengths.length===t+1&&!this.needsUpdate)return this.cacheArcLengths;this.needsUpdate=!1;const e=[];let n,i=this.getPoint(0),r=0;e.push(0);for(let s=1;s<=t;s++)n=this.getPoint(s/t),r+=n.distanceTo(i),e.push(r),i=n;return this.cacheArcLengths=e,e}updateArcLengths(){this.needsUpdate=!0,this.getLengths()}getUtoTmapping(t,e){const n=this.getLengths();let i=0;const r=n.length;let s;s=e||t*n[r-1];let a,o=0,l=r-1;for(;o<=l;)if(i=Math.floor(o+(l-o)/2),a=n[i]-s,a<0)o=i+1;else{if(!(a>0)){l=i;break}l=i-1}if(i=l,n[i]===s)return i/(r-1);const c=n[i];return(i+(s-c)/(n[i+1]-c))/(r-1)}getTangent(t,e){const n=1e-4;let i=t-n,r=t+n;i<0&&(i=0),r>1&&(r=1);const s=this.getPoint(i),a=this.getPoint(r),o=e||(s.isVector2?new ti:new Li);return o.copy(a).sub(s).normalize(),o}getTangentAt(t,e){const n=this.getUtoTmapping(t);return this.getTangent(n,e)}computeFrenetFrames(t,e){const n=new Li,i=[],r=[],s=[],a=new Li,o=new lr;for(let e=0;e<=t;e++){const n=e/t;i[e]=this.getTangentAt(n,new Li)}r[0]=new Li,s[0]=new Li;let l=Number.MAX_VALUE;const c=Math.abs(i[0].x),h=Math.abs(i[0].y),u=Math.abs(i[0].z);c<=l&&(l=c,n.set(1,0,0)),h<=l&&(l=h,n.set(0,1,0)),u<=l&&n.set(0,0,1),a.crossVectors(i[0],n).normalize(),r[0].crossVectors(i[0],a),s[0].crossVectors(i[0],r[0]);for(let e=1;e<=t;e++){if(r[e]=r[e-1].clone(),s[e]=s[e-1].clone(),a.crossVectors(i[e-1],i[e]),a.length()>Number.EPSILON){a.normalize();const t=Math.acos(Yn(i[e-1].dot(i[e]),-1,1));r[e].applyMatrix4(o.makeRotationAxis(a,t))}s[e].crossVectors(i[e],r[e])}if(!0===e){let e=Math.acos(Yn(r[0].dot(r[t]),-1,1));e/=t,i[0].dot(a.crossVectors(r[0],r[t]))>0&&(e=-e);for(let n=1;n<=t;n++)r[n].applyMatrix4(o.makeRotationAxis(i[n],e*n)),s[n].crossVectors(i[n],r[n])}return{tangents:i,normals:r,binormals:s}}clone(){return(new this.constructor).copy(this)}copy(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}toJSON(){const t={metadata:{version:4.6,type:"Curve",generator:"Curve.toJSON"}};return t.arcLengthDivisions=this.arcLengthDivisions,t.type=this.type,t}fromJSON(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}}class qh extends jh{constructor(t=0,e=0,n=1,i=1,r=0,s=2*Math.PI,a=!1,o=0){super(),this.isEllipseCurve=!0,this.type="EllipseCurve",this.aX=t,this.aY=e,this.xRadius=n,this.yRadius=i,this.aStartAngle=r,this.aEndAngle=s,this.aClockwise=a,this.aRotation=o}getPoint(t,e=new ti){const n=e,i=2*Math.PI;let r=this.aEndAngle-this.aStartAngle;const s=Math.abs(r)i;)r-=i;r0?0:(Math.floor(Math.abs(l)/r)+1)*r:0===c&&l===r-1&&(l=r-2,c=1),this.closed||l>0?a=i[(l-1)%r]:(Jh.subVectors(i[0],i[1]).add(i[0]),a=Jh);const h=i[l%r],u=i[(l+1)%r];if(this.closed||l+2i.length-2?i.length-1:s+1],h=i[s>i.length-3?i.length-1:s+2];return n.set(eu(a,o.x,l.x,c.x,h.x),eu(a,o.y,l.y,c.y,h.y)),n}copy(t){super.copy(t),this.points=[];for(let e=0,n=t.points.length;e=n){const t=i[r]-n,s=this.curves[r],a=s.getLength(),o=0===a?0:1-t/a;return s.getPointAt(o,e)}r++}return null}getLength(){const t=this.getCurveLengths();return t[t.length-1]}updateArcLengths(){this.needsUpdate=!0,this.cacheLengths=null,this.getCurveLengths()}getCurveLengths(){if(this.cacheLengths&&this.cacheLengths.length===this.curves.length)return this.cacheLengths;const t=[];let e=0;for(let n=0,i=this.curves.length;n1&&!e[e.length-1].equals(e[0])&&e.push(e[0]),e}copy(t){super.copy(t),this.curves=[];for(let e=0,n=t.curves.length;e0){const t=l.getPoint(0);t.equals(this.currentPoint)||this.lineTo(t.x,t.y)}this.curves.push(l);const c=l.getPoint(1);return this.currentPoint.copy(c),this}copy(t){return super.copy(t),this.currentPoint.copy(t.currentPoint),this}toJSON(){const t=super.toJSON();return t.currentPoint=this.currentPoint.toArray(),t}fromJSON(t){return super.fromJSON(t),this.currentPoint.fromArray(t.currentPoint),this}}class mu extends Cs{constructor(t=[new ti(0,-.5),new ti(.5,0),new ti(0,.5)],e=12,n=0,i=2*Math.PI){super(),this.type="LatheGeometry",this.parameters={points:t,segments:e,phiStart:n,phiLength:i},e=Math.floor(e),i=Yn(i,0,2*Math.PI);const r=[],s=[],a=[],o=[],l=[],c=1/e,h=new Li,u=new ti,d=new Li,p=new Li,m=new Li;let f=0,g=0;for(let e=0;e<=t.length-1;e++)switch(e){case 0:f=t[e+1].x-t[e].x,g=t[e+1].y-t[e].y,d.x=1*g,d.y=-f,d.z=0*g,m.copy(d),d.normalize(),o.push(d.x,d.y,d.z);break;case t.length-1:o.push(m.x,m.y,m.z);break;default:f=t[e+1].x-t[e].x,g=t[e+1].y-t[e].y,d.x=1*g,d.y=-f,d.z=0*g,p.copy(d),d.x+=m.x,d.y+=m.y,d.z+=m.z,d.normalize(),o.push(d.x,d.y,d.z),m.copy(p)}for(let r=0;r<=e;r++){const d=n+r*c*i,p=Math.sin(d),m=Math.cos(d);for(let n=0;n<=t.length-1;n++){h.x=t[n].x*p,h.y=t[n].y,h.z=t[n].x*m,s.push(h.x,h.y,h.z),u.x=r/e,u.y=n/(t.length-1),a.push(u.x,u.y);const i=o[3*n+0]*p,c=o[3*n+1],d=o[3*n+0]*m;l.push(i,c,d)}}for(let n=0;n0&&(c.push(r,s,o),_+=3),e>0&&(c.push(s,a,o),_+=3)}l.addGroup(g,_,0),g+=_}(),!1===s&&(t>0&&v(!0),e>0&&v(!1)),this.setIndex(c),this.setAttribute("position",new Ms(h,3)),this.setAttribute("normal",new Ms(u,3)),this.setAttribute("uv",new Ms(d,2))}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new vu(t.radiusTop,t.radiusBottom,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class _u extends vu{constructor(t=1,e=1,n=32,i=1,r=!1,s=0,a=2*Math.PI){super(0,t,e,n,i,r,s,a),this.type="ConeGeometry",this.parameters={radius:t,height:e,radialSegments:n,heightSegments:i,openEnded:r,thetaStart:s,thetaLength:a}}static fromJSON(t){return new _u(t.radius,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class xu extends Cs{constructor(t=[],e=[],n=1,i=0){super(),this.type="PolyhedronGeometry",this.parameters={vertices:t,indices:e,radius:n,detail:i};const r=[],s=[];function a(t,e,n,i){const r=i+1,s=[];for(let i=0;i<=r;i++){s[i]=[];const a=t.clone().lerp(n,i/r),o=e.clone().lerp(n,i/r),l=r-i;for(let t=0;t<=l;t++)s[i][t]=0===t&&i===r?a:a.clone().lerp(o,t/l)}for(let t=0;t.9&&a<.1&&(e<.2&&(s[t+0]+=1),n<.2&&(s[t+2]+=1),i<.2&&(s[t+4]+=1))}}()}(),this.setAttribute("position",new Ms(r,3)),this.setAttribute("normal",new Ms(r.slice(),3)),this.setAttribute("uv",new Ms(s,2)),0===i?this.computeVertexNormals():this.normalizeNormals()}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new xu(t.vertices,t.indices,t.radius,t.details)}}class yu extends xu{constructor(t=1,e=0){const n=(1+Math.sqrt(5))/2,i=1/n;super([-1,-1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,1,0,-i,-n,0,-i,n,0,i,-n,0,i,n,-i,-n,0,-i,n,0,i,-n,0,i,n,0,-n,0,-i,n,0,-i,-n,0,i,n,0,i],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],t,e),this.type="DodecahedronGeometry",this.parameters={radius:t,detail:e}}static fromJSON(t){return new yu(t.radius,t.detail)}}const Mu=new Li,Su=new Li,bu=new Li,wu=new Zr;class Tu extends Cs{constructor(t=null,e=1){if(super(),this.type="EdgesGeometry",this.parameters={geometry:t,thresholdAngle:e},null!==t){const n=4,i=Math.pow(10,n),r=Math.cos(Xn*e),s=t.getIndex(),a=t.getAttribute("position"),o=s?s.count:a.count,l=[0,0,0],c=["a","b","c"],h=new Array(3),u={},d=[];for(let t=0;t80*n){o=c=t[0],l=h=t[1];for(let e=n;ec&&(c=u),d>h&&(h=d);p=Math.max(c-o,h-l),p=0!==p?32767/p:0}return Pu(s,a,n,o,l,p,0),a};function Ru(t,e,n,i,r){let s,a;if(r===function(t,e,n,i){let r=0;for(let s=e,a=n-i;s0)for(s=e;s=e;s-=i)a=Zu(s,t[s],t[s+1],a);return a&&Gu(a,a.next)&&(Ju(a),a=a.next),a}function Cu(t,e){if(!t)return t;e||(e=t);let n,i=t;do{if(n=!1,i.steiner||!Gu(i,i.next)&&0!==Hu(i.prev,i,i.next))i=i.next;else{if(Ju(i),i=e=i.prev,i===i.next)break;n=!0}}while(n||i!==e);return e}function Pu(t,e,n,i,r,s,a){if(!t)return;!a&&s&&function(t,e,n,i){let r=t;do{0===r.z&&(r.z=Bu(r.x,r.y,e,n,i)),r.prevZ=r.prev,r.nextZ=r.next,r=r.next}while(r!==t);r.prevZ.nextZ=null,r.prevZ=null,function(t){let e,n,i,r,s,a,o,l,c=1;do{for(n=t,t=null,s=null,a=0;n;){for(a++,i=n,o=0,e=0;e0||l>0&&i;)0!==o&&(0===l||!i||n.z<=i.z)?(r=n,n=n.nextZ,o--):(r=i,i=i.nextZ,l--),s?s.nextZ=r:t=r,r.prevZ=s,s=r;n=i}s.nextZ=null,c*=2}while(a>1)}(r)}(t,i,r,s);let o,l,c=t;for(;t.prev!==t.next;)if(o=t.prev,l=t.next,s?Lu(t,i,r,s):Iu(t))e.push(o.i/n|0),e.push(t.i/n|0),e.push(l.i/n|0),Ju(t),t=l.next,c=l.next;else if((t=l)===c){a?1===a?Pu(t=Uu(Cu(t),e,n),e,n,i,r,s,2):2===a&&Nu(t,e,n,i,r,s):Pu(Cu(t),e,n,i,r,s,1);break}}function Iu(t){const e=t.prev,n=t,i=t.next;if(Hu(e,n,i)>=0)return!1;const r=e.x,s=n.x,a=i.x,o=e.y,l=n.y,c=i.y,h=rs?r>a?r:a:s>a?s:a,p=o>l?o>c?o:c:l>c?l:c;let m=i.next;for(;m!==e;){if(m.x>=h&&m.x<=d&&m.y>=u&&m.y<=p&&ku(r,o,s,l,a,c,m.x,m.y)&&Hu(m.prev,m,m.next)>=0)return!1;m=m.next}return!0}function Lu(t,e,n,i){const r=t.prev,s=t,a=t.next;if(Hu(r,s,a)>=0)return!1;const o=r.x,l=s.x,c=a.x,h=r.y,u=s.y,d=a.y,p=ol?o>c?o:c:l>c?l:c,g=h>u?h>d?h:d:u>d?u:d,v=Bu(p,m,e,n,i),_=Bu(f,g,e,n,i);let x=t.prevZ,y=t.nextZ;for(;x&&x.z>=v&&y&&y.z<=_;){if(x.x>=p&&x.x<=f&&x.y>=m&&x.y<=g&&x!==r&&x!==a&&ku(o,h,l,u,c,d,x.x,x.y)&&Hu(x.prev,x,x.next)>=0)return!1;if(x=x.prevZ,y.x>=p&&y.x<=f&&y.y>=m&&y.y<=g&&y!==r&&y!==a&&ku(o,h,l,u,c,d,y.x,y.y)&&Hu(y.prev,y,y.next)>=0)return!1;y=y.nextZ}for(;x&&x.z>=v;){if(x.x>=p&&x.x<=f&&x.y>=m&&x.y<=g&&x!==r&&x!==a&&ku(o,h,l,u,c,d,x.x,x.y)&&Hu(x.prev,x,x.next)>=0)return!1;x=x.prevZ}for(;y&&y.z<=_;){if(y.x>=p&&y.x<=f&&y.y>=m&&y.y<=g&&y!==r&&y!==a&&ku(o,h,l,u,c,d,y.x,y.y)&&Hu(y.prev,y,y.next)>=0)return!1;y=y.nextZ}return!0}function Uu(t,e,n){let i=t;do{const r=i.prev,s=i.next.next;!Gu(r,s)&&Wu(r,i,i.next,s)&&qu(r,s)&&qu(s,r)&&(e.push(r.i/n|0),e.push(i.i/n|0),e.push(s.i/n|0),Ju(i),Ju(i.next),i=t=s),i=i.next}while(i!==t);return Cu(i)}function Nu(t,e,n,i,r,s){let a=t;do{let t=a.next.next;for(;t!==a.prev;){if(a.i!==t.i&&Vu(a,t)){let o=Yu(a,t);return a=Cu(a,a.next),o=Cu(o,o.next),Pu(a,e,n,i,r,s,0),void Pu(o,e,n,i,r,s,0)}t=t.next}a=a.next}while(a!==t)}function Du(t,e){return t.x-e.x}function Ou(t,e){const n=function(t,e){let n,i=e,r=-1/0;const s=t.x,a=t.y;do{if(a<=i.y&&a>=i.next.y&&i.next.y!==i.y){const t=i.x+(a-i.y)*(i.next.x-i.x)/(i.next.y-i.y);if(t<=s&&t>r&&(r=t,n=i.x=i.x&&i.x>=l&&s!==i.x&&ku(an.x||i.x===n.x&&Fu(n,i)))&&(n=i,u=h)),i=i.next}while(i!==o);return n}(t,e);if(!n)return e;const i=Yu(n,t);return Cu(i,i.next),Cu(n,n.next)}function Fu(t,e){return Hu(t.prev,t,e.prev)<0&&Hu(e.next,t,t.next)<0}function Bu(t,e,n,i,r){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-n)*r|0)|t<<8))|t<<4))|t<<2))|t<<1))|(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=(e-i)*r|0)|e<<8))|e<<4))|e<<2))|e<<1))<<1}function zu(t){let e=t,n=t;do{(e.x=(t-a)*(s-o)&&(t-a)*(i-o)>=(n-a)*(e-o)&&(n-a)*(s-o)>=(r-a)*(i-o)}function Vu(t,e){return t.next.i!==e.i&&t.prev.i!==e.i&&!function(t,e){let n=t;do{if(n.i!==t.i&&n.next.i!==t.i&&n.i!==e.i&&n.next.i!==e.i&&Wu(n,n.next,t,e))return!0;n=n.next}while(n!==t);return!1}(t,e)&&(qu(t,e)&&qu(e,t)&&function(t,e){let n=t,i=!1;const r=(t.x+e.x)/2,s=(t.y+e.y)/2;do{n.y>s!=n.next.y>s&&n.next.y!==n.y&&r<(n.next.x-n.x)*(s-n.y)/(n.next.y-n.y)+n.x&&(i=!i),n=n.next}while(n!==t);return i}(t,e)&&(Hu(t.prev,t,e.prev)||Hu(t,e.prev,e))||Gu(t,e)&&Hu(t.prev,t,t.next)>0&&Hu(e.prev,e,e.next)>0)}function Hu(t,e,n){return(e.y-t.y)*(n.x-e.x)-(e.x-t.x)*(n.y-e.y)}function Gu(t,e){return t.x===e.x&&t.y===e.y}function Wu(t,e,n,i){const r=ju(Hu(t,e,n)),s=ju(Hu(t,e,i)),a=ju(Hu(n,i,t)),o=ju(Hu(n,i,e));return r!==s&&a!==o||(!(0!==r||!Xu(t,n,e))||(!(0!==s||!Xu(t,i,e))||(!(0!==a||!Xu(n,t,i))||!(0!==o||!Xu(n,e,i)))))}function Xu(t,e,n){return e.x<=Math.max(t.x,n.x)&&e.x>=Math.min(t.x,n.x)&&e.y<=Math.max(t.y,n.y)&&e.y>=Math.min(t.y,n.y)}function ju(t){return t>0?1:t<0?-1:0}function qu(t,e){return Hu(t.prev,t,t.next)<0?Hu(t,e,t.next)>=0&&Hu(t,t.prev,e)>=0:Hu(t,e,t.prev)<0||Hu(t,t.next,e)<0}function Yu(t,e){const n=new Ku(t.i,t.x,t.y),i=new Ku(e.i,e.x,e.y),r=t.next,s=e.prev;return t.next=e,e.prev=t,n.next=r,r.prev=n,i.next=n,n.prev=i,s.next=i,i.prev=s,i}function Zu(t,e,n,i){const r=new Ku(t,e,n);return i?(r.next=i.next,r.prev=i,i.next.prev=r,i.next=r):(r.prev=r,r.next=r),r}function Ju(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ)}function Ku(t,e,n){this.i=t,this.x=e,this.y=n,this.prev=null,this.next=null,this.z=0,this.prevZ=null,this.nextZ=null,this.steiner=!1}class $u{static area(t){const e=t.length;let n=0;for(let i=e-1,r=0;r2&&t[e-1].equals(t[0])&&t.pop()}function td(t,e){for(let n=0;nNumber.EPSILON){const u=Math.sqrt(h),d=Math.sqrt(l*l+c*c),p=e.x-o/u,m=e.y+a/u,f=((n.x-c/d-p)*c-(n.y+l/d-m)*l)/(a*c-o*l);i=p+a*f-t.x,r=m+o*f-t.y;const g=i*i+r*r;if(g<=2)return new ti(i,r);s=Math.sqrt(g/2)}else{let t=!1;a>Number.EPSILON?l>Number.EPSILON&&(t=!0):a<-Number.EPSILON?l<-Number.EPSILON&&(t=!0):Math.sign(o)===Math.sign(c)&&(t=!0),t?(i=-o,r=a,s=Math.sqrt(h)):(i=a,r=o,s=Math.sqrt(h/2))}return new ti(i/s,r/s)}const I=[];for(let t=0,e=E.length,n=e-1,i=t+1;t=0;t--){const e=t/p,n=h*Math.cos(e*Math.PI/2),i=u*Math.sin(e*Math.PI/2)+d;for(let t=0,e=E.length;t=0;){const i=n;let r=n-1;r<0&&(r=t.length-1);for(let t=0,n=o+2*p;t0)&&d.push(e,r,l),(t!==n-1||o0!=t>0&&this.version++,this._anisotropy=t}get clearcoat(){return this._clearcoat}set clearcoat(t){this._clearcoat>0!=t>0&&this.version++,this._clearcoat=t}get iridescence(){return this._iridescence}set iridescence(t){this._iridescence>0!=t>0&&this.version++,this._iridescence=t}get dispersion(){return this._dispersion}set dispersion(t){this._dispersion>0!=t>0&&this.version++,this._dispersion=t}get sheen(){return this._sheen}set sheen(t){this._sheen>0!=t>0&&this.version++,this._sheen=t}get transmission(){return this._transmission}set transmission(t){this._transmission>0!=t>0&&this.version++,this._transmission=t}copy(t){return super.copy(t),this.defines={STANDARD:"",PHYSICAL:""},this.anisotropy=t.anisotropy,this.anisotropyRotation=t.anisotropyRotation,this.anisotropyMap=t.anisotropyMap,this.clearcoat=t.clearcoat,this.clearcoatMap=t.clearcoatMap,this.clearcoatRoughness=t.clearcoatRoughness,this.clearcoatRoughnessMap=t.clearcoatRoughnessMap,this.clearcoatNormalMap=t.clearcoatNormalMap,this.clearcoatNormalScale.copy(t.clearcoatNormalScale),this.dispersion=t.dispersion,this.ior=t.ior,this.iridescence=t.iridescence,this.iridescenceMap=t.iridescenceMap,this.iridescenceIOR=t.iridescenceIOR,this.iridescenceThicknessRange=[...t.iridescenceThicknessRange],this.iridescenceThicknessMap=t.iridescenceThicknessMap,this.sheen=t.sheen,this.sheenColor.copy(t.sheenColor),this.sheenColorMap=t.sheenColorMap,this.sheenRoughness=t.sheenRoughness,this.sheenRoughnessMap=t.sheenRoughnessMap,this.transmission=t.transmission,this.transmissionMap=t.transmissionMap,this.thickness=t.thickness,this.thicknessMap=t.thicknessMap,this.attenuationDistance=t.attenuationDistance,this.attenuationColor.copy(t.attenuationColor),this.specularIntensity=t.specularIntensity,this.specularIntensityMap=t.specularIntensityMap,this.specularColor.copy(t.specularColor),this.specularColorMap=t.specularColorMap,this}}class xd extends is{constructor(t){super(),this.isMeshPhongMaterial=!0,this.type="MeshPhongMaterial",this.color=new ts(16777215),this.specular=new ts(1118481),this.shininess=30,this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new ts(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new ti(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new _r,this.combine=Y,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.specular.copy(t.specular),this.shininess=t.shininess,this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class yd extends is{constructor(t){super(),this.isMeshToonMaterial=!0,this.defines={TOON:""},this.type="MeshToonMaterial",this.color=new ts(16777215),this.map=null,this.gradientMap=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new ts(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new ti(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.gradientMap=t.gradientMap,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}class Md extends is{constructor(t){super(),this.isMeshNormalMaterial=!0,this.type="MeshNormalMaterial",this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new ti(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.flatShading=!1,this.setValues(t)}copy(t){return super.copy(t),this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.flatShading=t.flatShading,this}}class Sd extends is{constructor(t){super(),this.isMeshLambertMaterial=!0,this.type="MeshLambertMaterial",this.color=new ts(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new ts(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new ti(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new _r,this.combine=Y,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class bd extends is{constructor(t){super(),this.isMeshMatcapMaterial=!0,this.defines={MATCAP:""},this.type="MeshMatcapMaterial",this.color=new ts(16777215),this.matcap=null,this.map=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new ti(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.defines={MATCAP:""},this.color.copy(t.color),this.matcap=t.matcap,this.map=t.map,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.flatShading=t.flatShading,this.fog=t.fog,this}}class wd extends xh{constructor(t){super(),this.isLineDashedMaterial=!0,this.type="LineDashedMaterial",this.scale=1,this.dashSize=3,this.gapSize=1,this.setValues(t)}copy(t){return super.copy(t),this.scale=t.scale,this.dashSize=t.dashSize,this.gapSize=t.gapSize,this}}function Td(t,e,n){return!t||!n&&t.constructor===e?t:"number"==typeof e.BYTES_PER_ELEMENT?new e(t):Array.prototype.slice.call(t)}function Ed(t){return ArrayBuffer.isView(t)&&!(t instanceof DataView)}function Ad(t){const e=t.length,n=new Array(e);for(let t=0;t!==e;++t)n[t]=t;return n.sort((function(e,n){return t[e]-t[n]})),n}function Rd(t,e,n){const i=t.length,r=new t.constructor(i);for(let s=0,a=0;a!==i;++s){const i=n[s]*e;for(let n=0;n!==e;++n)r[a++]=t[i+n]}return r}function Cd(t,e,n,i){let r=1,s=t[0];for(;void 0!==s&&void 0===s[i];)s=t[r++];if(void 0===s)return;let a=s[i];if(void 0!==a)if(Array.isArray(a))do{a=s[i],void 0!==a&&(e.push(s.time),n.push.apply(n,a)),s=t[r++]}while(void 0!==s);else if(void 0!==a.toArray)do{a=s[i],void 0!==a&&(e.push(s.time),a.toArray(n,n.length)),s=t[r++]}while(void 0!==s);else do{a=s[i],void 0!==a&&(e.push(s.time),n.push(a)),s=t[r++]}while(void 0!==s)}const Pd={convertArray:Td,isTypedArray:Ed,getKeyframeOrder:Ad,sortedArray:Rd,flattenJSON:Cd,subclip:function(t,e,n,i,r=30){const s=t.clone();s.name=e;const a=[];for(let t=0;t=i)){l.push(e.times[t]);for(let n=0;ns.tracks[t].times[0]&&(o=s.tracks[t].times[0]);for(let t=0;t=i.times[u]){const t=u*l+o,e=t+l-o;d=i.values.slice(t,e)}else{const t=i.createInterpolant(),e=o,n=l-o;t.evaluate(s),d=t.resultBuffer.slice(e,n)}if("quaternion"===r){(new Ii).fromArray(d).normalize().conjugate().toArray(d)}const p=a.times.length;for(let t=0;t=r)break t;{const a=e[1];t=r)break e}s=n,n=0}}for(;n>>1;te;)--s;if(++s,0!==r||s!==i){r>=s&&(s=Math.max(s,1),r=s-1);const t=this.getValueSize();this.times=n.slice(r,s),this.values=this.values.slice(r*t,s*t)}return this}validate(){let t=!0;const e=this.getValueSize();e-Math.floor(e)!=0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),t=!1);const n=this.times,i=this.values,r=n.length;0===r&&(console.error("THREE.KeyframeTrack: Track is empty.",this),t=!1);let s=null;for(let e=0;e!==r;e++){const i=n[e];if("number"==typeof i&&isNaN(i)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,e,i),t=!1;break}if(null!==s&&s>i){console.error("THREE.KeyframeTrack: Out of order keys.",this,e,i,s),t=!1;break}s=i}if(void 0!==i&&Ed(i))for(let e=0,n=i.length;e!==n;++e){const n=i[e];if(isNaN(n)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,e,n),t=!1;break}}return t}optimize(){const t=this.times.slice(),e=this.values.slice(),n=this.getValueSize(),i=this.getInterpolation()===Ne,r=t.length-1;let s=1;for(let a=1;a0){t[s]=t[r];for(let t=r*n,i=s*n,a=0;a!==n;++a)e[i+a]=e[t+a];++s}return s!==t.length?(this.times=t.slice(0,s),this.values=e.slice(0,s*n)):(this.times=t,this.values=e),this}clone(){const t=this.times.slice(),e=this.values.slice(),n=new(0,this.constructor)(this.name,t,e);return n.createInterpolant=this.createInterpolant,n}}Dd.prototype.TimeBufferType=Float32Array,Dd.prototype.ValueBufferType=Float32Array,Dd.prototype.DefaultInterpolation=Ue;class Od extends Dd{constructor(t,e,n){super(t,e,n)}}Od.prototype.ValueTypeName="bool",Od.prototype.ValueBufferType=Array,Od.prototype.DefaultInterpolation=Le,Od.prototype.InterpolantFactoryMethodLinear=void 0,Od.prototype.InterpolantFactoryMethodSmooth=void 0;class Fd extends Dd{}Fd.prototype.ValueTypeName="color";class Bd extends Dd{}Bd.prototype.ValueTypeName="number";class zd extends Id{constructor(t,e,n,i){super(t,e,n,i)}interpolate_(t,e,n,i){const r=this.resultBuffer,s=this.sampleValues,a=this.valueSize,o=(n-e)/(i-e);let l=t*a;for(let t=l+a;l!==t;l+=4)Ii.slerpFlat(r,0,s,l-a,s,l,o);return r}}class kd extends Dd{InterpolantFactoryMethodLinear(t){return new zd(this.times,this.values,this.getValueSize(),t)}}kd.prototype.ValueTypeName="quaternion",kd.prototype.InterpolantFactoryMethodSmooth=void 0;class Vd extends Dd{constructor(t,e,n){super(t,e,n)}}Vd.prototype.ValueTypeName="string",Vd.prototype.ValueBufferType=Array,Vd.prototype.DefaultInterpolation=Le,Vd.prototype.InterpolantFactoryMethodLinear=void 0,Vd.prototype.InterpolantFactoryMethodSmooth=void 0;class Hd extends Dd{}Hd.prototype.ValueTypeName="vector";class Gd{constructor(t="",e=-1,n=[],i=2500){this.name=t,this.tracks=n,this.duration=e,this.blendMode=i,this.uuid=qn(),this.duration<0&&this.resetDuration()}static parse(t){const e=[],n=t.tracks,i=1/(t.fps||1);for(let t=0,r=n.length;t!==r;++t)e.push(Wd(n[t]).scale(i));const r=new this(t.name,t.duration,e,t.blendMode);return r.uuid=t.uuid,r}static toJSON(t){const e=[],n=t.tracks,i={name:t.name,duration:t.duration,tracks:e,uuid:t.uuid,blendMode:t.blendMode};for(let t=0,i=n.length;t!==i;++t)e.push(Dd.toJSON(n[t]));return i}static CreateFromMorphTargetSequence(t,e,n,i){const r=e.length,s=[];for(let t=0;t1){const t=s[1];let e=i[t];e||(i[t]=e=[]),e.push(n)}}const s=[];for(const t in i)s.push(this.CreateFromMorphTargetSequence(t,i[t],e,n));return s}static parseAnimation(t,e){if(!t)return console.error("THREE.AnimationClip: No animation in JSONLoader data."),null;const n=function(t,e,n,i,r){if(0!==n.length){const s=[],a=[];Cd(n,s,a,i),0!==s.length&&r.push(new t(e,s,a))}},i=[],r=t.name||"default",s=t.fps||30,a=t.blendMode;let o=t.length||-1;const l=t.hierarchy||[];for(let t=0;t{e&&e(r),this.manager.itemEnd(t)}),0),r;if(void 0!==Zd[t])return void Zd[t].push({onLoad:e,onProgress:n,onError:i});Zd[t]=[],Zd[t].push({onLoad:e,onProgress:n,onError:i});const s=new Request(t,{headers:new Headers(this.requestHeader),credentials:this.withCredentials?"include":"same-origin"}),a=this.mimeType,o=this.responseType;fetch(s).then((e=>{if(200===e.status||0===e.status){if(0===e.status&&console.warn("THREE.FileLoader: HTTP Status 0 received."),"undefined"==typeof ReadableStream||void 0===e.body||void 0===e.body.getReader)return e;const n=Zd[t],i=e.body.getReader(),r=e.headers.get("X-File-Size")||e.headers.get("Content-Length"),s=r?parseInt(r):0,a=0!==s;let o=0;const l=new ReadableStream({start(t){!function e(){i.read().then((({done:i,value:r})=>{if(i)t.close();else{o+=r.byteLength;const i=new ProgressEvent("progress",{lengthComputable:a,loaded:o,total:s});for(let t=0,e=n.length;t{t.error(e)}))}()}});return new Response(l)}throw new Jd(`fetch for "${e.url}" responded with ${e.status}: ${e.statusText}`,e)})).then((t=>{switch(o){case"arraybuffer":return t.arrayBuffer();case"blob":return t.blob();case"document":return t.text().then((t=>(new DOMParser).parseFromString(t,a)));case"json":return t.json();default:if(void 0===a)return t.text();{const e=/charset="?([^;"\s]*)"?/i.exec(a),n=e&&e[1]?e[1].toLowerCase():void 0,i=new TextDecoder(n);return t.arrayBuffer().then((t=>i.decode(t)))}}})).then((e=>{Xd.add(t,e);const n=Zd[t];delete Zd[t];for(let t=0,i=n.length;t{const n=Zd[t];if(void 0===n)throw this.manager.itemError(t),e;delete Zd[t];for(let t=0,i=n.length;t{this.manager.itemEnd(t)})),this.manager.itemStart(t)}setResponseType(t){return this.responseType=t,this}setMimeType(t){return this.mimeType=t,this}}class $d extends Yd{constructor(t){super(t)}load(t,e,n,i){const r=this,s=new Kd(this.manager);s.setPath(this.path),s.setRequestHeader(this.requestHeader),s.setWithCredentials(this.withCredentials),s.load(t,(function(n){try{e(r.parse(JSON.parse(n)))}catch(e){i?i(e):console.error(e),r.manager.itemError(t)}}),n,i)}parse(t){const e=[];for(let n=0;n0:i.vertexColors=t.vertexColors),void 0!==t.uniforms)for(const e in t.uniforms){const r=t.uniforms[e];switch(i.uniforms[e]={},r.type){case"t":i.uniforms[e].value=n(r.value);break;case"c":i.uniforms[e].value=(new ts).setHex(r.value);break;case"v2":i.uniforms[e].value=(new ti).fromArray(r.value);break;case"v3":i.uniforms[e].value=(new Li).fromArray(r.value);break;case"v4":i.uniforms[e].value=(new wi).fromArray(r.value);break;case"m3":i.uniforms[e].value=(new ei).fromArray(r.value);break;case"m4":i.uniforms[e].value=(new lr).fromArray(r.value);break;default:i.uniforms[e].value=r.value}}if(void 0!==t.defines&&(i.defines=t.defines),void 0!==t.vertexShader&&(i.vertexShader=t.vertexShader),void 0!==t.fragmentShader&&(i.fragmentShader=t.fragmentShader),void 0!==t.glslVersion&&(i.glslVersion=t.glslVersion),void 0!==t.extensions)for(const e in t.extensions)i.extensions[e]=t.extensions[e];if(void 0!==t.lights&&(i.lights=t.lights),void 0!==t.clipping&&(i.clipping=t.clipping),void 0!==t.size&&(i.size=t.size),void 0!==t.sizeAttenuation&&(i.sizeAttenuation=t.sizeAttenuation),void 0!==t.map&&(i.map=n(t.map)),void 0!==t.matcap&&(i.matcap=n(t.matcap)),void 0!==t.alphaMap&&(i.alphaMap=n(t.alphaMap)),void 0!==t.bumpMap&&(i.bumpMap=n(t.bumpMap)),void 0!==t.bumpScale&&(i.bumpScale=t.bumpScale),void 0!==t.normalMap&&(i.normalMap=n(t.normalMap)),void 0!==t.normalMapType&&(i.normalMapType=t.normalMapType),void 0!==t.normalScale){let e=t.normalScale;!1===Array.isArray(e)&&(e=[e,e]),i.normalScale=(new ti).fromArray(e)}return void 0!==t.displacementMap&&(i.displacementMap=n(t.displacementMap)),void 0!==t.displacementScale&&(i.displacementScale=t.displacementScale),void 0!==t.displacementBias&&(i.displacementBias=t.displacementBias),void 0!==t.roughnessMap&&(i.roughnessMap=n(t.roughnessMap)),void 0!==t.metalnessMap&&(i.metalnessMap=n(t.metalnessMap)),void 0!==t.emissiveMap&&(i.emissiveMap=n(t.emissiveMap)),void 0!==t.emissiveIntensity&&(i.emissiveIntensity=t.emissiveIntensity),void 0!==t.specularMap&&(i.specularMap=n(t.specularMap)),void 0!==t.specularIntensityMap&&(i.specularIntensityMap=n(t.specularIntensityMap)),void 0!==t.specularColorMap&&(i.specularColorMap=n(t.specularColorMap)),void 0!==t.envMap&&(i.envMap=n(t.envMap)),void 0!==t.envMapRotation&&i.envMapRotation.fromArray(t.envMapRotation),void 0!==t.envMapIntensity&&(i.envMapIntensity=t.envMapIntensity),void 0!==t.reflectivity&&(i.reflectivity=t.reflectivity),void 0!==t.refractionRatio&&(i.refractionRatio=t.refractionRatio),void 0!==t.lightMap&&(i.lightMap=n(t.lightMap)),void 0!==t.lightMapIntensity&&(i.lightMapIntensity=t.lightMapIntensity),void 0!==t.aoMap&&(i.aoMap=n(t.aoMap)),void 0!==t.aoMapIntensity&&(i.aoMapIntensity=t.aoMapIntensity),void 0!==t.gradientMap&&(i.gradientMap=n(t.gradientMap)),void 0!==t.clearcoatMap&&(i.clearcoatMap=n(t.clearcoatMap)),void 0!==t.clearcoatRoughnessMap&&(i.clearcoatRoughnessMap=n(t.clearcoatRoughnessMap)),void 0!==t.clearcoatNormalMap&&(i.clearcoatNormalMap=n(t.clearcoatNormalMap)),void 0!==t.clearcoatNormalScale&&(i.clearcoatNormalScale=(new ti).fromArray(t.clearcoatNormalScale)),void 0!==t.iridescenceMap&&(i.iridescenceMap=n(t.iridescenceMap)),void 0!==t.iridescenceThicknessMap&&(i.iridescenceThicknessMap=n(t.iridescenceThicknessMap)),void 0!==t.transmissionMap&&(i.transmissionMap=n(t.transmissionMap)),void 0!==t.thicknessMap&&(i.thicknessMap=n(t.thicknessMap)),void 0!==t.anisotropyMap&&(i.anisotropyMap=n(t.anisotropyMap)),void 0!==t.sheenColorMap&&(i.sheenColorMap=n(t.sheenColorMap)),void 0!==t.sheenRoughnessMap&&(i.sheenRoughnessMap=n(t.sheenRoughnessMap)),i}setTextures(t){return this.textures=t,this}createMaterialFromType(t){return bp.createMaterialFromType(t)}static createMaterialFromType(t){return new{ShadowMaterial:fd,SpriteMaterial:uc,RawShaderMaterial:gd,ShaderMaterial:Ys,PointsMaterial:Uh,MeshPhysicalMaterial:_d,MeshStandardMaterial:vd,MeshPhongMaterial:xd,MeshToonMaterial:yd,MeshNormalMaterial:Md,MeshLambertMaterial:Sd,MeshDepthMaterial:zl,MeshDistanceMaterial:kl,MeshBasicMaterial:rs,MeshMatcapMaterial:bd,LineDashedMaterial:wd,LineBasicMaterial:xh,Material:is}[t]}}class wp{static decodeText(t){if(console.warn("THREE.LoaderUtils: decodeText() has been deprecated with r165 and will be removed with r175. Use TextDecoder instead."),"undefined"!=typeof TextDecoder)return(new TextDecoder).decode(t);let e="";for(let n=0,i=t.length;n0){const n=new jd(e);r=new tp(n),r.setCrossOrigin(this.crossOrigin);for(let e=0,n=t.length;e0){i=new tp(this.manager),i.setCrossOrigin(this.crossOrigin);for(let e=0,i=t.length;e{const e=new Di;e.min.fromArray(t.boxMin),e.max.fromArray(t.boxMax);const n=new Qi;return n.radius=t.sphereRadius,n.center.fromArray(t.sphereCenter),{boxInitialized:t.boxInitialized,box:e,sphereInitialized:t.sphereInitialized,sphere:n}})),s._maxInstanceCount=t.maxInstanceCount,s._maxVertexCount=t.maxVertexCount,s._maxIndexCount=t.maxIndexCount,s._geometryInitialized=t.geometryInitialized,s._geometryCount=t.geometryCount,s._matricesTexture=h(t.matricesTexture.uuid),void 0!==t.colorsTexture&&(s._colorsTexture=h(t.colorsTexture.uuid));break;case"LOD":s=new Cc;break;case"Line":s=new Ah(l(t.geometry),c(t.material));break;case"LineLoop":s=new Lh(l(t.geometry),c(t.material));break;case"LineSegments":s=new Ih(l(t.geometry),c(t.material));break;case"PointCloud":case"Points":s=new Bh(l(t.geometry),c(t.material));break;case"Sprite":s=new Tc(c(t.material));break;case"Group":s=new Zl;break;case"Bone":s=new kc;break;default:s=new Dr}if(s.uuid=t.uuid,void 0!==t.name&&(s.name=t.name),void 0!==t.matrix?(s.matrix.fromArray(t.matrix),void 0!==t.matrixAutoUpdate&&(s.matrixAutoUpdate=t.matrixAutoUpdate),s.matrixAutoUpdate&&s.matrix.decompose(s.position,s.quaternion,s.scale)):(void 0!==t.position&&s.position.fromArray(t.position),void 0!==t.rotation&&s.rotation.fromArray(t.rotation),void 0!==t.quaternion&&s.quaternion.fromArray(t.quaternion),void 0!==t.scale&&s.scale.fromArray(t.scale)),void 0!==t.up&&s.up.fromArray(t.up),void 0!==t.castShadow&&(s.castShadow=t.castShadow),void 0!==t.receiveShadow&&(s.receiveShadow=t.receiveShadow),t.shadow&&(void 0!==t.shadow.intensity&&(s.shadow.intensity=t.shadow.intensity),void 0!==t.shadow.bias&&(s.shadow.bias=t.shadow.bias),void 0!==t.shadow.normalBias&&(s.shadow.normalBias=t.shadow.normalBias),void 0!==t.shadow.radius&&(s.shadow.radius=t.shadow.radius),void 0!==t.shadow.mapSize&&s.shadow.mapSize.fromArray(t.shadow.mapSize),void 0!==t.shadow.camera&&(s.shadow.camera=this.parseObject(t.shadow.camera))),void 0!==t.visible&&(s.visible=t.visible),void 0!==t.frustumCulled&&(s.frustumCulled=t.frustumCulled),void 0!==t.renderOrder&&(s.renderOrder=t.renderOrder),void 0!==t.userData&&(s.userData=t.userData),void 0!==t.layers&&(s.layers.mask=t.layers),void 0!==t.children){const a=t.children;for(let t=0;t{e&&e(n),r.manager.itemEnd(t)})).catch((t=>{i&&i(t)})):(setTimeout((function(){e&&e(s),r.manager.itemEnd(t)}),0),s);const a={};a.credentials="anonymous"===this.crossOrigin?"same-origin":"include",a.headers=this.requestHeader;const o=fetch(t,a).then((function(t){return t.blob()})).then((function(t){return createImageBitmap(t,Object.assign(r.options,{colorSpaceConversion:"none"}))})).then((function(n){return Xd.add(t,n),e&&e(n),r.manager.itemEnd(t),n})).catch((function(e){i&&i(e),Xd.remove(t),r.manager.itemError(t),r.manager.itemEnd(t)}));Xd.add(t,o),r.manager.itemStart(t)}}let Lp;class Up{static getContext(){return void 0===Lp&&(Lp=new(window.AudioContext||window.webkitAudioContext)),Lp}static setContext(t){Lp=t}}class Np extends Yd{constructor(t){super(t)}load(t,e,n,i){const r=this,s=new Kd(this.manager);function a(e){i?i(e):console.error(e),r.manager.itemError(t)}s.setResponseType("arraybuffer"),s.setPath(this.path),s.setRequestHeader(this.requestHeader),s.setWithCredentials(this.withCredentials),s.load(t,(function(t){try{const n=t.slice(0);Up.getContext().decodeAudioData(n,(function(t){e(t)})).catch(a)}catch(t){a(t)}}),n,i)}}const Dp=new lr,Op=new lr,Fp=new lr;class Bp{constructor(){this.type="StereoCamera",this.aspect=1,this.eyeSep=.064,this.cameraL=new Qs,this.cameraL.layers.enable(1),this.cameraL.matrixAutoUpdate=!1,this.cameraR=new Qs,this.cameraR.layers.enable(2),this.cameraR.matrixAutoUpdate=!1,this._cache={focus:null,fov:null,aspect:null,near:null,far:null,zoom:null,eyeSep:null}}update(t){const e=this._cache;if(e.focus!==t.focus||e.fov!==t.fov||e.aspect!==t.aspect*this.aspect||e.near!==t.near||e.far!==t.far||e.zoom!==t.zoom||e.eyeSep!==this.eyeSep){e.focus=t.focus,e.fov=t.fov,e.aspect=t.aspect*this.aspect,e.near=t.near,e.far=t.far,e.zoom=t.zoom,e.eyeSep=this.eyeSep,Fp.copy(t.projectionMatrix);const n=e.eyeSep/2,i=n*e.near/e.focus,r=e.near*Math.tan(Xn*e.fov*.5)/e.zoom;let s,a;Op.elements[12]=-n,Dp.elements[12]=n,s=-r*e.aspect+i,a=r*e.aspect+i,Fp.elements[0]=2*e.near/(a-s),Fp.elements[8]=(a+s)/(a-s),this.cameraL.projectionMatrix.copy(Fp),s=-r*e.aspect-i,a=r*e.aspect-i,Fp.elements[0]=2*e.near/(a-s),Fp.elements[8]=(a+s)/(a-s),this.cameraR.projectionMatrix.copy(Fp)}this.cameraL.matrixWorld.copy(t.matrixWorld).multiply(Op),this.cameraR.matrixWorld.copy(t.matrixWorld).multiply(Dp)}}class zp{constructor(t=!0){this.autoStart=t,this.startTime=0,this.oldTime=0,this.elapsedTime=0,this.running=!1}start(){this.startTime=kp(),this.oldTime=this.startTime,this.elapsedTime=0,this.running=!0}stop(){this.getElapsedTime(),this.running=!1,this.autoStart=!1}getElapsedTime(){return this.getDelta(),this.elapsedTime}getDelta(){let t=0;if(this.autoStart&&!this.running)return this.start(),0;if(this.running){const e=kp();t=(e-this.oldTime)/1e3,this.oldTime=e,this.elapsedTime+=t}return t}}function kp(){return performance.now()}const Vp=new Li,Hp=new Ii,Gp=new Li,Wp=new Li;class Xp extends Dr{constructor(){super(),this.type="AudioListener",this.context=Up.getContext(),this.gain=this.context.createGain(),this.gain.connect(this.context.destination),this.filter=null,this.timeDelta=0,this._clock=new zp}getInput(){return this.gain}removeFilter(){return null!==this.filter&&(this.gain.disconnect(this.filter),this.filter.disconnect(this.context.destination),this.gain.connect(this.context.destination),this.filter=null),this}getFilter(){return this.filter}setFilter(t){return null!==this.filter?(this.gain.disconnect(this.filter),this.filter.disconnect(this.context.destination)):this.gain.disconnect(this.context.destination),this.filter=t,this.gain.connect(this.filter),this.filter.connect(this.context.destination),this}getMasterVolume(){return this.gain.gain.value}setMasterVolume(t){return this.gain.gain.setTargetAtTime(t,this.context.currentTime,.01),this}updateMatrixWorld(t){super.updateMatrixWorld(t);const e=this.context.listener,n=this.up;if(this.timeDelta=this._clock.getDelta(),this.matrixWorld.decompose(Vp,Hp,Gp),Wp.set(0,0,-1).applyQuaternion(Hp),e.positionX){const t=this.context.currentTime+this.timeDelta;e.positionX.linearRampToValueAtTime(Vp.x,t),e.positionY.linearRampToValueAtTime(Vp.y,t),e.positionZ.linearRampToValueAtTime(Vp.z,t),e.forwardX.linearRampToValueAtTime(Wp.x,t),e.forwardY.linearRampToValueAtTime(Wp.y,t),e.forwardZ.linearRampToValueAtTime(Wp.z,t),e.upX.linearRampToValueAtTime(n.x,t),e.upY.linearRampToValueAtTime(n.y,t),e.upZ.linearRampToValueAtTime(n.z,t)}else e.setPosition(Vp.x,Vp.y,Vp.z),e.setOrientation(Wp.x,Wp.y,Wp.z,n.x,n.y,n.z)}}class jp extends Dr{constructor(t){super(),this.type="Audio",this.listener=t,this.context=t.context,this.gain=this.context.createGain(),this.gain.connect(t.getInput()),this.autoplay=!1,this.buffer=null,this.detune=0,this.loop=!1,this.loopStart=0,this.loopEnd=0,this.offset=0,this.duration=void 0,this.playbackRate=1,this.isPlaying=!1,this.hasPlaybackControl=!0,this.source=null,this.sourceType="empty",this._startedAt=0,this._progress=0,this._connected=!1,this.filters=[]}getOutput(){return this.gain}setNodeSource(t){return this.hasPlaybackControl=!1,this.sourceType="audioNode",this.source=t,this.connect(),this}setMediaElementSource(t){return this.hasPlaybackControl=!1,this.sourceType="mediaNode",this.source=this.context.createMediaElementSource(t),this.connect(),this}setMediaStreamSource(t){return this.hasPlaybackControl=!1,this.sourceType="mediaStreamNode",this.source=this.context.createMediaStreamSource(t),this.connect(),this}setBuffer(t){return this.buffer=t,this.sourceType="buffer",this.autoplay&&this.play(),this}play(t=0){if(!0===this.isPlaying)return void console.warn("THREE.Audio: Audio is already playing.");if(!1===this.hasPlaybackControl)return void console.warn("THREE.Audio: this Audio has no playback control.");this._startedAt=this.context.currentTime+t;const e=this.context.createBufferSource();return e.buffer=this.buffer,e.loop=this.loop,e.loopStart=this.loopStart,e.loopEnd=this.loopEnd,e.onended=this.onEnded.bind(this),e.start(this._startedAt,this._progress+this.offset,this.duration),this.isPlaying=!0,this.source=e,this.setDetune(this.detune),this.setPlaybackRate(this.playbackRate),this.connect()}pause(){if(!1!==this.hasPlaybackControl)return!0===this.isPlaying&&(this._progress+=Math.max(this.context.currentTime-this._startedAt,0)*this.playbackRate,!0===this.loop&&(this._progress=this._progress%(this.duration||this.buffer.duration)),this.source.stop(),this.source.onended=null,this.isPlaying=!1),this;console.warn("THREE.Audio: this Audio has no playback control.")}stop(t=0){if(!1!==this.hasPlaybackControl)return this._progress=0,null!==this.source&&(this.source.stop(this.context.currentTime+t),this.source.onended=null),this.isPlaying=!1,this;console.warn("THREE.Audio: this Audio has no playback control.")}connect(){if(this.filters.length>0){this.source.connect(this.filters[0]);for(let t=1,e=this.filters.length;t0){this.source.disconnect(this.filters[0]);for(let t=1,e=this.filters.length;t0&&this._mixBufferRegionAdditive(n,i,this._addIndex*e,1,e);for(let t=e,r=e+e;t!==r;++t)if(n[t]!==n[t+e]){a.setValue(n,i);break}}saveOriginalState(){const t=this.binding,e=this.buffer,n=this.valueSize,i=n*this._origIndex;t.getValue(e,i);for(let t=n,r=i;t!==r;++t)e[t]=e[i+t%n];this._setIdentity(),this.cumulativeWeight=0,this.cumulativeWeightAdditive=0}restoreOriginalState(){const t=3*this.valueSize;this.binding.setValue(this.buffer,t)}_setAdditiveIdentityNumeric(){const t=this._addIndex*this.valueSize,e=t+this.valueSize;for(let n=t;n=.5)for(let i=0;i!==r;++i)t[e+i]=t[n+i]}_slerp(t,e,n,i){Ii.slerpFlat(t,e,t,e,t,n,i)}_slerpAdditive(t,e,n,i,r){const s=this._workIndex*r;Ii.multiplyQuaternionsFlat(t,s,t,e,t,n),Ii.slerpFlat(t,e,t,e,t,s,i)}_lerp(t,e,n,i,r){const s=1-i;for(let a=0;a!==r;++a){const r=e+a;t[r]=t[r]*s+t[n+a]*i}}_lerpAdditive(t,e,n,i,r){for(let s=0;s!==r;++s){const r=e+s;t[r]=t[r]+t[n+s]*i}}}const tm="\\[\\]\\.:\\/",em=new RegExp("["+tm+"]","g"),nm="[^"+tm+"]",im="[^"+tm.replace("\\.","")+"]",rm=new RegExp("^"+/((?:WC+[\/:])*)/.source.replace("WC",nm)+/(WCOD+)?/.source.replace("WCOD",im)+/(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace("WC",nm)+/\.(WC+)(?:\[(.+)\])?/.source.replace("WC",nm)+"$"),sm=["material","materials","bones","map"];class am{constructor(t,e,n){this.path=e,this.parsedPath=n||am.parseTrackName(e),this.node=am.findNode(t,this.parsedPath.nodeName),this.rootNode=t,this.getValue=this._getValue_unbound,this.setValue=this._setValue_unbound}static create(t,e,n){return t&&t.isAnimationObjectGroup?new am.Composite(t,e,n):new am(t,e,n)}static sanitizeNodeName(t){return t.replace(/\s/g,"_").replace(em,"")}static parseTrackName(t){const e=rm.exec(t);if(null===e)throw new Error("PropertyBinding: Cannot parse trackName: "+t);const n={nodeName:e[2],objectName:e[3],objectIndex:e[4],propertyName:e[5],propertyIndex:e[6]},i=n.nodeName&&n.nodeName.lastIndexOf(".");if(void 0!==i&&-1!==i){const t=n.nodeName.substring(i+1);-1!==sm.indexOf(t)&&(n.nodeName=n.nodeName.substring(0,i),n.objectName=t)}if(null===n.propertyName||0===n.propertyName.length)throw new Error("PropertyBinding: can not parse propertyName from trackName: "+t);return n}static findNode(t,e){if(void 0===e||""===e||"."===e||-1===e||e===t.name||e===t.uuid)return t;if(t.skeleton){const n=t.skeleton.getBoneByName(e);if(void 0!==n)return n}if(t.children){const n=function(t){for(let i=0;i=r){const s=r++,c=t[s];e[c.uuid]=l,t[l]=c,e[o]=s,t[s]=a;for(let t=0,e=i;t!==e;++t){const e=n[t],i=e[s],r=e[l];e[l]=i,e[s]=r}}}this.nCachedObjects_=r}uncache(){const t=this._objects,e=this._indicesByUUID,n=this._bindings,i=n.length;let r=this.nCachedObjects_,s=t.length;for(let a=0,o=arguments.length;a!==o;++a){const o=arguments[a].uuid,l=e[o];if(void 0!==l)if(delete e[o],l0&&(e[a.uuid]=l),t[l]=a,t.pop();for(let t=0,e=i;t!==e;++t){const e=n[t];e[l]=e[r],e.pop()}}}this.nCachedObjects_=r}subscribe_(t,e){const n=this._bindingsIndicesByPath;let i=n[t];const r=this._bindings;if(void 0!==i)return r[i];const s=this._paths,a=this._parsedPaths,o=this._objects,l=o.length,c=this.nCachedObjects_,h=new Array(l);i=r.length,n[t]=i,s.push(t),a.push(e),r.push(h);for(let n=c,i=o.length;n!==i;++n){const i=o[n];h[n]=new am(i,t,e)}return h}unsubscribe_(t){const e=this._bindingsIndicesByPath,n=e[t];if(void 0!==n){const i=this._paths,r=this._parsedPaths,s=this._bindings,a=s.length-1,o=s[a];e[t[a]]=n,s[n]=o,s.pop(),r[n]=r[a],r.pop(),i[n]=i[a],i.pop()}}}class lm{constructor(t,e,n=null,i=e.blendMode){this._mixer=t,this._clip=e,this._localRoot=n,this.blendMode=i;const r=e.tracks,s=r.length,a=new Array(s),o={endingStart:De,endingEnd:De};for(let t=0;t!==s;++t){const e=r[t].createInterpolant(null);a[t]=e,e.settings=o}this._interpolantSettings=o,this._interpolants=a,this._propertyBindings=new Array(s),this._cacheIndex=null,this._byClipCacheIndex=null,this._timeScaleInterpolant=null,this._weightInterpolant=null,this.loop=2201,this._loopCount=-1,this._startTime=null,this.time=0,this.timeScale=1,this._effectiveTimeScale=1,this.weight=1,this._effectiveWeight=1,this.repetitions=1/0,this.paused=!1,this.enabled=!0,this.clampWhenFinished=!1,this.zeroSlopeAtStart=!0,this.zeroSlopeAtEnd=!0}play(){return this._mixer._activateAction(this),this}stop(){return this._mixer._deactivateAction(this),this.reset()}reset(){return this.paused=!1,this.enabled=!0,this.time=0,this._loopCount=-1,this._startTime=null,this.stopFading().stopWarping()}isRunning(){return this.enabled&&!this.paused&&0!==this.timeScale&&null===this._startTime&&this._mixer._isActiveAction(this)}isScheduled(){return this._mixer._isActiveAction(this)}startAt(t){return this._startTime=t,this}setLoop(t,e){return this.loop=t,this.repetitions=e,this}setEffectiveWeight(t){return this.weight=t,this._effectiveWeight=this.enabled?t:0,this.stopFading()}getEffectiveWeight(){return this._effectiveWeight}fadeIn(t){return this._scheduleFading(t,0,1)}fadeOut(t){return this._scheduleFading(t,1,0)}crossFadeFrom(t,e,n){if(t.fadeOut(e),this.fadeIn(e),n){const n=this._clip.duration,i=t._clip.duration,r=i/n,s=n/i;t.warp(1,r,e),this.warp(s,1,e)}return this}crossFadeTo(t,e,n){return t.crossFadeFrom(this,e,n)}stopFading(){const t=this._weightInterpolant;return null!==t&&(this._weightInterpolant=null,this._mixer._takeBackControlInterpolant(t)),this}setEffectiveTimeScale(t){return this.timeScale=t,this._effectiveTimeScale=this.paused?0:t,this.stopWarping()}getEffectiveTimeScale(){return this._effectiveTimeScale}setDuration(t){return this.timeScale=this._clip.duration/t,this.stopWarping()}syncWith(t){return this.time=t.time,this.timeScale=t.timeScale,this.stopWarping()}halt(t){return this.warp(this._effectiveTimeScale,0,t)}warp(t,e,n){const i=this._mixer,r=i.time,s=this.timeScale;let a=this._timeScaleInterpolant;null===a&&(a=i._lendControlInterpolant(),this._timeScaleInterpolant=a);const o=a.parameterPositions,l=a.sampleValues;return o[0]=r,o[1]=r+n,l[0]=t/s,l[1]=e/s,this}stopWarping(){const t=this._timeScaleInterpolant;return null!==t&&(this._timeScaleInterpolant=null,this._mixer._takeBackControlInterpolant(t)),this}getMixer(){return this._mixer}getClip(){return this._clip}getRoot(){return this._localRoot||this._mixer._root}_update(t,e,n,i){if(!this.enabled)return void this._updateWeight(t);const r=this._startTime;if(null!==r){const i=(t-r)*n;i<0||0===n?e=0:(this._startTime=null,e=n*i)}e*=this._updateTimeScale(t);const s=this._updateTime(e),a=this._updateWeight(t);if(a>0){const t=this._interpolants,e=this._propertyBindings;if(this.blendMode===ze)for(let n=0,i=t.length;n!==i;++n)t[n].evaluate(s),e[n].accumulateAdditive(a);else for(let n=0,r=t.length;n!==r;++n)t[n].evaluate(s),e[n].accumulate(i,a)}}_updateWeight(t){let e=0;if(this.enabled){e=this.weight;const n=this._weightInterpolant;if(null!==n){const i=n.evaluate(t)[0];e*=i,t>n.parameterPositions[1]&&(this.stopFading(),0===i&&(this.enabled=!1))}}return this._effectiveWeight=e,e}_updateTimeScale(t){let e=0;if(!this.paused){e=this.timeScale;const n=this._timeScaleInterpolant;if(null!==n){e*=n.evaluate(t)[0],t>n.parameterPositions[1]&&(this.stopWarping(),0===e?this.paused=!0:this.timeScale=e)}}return this._effectiveTimeScale=e,e}_updateTime(t){const e=this._clip.duration,n=this.loop;let i=this.time+t,r=this._loopCount;const s=2202===n;if(0===t)return-1===r?i:s&&1==(1&r)?e-i:i;if(2200===n){-1===r&&(this._loopCount=0,this._setEndings(!0,!0,!1));t:{if(i>=e)i=e;else{if(!(i<0)){this.time=i;break t}i=0}this.clampWhenFinished?this.paused=!0:this.enabled=!1,this.time=i,this._mixer.dispatchEvent({type:"finished",action:this,direction:t<0?-1:1})}}else{if(-1===r&&(t>=0?(r=0,this._setEndings(!0,0===this.repetitions,s)):this._setEndings(0===this.repetitions,!0,s)),i>=e||i<0){const n=Math.floor(i/e);i-=e*n,r+=Math.abs(n);const a=this.repetitions-r;if(a<=0)this.clampWhenFinished?this.paused=!0:this.enabled=!1,i=t>0?e:0,this.time=i,this._mixer.dispatchEvent({type:"finished",action:this,direction:t>0?1:-1});else{if(1===a){const e=t<0;this._setEndings(e,!e,s)}else this._setEndings(!1,!1,s);this._loopCount=r,this.time=i,this._mixer.dispatchEvent({type:"loop",action:this,loopDelta:n})}}else this.time=i;if(s&&1==(1&r))return e-i}return i}_setEndings(t,e,n){const i=this._interpolantSettings;n?(i.endingStart=Oe,i.endingEnd=Oe):(i.endingStart=t?this.zeroSlopeAtStart?Oe:De:Fe,i.endingEnd=e?this.zeroSlopeAtEnd?Oe:De:Fe)}_scheduleFading(t,e,n){const i=this._mixer,r=i.time;let s=this._weightInterpolant;null===s&&(s=i._lendControlInterpolant(),this._weightInterpolant=s);const a=s.parameterPositions,o=s.sampleValues;return a[0]=r,o[0]=e,a[1]=r+t,o[1]=n,this}}const cm=new Float32Array(1);class hm extends Hn{constructor(t){super(),this._root=t,this._initMemoryManager(),this._accuIndex=0,this.time=0,this.timeScale=1}_bindAction(t,e){const n=t._localRoot||this._root,i=t._clip.tracks,r=i.length,s=t._propertyBindings,a=t._interpolants,o=n.uuid,l=this._bindingsByRootAndName;let c=l[o];void 0===c&&(c={},l[o]=c);for(let t=0;t!==r;++t){const r=i[t],l=r.name;let h=c[l];if(void 0!==h)++h.referenceCount,s[t]=h;else{if(h=s[t],void 0!==h){null===h._cacheIndex&&(++h.referenceCount,this._addInactiveBinding(h,o,l));continue}const i=e&&e._propertyBindings[t].binding.parsedPath;h=new Qp(am.create(n,l,i),r.ValueTypeName,r.getValueSize()),++h.referenceCount,this._addInactiveBinding(h,o,l),s[t]=h}a[t].resultBuffer=h.buffer}}_activateAction(t){if(!this._isActiveAction(t)){if(null===t._cacheIndex){const e=(t._localRoot||this._root).uuid,n=t._clip.uuid,i=this._actionsByClip[n];this._bindAction(t,i&&i.knownActions[0]),this._addInactiveAction(t,n,e)}const e=t._propertyBindings;for(let t=0,n=e.length;t!==n;++t){const n=e[t];0==n.useCount++&&(this._lendBinding(n),n.saveOriginalState())}this._lendAction(t)}}_deactivateAction(t){if(this._isActiveAction(t)){const e=t._propertyBindings;for(let t=0,n=e.length;t!==n;++t){const n=e[t];0==--n.useCount&&(n.restoreOriginalState(),this._takeBackBinding(n))}this._takeBackAction(t)}}_initMemoryManager(){this._actions=[],this._nActiveActions=0,this._actionsByClip={},this._bindings=[],this._nActiveBindings=0,this._bindingsByRootAndName={},this._controlInterpolants=[],this._nActiveControlInterpolants=0;const t=this;this.stats={actions:{get total(){return t._actions.length},get inUse(){return t._nActiveActions}},bindings:{get total(){return t._bindings.length},get inUse(){return t._nActiveBindings}},controlInterpolants:{get total(){return t._controlInterpolants.length},get inUse(){return t._nActiveControlInterpolants}}}}_isActiveAction(t){const e=t._cacheIndex;return null!==e&&e=0;--e)t[e].stop();return this}update(t){t*=this.timeScale;const e=this._actions,n=this._nActiveActions,i=this.time+=t,r=Math.sign(t),s=this._accuIndex^=1;for(let a=0;a!==n;++a){e[a]._update(i,t,r,s)}const a=this._bindings,o=this._nActiveBindings;for(let t=0;t!==o;++t)a[t].apply(s);return this}setTime(t){this.time=0;for(let t=0;t=this.min.x&&t.x<=this.max.x&&t.y>=this.min.y&&t.y<=this.max.y}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y))}intersectsBox(t){return t.max.x>=this.min.x&&t.min.x<=this.max.x&&t.max.y>=this.min.y&&t.min.y<=this.max.y}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,bm).distanceTo(t)}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const Tm=new Li,Em=new Li;class Am{constructor(t=new Li,e=new Li){this.start=t,this.end=e}set(t,e){return this.start.copy(t),this.end.copy(e),this}copy(t){return this.start.copy(t.start),this.end.copy(t.end),this}getCenter(t){return t.addVectors(this.start,this.end).multiplyScalar(.5)}delta(t){return t.subVectors(this.end,this.start)}distanceSq(){return this.start.distanceToSquared(this.end)}distance(){return this.start.distanceTo(this.end)}at(t,e){return this.delta(e).multiplyScalar(t).add(this.start)}closestPointToPointParameter(t,e){Tm.subVectors(t,this.start),Em.subVectors(this.end,this.start);const n=Em.dot(Em);let i=Em.dot(Tm)/n;return e&&(i=Yn(i,0,1)),i}closestPointToPoint(t,e,n){const i=this.closestPointToPointParameter(t,e);return this.delta(n).multiplyScalar(i).add(this.start)}applyMatrix4(t){return this.start.applyMatrix4(t),this.end.applyMatrix4(t),this}equals(t){return t.start.equals(this.start)&&t.end.equals(this.end)}clone(){return(new this.constructor).copy(this)}}const Rm=new Li;class Cm extends Dr{constructor(t,e){super(),this.light=t,this.matrixAutoUpdate=!1,this.color=e,this.type="SpotLightHelper";const n=new Cs,i=[0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,-1,0,1,0,0,0,0,1,1,0,0,0,0,-1,1];for(let t=0,e=1,n=32;t1)for(let n=0;n.99999)this.quaternion.set(0,0,0,1);else if(t.y<-.99999)this.quaternion.set(1,0,0,0);else{tf.set(t.z,0,-t.x).normalize();const e=Math.acos(t.y);this.quaternion.setFromAxisAngle(tf,e)}}setLength(t,e=.2*t,n=.2*e){this.line.scale.set(1,Math.max(1e-4,t-e),1),this.line.updateMatrix(),this.cone.scale.set(n,e,n),this.cone.position.y=t,this.cone.updateMatrix()}setColor(t){this.line.material.color.set(t),this.cone.material.color.set(t)}copy(t){return super.copy(t,!1),this.line.copy(t.line),this.cone.copy(t.cone),this}dispose(){this.line.geometry.dispose(),this.line.material.dispose(),this.cone.geometry.dispose(),this.cone.material.dispose()}}class sf extends Ih{constructor(t=1){const e=[0,0,0,t,0,0,0,0,0,0,t,0,0,0,0,0,0,t],n=new Cs;n.setAttribute("position",new Ms(e,3)),n.setAttribute("color",new Ms([1,0,0,1,.6,0,0,1,0,.6,1,0,0,0,1,0,.6,1],3));super(n,new xh({vertexColors:!0,toneMapped:!1})),this.type="AxesHelper"}setColors(t,e,n){const i=new ts,r=this.geometry.attributes.color.array;return i.set(t),i.toArray(r,0),i.toArray(r,3),i.set(e),i.toArray(r,6),i.toArray(r,9),i.set(n),i.toArray(r,12),i.toArray(r,15),this.geometry.attributes.color.needsUpdate=!0,this}dispose(){this.geometry.dispose(),this.material.dispose()}}class af{constructor(){this.type="ShapePath",this.color=new ts,this.subPaths=[],this.currentPath=null}moveTo(t,e){return this.currentPath=new pu,this.subPaths.push(this.currentPath),this.currentPath.moveTo(t,e),this}lineTo(t,e){return this.currentPath.lineTo(t,e),this}quadraticCurveTo(t,e,n,i){return this.currentPath.quadraticCurveTo(t,e,n,i),this}bezierCurveTo(t,e,n,i,r,s){return this.currentPath.bezierCurveTo(t,e,n,i,r,s),this}splineThru(t){return this.currentPath.splineThru(t),this}toShapes(t){function e(t,e){const n=e.length;let i=!1;for(let r=n-1,s=0;sNumber.EPSILON){if(l<0&&(n=e[s],o=-o,a=e[r],l=-l),t.ya.y)continue;if(t.y===n.y){if(t.x===n.x)return!0}else{const e=l*(t.x-n.x)-o*(t.y-n.y);if(0===e)return!0;if(e<0)continue;i=!i}}else{if(t.y!==n.y)continue;if(a.x<=t.x&&t.x<=n.x||n.x<=t.x&&t.x<=a.x)return!0}}return i}const n=$u.isClockWise,i=this.subPaths;if(0===i.length)return[];let r,s,a;const o=[];if(1===i.length)return s=i[0],a=new Eu,a.curves=s.curves,o.push(a),o;let l=!n(i[0].getPoints());l=t?!l:l;const c=[],h=[];let u,d,p=[],m=0;h[m]=void 0,p[m]=[];for(let e=0,a=i.length;e1){let t=!1,n=0;for(let t=0,e=h.length;t0&&!1===t&&(p=c)}for(let t=0,e=h.length;t/dev/null 2>&1; then + nohup /home/gardentech/scan_web/run_scan_web.sh >>"$LOG" 2>&1 & + for _ in $(seq 1 60); do + if curl -sf "$URL/api/status" >/dev/null 2>&1; then + break + fi + sleep 0.5 + done +fi + +xdg-open "$URL" >/dev/null 2>&1 & diff --git a/run_scan_web.sh b/run_scan_web.sh new file mode 100755 index 0000000..d2abe56 --- /dev/null +++ b/run_scan_web.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Launches scan_web: sources the ROS overlays the backend's ManagedProcess +# commands need to be able to `ros2 launch`/`ros2 bag record` against, then +# runs the FastAPI app with a single uvicorn worker (required — see +# backend/app.py: the rclpy context + ProcessManager registry live in +# process memory and must not be duplicated across workers). +# NOTE: no `-u` (nounset) — ROS's setup.bash references unset variables +# internally (e.g. AMENT_TRACE_SETUP_FILES) and isn't nounset-safe. +set -eo pipefail + +HOME_DIR="/home/gardentech" + +source /opt/ros/humble/setup.bash +# fast_ws deprecated (2026-08-05) — it was the pre-3-camera predecessor to +# fast_dual_ws and never had its own livox_ros_driver2 build (only chained +# to lidar2_ws/install as an underlay). Source lidar2_ws directly. +source "${HOME_DIR}/lidar2_ws/install/setup.bash" +source "${HOME_DIR}/camera2_ws/install/setup.bash" +if [ -f "${HOME_DIR}/fast_dual_ws/install/setup.bash" ]; then + source "${HOME_DIR}/fast_dual_ws/install/setup.bash" +fi +# rtk_ws intentionally not sourced yet — GPS/RTK integration is Phase 1 +# Milestone 2, unused until then. + +cd "${HOME_DIR}/scan_web/backend" +exec python3 -m uvicorn app:app --host 0.0.0.0 --port 8000 --workers 1