diff --git a/imgui.ini b/imgui.ini index 6424dec7..6afc62ad 100644 --- a/imgui.ini +++ b/imgui.ini @@ -31,19 +31,19 @@ DockId=0x0000000D,0 [Window][场景树] Pos=0,20 -Size=339,1008 +Size=339,996 Collapsed=0 DockId=0x00000007,0 [Window][属性面板] -Pos=1506,20 -Size=346,1008 +Pos=1502,20 +Size=346,996 Collapsed=0 DockId=0x00000002,0 [Window][控制台] -Pos=341,629 -Size=1163,399 +Pos=341,617 +Size=1159,399 Collapsed=0 DockId=0x00000006,1 @@ -59,7 +59,7 @@ Collapsed=0 [Window][WindowOverViewport_11111111] Pos=0,20 -Size=1852,1008 +Size=1848,996 Collapsed=0 [Window][测试窗口1] @@ -78,17 +78,17 @@ Size=93,65 Collapsed=0 [Window][新建项目] -Pos=824,402 +Pos=724,358 Size=400,300 Collapsed=0 [Window][选择路径] -Pos=626,264 +Pos=624,258 Size=600,500 Collapsed=0 [Window][打开项目] -Pos=676,314 +Pos=674,308 Size=500,400 Collapsed=0 @@ -98,8 +98,8 @@ Size=600,500 Collapsed=0 [Window][资源管理器] -Pos=341,629 -Size=1163,399 +Pos=341,617 +Size=1159,399 Collapsed=0 DockId=0x00000006,0 @@ -226,13 +226,13 @@ Size=460,260 Collapsed=0 [Docking][Data] -DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1852,1008 Split=X +DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1848,996 Split=X DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=2212,1012 Split=X DockNode ID=0x00000007 Parent=0x00000001 SizeRef=339,1084 Selected=0xE0015051 DockNode ID=0x00000008 Parent=0x00000001 SizeRef=1871,1084 Split=Y - DockNode ID=0x00000005 Parent=0x00000008 SizeRef=2048,683 Split=Y + DockNode ID=0x00000005 Parent=0x00000008 SizeRef=2048,595 Split=Y DockNode ID=0x0000000D Parent=0x00000005 SizeRef=1318,383 HiddenTabBar=1 Selected=0x43A39006 DockNode ID=0x0000000E Parent=0x00000005 SizeRef=1318,363 CentralNode=1 Selected=0xE0015051 - DockNode ID=0x00000006 Parent=0x00000008 SizeRef=2048,399 Selected=0x3A2E05C3 + DockNode ID=0x00000006 Parent=0x00000008 SizeRef=2048,399 Selected=0x5428E753 DockNode ID=0x00000002 Parent=0x08BD597D SizeRef=346,1012 Selected=0x5DB6FF37 diff --git a/project/project_manager.py b/project/project_manager.py index 465a52c3..0e3797ea 100644 --- a/project/project_manager.py +++ b/project/project_manager.py @@ -32,6 +32,7 @@ from project.scene_description import ( normalize_scene_description, save_json, ) +from project.webgl_packager import WebGLPackager class ProjectManager: @@ -41,6 +42,7 @@ class ProjectManager: self.project_config = None self.current_scene_guid = None self._asset_database = None + self.last_webgl_export_report = None print("✓ 项目管理系统初始化完成") @@ -2091,6 +2093,55 @@ class ProjectManager: ) return manifest + def buildWebGLPackage(self, output_dir): + """将当前项目导出为 WebGL 静态站点目录。""" + try: + if not self.current_project_path: + print("错误: 请先创建或打开一个项目!") + return False + + if not output_dir: + print("错误: 请指定 WebGL 打包输出目录!") + return False + + project_path = normalize_path(self.current_project_path) + ensure_project_directories(ProjectLayout(project_path)) + + if hasattr(self.world, "selection") and self.world.selection: + self.world.selection.clearSelection() + print("已取消场景中的物体选中状态") + + if not self.saveProject(): + print("错误: WebGL 打包前保存场景失败!") + return False + + output_dir = normalize_path(output_dir) + + packager = WebGLPackager(self.world) + report = packager.package(project_path, output_dir) + self.last_webgl_export_report = report + + status = str(report.get("status", "failed") or "failed") + report_path = os.path.join( + str(report.get("output_dir", "") or ""), + "reports", + "export_report.json", + ) + + if status in ("success", "partial"): + print(f"WebGL打包完成: {status}") + print(f"输出目录: {report.get('output_dir', '')}") + print(f"报告路径: {report_path}") + return True + + print("WebGL打包失败") + if report_path: + print(f"报告路径: {report_path}") + return False + except Exception as exc: + print(f"WebGL打包过程出错: {exc}") + return False + def buildPackage(self, build_dir): """将当前项目打包为最终运行程序。""" try: @@ -2707,4 +2758,3 @@ class ProjectManager: """更新窗口标题(保留方法以兼容旧代码)""" # 这个方法现在不需要做任何事情,因为我们不再处理UI pass - diff --git a/project/webgl_packager.py b/project/webgl_packager.py new file mode 100644 index 00000000..437a33f8 --- /dev/null +++ b/project/webgl_packager.py @@ -0,0 +1,2405 @@ +"""WebGL project packager for EG editor (Three.js static scene export).""" + +from __future__ import annotations + +import datetime +import json +import os +import re +import shutil +import stat +import subprocess +import tempfile +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple + + +class WebGLPackager: + """Export current EG scene into a static WebGL package directory.""" + + BASIS_MATRIX_ROW_MAJOR = [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + ] + + ENERGY_TO_INTENSITY_SCALE = 0.001 + + def __init__(self, world): + self.world = world + self.scene_manager = getattr(world, "scene_manager", None) + + self._project_path = "" + self._output_root = "" + self._assets_model_dir = "" + self._assets_texture_dir = "" + + self._copied_source_to_uri: Dict[str, str] = {} + self._name_counter: Dict[str, int] = {} + self._node_id_by_pointer: Dict[int, str] = {} + self._baseline_subnode_cache: Dict[str, Dict[str, Any]] = {} + + self.report: Dict[str, Any] = { + "status": "failed", + "warnings": [], + "missing_assets": [], + "unsupported_assets": [], + "converted_assets": [], + "copied_assets": [], + "output_dir": "", + } + + def package(self, project_path: str, output_dir: str) -> Dict[str, Any]: + """Export project as WebGL static site and return export report.""" + self._project_path = os.path.normpath(project_path) + + project_name = os.path.basename(self._project_path.rstrip(os.sep)) or "project" + self._output_root = os.path.normpath(os.path.join(output_dir, f"{project_name}_webgl")) + self._assets_model_dir = os.path.join(self._output_root, "assets", "models") + self._assets_texture_dir = os.path.join(self._output_root, "assets", "textures") + self.report["output_dir"] = self._output_root + + try: + if not os.path.isdir(self._project_path): + self._fail(f"项目路径不存在: {self._project_path}") + return self.report + + self._prepare_output_dir() + self._copy_templates() + + scene_manifest = self._build_scene_manifest(project_name) + self._write_json( + os.path.join(self._output_root, "scene", "scene_webgl.json"), + scene_manifest, + ) + + self._write_preview_scripts() + + status = "success" + if self.report["missing_assets"] or self.report["unsupported_assets"]: + status = "partial" + self.report["status"] = status + + except Exception as exc: + self._fail(f"WebGL打包失败: {exc}") + + finally: + self._write_json( + os.path.join(self._output_root, "reports", "export_report.json"), + self.report, + ) + + return self.report + + def _prepare_output_dir(self) -> None: + if os.path.isdir(self._output_root): + shutil.rmtree(self._output_root) + + os.makedirs(os.path.join(self._output_root, "js"), exist_ok=True) + os.makedirs(os.path.join(self._output_root, "vendor"), exist_ok=True) + os.makedirs(self._assets_model_dir, exist_ok=True) + os.makedirs(self._assets_texture_dir, exist_ok=True) + os.makedirs(os.path.join(self._output_root, "scene"), exist_ok=True) + os.makedirs(os.path.join(self._output_root, "reports"), exist_ok=True) + + def _copy_templates(self) -> None: + repo_root = Path(__file__).resolve().parent.parent + template_root = repo_root / "templates" / "webgl" + if not template_root.exists(): + raise FileNotFoundError(f"模板目录不存在: {template_root}") + + file_mapping = { + "index.html": "index.html", + "style.css": "style.css", + "viewer.js": os.path.join("js", "viewer.js"), + } + for src_name, dst_rel in file_mapping.items(): + src = template_root / src_name + dst = Path(self._output_root) / dst_rel + if not src.exists(): + raise FileNotFoundError(f"模板文件不存在: {src}") + dst.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(str(src), str(dst)) + + vendor_src = template_root / "vendor" + vendor_dst = Path(self._output_root) / "vendor" + if vendor_src.exists(): + for entry in vendor_src.iterdir(): + if entry.is_file(): + shutil.copy2(str(entry), str(vendor_dst / entry.name)) + + self._try_resolve_vendor_files(vendor_dst) + self._copy_optional_postprocess_vendor_files(vendor_dst) + self._rewrite_vendor_module_imports(vendor_dst) + self._strip_vendor_source_mapping_urls(vendor_dst) + + # Placeholder marker warning + placeholder_file = vendor_dst / "three.module.min.js" + if placeholder_file.exists(): + content = placeholder_file.read_text(encoding="utf-8", errors="ignore") + if "EG_VENDOR_PLACEHOLDER" in content: + self.report["warnings"].append( + "当前 vendor 为占位文件,请替换为官方 three.module.min.js / OrbitControls.js / GLTFLoader.js 后再预览。" + ) + + def _try_resolve_vendor_files(self, vendor_dst: Path) -> None: + """Try to replace template placeholders with system-installed Three.js modules.""" + lookup_roots = [ + Path("/usr/share/javascript/three"), + Path("/usr/share/nodejs/three"), + Path("/usr/lib/node_modules/three"), + Path.home() / ".local/lib/node_modules/three", + ] + + targets = { + "three.module.min.js": [ + "build/three.module.min.js", + "build/three.module.js", + ], + "OrbitControls.js": [ + "examples/jsm/controls/OrbitControls.js", + ], + "GLTFLoader.js": [ + "examples/jsm/loaders/GLTFLoader.js", + ], + } + + for dst_name, rel_candidates in targets.items(): + dst_path = vendor_dst / dst_name + if not dst_path.exists(): + continue + + try: + content = dst_path.read_text(encoding="utf-8", errors="ignore") + except Exception: + content = "" + + # Only replace placeholders. + if "EG_VENDOR_PLACEHOLDER" not in content: + continue + + found_source = None + for root in lookup_roots: + if not root.exists(): + continue + for rel in rel_candidates: + candidate = root / rel + if candidate.exists() and candidate.is_file(): + found_source = candidate + break + if found_source: + break + + if found_source: + shutil.copy2(str(found_source), str(dst_path)) + + def _copy_optional_postprocess_vendor_files(self, vendor_dst: Path) -> None: + """Try to copy optional postprocessing modules used by viewer approximation.""" + lookup_roots = [ + Path("/usr/share/javascript/three"), + Path("/usr/share/nodejs/three"), + Path("/usr/lib/node_modules/three"), + Path.home() / ".local/lib/node_modules/three", + ] + + optional_targets = { + "EffectComposer.js": [ + "examples/jsm/postprocessing/EffectComposer.js", + ], + "RenderPass.js": [ + "examples/jsm/postprocessing/RenderPass.js", + ], + "UnrealBloomPass.js": [ + "examples/jsm/postprocessing/UnrealBloomPass.js", + ], + "ShaderPass.js": [ + "examples/jsm/postprocessing/ShaderPass.js", + ], + "OutputPass.js": [ + "examples/jsm/postprocessing/OutputPass.js", + ], + "CopyShader.js": [ + "examples/jsm/shaders/CopyShader.js", + ], + "LuminosityHighPassShader.js": [ + "examples/jsm/shaders/LuminosityHighPassShader.js", + ], + } + + for dst_name, rel_candidates in optional_targets.items(): + dst_path = vendor_dst / dst_name + if dst_path.exists(): + continue + + found_source = None + for root in lookup_roots: + if not root.exists(): + continue + for rel in rel_candidates: + candidate = root / rel + if candidate.exists() and candidate.is_file(): + found_source = candidate + break + if found_source: + break + + if found_source: + try: + shutil.copy2(str(found_source), str(dst_path)) + except Exception: + continue + + def _rewrite_vendor_module_imports(self, vendor_dst: Path) -> None: + """Rewrite bare/relative Three.js addon imports to local vendor paths.""" + js_files = [p for p in vendor_dst.glob("*.js") if p.is_file()] + if not js_files: + return + + available_names = {p.name for p in js_files} + import_re = re.compile( + r"""(?P\bfrom\s+)(?P['"])(?P[^'"]+)(?P=quote)""" + ) + + def rewrite_spec(spec: str) -> str: + raw = str(spec or "").strip() + if not raw: + return raw + + if raw in {"three", "three.module.js", "three.module.min.js"}: + return "./three.module.min.js" + + if raw.startswith("three/addons/") or raw.startswith("three/examples/jsm/"): + return "./" + raw.split("/")[-1] + + if raw.endswith(".js"): + basename = os.path.basename(raw) + if basename in available_names: + return "./" + basename + + return raw + + for js_file in js_files: + if js_file.name == "three.module.min.js": + continue + try: + content = js_file.read_text(encoding="utf-8", errors="ignore") + except Exception: + continue + + replaced = False + + def _replace(match): + nonlocal replaced + old_spec = match.group("spec") + new_spec = rewrite_spec(old_spec) + if new_spec == old_spec: + return match.group(0) + replaced = True + return f"{match.group('prefix')}{match.group('quote')}{new_spec}{match.group('quote')}" + + updated = import_re.sub(_replace, content) + if not replaced: + continue + try: + js_file.write_text(updated, encoding="utf-8") + except Exception: + continue + + def _strip_vendor_source_mapping_urls(self, vendor_dst: Path) -> None: + """Remove sourceMappingURL hints to avoid noisy 404s in offline preview.""" + line_patterns = ( + re.compile(r"^\s*//[#@]\s*sourceMappingURL=.*$", re.IGNORECASE), + re.compile(r"^\s*/\*[#@]\s*sourceMappingURL=.*\*/\s*$", re.IGNORECASE), + ) + + for js_file in vendor_dst.glob("*.js"): + try: + content = js_file.read_text(encoding="utf-8", errors="ignore") + except Exception: + continue + + lines = content.splitlines() + filtered = [ + line for line in lines + if not any(pattern.match(line) for pattern in line_patterns) + ] + if len(filtered) == len(lines): + continue + + text = "\n".join(filtered) + if content.endswith("\n"): + text += "\n" + try: + js_file.write_text(text, encoding="utf-8") + self.report["warnings"].append(f"已移除 sourceMappingURL: vendor/{js_file.name}") + except Exception: + continue + + def _build_scene_manifest(self, project_name: str) -> Dict[str, Any]: + render = getattr(self.world, "render", None) + if not render: + raise RuntimeError("world.render 不可用") + + export_nodes: List[Any] = [] + + model_nodes = self._collect_model_nodes() + spot_nodes = self._collect_valid_nodes(getattr(self.scene_manager, "Spotlight", [])) + point_nodes = self._collect_valid_nodes(getattr(self.scene_manager, "Pointlight", [])) + ground_node = self._get_default_ground_node() + + export_nodes.extend(model_nodes) + export_nodes.extend(spot_nodes) + export_nodes.extend(point_nodes) + if ground_node is not None: + export_nodes.append(ground_node) + + # Unique by pointer + uniq: Dict[int, Any] = {} + for node in export_nodes: + uniq[id(node)] = node + export_nodes = list(uniq.values()) + + for index, node in enumerate(export_nodes, start=1): + self._node_id_by_pointer[id(node)] = f"node_{index:04d}" + + nodes_json: List[Dict[str, Any]] = [] + + for node in model_nodes: + entry = self._build_model_node_entry(node) + if entry: + nodes_json.append(entry) + + for node in point_nodes: + entry = self._build_light_node_entry(node, kind="point_light") + if entry: + nodes_json.append(entry) + + for node in spot_nodes: + entry = self._build_light_node_entry(node, kind="spot_light") + if entry: + nodes_json.append(entry) + + if ground_node is not None: + entry = self._build_ground_node_entry(ground_node) + if entry: + nodes_json.append(entry) + + manifest = { + "meta": { + "format_version": "1.0", + "project_name": project_name, + "exported_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + }, + "coordinate": { + "source": "panda3d_zup", + "target": "threejs_yup", + "matrix_convention": "panda_row_vector_row_major", + "basis_matrix": self.BASIS_MATRIX_ROW_MAJOR, + }, + "camera": self._build_camera_entry(), + "environment": self._build_environment_entry(), + "nodes": nodes_json, + } + return manifest + + def _collect_model_nodes(self) -> List[Any]: + if not self.scene_manager: + return [] + + all_models: List[Any] = [] + ssbo_editor = getattr(self.world, "ssbo_editor", None) + source_model_root = getattr(ssbo_editor, "source_model_root", None) if ssbo_editor else None + if source_model_root: + try: + if not source_model_root.isEmpty(): + snapshot_fn = getattr(ssbo_editor, "_snapshot_top_level_transforms_to_source_root", None) + snapshot_material_fn = getattr(ssbo_editor, "_snapshot_runtime_materials_to_source_root", None) + if callable(snapshot_fn): + try: + snapshot_fn() + except Exception: + pass + if callable(snapshot_material_fn): + try: + snapshot_material_fn() + except Exception: + pass + all_models = self._collect_valid_nodes(list(source_model_root.getChildren())) + except Exception: + all_models = [] + + if not all_models: + all_models = self._collect_valid_nodes(getattr(self.scene_manager, "models", [])) + + light_ptrs = {id(n) for n in self._collect_valid_nodes(getattr(self.scene_manager, "Spotlight", []))} + light_ptrs |= {id(n) for n in self._collect_valid_nodes(getattr(self.scene_manager, "Pointlight", []))} + + models: List[Any] = [] + for node in all_models: + if id(node) in light_ptrs: + continue + if node.hasTag("light_type"): + continue + if node.getName() in {"render", "camera", "cam"}: + continue + # Only export true model roots. Scene traversal may put many child nodes + # into scene_manager.models, but they should be replayed via subnode overrides. + is_model_root = False + try: + is_model_root = node.hasTag("is_model_root") + except Exception: + is_model_root = False + has_model_path_tag = False + for tag_name in ("model_path", "saved_model_path", "original_path", "asset_path", "file"): + try: + if node.hasTag(tag_name): + has_model_path_tag = True + break + except Exception: + continue + if not is_model_root and not has_model_path_tag: + continue + models.append(node) + return models + + @staticmethod + def _collect_valid_nodes(nodes: List[Any]) -> List[Any]: + valid = [] + for node in nodes or []: + if not node: + continue + try: + if node.isEmpty(): + continue + except Exception: + continue + valid.append(node) + return valid + + def _get_default_ground_node(self): + ground = getattr(self.world, "ground", None) + if not ground: + return None + try: + if ground.isEmpty(): + return None + return ground + except Exception: + return None + + def _build_camera_entry(self) -> Dict[str, Any]: + render = getattr(self.world, "render", None) + cam = getattr(self.world, "cam", None) or getattr(self.world, "camera", None) + + default = { + "matrix_local_row_major": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + -50.0, + 20.0, + 1.0, + ], + "fov_deg": 80.0, + "near": 0.1, + "far": 10000.0, + } + + if not cam or not render: + return default + + try: + cam_mat = cam.getMat(render) + fov = 80.0 + near = 0.1 + far = 10000.0 + lens = cam.node().getLens() if cam.node() else None + if lens: + try: + fov = float(lens.getFov()[0]) + except Exception: + pass + try: + near = float(lens.getNear()) + except Exception: + pass + try: + far = float(lens.getFar()) + except Exception: + pass + return { + "matrix_local_row_major": self._mat4_to_row_major_list(cam_mat), + "fov_deg": fov, + "near": near, + "far": far, + } + except Exception: + return default + + def _build_environment_entry(self) -> Dict[str, Any]: + ambient = getattr(self.world, "ambient_light", None) + directional = getattr(self.world, "directional_light", None) + + ambient_color = [0.2, 0.2, 0.2] + directional_color = [0.8, 0.8, 0.8] + directional_dir = [0.0, 0.0, -1.0] + + if ambient and not ambient.isEmpty(): + try: + c = ambient.node().getColor() + ambient_color = [float(c[0]), float(c[1]), float(c[2])] + except Exception: + pass + + if directional and not directional.isEmpty(): + try: + c = directional.node().getColor() + directional_color = [float(c[0]), float(c[1]), float(c[2])] + except Exception: + pass + try: + q = directional.getQuat(getattr(self.world, "render", directional.getParent())) + fwd = q.getForward() + directional_dir = [float(fwd[0]), float(fwd[1]), float(fwd[2])] + except Exception: + pass + + entry = { + "include_default_ground": True, + "ambient_light": { + "color": ambient_color, + "intensity": 1.0, + }, + "directional_light": { + "color": directional_color, + "intensity": 1.0, + "direction": directional_dir, + }, + } + skybox_entry = self._extract_skybox_entry() + if skybox_entry: + entry["skybox"] = skybox_entry + render_pipeline = self._extract_render_pipeline_settings() + if render_pipeline: + entry["render_pipeline"] = render_pipeline + return entry + + def _extract_skybox_entry(self) -> Dict[str, Any]: + """Export skybox texture config for viewer background.""" + skybox_source = self._find_runtime_skybox_texture_source() + if not skybox_source: + skybox_source = self._find_fallback_skybox_source() + if not skybox_source: + return {} + + if not self._is_supported_skybox_image(skybox_source): + self.report["warnings"].append(f"天空盒格式暂不支持,已跳过: {skybox_source}") + return {} + + uri = self._copy_asset_to_textures(skybox_source) + if not uri: + self.report["warnings"].append(f"天空盒资源复制失败: {skybox_source}") + return {} + + projection = self._guess_skybox_projection(skybox_source) + lower_color = self._extract_skybox_lower_hemisphere_color(skybox_source) + return { + "enabled": True, + "type": projection, + "uri": uri, + "apply_environment": projection != "skydome", + "clip_lower_hemisphere": projection == "skydome", + "lower_hemisphere_color": lower_color, + "horizon_blend": 0.06, + "horizon_sample_v": 0.01, + "lower_tint_strength": 0.65, + } + + def _extract_skybox_lower_hemisphere_color(self, skybox_source: str) -> List[float]: + """Pick lower hemisphere fill color, prefer horizon-adjacent sky tone.""" + horizon_rgb = self._estimate_skybox_horizon_rgb(skybox_source) + if horizon_rgb: + return horizon_rgb + + clear_rgb = self._extract_world_clear_rgb() + if clear_rgb and max(clear_rgb) > 0.02: + return clear_rgb + + # Final fallback: dark bluish neutral. + return [0.08, 0.10, 0.13] + + def _extract_world_clear_rgb(self) -> List[float]: + """Read active clear/background color from world/showbase if available.""" + + def _to_rgb(value: Any) -> List[float]: + if value is None: + return [] + try: + if hasattr(value, "__len__") and len(value) >= 3: # type: ignore[arg-type] + r = float(value[0]) # type: ignore[index] + g = float(value[1]) # type: ignore[index] + b = float(value[2]) # type: ignore[index] + return [max(0.0, min(1.0, r)), max(0.0, min(1.0, g)), max(0.0, min(1.0, b))] + except Exception: + return [] + return [] + + world_getters = ("getBackgroundColor", "get_background_color") + for name in world_getters: + getter = getattr(self.world, name, None) + if callable(getter): + try: + rgb = _to_rgb(getter()) + except Exception: + rgb = [] + if rgb: + return rgb + + win = getattr(self.world, "win", None) + if win and hasattr(win, "getClearColor"): + try: + rgb = _to_rgb(win.getClearColor()) + except Exception: + rgb = [] + if rgb: + return rgb + + rp = getattr(self.world, "render_pipeline", None) + showbase = getattr(rp, "_showbase", None) if rp else None + rp_win = getattr(showbase, "win", None) if showbase else None + if rp_win and hasattr(rp_win, "getClearColor"): + try: + rgb = _to_rgb(rp_win.getClearColor()) + except Exception: + rgb = [] + if rgb: + return rgb + + return [] + + @staticmethod + def _estimate_skybox_horizon_rgb(path: str) -> List[float]: + """Estimate horizon color from skydome texture bottom band.""" + try: + from PIL import Image, ImageStat # type: ignore + except Exception: + return [] + + try: + with Image.open(path) as im: + rgb_img = im.convert("RGB") + width, height = rgb_img.size + if width <= 0 or height <= 0: + return [] + band_h = max(1, int(height // 64)) + band = rgb_img.crop((0, height - band_h, width, height)) + stat = ImageStat.Stat(band) + mean = stat.mean[:3] if stat.mean else [0.0, 0.0, 0.0] + return [ + max(0.0, min(1.0, float(mean[0]) / 255.0)), + max(0.0, min(1.0, float(mean[1]) / 255.0)), + max(0.0, min(1.0, float(mean[2]) / 255.0)), + ] + except Exception: + return [] + + def _find_runtime_skybox_texture_source(self) -> str: + candidates: List[str] = [] + skybox_np = getattr(self.world, "skybox", None) + if skybox_np and not self._is_np_empty(skybox_np): + candidates.extend(self._extract_texture_sources_from_node(skybox_np)) + + render = getattr(self.world, "render", None) + if render: + try: + skyboxes = render.findAllMatches("**/skybox*") + except Exception: + skyboxes = None + if skyboxes: + try: + count = skyboxes.getNumPaths() + except Exception: + count = 0 + for i in range(count): + try: + np = skyboxes.getPath(i) + except Exception: + continue + if self._is_np_empty(np): + continue + candidates.extend(self._extract_texture_sources_from_node(np)) + + for source in candidates: + if source and self._is_supported_skybox_image(source): + return source + return "" + + def _extract_texture_sources_from_node(self, node) -> List[str]: + out: List[str] = [] + for _, tex_hint in self._extract_texture_stage_and_paths(node): + resolved = self._resolve_skybox_path(tex_hint) + if resolved: + out.append(resolved) + return out + + def _resolve_skybox_path(self, path_hint: str) -> str: + text = str(path_hint or "").strip() + if not text: + return "" + + if os.path.isabs(text) and os.path.exists(text): + return os.path.normpath(text) + + repo_root = str(Path(__file__).resolve().parent.parent) + roots = [ + self._project_path, + os.path.join(self._project_path, "Resources"), + os.path.join(self._project_path, "scenes", "resources"), + repo_root, + os.path.join(repo_root, "RenderPipelineFile"), + os.path.join(repo_root, "RenderPipelineFile", "data"), + os.path.join(repo_root, "RenderPipelineFile", "data", "builtin_models", "skybox"), + os.getcwd(), + ] + for root in roots: + if not root: + continue + full = os.path.normpath(os.path.join(root, text)) + if os.path.exists(full): + return full + return "" + + def _find_fallback_skybox_source(self) -> str: + repo_root = str(Path(__file__).resolve().parent.parent) + candidates = [ + os.path.join(self._project_path, "Resources", "skybox.jpg"), + os.path.join(self._project_path, "Resources", "skybox.png"), + os.path.join(self._project_path, "skybox.jpg"), + os.path.join(self._project_path, "skybox.png"), + os.path.join(repo_root, "RenderPipelineFile", "data", "builtin_models", "skybox", "skybox.jpg"), + os.path.join(repo_root, "RenderPipelineFile", "data", "builtin_models", "skybox", "skybox-2.jpg"), + ] + for path in candidates: + if path and os.path.exists(path) and self._is_supported_skybox_image(path): + return os.path.normpath(path) + return "" + + @staticmethod + def _is_supported_skybox_image(path: str) -> bool: + ext = os.path.splitext(str(path or ""))[1].lower() + return ext in {".jpg", ".jpeg", ".png", ".webp", ".avif"} + + @staticmethod + def _guess_skybox_projection(path: str) -> str: + text = str(path or "").replace("\\", "/").lower() + if "/builtin_models/skybox/skybox" in text: + # RenderPipeline default assets are skydome textures. + return "skydome" + + width = 0 + height = 0 + try: + from PIL import Image # type: ignore + with Image.open(path) as im: + width, height = im.size + except Exception: + width, height = 0, 0 + + if width > 0 and height > 0: + ratio = float(width) / float(max(1, height)) + if 3.2 <= ratio <= 4.8: + return "skydome" + if 1.8 <= ratio <= 2.2: + return "equirectangular" + return "equirectangular" + + def _extract_render_pipeline_settings(self) -> Dict[str, Any]: + rp = getattr(self.world, "render_pipeline", None) + plugin_mgr = getattr(rp, "plugin_mgr", None) if rp else None + if not plugin_mgr: + return {} + + enabled_plugins = set(getattr(plugin_mgr, "enabled_plugins", set()) or set()) + + tone_operator = str( + self._get_rp_plugin_setting(plugin_mgr, "color_correction", "tonemap_operator", "optimized") + ).strip().lower() + if not tone_operator: + tone_operator = "optimized" + + exposure_scale = self._safe_float( + self._get_rp_plugin_setting(plugin_mgr, "color_correction", "exposure_scale", 1.0), + 1.0, + ) + min_exposure = self._safe_float( + self._get_rp_plugin_setting(plugin_mgr, "color_correction", "min_exposure_value", 0.01), + 0.01, + ) + max_exposure = self._safe_float( + self._get_rp_plugin_setting(plugin_mgr, "color_correction", "max_exposure_value", 1.0), + 1.0, + ) + if max_exposure < min_exposure: + min_exposure, max_exposure = max_exposure, min_exposure + + manual_camera = bool( + self._get_rp_plugin_setting(plugin_mgr, "color_correction", "manual_camera_parameters", False) + ) + web_exposure_boost = 1.0 + if tone_operator in {"optimized", "uncharted2"}: + web_exposure_boost = 1.6 + elif tone_operator == "reinhard": + web_exposure_boost = 1.25 + elif tone_operator in {"exponential", "exponential2"}: + web_exposure_boost = 1.15 + + bloom_enabled = "bloom" in enabled_plugins + bloom_strength = self._safe_float( + self._get_rp_plugin_setting(plugin_mgr, "bloom", "bloom_strength", 0.0), + 0.0, + ) + bloom_mips = int(self._safe_float( + self._get_rp_plugin_setting(plugin_mgr, "bloom", "num_mipmaps", 6), + 6.0, + )) + bloom_lens_dirt = self._safe_float( + self._get_rp_plugin_setting(plugin_mgr, "bloom", "lens_dirt_factor", 0.0), + 0.0, + ) + + pssm_enabled = "pssm" in enabled_plugins + shadow_resolution = int(self._safe_float( + self._get_rp_plugin_setting(plugin_mgr, "pssm", "resolution", 1024), + 1024.0, + )) + shadow_resolution = max(256, min(4096, shadow_resolution)) + shadow_max_distance = self._safe_float( + self._get_rp_plugin_setting(plugin_mgr, "pssm", "max_distance", 50.0), + 50.0, + ) + shadow_use_pcf = bool( + self._get_rp_plugin_setting(plugin_mgr, "pssm", "use_pcf", True) + ) + + ao_enabled = "ao" in enabled_plugins + ao_strength = self._safe_float( + self._get_rp_plugin_setting(plugin_mgr, "ao", "occlusion_strength", 1.0), + 1.0, + ) + ao_technique = str( + self._get_rp_plugin_setting(plugin_mgr, "ao", "technique", "SSAO") + ).strip().upper() or "SSAO" + + daytime_value = self._safe_float(getattr(getattr(rp, "daytime_mgr", None), "time", 12.0), 12.0) + fog_enabled = "volumetrics" in enabled_plugins + fog_ramp = self._safe_float( + self._get_rp_day_setting(plugin_mgr, rp, "volumetrics", "fog_ramp_size", 2000.0), + 2000.0, + ) + fog_intensity = self._safe_float( + self._get_rp_day_setting(plugin_mgr, rp, "volumetrics", "fog_intensity", 0.0), + 0.0, + ) + fog_color_raw = self._get_rp_day_setting(plugin_mgr, rp, "volumetrics", "fog_color", (140.0, 150.0, 165.0)) + fog_color = self._normalize_daytime_color(fog_color_raw) + + # Viewer side optional postprocessing modules. + has_bloom_vendor = all( + os.path.exists(os.path.join(self._output_root, "vendor", name)) + for name in ("EffectComposer.js", "RenderPass.js", "UnrealBloomPass.js") + ) + if bloom_enabled and not has_bloom_vendor: + self.report["warnings"].append( + "Bloom 已启用,但未找到 EffectComposer/RenderPass/UnrealBloomPass,本次 Web 导出将退化为无 Bloom。" + ) + + return { + "source": "render_pipeline", + "daytime": daytime_value, + "enabled_plugins": sorted(str(v) for v in enabled_plugins), + "tone_mapping": { + "operator": tone_operator, + "exposure_scale": exposure_scale, + "web_exposure_boost": web_exposure_boost, + "web_use_tone_mapping": False, + "manual_camera_parameters": manual_camera, + "min_exposure": min_exposure, + "max_exposure": max_exposure, + }, + "bloom": { + "enabled": bloom_enabled and bloom_strength > 1e-5, + "strength": max(0.0, bloom_strength), + "mipmaps": max(2, bloom_mips), + "lens_dirt_factor": max(0.0, min(1.0, bloom_lens_dirt)), + "vendor_available": has_bloom_vendor, + }, + "shadows": { + "enabled": pssm_enabled, + "resolution": shadow_resolution, + "max_distance": max(1.0, shadow_max_distance), + "use_pcf": shadow_use_pcf, + "web_enable": False, + }, + "ao": { + "enabled": ao_enabled, + "technique": ao_technique, + "occlusion_strength": max(0.0, ao_strength), + }, + "fog": { + "enabled": fog_enabled and fog_intensity > 1e-6, + "color": fog_color, + "intensity": max(0.0, fog_intensity), + "ramp_size": max(1.0, fog_ramp), + }, + "aa": { + "smaa_enabled": "smaa" in enabled_plugins, + "fxaa_enabled": "fxaa" in enabled_plugins, + }, + } + + @staticmethod + def _get_rp_plugin_setting(plugin_mgr, plugin_id: str, setting_id: str, default: Any) -> Any: + try: + plugin_settings = getattr(plugin_mgr, "settings", {}).get(plugin_id) + if plugin_settings and setting_id in plugin_settings: + handle = plugin_settings[setting_id] + return getattr(handle, "value", default) + except Exception: + pass + return default + + def _get_rp_day_setting(self, plugin_mgr, rp, plugin_id: str, setting_id: str, default: Any) -> Any: + try: + day_settings = getattr(plugin_mgr, "day_settings", {}).get(plugin_id) + if not day_settings or setting_id not in day_settings: + return default + handle = day_settings[setting_id] + if not hasattr(handle, "get_scaled_value_at"): + return default + daytime = self._safe_float(getattr(getattr(rp, "daytime_mgr", None), "time", 12.0), 12.0) + return handle.get_scaled_value_at(daytime) + except Exception: + return default + + @staticmethod + def _normalize_daytime_color(value: Any) -> List[float]: + if isinstance(value, (list, tuple)) and len(value) >= 3: + out = [ + float(value[0]), + float(value[1]), + float(value[2]), + ] + else: + out = [0.55, 0.6, 0.65] + if any(v > 1.0 for v in out): + out = [v / 255.0 for v in out] + return [ + max(0.0, min(1.0, out[0])), + max(0.0, min(1.0, out[1])), + max(0.0, min(1.0, out[2])), + ] + + def _build_model_node_entry(self, node) -> Optional[Dict[str, Any]]: + node_id = self._node_id_by_pointer.get(id(node)) + if not node_id: + return None + + parent_id, mat = self._get_parent_and_matrix(node) + node_name = node.getName() or node_id + + model_source, source_tag = self._resolve_model_source(node) + if not model_source: + self.report["missing_assets"].append( + { + "node": node_name, + "reason": "model_path_not_found", + "tags_checked": ["model_path", "saved_model_path", "original_path", "asset_path", "file"], + } + ) + return None + + model_uri = self._prepare_model_asset(model_source, node_name) + if not model_uri: + self.report["unsupported_assets"].append( + { + "node": node_name, + "source": model_source, + "reason": "model_conversion_failed", + } + ) + return None + + material_override = self._extract_material_override(node) + + textures = self._collect_and_copy_texture_overrides(node, model_source) + subnode_overrides = self._collect_ssbo_subnode_transform_overrides(node) + if not subnode_overrides: + subnode_overrides = self._collect_subnode_transform_overrides(node, model_source) + + entry: Dict[str, Any] = { + "id": node_id, + "name": node_name, + "kind": "model", + "parent_id": parent_id, + "matrix_local_row_major": self._mat4_to_row_major_list(mat), + "model": {"uri": model_uri}, + "material_override": material_override, + "source_model_tag": source_tag, + } + animation = self._extract_animation_settings(node, model_source) + if animation: + entry["animation"] = animation + scripts = self._extract_script_settings(node) + if scripts: + entry["scripts"] = scripts + if textures: + entry["texture_overrides"] = textures + if subnode_overrides: + entry["subnode_overrides"] = subnode_overrides + return entry + + def _extract_animation_settings(self, node, model_source: str) -> Dict[str, Any]: + has_animation_hint = False + try: + if node.hasTag("has_animations"): + has_animation_hint = (node.getTag("has_animations").strip().lower() == "true") + except Exception: + has_animation_hint = False + + python_animation_flag = self._safe_get_python_tag(node, "animation") + if python_animation_flag is True: + has_animation_hint = True + + clip_name = ( + self._safe_get_python_tag(node, "selected_animation") + or self._safe_get_tag_value(node, "saved_selected_animation") + or self._safe_get_tag_value(node, "selected_animation") + ) + clip_name = str(clip_name).strip() if clip_name is not None else "" + + speed_value = ( + self._safe_get_python_tag(node, "anim_speed") + or self._safe_get_tag_value(node, "saved_anim_speed") + or self._safe_get_tag_value(node, "anim_speed") + ) + speed = self._safe_float(speed_value, 1.0) + if abs(speed) < 1e-4: + speed = 1.0 + + mode = ( + self._safe_get_python_tag(node, "anim_play_mode") + or self._safe_get_tag_value(node, "saved_anim_play_mode") + or self._safe_get_tag_value(node, "anim_play_mode") + or "" + ) + mode = str(mode).strip().lower() + if mode not in {"play", "loop", "pause", "stop"}: + mode = "loop" + + if not (has_animation_hint or clip_name): + return {} + + return { + "enabled": True, + "clip_name": clip_name, + "speed": speed, + "mode": mode, + "autoplay": mode != "stop", + } + + def _extract_script_settings(self, node) -> List[Dict[str, Any]]: + runtime_entries = self._extract_runtime_script_entries(node) + tag_entries = self._extract_tag_script_entries(node) + + if not runtime_entries and not tag_entries: + return [] + + merged_entries: List[Dict[str, Any]] = [] + merged_index: Dict[str, int] = {} + + for entry in runtime_entries: + name = str(entry.get("name", "")).strip() + key = self._normalize_script_name_key(name) + if not key: + continue + merged_index[key] = len(merged_entries) + merged_entries.append(dict(entry)) + + for entry in tag_entries: + name = str(entry.get("name", "")).strip() + key = self._normalize_script_name_key(name) + if not key: + continue + if key in merged_index: + runtime_entry = merged_entries[merged_index[key]] + if entry.get("file") and not runtime_entry.get("file"): + runtime_entry["file"] = entry.get("file") + continue + merged_index[key] = len(merged_entries) + merged_entries.append(dict(entry)) + + return merged_entries + + def _extract_runtime_script_entries(self, node) -> List[Dict[str, Any]]: + script_manager = getattr(self.world, "script_manager", None) + if not script_manager: + return [] + + get_scripts = getattr(script_manager, "get_scripts_on_object", None) + if not callable(get_scripts): + return [] + + try: + script_components = get_scripts(node) or [] + except Exception: + script_components = [] + + if not script_components: + return [] + + out: List[Dict[str, Any]] = [] + loader = getattr(script_manager, "loader", None) + find_script_file = getattr(loader, "find_script_file", None) if loader else None + + for component in script_components: + script_instance = getattr(component, "script_instance", None) + script_name = str(getattr(component, "script_name", "")).strip() + if not script_name and script_instance is not None: + script_name = script_instance.__class__.__name__ + script_name = str(script_name or "").strip() + if not script_name: + continue + + script_file = "" + if callable(find_script_file): + try: + script_file = str(find_script_file(script_name) or "").strip() + except Exception: + script_file = "" + + entry: Dict[str, Any] = { + "name": script_name, + "enabled": bool(getattr(component, "enabled", True)), + } + if script_file: + entry["file"] = script_file + + params = self._serialize_script_instance_params(script_instance) + if params: + entry["params"] = params + + out.append(entry) + + return out + + def _extract_tag_script_entries(self, node) -> List[Dict[str, Any]]: + has_scripts = self._safe_get_tag_value(node, "has_scripts").lower() == "true" + raw = self._safe_get_tag_value(node, "scripts_info") + if not has_scripts and not raw: + return [] + if not raw: + return [] + + try: + info_list = json.loads(raw) + except Exception: + self.report["warnings"].append( + f"scripts_info 解析失败,已忽略: {node.getName()}" + ) + return [] + + if not isinstance(info_list, list): + return [] + + out: List[Dict[str, Any]] = [] + for item in info_list: + if not isinstance(item, dict): + continue + script_name = str(item.get("name", "")).strip() + if not script_name: + continue + entry: Dict[str, Any] = { + "name": script_name, + "enabled": bool(item.get("enabled", True)), + } + script_file = str(item.get("file", "")).strip() + if script_file: + entry["file"] = script_file + params = item.get("params") + if isinstance(params, dict) and params: + entry["params"] = params + out.append(entry) + + return out + + @staticmethod + def _normalize_script_name_key(script_name: str) -> str: + normalized = str(script_name or "").strip().lower() + if normalized.endswith(".py"): + normalized = normalized[:-3] + normalized = re.sub(r"[^a-z0-9]+", "", normalized) + return normalized + + def _serialize_script_instance_params(self, script_instance) -> Dict[str, Any]: + if not script_instance: + return {} + + blocked_keys = { + "enabled", + "gameObject", + "transform", + "world", + } + out: Dict[str, Any] = {} + instance_vars = getattr(script_instance, "__dict__", {}) or {} + + for key, value in instance_vars.items(): + key_str = str(key).strip() + if not key_str: + continue + if key_str.startswith("_") or key_str in blocked_keys: + continue + serialized = self._serialize_script_value(value, depth=0) + if serialized is None and value is not None: + continue + out[key_str] = serialized + + return out + + def _serialize_script_value(self, value: Any, depth: int = 0) -> Any: + if depth > 4: + return None + if value is None: + return None + if isinstance(value, (bool, int, float, str)): + return value + + if self._is_nodepath_like(value): + ref = self._serialize_node_reference(value) + return {"__node_ref__": ref} if ref else None + + vec_value = self._serialize_vec_like(value) + if vec_value is not None: + return vec_value + + if isinstance(value, (list, tuple)): + out = [] + for item in value: + serialized = self._serialize_script_value(item, depth + 1) + if serialized is None and item is not None: + continue + out.append(serialized) + return out + + if isinstance(value, dict): + out = {} + for k, v in value.items(): + key = str(k) + if not key: + continue + serialized = self._serialize_script_value(v, depth + 1) + if serialized is None and v is not None: + continue + out[key] = serialized + return out + + return None + + @staticmethod + def _is_nodepath_like(value: Any) -> bool: + return ( + value is not None + and hasattr(value, "getName") + and hasattr(value, "getParent") + and hasattr(value, "isEmpty") + ) + + def _serialize_node_reference(self, node) -> Dict[str, Any]: + if self._is_np_empty(node): + return {} + + ref: Dict[str, Any] = {} + + node_id = self._node_id_by_pointer.get(id(node)) + if node_id: + ref["node_id"] = node_id + try: + node_name = str(node.getName() or "").strip() + except Exception: + node_name = "" + if node_name: + ref["node_name"] = node_name + + if node_id: + return ref + + # Fallback: track ancestor exported node + child name chain. + chain: List[str] = [] + current = node + for _ in range(64): + if self._is_np_empty(current): + break + current_id = self._node_id_by_pointer.get(id(current)) + if current_id: + ref["ancestor_node_id"] = current_id + chain.reverse() + if chain: + ref["child_name_chain"] = chain + break + try: + current_name = str(current.getName() or "").strip() + except Exception: + current_name = "" + if current_name: + chain.append(current_name) + try: + current = current.getParent() + except Exception: + break + + return ref + + @staticmethod + def _serialize_vec_like(value: Any) -> Optional[List[float]]: + get_components = [] + for attr in ("getX", "getY", "getZ", "getW"): + getter = getattr(value, attr, None) + if callable(getter): + get_components.append(getter) + if not get_components: + return None + + out: List[float] = [] + for getter in get_components: + try: + out.append(float(getter())) + except Exception: + return None + return out + + def _collect_ssbo_subnode_transform_overrides(self, model_node) -> Dict[str, Any]: + """ + Collect changed subnode transforms from SSBO controller runtime state. + This path is required because SSBO edits may not exist in model_node's + ordinary child hierarchy. + """ + ssbo_editor = getattr(self.world, "ssbo_editor", None) + controller = getattr(ssbo_editor, "controller", None) if ssbo_editor else None + controller_model = getattr(controller, "model", None) if controller else None + + if not controller or self._is_np_empty(controller_model) or self._is_np_empty(model_node): + return {} + + if not self._nodepath_equivalent(controller_model, model_node): + model_source_key = self._nodepath_source_key(model_node) + controller_source_key = self._nodepath_source_key(controller_model) + same_source = bool(model_source_key and controller_source_key and model_source_key == controller_source_key) + same_name = False + try: + same_name = (controller_model.getName() == model_node.getName()) + except Exception: + same_name = False + if not same_source and not same_name: + return {} + + id_to_object_np = getattr(controller, "id_to_object_np", {}) + id_to_name = getattr(controller, "id_to_name", {}) + global_transforms = getattr(controller, "global_transforms", []) + tree_root_key = str(getattr(controller, "tree_root_key", "0") or "0") + + if not isinstance(id_to_object_np, dict) or not isinstance(id_to_name, dict): + return {} + + by_index_map: Dict[Tuple[int, ...], List[float]] = {} + by_index_base_map: Dict[Tuple[int, ...], List[float]] = {} + by_name_map: Dict[Tuple[str, ...], List[float]] = {} + by_name_base_map: Dict[Tuple[str, ...], List[float]] = {} + + for gid, obj_np in id_to_object_np.items(): + if self._is_np_empty(obj_np): + continue + + key = id_to_name.get(gid) + if key is None: + continue + key = str(key) + if not key: + continue + + index_path = self._parse_ssbo_tree_key_to_index_path(key, tree_root_key) + if not index_path: + continue + + try: + cur_mat = obj_np.getMat(controller_model) + except Exception: + try: + cur_mat = obj_np.get_mat(controller_model) + except Exception: + continue + + base_mat = None + try: + if int(gid) < len(global_transforms): + base_mat = global_transforms[int(gid)] + except Exception: + base_mat = None + + if base_mat is not None and self._mat4_objects_close(cur_mat, base_mat): + continue + + mat_values = self._mat4_to_row_major_list(cur_mat) + if not mat_values or self._is_identity_matrix_list(mat_values): + continue + + base_values = None + if base_mat is not None: + try: + base_values = self._mat4_to_row_major_list(base_mat) + except Exception: + base_values = None + if base_values and len(base_values) != 16: + base_values = None + + existing = by_index_map.get(index_path) + if existing and not self._matrix_list_close(existing, mat_values): + # Multiple geoms mapped to same tree node with different mats: + # keep first one and warn once. + self.report["warnings"].append( + f"SSBO 子节点矩阵冲突,保留首个值: {model_node.getName()} path={list(index_path)}" + ) + continue + if not existing: + by_index_map[index_path] = mat_values + if base_values: + by_index_base_map[index_path] = base_values + + name_path = self._ssbo_index_path_to_name_path(controller, index_path, tree_root_key) + if name_path: + raw_name_path = tuple(str(v).strip() for v in name_path if str(v).strip()) + normalized_name_path = self._normalize_name_path(name_path) + export_paths = [] + if raw_name_path: + export_paths.append(raw_name_path) + if normalized_name_path and normalized_name_path != raw_name_path: + export_paths.append(normalized_name_path) + + for p in export_paths: + by_name_map[p] = mat_values + if base_values and (p not in by_name_base_map): + by_name_base_map[p] = base_values + + if not by_index_map: + return {} + + by_index = [] + for path, mat in sorted(by_index_map.items(), key=lambda x: (len(x[0]), x[0])): + item = { + "path": [int(v) for v in path], + "matrix_local_row_major": mat, + } + base_values = by_index_base_map.get(path) + if base_values: + item["base_matrix_model_root_row_major"] = base_values + by_index.append(item) + + by_name = [] + for path, mat in sorted(by_name_map.items(), key=lambda x: (len(x[0]), x[0])): + item = { + "path": [str(v) for v in path], + "matrix_local_row_major": mat, + } + base_values = by_name_base_map.get(path) + if base_values: + item["base_matrix_model_root_row_major"] = base_values + by_name.append(item) + + payload = { + "source": "ssbo", + # SSBO runtime stores matrices in model-root space + # (obj_np.getMat(controller.model)). + "matrix_space": "model_root", + "by_index": by_index, + } + if by_name: + payload["by_name"] = by_name + self.report["warnings"].append( + f"SSBO子节点变换已导出: {model_node.getName()} ({len(by_index)} items)" + ) + return payload + + @staticmethod + def _is_np_empty(np) -> bool: + if not np: + return True + try: + return np.isEmpty() + except Exception: + try: + return np.is_empty() + except Exception: + return True + + @staticmethod + def _nodepath_equivalent(a, b) -> bool: + try: + if a == b: + return True + except Exception: + pass + try: + return a.node() == b.node() + except Exception: + return False + + @staticmethod + def _nodepath_source_key(np) -> str: + if not np: + return "" + for tag_name in ("saved_model_path", "model_path", "original_path", "file"): + try: + if np.hasTag(tag_name): + value = (np.getTag(tag_name) or "").strip() + if value: + return value.replace("\\", "/").lower() + except Exception: + continue + return "" + + @staticmethod + def _parse_ssbo_tree_key_to_index_path(key: str, tree_root_key: str) -> Tuple[int, ...]: + text = str(key).strip() + if not text: + return tuple() + parts = [p for p in text.split("/") if p != ""] + if not parts: + return tuple() + # keys are like "0/1/2"; drop tree root segment. + if parts and parts[0] == str(tree_root_key): + parts = parts[1:] + out: List[int] = [] + for p in parts: + try: + out.append(int(p)) + except Exception: + return tuple() + return tuple(out) + + def _ssbo_index_path_to_name_path(self, controller, index_path: Tuple[int, ...], tree_root_key: str) -> Tuple[str, ...]: + tree_nodes = getattr(controller, "tree_nodes", {}) or {} + if str(tree_root_key) not in tree_nodes: + return tuple() + + parent_key = str(tree_root_key) + out: List[str] = [] + for child_slot in index_path: + parent = tree_nodes.get(parent_key) + if not isinstance(parent, dict): + return tuple() + children = parent.get("children", []) + if not isinstance(children, list): + return tuple() + if child_slot < 0 or child_slot >= len(children): + return tuple() + + child_key = children[child_slot] + child = tree_nodes.get(child_key, {}) + child_name = str(child.get("name", "")) + + # Build occurrence index among same-name siblings. + occur = 0 + for i in range(child_slot): + prev_key = children[i] + prev_name = str(tree_nodes.get(prev_key, {}).get("name", "")) + if prev_name == child_name: + occur += 1 + out.append(f"{child_name}#{occur}") + parent_key = child_key + + return tuple(out) + + @staticmethod + def _normalize_name_path(path: Tuple[str, ...]) -> Tuple[str, ...]: + """Drop empty segments only; keep duplicate segments to preserve depth.""" + normalized: List[str] = [] + for seg in path: + s = str(seg).strip() + if not s: + continue + normalized.append(s) + return tuple(normalized) + + def _collect_subnode_transform_overrides(self, root_node, model_source: str) -> Dict[str, Any]: + """ + Collect changed local transforms for descendants under a model root. + Only export deltas against a baseline loaded from model_source to avoid + replaying all intrinsic node transforms (which can cause double transforms). + """ + try: + if not root_node or root_node.isEmpty(): + return {} + except Exception: + return {} + + current_snapshot = self._snapshot_subnode_local_matrices(root_node) + if not current_snapshot["by_index"]: + return {} + + baseline_snapshot = self._load_baseline_subnode_snapshot(model_source) + if not baseline_snapshot["by_index"] and not baseline_snapshot["by_name"]: + # No reliable baseline -> skip overrides to avoid corrupt scale/rotation. + self.report["warnings"].append( + f"跳过子节点变换导出(缺少基线): {root_node.getName() if hasattr(root_node, 'getName') else 'unknown'}" + ) + return {} + + changed_by_index: List[Dict[str, Any]] = [] + changed_by_name: List[Dict[str, Any]] = [] + + for index_path, payload in current_snapshot["by_index"].items(): + current_mat = payload["matrix_local_row_major"] + name_path = payload["name_path"] + + baseline_payload = baseline_snapshot["by_index"].get(index_path) + if baseline_payload is None: + baseline_payload = baseline_snapshot["by_name"].get(name_path) + + baseline_mat = baseline_payload["matrix_local_row_major"] if baseline_payload else None + if baseline_mat is not None and self._matrix_list_close(current_mat, baseline_mat): + continue + # Safety: identity overrides often indicate hierarchy mismatch and can + # destroy source glTF built-in transforms when replayed in web runtime. + if self._is_identity_matrix_list(current_mat): + continue + + changed_by_index.append( + { + "path": [int(v) for v in index_path], + "matrix_local_row_major": current_mat, + } + ) + changed_by_name.append( + { + "path": [str(v) for v in name_path], + "matrix_local_row_major": current_mat, + } + ) + + if not changed_by_index and not changed_by_name: + return {} + + return { + "matrix_space": "local_parent", + "by_index": changed_by_index, + "by_name": changed_by_name, + } + + def _snapshot_subnode_local_matrices(self, root_node) -> Dict[str, Dict[Tuple[Any, ...], Dict[str, Any]]]: + snapshot: Dict[str, Dict[Tuple[Any, ...], Dict[str, Any]]] = { + "by_index": {}, + "by_name": {}, + } + + try: + if not root_node or root_node.isEmpty(): + return snapshot + except Exception: + return snapshot + + def walk(node, index_path: Tuple[int, ...], name_path: Tuple[str, ...]) -> None: + if index_path: + parent = None + try: + parent = node.getParent() + except Exception: + parent = None + + if parent and not parent.isEmpty(): + local_mat = node.getMat(parent) + else: + local_mat = node.getMat() + + matrix_row_major = self._mat4_to_row_major_list(local_mat) + payload = { + "index_path": index_path, + "name_path": name_path, + "matrix_local_row_major": matrix_row_major, + } + snapshot["by_index"][index_path] = payload + snapshot["by_name"][name_path] = payload + + name_count: Dict[str, int] = {} + for child_index, child in enumerate(node.getChildren()): + child_name = "" + try: + child_name = child.getName() or "" + except Exception: + child_name = "" + occur = name_count.get(child_name, 0) + name_count[child_name] = occur + 1 + walk( + child, + index_path + (child_index,), + name_path + (f"{child_name}#{occur}",), + ) + + try: + walk(root_node, tuple(), tuple()) + except Exception as exc: + self.report["warnings"].append( + f"采集子节点矩阵失败: {root_node.getName() if hasattr(root_node, 'getName') else 'unknown'} ({exc})" + ) + + return snapshot + + def _load_baseline_subnode_snapshot(self, model_source: str) -> Dict[str, Dict[Tuple[Any, ...], Dict[str, Any]]]: + key = os.path.normpath(model_source or "") + if not key: + return {"by_index": {}, "by_name": {}} + if key in self._baseline_subnode_cache: + return self._baseline_subnode_cache[key] + + snapshot: Dict[str, Dict[Tuple[Any, ...], Dict[str, Any]]] = {"by_index": {}, "by_name": {}} + loader = getattr(self.world, "loader", None) + if loader is None: + self._baseline_subnode_cache[key] = snapshot + return snapshot + + baseline_root = None + try: + baseline_root = loader.loadModel(key) + if baseline_root and not baseline_root.isEmpty(): + snapshot = self._snapshot_subnode_local_matrices(baseline_root) + except Exception as exc: + self.report["warnings"].append(f"加载基线模型失败: {key} ({exc})") + snapshot = {"by_index": {}, "by_name": {}} + finally: + try: + if baseline_root and not baseline_root.isEmpty(): + baseline_root.removeNode() + except Exception: + pass + + self._baseline_subnode_cache[key] = snapshot + return snapshot + + @staticmethod + def _matrix_list_close(a: List[float], b: List[float], eps: float = 1e-5) -> bool: + if (not isinstance(a, list)) or (not isinstance(b, list)) or len(a) != 16 or len(b) != 16: + return False + for i in range(16): + try: + if abs(float(a[i]) - float(b[i])) > eps: + return False + except Exception: + return False + return True + + @staticmethod + def _mat4_objects_close(a, b, eps: float = 1e-5) -> bool: + if a is None or b is None: + return False + try: + for r in range(4): + for c in range(4): + if abs(float(a.getCell(r, c)) - float(b.getCell(r, c))) > eps: + return False + return True + except Exception: + return False + + @staticmethod + def _is_identity_matrix_list(values: List[float], eps: float = 1e-6) -> bool: + if not isinstance(values, list) or len(values) != 16: + return False + identity = [ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0, + ] + for i in range(16): + try: + if abs(float(values[i]) - identity[i]) > eps: + return False + except Exception: + return False + return True + + def _build_light_node_entry(self, node, kind: str) -> Optional[Dict[str, Any]]: + node_id = self._node_id_by_pointer.get(id(node)) + if not node_id: + return None + + parent_id, mat = self._get_parent_and_matrix(node) + node_name = node.getName() or node_id + + light_obj = node.getPythonTag("rp_light_object") if node.hasPythonTag("rp_light_object") else None + + color = [1.0, 1.0, 1.0] + energy = self._safe_float(node.getTag("light_energy"), 5000.0) if node.hasTag("light_energy") else 5000.0 + radius = self._safe_float(node.getTag("light_radius"), 30.0) if node.hasTag("light_radius") else 30.0 + spot_fov = self._safe_float(node.getTag("light_fov"), 45.0) if node.hasTag("light_fov") else 45.0 + inner_ratio = 0.4 + + if light_obj is not None: + try: + c = getattr(light_obj, "color", None) + if c is not None: + color = [float(c[0]), float(c[1]), float(c[2])] + except Exception: + pass + try: + energy = float(getattr(light_obj, "energy", energy)) + except Exception: + pass + try: + radius = float(getattr(light_obj, "radius", radius)) + except Exception: + pass + try: + if hasattr(light_obj, "fov"): + spot_fov = float(getattr(light_obj, "fov", spot_fov)) + except Exception: + pass + try: + if hasattr(light_obj, "inner_radius"): + inner_ratio = float(getattr(light_obj, "inner_radius", inner_ratio)) + except Exception: + pass + + intensity = max(0.0, energy * self.ENERGY_TO_INTENSITY_SCALE) + + return { + "id": node_id, + "name": node_name, + "kind": kind, + "parent_id": parent_id, + "matrix_local_row_major": self._mat4_to_row_major_list(mat), + "light": { + "color": color, + "intensity": intensity, + "range": max(0.0, radius), + "spot_angle_deg": max(1.0, spot_fov), + "inner_cone_ratio": max(0.0, min(1.0, inner_ratio)), + }, + } + + def _build_ground_node_entry(self, node) -> Optional[Dict[str, Any]]: + node_id = self._node_id_by_pointer.get(id(node)) + if not node_id: + return None + + parent_id, mat = self._get_parent_and_matrix(node) + node_name = node.getName() or "ground" + + color = [0.8, 0.8, 0.8, 1.0] + roughness = 1.0 + metallic = 0.0 + + material = None + try: + material = node.getMaterial() + except Exception: + material = None + + if material: + try: + if material.hasBaseColor(): + c = material.getBaseColor() + color = [float(c[0]), float(c[1]), float(c[2]), float(c[3])] + except Exception: + pass + try: + roughness = float(material.getRoughness()) + except Exception: + pass + try: + metallic = float(material.getMetallic()) + except Exception: + pass + + return { + "id": node_id, + "name": node_name, + "kind": "ground", + "parent_id": parent_id, + "matrix_local_row_major": self._mat4_to_row_major_list(mat), + "ground": { + "width": 100.0, + "height": 100.0, + }, + "material_override": { + "base_color": [color[0], color[1], color[2], 1.0], + "roughness": roughness, + "metallic": metallic, + "opacity": 1.0, + }, + } + + def _resolve_model_source(self, node) -> Tuple[Optional[str], str]: + tags = ["model_path", "saved_model_path", "original_path", "asset_path", "file"] + for tag in tags: + if not node.hasTag(tag): + continue + value = (node.getTag(tag) or "").strip() + if not value: + continue + resolved = self._resolve_asset_path(value) + if resolved: + return resolved, tag + return None, "" + + def _resolve_asset_path(self, candidate: str) -> Optional[str]: + candidate = (candidate or "").strip() + if not candidate: + return None + + if candidate.startswith(("http://", "https://")): + return None + + # Absolute path: use directly. + if os.path.isabs(candidate): + abs_candidate = os.path.normpath(candidate) + if os.path.exists(abs_candidate): + return abs_candidate + return None + + # Relative path resolution policy. + search_roots = [ + self._project_path, + os.path.join(self._project_path, "models"), + os.path.join(self._project_path, "Resources"), + os.path.join(self._project_path, "scenes", "resources"), + os.getcwd(), + ] + + for root in search_roots: + full = os.path.normpath(os.path.join(root, candidate)) + if os.path.exists(full): + return full + + return None + + def _prepare_model_asset(self, source_path: str, node_name: str) -> Optional[str]: + source_path = os.path.normpath(source_path) + ext = os.path.splitext(source_path)[1].lower() + + if ext in {".gltf", ".glb"}: + return self._copy_asset_to_models(source_path) + + with tempfile.TemporaryDirectory(prefix="eg_webgl_conv_") as temp_dir: + conversion_source = source_path + + if ext == ".bam": + temp_egg = os.path.join(temp_dir, self._sanitize_filename(Path(source_path).stem) + ".egg") + if not self._run_tool_command(["bam2egg", source_path, temp_egg], timeout=120): + return None + + temp_obj = os.path.join(temp_dir, self._sanitize_filename(Path(source_path).stem) + ".obj") + if not self._run_tool_command(["egg2obj", temp_egg, temp_obj], timeout=120): + return None + conversion_source = temp_obj + + elif ext == ".egg": + temp_obj = os.path.join(temp_dir, self._sanitize_filename(Path(source_path).stem) + ".obj") + if not self._run_tool_command(["egg2obj", source_path, temp_obj], timeout=120): + return None + conversion_source = temp_obj + + target_filename = self._unique_filename(node_name or Path(source_path).stem, ".glb") + target_abs = os.path.join(self._assets_model_dir, target_filename) + + converter = self._convert_to_glb(conversion_source, target_abs) + if not converter: + return None + + self.report["converted_assets"].append( + { + "source": source_path, + "converted_from": conversion_source, + "target": os.path.relpath(target_abs, self._output_root).replace("\\", "/"), + "converter": converter, + } + ) + return os.path.relpath(target_abs, self._output_root).replace("\\", "/") + + def _convert_to_glb(self, source_path: str, target_path: str) -> str: + scene_manager = self.scene_manager + if scene_manager is None: + return "" + + conversion_order = [ + "_convertWithBlender", + "_convertWithFBX2glTF", + "_convertWithAssimp", + ] + + for method_name in conversion_order: + method = getattr(scene_manager, method_name, None) + if not callable(method): + continue + try: + ok = method(source_path, target_path, None) + except TypeError: + ok = method(source_path, target_path) + except Exception: + ok = False + + if ok and os.path.exists(target_path): + return method_name + + return "" + + def _run_tool_command(self, args: List[str], timeout: int) -> bool: + executable = shutil.which(args[0]) + if not executable: + self.report["warnings"].append(f"缺少转换工具: {args[0]}") + return False + + try: + result = subprocess.run( + args, + check=False, + capture_output=True, + text=True, + timeout=timeout, + ) + except Exception as exc: + self.report["warnings"].append(f"执行命令失败: {' '.join(args)} ({exc})") + return False + + if result.returncode != 0: + stderr = (result.stderr or "").strip() + stdout = (result.stdout or "").strip() + detail = stderr or stdout or f"exit={result.returncode}" + self.report["warnings"].append(f"命令失败: {' '.join(args)} -> {detail}") + return False + return True + + def _copy_asset_to_models(self, source_path: str) -> str: + source_path = os.path.normpath(source_path) + if source_path in self._copied_source_to_uri: + return self._copied_source_to_uri[source_path] + + ext = os.path.splitext(source_path)[1].lower() or ".bin" + safe_name = self._unique_filename(Path(source_path).stem, ext) + target_abs = os.path.join(self._assets_model_dir, safe_name) + shutil.copy2(source_path, target_abs) + + rel_uri = os.path.relpath(target_abs, self._output_root).replace("\\", "/") + self._copied_source_to_uri[source_path] = rel_uri + self.report["copied_assets"].append( + { + "source": source_path, + "target": rel_uri, + "type": "model", + } + ) + return rel_uri + + def _collect_and_copy_texture_overrides(self, node, model_source: str) -> List[Dict[str, Any]]: + textures: List[Dict[str, Any]] = [] + texture_pairs = self._extract_texture_stage_and_paths(node) + if not texture_pairs: + return textures + + model_dir = os.path.dirname(model_source) + for stage_name, tex_path in texture_pairs: + abs_path = self._resolve_texture_path(tex_path, model_dir) + if not abs_path: + continue + rel_uri = self._copy_asset_to_textures(abs_path) + if rel_uri: + textures.append({"stage": stage_name, "uri": rel_uri}) + + return textures + + def _extract_texture_stage_and_paths(self, node) -> List[Tuple[str, str]]: + pairs: List[Tuple[str, str]] = [] + seen: set = set() + + nodes_to_scan = [node] + try: + geom_paths = node.findAllMatches("**/+GeomNode") + for i in range(geom_paths.getNumPaths()): + nodes_to_scan.append(geom_paths.getPath(i)) + except Exception: + pass + + for np in nodes_to_scan: + try: + stages = np.findAllTextureStages() + except Exception: + continue + + try: + stage_count = stages.getNumTextureStages() + except Exception: + stage_count = 0 + + for idx in range(stage_count): + try: + stage = stages.getTextureStage(idx) + texture = np.getTexture(stage) + except Exception: + continue + if not texture: + continue + + tex_path = "" + try: + if texture.hasFullpath(): + tex_path = texture.getFullpath().toOsSpecific() + except Exception: + tex_path = "" + + if not tex_path: + continue + + stage_name = stage.getName() if stage else f"stage_{idx}" + key = (stage_name, tex_path) + if key in seen: + continue + seen.add(key) + pairs.append(key) + + return pairs + + def _resolve_texture_path(self, path_hint: str, model_dir: str) -> Optional[str]: + path_hint = (path_hint or "").strip() + if not path_hint: + return None + + if os.path.isabs(path_hint) and os.path.exists(path_hint): + return os.path.normpath(path_hint) + + search_roots = [ + model_dir, + self._project_path, + os.path.join(self._project_path, "scenes", "resources"), + os.getcwd(), + ] + for root in search_roots: + full = os.path.normpath(os.path.join(root, path_hint)) + if os.path.exists(full): + return full + + return None + + def _copy_asset_to_textures(self, source_path: str) -> Optional[str]: + source_path = os.path.normpath(source_path) + cache_key = f"texture::{source_path}" + if cache_key in self._copied_source_to_uri: + return self._copied_source_to_uri[cache_key] + + ext = os.path.splitext(source_path)[1].lower() or ".png" + safe_name = self._unique_filename(Path(source_path).stem, ext) + target_abs = os.path.join(self._assets_texture_dir, safe_name) + try: + shutil.copy2(source_path, target_abs) + except Exception: + return None + + rel_uri = os.path.relpath(target_abs, self._output_root).replace("\\", "/") + self._copied_source_to_uri[cache_key] = rel_uri + self.report["copied_assets"].append( + { + "source": source_path, + "target": rel_uri, + "type": "texture", + } + ) + return rel_uri + + def _extract_material_override(self, node) -> Dict[str, Any]: + base_color = [1.0, 1.0, 1.0, 1.0] + roughness = 0.5 + metallic = 0.0 + + material = None + try: + material = node.getMaterial() + except Exception: + material = None + + if material: + try: + if material.hasBaseColor(): + c = material.getBaseColor() + base_color = [float(c[0]), float(c[1]), float(c[2]), float(c[3])] + except Exception: + pass + try: + roughness = float(material.getRoughness()) + except Exception: + pass + try: + metallic = float(material.getMetallic()) + except Exception: + pass + else: + try: + c = node.getColor() + base_color = [float(c[0]), float(c[1]), float(c[2]), float(c[3])] + except Exception: + pass + + opacity = max(0.0, min(1.0, float(base_color[3]))) + if opacity >= 0.999: + opacity = 1.0 + elif opacity <= 0.001: + opacity = 0.0 + + return { + "base_color": base_color, + "roughness": roughness, + "metallic": metallic, + "opacity": opacity, + } + + def _get_parent_and_matrix(self, node) -> Tuple[Optional[str], Any]: + render = getattr(self.world, "render", None) + parent_id = None + + try: + parent = node.getParent() + except Exception: + parent = None + + if parent and not parent.isEmpty() and id(parent) in self._node_id_by_pointer: + parent_id = self._node_id_by_pointer[id(parent)] + try: + return parent_id, node.getMat(parent) + except Exception: + pass + + if render: + try: + return None, node.getMat(render) + except Exception: + pass + + try: + return None, node.getMat() + except Exception: + return None, node.getTransform().getMat() + + @staticmethod + def _mat4_to_row_major_list(mat4_obj) -> List[float]: + try: + values = [] + for r in range(4): + for c in range(4): + values.append(float(mat4_obj.getCell(r, c))) + return values + except Exception: + return [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + ] + + @staticmethod + def _safe_float(value: Any, default: float) -> float: + try: + return float(value) + except Exception: + return float(default) + + @staticmethod + def _safe_get_python_tag(node, tag_name: str) -> Any: + try: + if hasattr(node, "hasPythonTag") and node.hasPythonTag(tag_name): + return node.getPythonTag(tag_name) + except Exception: + pass + try: + value = node.getPythonTag(tag_name) + return value + except Exception: + return None + + @staticmethod + def _safe_get_tag_value(node, tag_name: str) -> str: + try: + if node.hasTag(tag_name): + return (node.getTag(tag_name) or "").strip() + except Exception: + return "" + return "" + + def _unique_filename(self, stem: str, suffix: str) -> str: + safe_stem = self._sanitize_filename(stem) or "asset" + key = f"{safe_stem}{suffix.lower()}" + index = self._name_counter.get(key, 0) + self._name_counter[key] = index + 1 + if index == 0: + return f"{safe_stem}{suffix}" + return f"{safe_stem}_{index:03d}{suffix}" + + @staticmethod + def _sanitize_filename(name: str) -> str: + normalized = re.sub(r"[^A-Za-z0-9._-]+", "_", str(name or "")).strip("._") + return normalized or "asset" + + def _write_preview_scripts(self) -> None: + sh_path = os.path.join(self._output_root, "run_preview.sh") + bat_path = os.path.join(self._output_root, "run_preview.bat") + + sh_content = "#!/usr/bin/env bash\npython3 -m http.server 8000\n" + bat_content = "@echo off\npython -m http.server 8000\n" + + with open(sh_path, "w", encoding="utf-8") as f: + f.write(sh_content) + with open(bat_path, "w", encoding="utf-8") as f: + f.write(bat_content) + + current_mode = os.stat(sh_path).st_mode + os.chmod(sh_path, current_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + + @staticmethod + def _write_json(path: str, payload: Dict[str, Any]) -> None: + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, "w", encoding="utf-8") as f: + json.dump(payload, f, ensure_ascii=False, indent=2) + + def _fail(self, message: str) -> None: + self.report["status"] = "failed" + self.report["warnings"].append(message) diff --git a/templates/webgl/index.html b/templates/webgl/index.html new file mode 100644 index 00000000..de20292c --- /dev/null +++ b/templates/webgl/index.html @@ -0,0 +1,16 @@ + + + + + + EG WebGL Scene + + + +
+ +
Loading scene...
+
+ + + diff --git a/templates/webgl/style.css b/templates/webgl/style.css new file mode 100644 index 00000000..85e3d98a --- /dev/null +++ b/templates/webgl/style.css @@ -0,0 +1,61 @@ +:root { + --bg: #0f1115; + --panel: rgba(16, 20, 28, 0.88); + --text: #d6dde9; + --ok: #77d0b9; + --warn: #e2b272; + --err: #f27878; +} + +* { + box-sizing: border-box; +} + +html, +body, +#app { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; + background: radial-gradient(circle at 20% 20%, #1b2230 0%, var(--bg) 50%, #090b10 100%); + color: var(--text); + font-family: "Segoe UI", "SF Pro Text", "PingFang SC", sans-serif; +} + +#scene-canvas { + width: 100%; + height: 100%; + display: block; +} + +.status { + position: fixed; + left: 16px; + bottom: 16px; + max-width: min(640px, calc(100vw - 32px)); + padding: 10px 12px; + background: var(--panel); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 8px; + backdrop-filter: blur(4px); + line-height: 1.45; + white-space: pre-wrap; + font-size: 13px; +} + +.status.ok { + border-color: rgba(119, 208, 185, 0.5); + color: var(--ok); +} + +.status.warn { + border-color: rgba(226, 178, 114, 0.55); + color: var(--warn); +} + +.status.error { + border-color: rgba(242, 120, 120, 0.65); + color: var(--err); +} diff --git a/templates/webgl/vendor/GLTFLoader.js b/templates/webgl/vendor/GLTFLoader.js new file mode 100644 index 00000000..128c7e86 --- /dev/null +++ b/templates/webgl/vendor/GLTFLoader.js @@ -0,0 +1,3923 @@ +import { + AnimationClip, + Bone, + Box3, + BufferAttribute, + BufferGeometry, + CanvasTexture, + ClampToEdgeWrapping, + Color, + DirectionalLight, + DoubleSide, + FileLoader, + FrontSide, + Group, + ImageBitmapLoader, + InterleavedBuffer, + InterleavedBufferAttribute, + Interpolant, + InterpolateDiscrete, + InterpolateLinear, + Line, + LineBasicMaterial, + LineLoop, + LineSegments, + LinearFilter, + LinearMipmapLinearFilter, + LinearMipmapNearestFilter, + Loader, + LoaderUtils, + Material, + MathUtils, + Matrix4, + Mesh, + MeshBasicMaterial, + MeshPhysicalMaterial, + MeshStandardMaterial, + MirroredRepeatWrapping, + NearestFilter, + NearestMipmapLinearFilter, + NearestMipmapNearestFilter, + NumberKeyframeTrack, + Object3D, + OrthographicCamera, + PerspectiveCamera, + PointLight, + Points, + PointsMaterial, + PropertyBinding, + QuaternionKeyframeTrack, + RGBFormat, + RepeatWrapping, + Skeleton, + SkinnedMesh, + Sphere, + SpotLight, + TangentSpaceNormalMap, + TextureLoader, + TriangleFanDrawMode, + TriangleStripDrawMode, + Vector2, + Vector3, + VectorKeyframeTrack, + sRGBEncoding +} from './three.module.min.js'; + +var GLTFLoader = ( function () { + + function GLTFLoader( manager ) { + + Loader.call( this, manager ); + + this.dracoLoader = null; + this.ktx2Loader = null; + this.meshoptDecoder = null; + + this.pluginCallbacks = []; + + this.register( function ( parser ) { + + return new GLTFMaterialsClearcoatExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFTextureBasisUExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFTextureWebPExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMaterialsTransmissionExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFLightsExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMeshoptCompression( parser ); + + } ); + + } + + GLTFLoader.prototype = Object.assign( Object.create( Loader.prototype ), { + + constructor: GLTFLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var resourcePath; + + if ( this.resourcePath !== '' ) { + + resourcePath = this.resourcePath; + + } else if ( this.path !== '' ) { + + resourcePath = this.path; + + } else { + + resourcePath = LoaderUtils.extractUrlBase( url ); + + } + + // Tells the LoadingManager to track an extra item, which resolves after + // the model is fully loaded. This means the count of items loaded will + // be incorrect, but ensures manager.onLoad() does not fire early. + this.manager.itemStart( url ); + + var _onError = function ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); + + }; + + var loader = new FileLoader( this.manager ); + + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + + loader.load( url, function ( data ) { + + try { + + scope.parse( data, resourcePath, function ( gltf ) { + + onLoad( gltf ); + + scope.manager.itemEnd( url ); + + }, _onError ); + + } catch ( e ) { + + _onError( e ); + + } + + }, onProgress, _onError ); + + }, + + setDRACOLoader: function ( dracoLoader ) { + + this.dracoLoader = dracoLoader; + return this; + + }, + + setDDSLoader: function () { + + throw new Error( + + 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' + + ); + + }, + + setKTX2Loader: function ( ktx2Loader ) { + + this.ktx2Loader = ktx2Loader; + return this; + + }, + + setMeshoptDecoder: function ( meshoptDecoder ) { + + this.meshoptDecoder = meshoptDecoder; + return this; + + }, + + register: function ( callback ) { + + if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { + + this.pluginCallbacks.push( callback ); + + } + + return this; + + }, + + unregister: function ( callback ) { + + if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { + + this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); + + } + + return this; + + }, + + parse: function ( data, path, onLoad, onError ) { + + var content; + var extensions = {}; + var plugins = {}; + + if ( typeof data === 'string' ) { + + content = data; + + } else { + + var magic = LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) ); + + if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { + + try { + + extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); + + } catch ( error ) { + + if ( onError ) onError( error ); + return; + + } + + content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; + + } else { + + content = LoaderUtils.decodeText( new Uint8Array( data ) ); + + } + + } + + var json = JSON.parse( content ); + + if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { + + if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) ); + return; + + } + + var parser = new GLTFParser( json, { + + path: path || this.resourcePath || '', + crossOrigin: this.crossOrigin, + requestHeader: this.requestHeader, + manager: this.manager, + ktx2Loader: this.ktx2Loader, + meshoptDecoder: this.meshoptDecoder + + } ); + + parser.fileLoader.setRequestHeader( this.requestHeader ); + + for ( var i = 0; i < this.pluginCallbacks.length; i ++ ) { + + var plugin = this.pluginCallbacks[ i ]( parser ); + plugins[ plugin.name ] = plugin; + + // Workaround to avoid determining as unknown extension + // in addUnknownExtensionsToUserData(). + // Remove this workaround if we move all the existing + // extension handlers to plugin system + extensions[ plugin.name ] = true; + + } + + if ( json.extensionsUsed ) { + + for ( var i = 0; i < json.extensionsUsed.length; ++ i ) { + + var extensionName = json.extensionsUsed[ i ]; + var extensionsRequired = json.extensionsRequired || []; + + switch ( extensionName ) { + + case EXTENSIONS.KHR_MATERIALS_UNLIT: + extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); + break; + + case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: + extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); + break; + + case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: + extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); + break; + + case EXTENSIONS.KHR_TEXTURE_TRANSFORM: + extensions[ extensionName ] = new GLTFTextureTransformExtension(); + break; + + case EXTENSIONS.KHR_MESH_QUANTIZATION: + extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); + break; + + default: + + if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) { + + console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); + + } + + } + + } + + } + + parser.setExtensions( extensions ); + parser.setPlugins( plugins ); + parser.parse( onLoad, onError ); + + } + + } ); + + /* GLTFREGISTRY */ + + function GLTFRegistry() { + + var objects = {}; + + return { + + get: function ( key ) { + + return objects[ key ]; + + }, + + add: function ( key, object ) { + + objects[ key ] = object; + + }, + + remove: function ( key ) { + + delete objects[ key ]; + + }, + + removeAll: function () { + + objects = {}; + + } + + }; + + } + + /*********************************/ + /********** EXTENSIONS ***********/ + /*********************************/ + + var EXTENSIONS = { + KHR_BINARY_GLTF: 'KHR_binary_glTF', + KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', + KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', + KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat', + KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness', + KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission', + KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', + KHR_TEXTURE_BASISU: 'KHR_texture_basisu', + KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform', + KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization', + EXT_TEXTURE_WEBP: 'EXT_texture_webp', + EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression' + }; + + /** + * Punctual Lights Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + */ + function GLTFLightsExtension( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; + + // Object3D instance caches + this.cache = { refs: {}, uses: {} }; + + } + + GLTFLightsExtension.prototype._markDefs = function () { + + var parser = this.parser; + var nodeDefs = this.parser.json.nodes || []; + + for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { + + var nodeDef = nodeDefs[ nodeIndex ]; + + if ( nodeDef.extensions + && nodeDef.extensions[ this.name ] + && nodeDef.extensions[ this.name ].light !== undefined ) { + + parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light ); + + } + + } + + }; + + GLTFLightsExtension.prototype._loadLight = function ( lightIndex ) { + + var parser = this.parser; + var cacheKey = 'light:' + lightIndex; + var dependency = parser.cache.get( cacheKey ); + + if ( dependency ) return dependency; + + var json = parser.json; + var extensions = ( json.extensions && json.extensions[ this.name ] ) || {}; + var lightDefs = extensions.lights || []; + var lightDef = lightDefs[ lightIndex ]; + var lightNode; + + var color = new Color( 0xffffff ); + + if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); + + var range = lightDef.range !== undefined ? lightDef.range : 0; + + switch ( lightDef.type ) { + + case 'directional': + lightNode = new DirectionalLight( color ); + lightNode.target.position.set( 0, 0, - 1 ); + lightNode.add( lightNode.target ); + break; + + case 'point': + lightNode = new PointLight( color ); + lightNode.distance = range; + break; + + case 'spot': + lightNode = new SpotLight( color ); + lightNode.distance = range; + // Handle spotlight properties. + lightDef.spot = lightDef.spot || {}; + lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0; + lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0; + lightNode.angle = lightDef.spot.outerConeAngle; + lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle; + lightNode.target.position.set( 0, 0, - 1 ); + lightNode.add( lightNode.target ); + break; + + default: + throw new Error( 'THREE.GLTFLoader: Unexpected light type: ' + lightDef.type ); + + } + + // Some lights (e.g. spot) default to a position other than the origin. Reset the position + // here, because node-level parsing will only override position if explicitly specified. + lightNode.position.set( 0, 0, 0 ); + + lightNode.decay = 2; + + if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; + + lightNode.name = parser.createUniqueName( lightDef.name || ( 'light_' + lightIndex ) ); + + dependency = Promise.resolve( lightNode ); + + parser.cache.add( cacheKey, dependency ); + + return dependency; + + }; + + GLTFLightsExtension.prototype.createNodeAttachment = function ( nodeIndex ) { + + var self = this; + var parser = this.parser; + var json = parser.json; + var nodeDef = json.nodes[ nodeIndex ]; + var lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {}; + var lightIndex = lightDef.light; + + if ( lightIndex === undefined ) return null; + + return this._loadLight( lightIndex ).then( function ( light ) { + + return parser._getNodeRef( self.cache, lightIndex, light ); + + } ); + + }; + + /** + * Unlit Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit + */ + function GLTFMaterialsUnlitExtension() { + + this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; + + } + + GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () { + + return MeshBasicMaterial; + + }; + + GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, materialDef, parser ) { + + var pending = []; + + materialParams.color = new Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + + var metallicRoughness = materialDef.pbrMetallicRoughness; + + if ( metallicRoughness ) { + + if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { + + var array = metallicRoughness.baseColorFactor; + + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; + + } + + if ( metallicRoughness.baseColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) ); + + } + + } + + return Promise.all( pending ); + + }; + + /** + * Clearcoat Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + */ + function GLTFMaterialsClearcoatExtension( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; + + } + + GLTFMaterialsClearcoatExtension.prototype.getMaterialType = function ( materialIndex ) { + + var parser = this.parser; + var materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + }; + + GLTFMaterialsClearcoatExtension.prototype.extendMaterialParams = function ( materialIndex, materialParams ) { + + var parser = this.parser; + var materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + var pending = []; + + var extension = materialDef.extensions[ this.name ]; + + if ( extension.clearcoatFactor !== undefined ) { + + materialParams.clearcoat = extension.clearcoatFactor; + + } + + if ( extension.clearcoatTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'clearcoatMap', extension.clearcoatTexture ) ); + + } + + if ( extension.clearcoatRoughnessFactor !== undefined ) { + + materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor; + + } + + if ( extension.clearcoatRoughnessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture ) ); + + } + + if ( extension.clearcoatNormalTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture ) ); + + if ( extension.clearcoatNormalTexture.scale !== undefined ) { + + var scale = extension.clearcoatNormalTexture.scale; + + // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 + materialParams.clearcoatNormalScale = new Vector2( scale, - scale ); + + } + + } + + return Promise.all( pending ); + + }; + + /** + * Transmission Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission + * Draft: https://github.com/KhronosGroup/glTF/pull/1698 + */ + function GLTFMaterialsTransmissionExtension( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; + + } + + GLTFMaterialsTransmissionExtension.prototype.getMaterialType = function ( materialIndex ) { + + var parser = this.parser; + var materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + }; + + GLTFMaterialsTransmissionExtension.prototype.extendMaterialParams = function ( materialIndex, materialParams ) { + + var parser = this.parser; + var materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + var pending = []; + + var extension = materialDef.extensions[ this.name ]; + + if ( extension.transmissionFactor !== undefined ) { + + materialParams.transmission = extension.transmissionFactor; + + } + + if ( extension.transmissionTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'transmissionMap', extension.transmissionTexture ) ); + + } + + return Promise.all( pending ); + + }; + + /** + * BasisU Texture Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu + */ + function GLTFTextureBasisUExtension( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_TEXTURE_BASISU; + + } + + GLTFTextureBasisUExtension.prototype.loadTexture = function ( textureIndex ) { + + var parser = this.parser; + var json = parser.json; + + var textureDef = json.textures[ textureIndex ]; + + if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) { + + return null; + + } + + var extension = textureDef.extensions[ this.name ]; + var source = json.images[ extension.source ]; + var loader = parser.options.ktx2Loader; + + if ( ! loader ) { + + if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { + + throw new Error( 'THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures' ); + + } else { + + // Assumes that the extension is optional and that a fallback texture is present + return null; + + } + + } + + return parser.loadTextureImage( textureIndex, source, loader ); + + }; + + /** + * WebP Texture Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp + */ + function GLTFTextureWebPExtension( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.EXT_TEXTURE_WEBP; + this.isSupported = null; + + } + + GLTFTextureWebPExtension.prototype.loadTexture = function ( textureIndex ) { + + var name = this.name; + var parser = this.parser; + var json = parser.json; + + var textureDef = json.textures[ textureIndex ]; + + if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { + + return null; + + } + + var extension = textureDef.extensions[ name ]; + var source = json.images[ extension.source ]; + + var loader = parser.textureLoader; + if ( source.uri ) { + + var handler = parser.options.manager.getHandler( source.uri ); + if ( handler !== null ) loader = handler; + + } + + return this.detectSupport().then( function ( isSupported ) { + + if ( isSupported ) return parser.loadTextureImage( textureIndex, source, loader ); + + if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { + + throw new Error( 'THREE.GLTFLoader: WebP required by asset but unsupported.' ); + + } + + // Fall back to PNG or JPEG. + return parser.loadTexture( textureIndex ); + + } ); + + }; + + GLTFTextureWebPExtension.prototype.detectSupport = function () { + + if ( ! this.isSupported ) { + + this.isSupported = new Promise( function ( resolve ) { + + var image = new Image(); + + // Lossy test image. Support for lossy images doesn't guarantee support for all + // WebP images, unfortunately. + image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'; + + image.onload = image.onerror = function () { + + resolve( image.height === 1 ); + + }; + + } ); + + } + + return this.isSupported; + + }; + + /** + * meshopt BufferView Compression Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression + */ + function GLTFMeshoptCompression( parser ) { + + this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; + this.parser = parser; + + } + + GLTFMeshoptCompression.prototype.loadBufferView = function ( index ) { + + var json = this.parser.json; + var bufferView = json.bufferViews[ index ]; + + if ( bufferView.extensions && bufferView.extensions[ this.name ] ) { + + var extensionDef = bufferView.extensions[ this.name ]; + + var buffer = this.parser.getDependency( 'buffer', extensionDef.buffer ); + var decoder = this.parser.options.meshoptDecoder; + + if ( ! decoder || ! decoder.supported ) { + + if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { + + throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' ); + + } else { + + // Assumes that the extension is optional and that fallback buffer data is present + return null; + + } + + } + + return Promise.all( [ buffer, decoder.ready ] ).then( function ( res ) { + + var byteOffset = extensionDef.byteOffset || 0; + var byteLength = extensionDef.byteLength || 0; + + var count = extensionDef.count; + var stride = extensionDef.byteStride; + + var result = new ArrayBuffer( count * stride ); + var source = new Uint8Array( res[ 0 ], byteOffset, byteLength ); + + decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter ); + return result; + + } ); + + } else { + + return null; + + } + + }; + + /* BINARY EXTENSION */ + var BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; + var BINARY_EXTENSION_HEADER_LENGTH = 12; + var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; + + function GLTFBinaryExtension( data ) { + + this.name = EXTENSIONS.KHR_BINARY_GLTF; + this.content = null; + this.body = null; + + var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); + + this.header = { + magic: LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ), + version: headerView.getUint32( 4, true ), + length: headerView.getUint32( 8, true ) + }; + + if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { + + throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); + + } else if ( this.header.version < 2.0 ) { + + throw new Error( 'THREE.GLTFLoader: Legacy binary file detected.' ); + + } + + var chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; + var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); + var chunkIndex = 0; + + while ( chunkIndex < chunkContentsLength ) { + + var chunkLength = chunkView.getUint32( chunkIndex, true ); + chunkIndex += 4; + + var chunkType = chunkView.getUint32( chunkIndex, true ); + chunkIndex += 4; + + if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { + + var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); + this.content = LoaderUtils.decodeText( contentArray ); + + } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { + + var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; + this.body = data.slice( byteOffset, byteOffset + chunkLength ); + + } + + // Clients must ignore chunks with unknown types. + + chunkIndex += chunkLength; + + } + + if ( this.content === null ) { + + throw new Error( 'THREE.GLTFLoader: JSON content not found.' ); + + } + + } + + /** + * DRACO Mesh Compression Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression + */ + function GLTFDracoMeshCompressionExtension( json, dracoLoader ) { + + if ( ! dracoLoader ) { + + throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' ); + + } + + this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; + this.json = json; + this.dracoLoader = dracoLoader; + this.dracoLoader.preload(); + + } + + GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) { + + var json = this.json; + var dracoLoader = this.dracoLoader; + var bufferViewIndex = primitive.extensions[ this.name ].bufferView; + var gltfAttributeMap = primitive.extensions[ this.name ].attributes; + var threeAttributeMap = {}; + var attributeNormalizedMap = {}; + var attributeTypeMap = {}; + + for ( var attributeName in gltfAttributeMap ) { + + var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + + threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; + + } + + for ( attributeName in primitive.attributes ) { + + var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + + if ( gltfAttributeMap[ attributeName ] !== undefined ) { + + var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; + var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + + attributeTypeMap[ threeAttributeName ] = componentType; + attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; + + } + + } + + return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { + + return new Promise( function ( resolve ) { + + dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { + + for ( var attributeName in geometry.attributes ) { + + var attribute = geometry.attributes[ attributeName ]; + var normalized = attributeNormalizedMap[ attributeName ]; + + if ( normalized !== undefined ) attribute.normalized = normalized; + + } + + resolve( geometry ); + + }, threeAttributeMap, attributeTypeMap ); + + } ); + + } ); + + }; + + /** + * Texture Transform Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform + */ + function GLTFTextureTransformExtension() { + + this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; + + } + + GLTFTextureTransformExtension.prototype.extendTexture = function ( texture, transform ) { + + texture = texture.clone(); + + if ( transform.offset !== undefined ) { + + texture.offset.fromArray( transform.offset ); + + } + + if ( transform.rotation !== undefined ) { + + texture.rotation = transform.rotation; + + } + + if ( transform.scale !== undefined ) { + + texture.repeat.fromArray( transform.scale ); + + } + + if ( transform.texCoord !== undefined ) { + + console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' ); + + } + + texture.needsUpdate = true; + + return texture; + + }; + + /** + * Specular-Glossiness Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness + */ + + /** + * A sub class of StandardMaterial with some of the functionality + * changed via the `onBeforeCompile` callback + * @pailhead + */ + + function GLTFMeshStandardSGMaterial( params ) { + + MeshStandardMaterial.call( this ); + + this.isGLTFSpecularGlossinessMaterial = true; + + //various chunks that need replacing + var specularMapParsFragmentChunk = [ + '#ifdef USE_SPECULARMAP', + ' uniform sampler2D specularMap;', + '#endif' + ].join( '\n' ); + + var glossinessMapParsFragmentChunk = [ + '#ifdef USE_GLOSSINESSMAP', + ' uniform sampler2D glossinessMap;', + '#endif' + ].join( '\n' ); + + var specularMapFragmentChunk = [ + 'vec3 specularFactor = specular;', + '#ifdef USE_SPECULARMAP', + ' vec4 texelSpecular = texture2D( specularMap, vUv );', + ' texelSpecular = sRGBToLinear( texelSpecular );', + ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' specularFactor *= texelSpecular.rgb;', + '#endif' + ].join( '\n' ); + + var glossinessMapFragmentChunk = [ + 'float glossinessFactor = glossiness;', + '#ifdef USE_GLOSSINESSMAP', + ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', + ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' glossinessFactor *= texelGlossiness.a;', + '#endif' + ].join( '\n' ); + + var lightPhysicalFragmentChunk = [ + 'PhysicalMaterial material;', + 'material.diffuseColor = diffuseColor.rgb * ( 1. - max( specularFactor.r, max( specularFactor.g, specularFactor.b ) ) );', + 'vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );', + 'float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );', + 'material.specularRoughness = max( 1.0 - glossinessFactor, 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap.', + 'material.specularRoughness += geometryRoughness;', + 'material.specularRoughness = min( material.specularRoughness, 1.0 );', + 'material.specularColor = specularFactor;', + ].join( '\n' ); + + var uniforms = { + specular: { value: new Color().setHex( 0xffffff ) }, + glossiness: { value: 1 }, + specularMap: { value: null }, + glossinessMap: { value: null } + }; + + this._extraUniforms = uniforms; + + this.onBeforeCompile = function ( shader ) { + + for ( var uniformName in uniforms ) { + + shader.uniforms[ uniformName ] = uniforms[ uniformName ]; + + } + + shader.fragmentShader = shader.fragmentShader + .replace( 'uniform float roughness;', 'uniform vec3 specular;' ) + .replace( 'uniform float metalness;', 'uniform float glossiness;' ) + .replace( '#include ', specularMapParsFragmentChunk ) + .replace( '#include ', glossinessMapParsFragmentChunk ) + .replace( '#include ', specularMapFragmentChunk ) + .replace( '#include ', glossinessMapFragmentChunk ) + .replace( '#include ', lightPhysicalFragmentChunk ); + + }; + + Object.defineProperties( this, { + + specular: { + get: function () { + + return uniforms.specular.value; + + }, + set: function ( v ) { + + uniforms.specular.value = v; + + } + }, + + specularMap: { + get: function () { + + return uniforms.specularMap.value; + + }, + set: function ( v ) { + + uniforms.specularMap.value = v; + + if ( v ) { + + this.defines.USE_SPECULARMAP = ''; // USE_UV is set by the renderer for specular maps + + } else { + + delete this.defines.USE_SPECULARMAP; + + } + + } + }, + + glossiness: { + get: function () { + + return uniforms.glossiness.value; + + }, + set: function ( v ) { + + uniforms.glossiness.value = v; + + } + }, + + glossinessMap: { + get: function () { + + return uniforms.glossinessMap.value; + + }, + set: function ( v ) { + + uniforms.glossinessMap.value = v; + + if ( v ) { + + this.defines.USE_GLOSSINESSMAP = ''; + this.defines.USE_UV = ''; + + } else { + + delete this.defines.USE_GLOSSINESSMAP; + delete this.defines.USE_UV; + + } + + } + } + + } ); + + delete this.metalness; + delete this.roughness; + delete this.metalnessMap; + delete this.roughnessMap; + + this.setValues( params ); + + } + + GLTFMeshStandardSGMaterial.prototype = Object.create( MeshStandardMaterial.prototype ); + GLTFMeshStandardSGMaterial.prototype.constructor = GLTFMeshStandardSGMaterial; + + GLTFMeshStandardSGMaterial.prototype.copy = function ( source ) { + + MeshStandardMaterial.prototype.copy.call( this, source ); + this.specularMap = source.specularMap; + this.specular.copy( source.specular ); + this.glossinessMap = source.glossinessMap; + this.glossiness = source.glossiness; + delete this.metalness; + delete this.roughness; + delete this.metalnessMap; + delete this.roughnessMap; + return this; + + }; + + function GLTFMaterialsPbrSpecularGlossinessExtension() { + + return { + + name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS, + + specularGlossinessParams: [ + 'color', + 'map', + 'lightMap', + 'lightMapIntensity', + 'aoMap', + 'aoMapIntensity', + 'emissive', + 'emissiveIntensity', + 'emissiveMap', + 'bumpMap', + 'bumpScale', + 'normalMap', + 'normalMapType', + 'displacementMap', + 'displacementScale', + 'displacementBias', + 'specularMap', + 'specular', + 'glossinessMap', + 'glossiness', + 'alphaMap', + 'envMap', + 'envMapIntensity', + 'refractionRatio', + ], + + getMaterialType: function () { + + return GLTFMeshStandardSGMaterial; + + }, + + extendParams: function ( materialParams, materialDef, parser ) { + + var pbrSpecularGlossiness = materialDef.extensions[ this.name ]; + + materialParams.color = new Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + + var pending = []; + + if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { + + var array = pbrSpecularGlossiness.diffuseFactor; + + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; + + } + + if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) ); + + } + + materialParams.emissive = new Color( 0.0, 0.0, 0.0 ); + materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; + materialParams.specular = new Color( 1.0, 1.0, 1.0 ); + + if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { + + materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor ); + + } + + if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { + + var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; + pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) ); + pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) ); + + } + + return Promise.all( pending ); + + }, + + createMaterial: function ( materialParams ) { + + var material = new GLTFMeshStandardSGMaterial( materialParams ); + material.fog = true; + + material.color = materialParams.color; + + material.map = materialParams.map === undefined ? null : materialParams.map; + + material.lightMap = null; + material.lightMapIntensity = 1.0; + + material.aoMap = materialParams.aoMap === undefined ? null : materialParams.aoMap; + material.aoMapIntensity = 1.0; + + material.emissive = materialParams.emissive; + material.emissiveIntensity = 1.0; + material.emissiveMap = materialParams.emissiveMap === undefined ? null : materialParams.emissiveMap; + + material.bumpMap = materialParams.bumpMap === undefined ? null : materialParams.bumpMap; + material.bumpScale = 1; + + material.normalMap = materialParams.normalMap === undefined ? null : materialParams.normalMap; + material.normalMapType = TangentSpaceNormalMap; + + if ( materialParams.normalScale ) material.normalScale = materialParams.normalScale; + + material.displacementMap = null; + material.displacementScale = 1; + material.displacementBias = 0; + + material.specularMap = materialParams.specularMap === undefined ? null : materialParams.specularMap; + material.specular = materialParams.specular; + + material.glossinessMap = materialParams.glossinessMap === undefined ? null : materialParams.glossinessMap; + material.glossiness = materialParams.glossiness; + + material.alphaMap = null; + + material.envMap = materialParams.envMap === undefined ? null : materialParams.envMap; + material.envMapIntensity = 1.0; + + material.refractionRatio = 0.98; + + return material; + + }, + + }; + + } + + /** + * Mesh Quantization Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization + */ + function GLTFMeshQuantizationExtension() { + + this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; + + } + + /*********************************/ + /********** INTERPOLATION ********/ + /*********************************/ + + // Spline Interpolation + // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation + function GLTFCubicSplineInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); + + } + + GLTFCubicSplineInterpolant.prototype = Object.create( Interpolant.prototype ); + GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant; + + GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function ( index ) { + + // Copies a sample value to the result buffer. See description of glTF + // CUBICSPLINE values layout in interpolate_() function below. + + var result = this.resultBuffer, + values = this.sampleValues, + valueSize = this.valueSize, + offset = index * valueSize * 3 + valueSize; + + for ( var i = 0; i !== valueSize; i ++ ) { + + result[ i ] = values[ offset + i ]; + + } + + return result; + + }; + + GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; + + GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; + + GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) { + + var result = this.resultBuffer; + var values = this.sampleValues; + var stride = this.valueSize; + + var stride2 = stride * 2; + var stride3 = stride * 3; + + var td = t1 - t0; + + var p = ( t - t0 ) / td; + var pp = p * p; + var ppp = pp * p; + + var offset1 = i1 * stride3; + var offset0 = offset1 - stride3; + + var s2 = - 2 * ppp + 3 * pp; + var s3 = ppp - pp; + var s0 = 1 - s2; + var s1 = s3 - pp + p; + + // Layout of keyframe output values for CUBICSPLINE animations: + // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] + for ( var i = 0; i !== stride; i ++ ) { + + var p0 = values[ offset0 + i + stride ]; // splineVertex_k + var m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) + var p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 + var m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k) + + result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; + + } + + return result; + + }; + + /*********************************/ + /********** INTERNALS ************/ + /*********************************/ + + /* CONSTANTS */ + + var WEBGL_CONSTANTS = { + FLOAT: 5126, + //FLOAT_MAT2: 35674, + FLOAT_MAT3: 35675, + FLOAT_MAT4: 35676, + FLOAT_VEC2: 35664, + FLOAT_VEC3: 35665, + FLOAT_VEC4: 35666, + LINEAR: 9729, + REPEAT: 10497, + SAMPLER_2D: 35678, + POINTS: 0, + LINES: 1, + LINE_LOOP: 2, + LINE_STRIP: 3, + TRIANGLES: 4, + TRIANGLE_STRIP: 5, + TRIANGLE_FAN: 6, + UNSIGNED_BYTE: 5121, + UNSIGNED_SHORT: 5123 + }; + + var WEBGL_COMPONENT_TYPES = { + 5120: Int8Array, + 5121: Uint8Array, + 5122: Int16Array, + 5123: Uint16Array, + 5125: Uint32Array, + 5126: Float32Array + }; + + var WEBGL_FILTERS = { + 9728: NearestFilter, + 9729: LinearFilter, + 9984: NearestMipmapNearestFilter, + 9985: LinearMipmapNearestFilter, + 9986: NearestMipmapLinearFilter, + 9987: LinearMipmapLinearFilter + }; + + var WEBGL_WRAPPINGS = { + 33071: ClampToEdgeWrapping, + 33648: MirroredRepeatWrapping, + 10497: RepeatWrapping + }; + + var WEBGL_TYPE_SIZES = { + 'SCALAR': 1, + 'VEC2': 2, + 'VEC3': 3, + 'VEC4': 4, + 'MAT2': 4, + 'MAT3': 9, + 'MAT4': 16 + }; + + var ATTRIBUTES = { + POSITION: 'position', + NORMAL: 'normal', + TANGENT: 'tangent', + TEXCOORD_0: 'uv', + TEXCOORD_1: 'uv2', + COLOR_0: 'color', + WEIGHTS_0: 'skinWeight', + JOINTS_0: 'skinIndex', + }; + + var PATH_PROPERTIES = { + scale: 'scale', + translation: 'position', + rotation: 'quaternion', + weights: 'morphTargetInfluences' + }; + + var INTERPOLATION = { + CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each + // keyframe track will be initialized with a default interpolation type, then modified. + LINEAR: InterpolateLinear, + STEP: InterpolateDiscrete + }; + + var ALPHA_MODES = { + OPAQUE: 'OPAQUE', + MASK: 'MASK', + BLEND: 'BLEND' + }; + + /* UTILITY FUNCTIONS */ + + function resolveURL( url, path ) { + + // Invalid URL + if ( typeof url !== 'string' || url === '' ) return ''; + + // Host Relative URL + if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) { + + path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' ); + + } + + // Absolute URL http://,https://,// + if ( /^(https?:)?\/\//i.test( url ) ) return url; + + // Data URI + if ( /^data:.*,.*$/i.test( url ) ) return url; + + // Blob URL + if ( /^blob:.*$/i.test( url ) ) return url; + + // Relative URL + return path + url; + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material + */ + function createDefaultMaterial( cache ) { + + if ( cache[ 'DefaultMaterial' ] === undefined ) { + + cache[ 'DefaultMaterial' ] = new MeshStandardMaterial( { + color: 0xFFFFFF, + emissive: 0x000000, + metalness: 1, + roughness: 1, + transparent: false, + depthTest: true, + side: FrontSide + } ); + + } + + return cache[ 'DefaultMaterial' ]; + + } + + function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { + + // Add unknown glTF extensions to an object's userData. + + for ( var name in objectDef.extensions ) { + + if ( knownExtensions[ name ] === undefined ) { + + object.userData.gltfExtensions = object.userData.gltfExtensions || {}; + object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; + + } + + } + + } + + /** + * @param {Object3D|Material|BufferGeometry} object + * @param {GLTF.definition} gltfDef + */ + function assignExtrasToUserData( object, gltfDef ) { + + if ( gltfDef.extras !== undefined ) { + + if ( typeof gltfDef.extras === 'object' ) { + + Object.assign( object.userData, gltfDef.extras ); + + } else { + + console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras ); + + } + + } + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets + * + * @param {BufferGeometry} geometry + * @param {Array} targets + * @param {GLTFParser} parser + * @return {Promise} + */ + function addMorphTargets( geometry, targets, parser ) { + + var hasMorphPosition = false; + var hasMorphNormal = false; + + for ( var i = 0, il = targets.length; i < il; i ++ ) { + + var target = targets[ i ]; + + if ( target.POSITION !== undefined ) hasMorphPosition = true; + if ( target.NORMAL !== undefined ) hasMorphNormal = true; + + if ( hasMorphPosition && hasMorphNormal ) break; + + } + + if ( ! hasMorphPosition && ! hasMorphNormal ) return Promise.resolve( geometry ); + + var pendingPositionAccessors = []; + var pendingNormalAccessors = []; + + for ( var i = 0, il = targets.length; i < il; i ++ ) { + + var target = targets[ i ]; + + if ( hasMorphPosition ) { + + var pendingAccessor = target.POSITION !== undefined + ? parser.getDependency( 'accessor', target.POSITION ) + : geometry.attributes.position; + + pendingPositionAccessors.push( pendingAccessor ); + + } + + if ( hasMorphNormal ) { + + var pendingAccessor = target.NORMAL !== undefined + ? parser.getDependency( 'accessor', target.NORMAL ) + : geometry.attributes.normal; + + pendingNormalAccessors.push( pendingAccessor ); + + } + + } + + return Promise.all( [ + Promise.all( pendingPositionAccessors ), + Promise.all( pendingNormalAccessors ) + ] ).then( function ( accessors ) { + + var morphPositions = accessors[ 0 ]; + var morphNormals = accessors[ 1 ]; + + if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; + if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; + geometry.morphTargetsRelative = true; + + return geometry; + + } ); + + } + + /** + * @param {Mesh} mesh + * @param {GLTF.Mesh} meshDef + */ + function updateMorphTargets( mesh, meshDef ) { + + mesh.updateMorphTargets(); + + if ( meshDef.weights !== undefined ) { + + for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) { + + mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; + + } + + } + + // .extras has user-defined data, so check that .extras.targetNames is an array. + if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) { + + var targetNames = meshDef.extras.targetNames; + + if ( mesh.morphTargetInfluences.length === targetNames.length ) { + + mesh.morphTargetDictionary = {}; + + for ( var i = 0, il = targetNames.length; i < il; i ++ ) { + + mesh.morphTargetDictionary[ targetNames[ i ] ] = i; + + } + + } else { + + console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' ); + + } + + } + + } + + function createPrimitiveKey( primitiveDef ) { + + var dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; + var geometryKey; + + if ( dracoExtension ) { + + geometryKey = 'draco:' + dracoExtension.bufferView + + ':' + dracoExtension.indices + + ':' + createAttributesKey( dracoExtension.attributes ); + + } else { + + geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; + + } + + return geometryKey; + + } + + function createAttributesKey( attributes ) { + + var attributesKey = ''; + + var keys = Object.keys( attributes ).sort(); + + for ( var i = 0, il = keys.length; i < il; i ++ ) { + + attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; + + } + + return attributesKey; + + } + + /* GLTF PARSER */ + + function GLTFParser( json, options ) { + + this.json = json || {}; + this.extensions = {}; + this.plugins = {}; + this.options = options || {}; + + // loader object cache + this.cache = new GLTFRegistry(); + + // associations between Three.js objects and glTF elements + this.associations = new Map(); + + // BufferGeometry caching + this.primitiveCache = {}; + + // Object3D instance caches + this.meshCache = { refs: {}, uses: {} }; + this.cameraCache = { refs: {}, uses: {} }; + this.lightCache = { refs: {}, uses: {} }; + + // Track node names, to ensure no duplicates + this.nodeNamesUsed = {}; + + // Use an ImageBitmapLoader if imageBitmaps are supported. Moves much of the + // expensive work of uploading a texture to the GPU off the main thread. + if ( typeof createImageBitmap !== 'undefined' && /Firefox/.test( navigator.userAgent ) === false ) { + + this.textureLoader = new ImageBitmapLoader( this.options.manager ); + + } else { + + this.textureLoader = new TextureLoader( this.options.manager ); + + } + + this.textureLoader.setCrossOrigin( this.options.crossOrigin ); + this.textureLoader.setRequestHeader( this.options.requestHeader ); + + this.fileLoader = new FileLoader( this.options.manager ); + this.fileLoader.setResponseType( 'arraybuffer' ); + + if ( this.options.crossOrigin === 'use-credentials' ) { + + this.fileLoader.setWithCredentials( true ); + + } + + } + + GLTFParser.prototype.setExtensions = function ( extensions ) { + + this.extensions = extensions; + + }; + + GLTFParser.prototype.setPlugins = function ( plugins ) { + + this.plugins = plugins; + + }; + + GLTFParser.prototype.parse = function ( onLoad, onError ) { + + var parser = this; + var json = this.json; + var extensions = this.extensions; + + // Clear the loader cache + this.cache.removeAll(); + + // Mark the special nodes/meshes in json for efficient parse + this._invokeAll( function ( ext ) { + + return ext._markDefs && ext._markDefs(); + + } ); + + Promise.all( this._invokeAll( function ( ext ) { + + return ext.beforeRoot && ext.beforeRoot(); + + } ) ).then( function () { + + return Promise.all( [ + + parser.getDependencies( 'scene' ), + parser.getDependencies( 'animation' ), + parser.getDependencies( 'camera' ), + + ] ); + + } ).then( function ( dependencies ) { + + var result = { + scene: dependencies[ 0 ][ json.scene || 0 ], + scenes: dependencies[ 0 ], + animations: dependencies[ 1 ], + cameras: dependencies[ 2 ], + asset: json.asset, + parser: parser, + userData: {} + }; + + addUnknownExtensionsToUserData( extensions, result, json ); + + assignExtrasToUserData( result, json ); + + Promise.all( parser._invokeAll( function ( ext ) { + + return ext.afterRoot && ext.afterRoot( result ); + + } ) ).then( function () { + + onLoad( result ); + + } ); + + } ).catch( onError ); + + }; + + /** + * Marks the special nodes/meshes in json for efficient parse. + */ + GLTFParser.prototype._markDefs = function () { + + var nodeDefs = this.json.nodes || []; + var skinDefs = this.json.skins || []; + var meshDefs = this.json.meshes || []; + + // Nothing in the node definition indicates whether it is a Bone or an + // Object3D. Use the skins' joint references to mark bones. + for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { + + var joints = skinDefs[ skinIndex ].joints; + + for ( var i = 0, il = joints.length; i < il; i ++ ) { + + nodeDefs[ joints[ i ] ].isBone = true; + + } + + } + + // Iterate over all nodes, marking references to shared resources, + // as well as skeleton joints. + for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { + + var nodeDef = nodeDefs[ nodeIndex ]; + + if ( nodeDef.mesh !== undefined ) { + + this._addNodeRef( this.meshCache, nodeDef.mesh ); + + // Nothing in the mesh definition indicates whether it is + // a SkinnedMesh or Mesh. Use the node's mesh reference + // to mark SkinnedMesh if node has skin. + if ( nodeDef.skin !== undefined ) { + + meshDefs[ nodeDef.mesh ].isSkinnedMesh = true; + + } + + } + + if ( nodeDef.camera !== undefined ) { + + this._addNodeRef( this.cameraCache, nodeDef.camera ); + + } + + } + + }; + + /** + * Counts references to shared node / Object3D resources. These resources + * can be reused, or "instantiated", at multiple nodes in the scene + * hierarchy. Mesh, Camera, and Light instances are instantiated and must + * be marked. Non-scenegraph resources (like Materials, Geometries, and + * Textures) can be reused directly and are not marked here. + * + * Example: CesiumMilkTruck sample model reuses "Wheel" meshes. + */ + GLTFParser.prototype._addNodeRef = function ( cache, index ) { + + if ( index === undefined ) return; + + if ( cache.refs[ index ] === undefined ) { + + cache.refs[ index ] = cache.uses[ index ] = 0; + + } + + cache.refs[ index ] ++; + + }; + + /** Returns a reference to a shared resource, cloning it if necessary. */ + GLTFParser.prototype._getNodeRef = function ( cache, index, object ) { + + if ( cache.refs[ index ] <= 1 ) return object; + + var ref = object.clone(); + + ref.name += '_instance_' + ( cache.uses[ index ] ++ ); + + return ref; + + }; + + GLTFParser.prototype._invokeOne = function ( func ) { + + var extensions = Object.values( this.plugins ); + extensions.push( this ); + + for ( var i = 0; i < extensions.length; i ++ ) { + + var result = func( extensions[ i ] ); + + if ( result ) return result; + + } + + }; + + GLTFParser.prototype._invokeAll = function ( func ) { + + var extensions = Object.values( this.plugins ); + extensions.unshift( this ); + + var pending = []; + + for ( var i = 0; i < extensions.length; i ++ ) { + + var result = func( extensions[ i ] ); + + if ( result ) pending.push( result ); + + } + + return pending; + + }; + + /** + * Requests the specified dependency asynchronously, with caching. + * @param {string} type + * @param {number} index + * @return {Promise} + */ + GLTFParser.prototype.getDependency = function ( type, index ) { + + var cacheKey = type + ':' + index; + var dependency = this.cache.get( cacheKey ); + + if ( ! dependency ) { + + switch ( type ) { + + case 'scene': + dependency = this.loadScene( index ); + break; + + case 'node': + dependency = this.loadNode( index ); + break; + + case 'mesh': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadMesh && ext.loadMesh( index ); + + } ); + break; + + case 'accessor': + dependency = this.loadAccessor( index ); + break; + + case 'bufferView': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadBufferView && ext.loadBufferView( index ); + + } ); + break; + + case 'buffer': + dependency = this.loadBuffer( index ); + break; + + case 'material': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadMaterial && ext.loadMaterial( index ); + + } ); + break; + + case 'texture': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadTexture && ext.loadTexture( index ); + + } ); + break; + + case 'skin': + dependency = this.loadSkin( index ); + break; + + case 'animation': + dependency = this.loadAnimation( index ); + break; + + case 'camera': + dependency = this.loadCamera( index ); + break; + + default: + throw new Error( 'Unknown type: ' + type ); + + } + + this.cache.add( cacheKey, dependency ); + + } + + return dependency; + + }; + + /** + * Requests all dependencies of the specified type asynchronously, with caching. + * @param {string} type + * @return {Promise>} + */ + GLTFParser.prototype.getDependencies = function ( type ) { + + var dependencies = this.cache.get( type ); + + if ( ! dependencies ) { + + var parser = this; + var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; + + dependencies = Promise.all( defs.map( function ( def, index ) { + + return parser.getDependency( type, index ); + + } ) ); + + this.cache.add( type, dependencies ); + + } + + return dependencies; + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views + * @param {number} bufferIndex + * @return {Promise} + */ + GLTFParser.prototype.loadBuffer = function ( bufferIndex ) { + + var bufferDef = this.json.buffers[ bufferIndex ]; + var loader = this.fileLoader; + + if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { + + throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' ); + + } + + // If present, GLB container is required to be the first buffer. + if ( bufferDef.uri === undefined && bufferIndex === 0 ) { + + return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); + + } + + var options = this.options; + + return new Promise( function ( resolve, reject ) { + + loader.load( resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () { + + reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) ); + + } ); + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views + * @param {number} bufferViewIndex + * @return {Promise} + */ + GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) { + + var bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; + + return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { + + var byteLength = bufferViewDef.byteLength || 0; + var byteOffset = bufferViewDef.byteOffset || 0; + return buffer.slice( byteOffset, byteOffset + byteLength ); + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors + * @param {number} accessorIndex + * @return {Promise} + */ + GLTFParser.prototype.loadAccessor = function ( accessorIndex ) { + + var parser = this; + var json = this.json; + + var accessorDef = this.json.accessors[ accessorIndex ]; + + if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { + + // Ignore empty accessors, which may be used to declare runtime + // information about attributes coming from another source (e.g. Draco + // compression extension). + return Promise.resolve( null ); + + } + + var pendingBufferViews = []; + + if ( accessorDef.bufferView !== undefined ) { + + pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) ); + + } else { + + pendingBufferViews.push( null ); + + } + + if ( accessorDef.sparse !== undefined ) { + + pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) ); + pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) ); + + } + + return Promise.all( pendingBufferViews ).then( function ( bufferViews ) { + + var bufferView = bufferViews[ 0 ]; + + var itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; + var TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + + // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. + var elementBytes = TypedArray.BYTES_PER_ELEMENT; + var itemBytes = elementBytes * itemSize; + var byteOffset = accessorDef.byteOffset || 0; + var byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; + var normalized = accessorDef.normalized === true; + var array, bufferAttribute; + + // The buffer is not interleaved if the stride is the item size in bytes. + if ( byteStride && byteStride !== itemBytes ) { + + // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer + // This makes sure that IBA.count reflects accessor.count properly + var ibSlice = Math.floor( byteOffset / byteStride ); + var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; + var ib = parser.cache.get( ibCacheKey ); + + if ( ! ib ) { + + array = new TypedArray( bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes ); + + // Integer parameters to IB/IBA are in array elements, not bytes. + ib = new InterleavedBuffer( array, byteStride / elementBytes ); + + parser.cache.add( ibCacheKey, ib ); + + } + + bufferAttribute = new InterleavedBufferAttribute( ib, itemSize, ( byteOffset % byteStride ) / elementBytes, normalized ); + + } else { + + if ( bufferView === null ) { + + array = new TypedArray( accessorDef.count * itemSize ); + + } else { + + array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize ); + + } + + bufferAttribute = new BufferAttribute( array, itemSize, normalized ); + + } + + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors + if ( accessorDef.sparse !== undefined ) { + + var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; + var TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; + + var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; + var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; + + var sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); + var sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize ); + + if ( bufferView !== null ) { + + // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes. + bufferAttribute = new BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized ); + + } + + for ( var i = 0, il = sparseIndices.length; i < il; i ++ ) { + + var index = sparseIndices[ i ]; + + bufferAttribute.setX( index, sparseValues[ i * itemSize ] ); + if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] ); + if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] ); + if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] ); + if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' ); + + } + + } + + return bufferAttribute; + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures + * @param {number} textureIndex + * @return {Promise} + */ + GLTFParser.prototype.loadTexture = function ( textureIndex ) { + + var json = this.json; + var options = this.options; + var textureDef = json.textures[ textureIndex ]; + var source = json.images[ textureDef.source ]; + + var loader = this.textureLoader; + + if ( source.uri ) { + + var handler = options.manager.getHandler( source.uri ); + if ( handler !== null ) loader = handler; + + } + + return this.loadTextureImage( textureIndex, source, loader ); + + }; + + GLTFParser.prototype.loadTextureImage = function ( textureIndex, source, loader ) { + + var parser = this; + var json = this.json; + var options = this.options; + + var textureDef = json.textures[ textureIndex ]; + + var URL = self.URL || self.webkitURL; + + var sourceURI = source.uri; + var isObjectURL = false; + var hasAlpha = true; + + if ( source.mimeType === 'image/jpeg' ) hasAlpha = false; + + if ( source.bufferView !== undefined ) { + + // Load binary image data from bufferView, if provided. + + sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) { + + if ( source.mimeType === 'image/png' ) { + + // Inspect the PNG 'IHDR' chunk to determine whether the image could have an + // alpha channel. This check is conservative — the image could have an alpha + // channel with all values == 1, and the indexed type (colorType == 3) only + // sometimes contains alpha. + // + // https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header + var colorType = new DataView( bufferView, 25, 1 ).getUint8( 0, false ); + hasAlpha = colorType === 6 || colorType === 4 || colorType === 3; + + } + + isObjectURL = true; + var blob = new Blob( [ bufferView ], { type: source.mimeType } ); + sourceURI = URL.createObjectURL( blob ); + return sourceURI; + + } ); + + } else if ( source.uri === undefined ) { + + throw new Error( 'THREE.GLTFLoader: Image ' + textureIndex + ' is missing URI and bufferView' ); + + } + + return Promise.resolve( sourceURI ).then( function ( sourceURI ) { + + return new Promise( function ( resolve, reject ) { + + var onLoad = resolve; + + if ( loader.isImageBitmapLoader === true ) { + + onLoad = function ( imageBitmap ) { + + resolve( new CanvasTexture( imageBitmap ) ); + + }; + + } + + loader.load( resolveURL( sourceURI, options.path ), onLoad, undefined, reject ); + + } ); + + } ).then( function ( texture ) { + + // Clean up resources and configure Texture. + + if ( isObjectURL === true ) { + + URL.revokeObjectURL( sourceURI ); + + } + + texture.flipY = false; + + if ( textureDef.name ) texture.name = textureDef.name; + + // When there is definitely no alpha channel in the texture, set RGBFormat to save space. + if ( ! hasAlpha ) texture.format = RGBFormat; + + var samplers = json.samplers || {}; + var sampler = samplers[ textureDef.sampler ] || {}; + + texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || LinearFilter; + texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || LinearMipmapLinearFilter; + texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || RepeatWrapping; + texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || RepeatWrapping; + + parser.associations.set( texture, { + type: 'textures', + index: textureIndex + } ); + + return texture; + + } ); + + }; + + /** + * Asynchronously assigns a texture to the given material parameters. + * @param {Object} materialParams + * @param {string} mapName + * @param {Object} mapDef + * @return {Promise} + */ + GLTFParser.prototype.assignTexture = function ( materialParams, mapName, mapDef ) { + + var parser = this; + + return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) { + + // Materials sample aoMap from UV set 1 and other maps from UV set 0 - this can't be configured + // However, we will copy UV set 0 to UV set 1 on demand for aoMap + if ( mapDef.texCoord !== undefined && mapDef.texCoord != 0 && ! ( mapName === 'aoMap' && mapDef.texCoord == 1 ) ) { + + console.warn( 'THREE.GLTFLoader: Custom UV set ' + mapDef.texCoord + ' for texture ' + mapName + ' not yet supported.' ); + + } + + if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { + + var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; + + if ( transform ) { + + var gltfReference = parser.associations.get( texture ); + texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); + parser.associations.set( texture, gltfReference ); + + } + + } + + materialParams[ mapName ] = texture; + + } ); + + }; + + /** + * Assigns final material to a Mesh, Line, or Points instance. The instance + * already has a material (generated from the glTF material options alone) + * but reuse of the same glTF material may require multiple threejs materials + * to accommodate different primitive types, defines, etc. New materials will + * be created if necessary, and reused from a cache. + * @param {Object3D} mesh Mesh, Line, or Points instance. + */ + GLTFParser.prototype.assignFinalMaterial = function ( mesh ) { + + var geometry = mesh.geometry; + var material = mesh.material; + + var useVertexTangents = geometry.attributes.tangent !== undefined; + var useVertexColors = geometry.attributes.color !== undefined; + var useFlatShading = geometry.attributes.normal === undefined; + var useSkinning = mesh.isSkinnedMesh === true; + var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0; + var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined; + + if ( mesh.isPoints ) { + + var cacheKey = 'PointsMaterial:' + material.uuid; + + var pointsMaterial = this.cache.get( cacheKey ); + + if ( ! pointsMaterial ) { + + pointsMaterial = new PointsMaterial(); + Material.prototype.copy.call( pointsMaterial, material ); + pointsMaterial.color.copy( material.color ); + pointsMaterial.map = material.map; + pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px + + this.cache.add( cacheKey, pointsMaterial ); + + } + + material = pointsMaterial; + + } else if ( mesh.isLine ) { + + var cacheKey = 'LineBasicMaterial:' + material.uuid; + + var lineMaterial = this.cache.get( cacheKey ); + + if ( ! lineMaterial ) { + + lineMaterial = new LineBasicMaterial(); + Material.prototype.copy.call( lineMaterial, material ); + lineMaterial.color.copy( material.color ); + + this.cache.add( cacheKey, lineMaterial ); + + } + + material = lineMaterial; + + } + + // Clone the material if it will be modified + if ( useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets ) { + + var cacheKey = 'ClonedMaterial:' + material.uuid + ':'; + + if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:'; + if ( useSkinning ) cacheKey += 'skinning:'; + if ( useVertexTangents ) cacheKey += 'vertex-tangents:'; + if ( useVertexColors ) cacheKey += 'vertex-colors:'; + if ( useFlatShading ) cacheKey += 'flat-shading:'; + if ( useMorphTargets ) cacheKey += 'morph-targets:'; + if ( useMorphNormals ) cacheKey += 'morph-normals:'; + + var cachedMaterial = this.cache.get( cacheKey ); + + if ( ! cachedMaterial ) { + + cachedMaterial = material.clone(); + + if ( useSkinning ) cachedMaterial.skinning = true; + if ( useVertexColors ) cachedMaterial.vertexColors = true; + if ( useFlatShading ) cachedMaterial.flatShading = true; + if ( useMorphTargets ) cachedMaterial.morphTargets = true; + if ( useMorphNormals ) cachedMaterial.morphNormals = true; + + if ( useVertexTangents ) { + + cachedMaterial.vertexTangents = true; + + // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 + if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1; + if ( cachedMaterial.clearcoatNormalScale ) cachedMaterial.clearcoatNormalScale.y *= - 1; + + } + + this.cache.add( cacheKey, cachedMaterial ); + + this.associations.set( cachedMaterial, this.associations.get( material ) ); + + } + + material = cachedMaterial; + + } + + // workarounds for mesh and geometry + + if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) { + + geometry.setAttribute( 'uv2', geometry.attributes.uv ); + + } + + mesh.material = material; + + }; + + GLTFParser.prototype.getMaterialType = function ( /* materialIndex */ ) { + + return MeshStandardMaterial; + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials + * @param {number} materialIndex + * @return {Promise} + */ + GLTFParser.prototype.loadMaterial = function ( materialIndex ) { + + var parser = this; + var json = this.json; + var extensions = this.extensions; + var materialDef = json.materials[ materialIndex ]; + + var materialType; + var materialParams = {}; + var materialExtensions = materialDef.extensions || {}; + + var pending = []; + + if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { + + var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; + materialType = sgExtension.getMaterialType(); + pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) ); + + } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { + + var kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; + materialType = kmuExtension.getMaterialType(); + pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); + + } else { + + // Specification: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material + + var metallicRoughness = materialDef.pbrMetallicRoughness || {}; + + materialParams.color = new Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + + if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { + + var array = metallicRoughness.baseColorFactor; + + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; + + } + + if ( metallicRoughness.baseColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) ); + + } + + materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0; + materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0; + + if ( metallicRoughness.metallicRoughnessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) ); + pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) ); + + } + + materialType = this._invokeOne( function ( ext ) { + + return ext.getMaterialType && ext.getMaterialType( materialIndex ); + + } ); + + pending.push( Promise.all( this._invokeAll( function ( ext ) { + + return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams ); + + } ) ) ); + + } + + if ( materialDef.doubleSided === true ) { + + materialParams.side = DoubleSide; + + } + + var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; + + if ( alphaMode === ALPHA_MODES.BLEND ) { + + materialParams.transparent = true; + + // See: https://github.com/mrdoob/three.js/issues/17706 + materialParams.depthWrite = false; + + } else { + + materialParams.transparent = false; + + if ( alphaMode === ALPHA_MODES.MASK ) { + + materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; + + } + + } + + if ( materialDef.normalTexture !== undefined && materialType !== MeshBasicMaterial ) { + + pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); + + // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 + materialParams.normalScale = new Vector2( 1, - 1 ); + + if ( materialDef.normalTexture.scale !== undefined ) { + + materialParams.normalScale.set( materialDef.normalTexture.scale, - materialDef.normalTexture.scale ); + + } + + } + + if ( materialDef.occlusionTexture !== undefined && materialType !== MeshBasicMaterial ) { + + pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) ); + + if ( materialDef.occlusionTexture.strength !== undefined ) { + + materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; + + } + + } + + if ( materialDef.emissiveFactor !== undefined && materialType !== MeshBasicMaterial ) { + + materialParams.emissive = new Color().fromArray( materialDef.emissiveFactor ); + + } + + if ( materialDef.emissiveTexture !== undefined && materialType !== MeshBasicMaterial ) { + + pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture ) ); + + } + + return Promise.all( pending ).then( function () { + + var material; + + if ( materialType === GLTFMeshStandardSGMaterial ) { + + material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); + + } else { + + material = new materialType( materialParams ); + + } + + if ( materialDef.name ) material.name = materialDef.name; + + // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding. + if ( material.map ) material.map.encoding = sRGBEncoding; + if ( material.emissiveMap ) material.emissiveMap.encoding = sRGBEncoding; + + assignExtrasToUserData( material, materialDef ); + + parser.associations.set( material, { type: 'materials', index: materialIndex } ); + + if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); + + return material; + + } ); + + }; + + /** When Object3D instances are targeted by animation, they need unique names. */ + GLTFParser.prototype.createUniqueName = function ( originalName ) { + + var sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' ); + + var name = sanitizedName; + + for ( var i = 1; this.nodeNamesUsed[ name ]; ++ i ) { + + name = sanitizedName + '_' + i; + + } + + this.nodeNamesUsed[ name ] = true; + + return name; + + }; + + /** + * @param {BufferGeometry} geometry + * @param {GLTF.Primitive} primitiveDef + * @param {GLTFParser} parser + */ + function computeBounds( geometry, primitiveDef, parser ) { + + var attributes = primitiveDef.attributes; + + var box = new Box3(); + + if ( attributes.POSITION !== undefined ) { + + var accessor = parser.json.accessors[ attributes.POSITION ]; + + var min = accessor.min; + var max = accessor.max; + + // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. + + if ( min !== undefined && max !== undefined ) { + + box.set( + new Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), + new Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) ); + + } else { + + console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); + + return; + + } + + } else { + + return; + + } + + var targets = primitiveDef.targets; + + if ( targets !== undefined ) { + + var maxDisplacement = new Vector3(); + var vector = new Vector3(); + + for ( var i = 0, il = targets.length; i < il; i ++ ) { + + var target = targets[ i ]; + + if ( target.POSITION !== undefined ) { + + var accessor = parser.json.accessors[ target.POSITION ]; + var min = accessor.min; + var max = accessor.max; + + // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. + + if ( min !== undefined && max !== undefined ) { + + // we need to get max of absolute components because target weight is [-1,1] + vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) ); + vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) ); + vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) ); + + // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative + // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets + // are used to implement key-frame animations and as such only two are active at a time - this results in very large + // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. + maxDisplacement.max( vector ); + + } else { + + console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); + + } + + } + + } + + // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. + box.expandByVector( maxDisplacement ); + + } + + geometry.boundingBox = box; + + var sphere = new Sphere(); + + box.getCenter( sphere.center ); + sphere.radius = box.min.distanceTo( box.max ) / 2; + + geometry.boundingSphere = sphere; + + } + + /** + * @param {BufferGeometry} geometry + * @param {GLTF.Primitive} primitiveDef + * @param {GLTFParser} parser + * @return {Promise} + */ + function addPrimitiveAttributes( geometry, primitiveDef, parser ) { + + var attributes = primitiveDef.attributes; + + var pending = []; + + function assignAttributeAccessor( accessorIndex, attributeName ) { + + return parser.getDependency( 'accessor', accessorIndex ) + .then( function ( accessor ) { + + geometry.setAttribute( attributeName, accessor ); + + } ); + + } + + for ( var gltfAttributeName in attributes ) { + + var threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); + + // Skip attributes already provided by e.g. Draco extension. + if ( threeAttributeName in geometry.attributes ) continue; + + pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) ); + + } + + if ( primitiveDef.indices !== undefined && ! geometry.index ) { + + var accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) { + + geometry.setIndex( accessor ); + + } ); + + pending.push( accessor ); + + } + + assignExtrasToUserData( geometry, primitiveDef ); + + computeBounds( geometry, primitiveDef, parser ); + + return Promise.all( pending ).then( function () { + + return primitiveDef.targets !== undefined + ? addMorphTargets( geometry, primitiveDef.targets, parser ) + : geometry; + + } ); + + } + + /** + * @param {BufferGeometry} geometry + * @param {Number} drawMode + * @return {BufferGeometry} + */ + function toTrianglesDrawMode( geometry, drawMode ) { + + var index = geometry.getIndex(); + + // generate index if not present + + if ( index === null ) { + + var indices = []; + + var position = geometry.getAttribute( 'position' ); + + if ( position !== undefined ) { + + for ( var i = 0; i < position.count; i ++ ) { + + indices.push( i ); + + } + + geometry.setIndex( indices ); + index = geometry.getIndex(); + + } else { + + console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' ); + return geometry; + + } + + } + + // + + var numberOfTriangles = index.count - 2; + var newIndices = []; + + if ( drawMode === TriangleFanDrawMode ) { + + // gl.TRIANGLE_FAN + + for ( var i = 1; i <= numberOfTriangles; i ++ ) { + + newIndices.push( index.getX( 0 ) ); + newIndices.push( index.getX( i ) ); + newIndices.push( index.getX( i + 1 ) ); + + } + + } else { + + // gl.TRIANGLE_STRIP + + for ( var i = 0; i < numberOfTriangles; i ++ ) { + + if ( i % 2 === 0 ) { + + newIndices.push( index.getX( i ) ); + newIndices.push( index.getX( i + 1 ) ); + newIndices.push( index.getX( i + 2 ) ); + + + } else { + + newIndices.push( index.getX( i + 2 ) ); + newIndices.push( index.getX( i + 1 ) ); + newIndices.push( index.getX( i ) ); + + } + + } + + } + + if ( ( newIndices.length / 3 ) !== numberOfTriangles ) { + + console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' ); + + } + + // build final geometry + + var newGeometry = geometry.clone(); + newGeometry.setIndex( newIndices ); + + return newGeometry; + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry + * + * Creates BufferGeometries from primitives. + * + * @param {Array} primitives + * @return {Promise>} + */ + GLTFParser.prototype.loadGeometries = function ( primitives ) { + + var parser = this; + var extensions = this.extensions; + var cache = this.primitiveCache; + + function createDracoPrimitive( primitive ) { + + return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] + .decodePrimitive( primitive, parser ) + .then( function ( geometry ) { + + return addPrimitiveAttributes( geometry, primitive, parser ); + + } ); + + } + + var pending = []; + + for ( var i = 0, il = primitives.length; i < il; i ++ ) { + + var primitive = primitives[ i ]; + var cacheKey = createPrimitiveKey( primitive ); + + // See if we've already created this geometry + var cached = cache[ cacheKey ]; + + if ( cached ) { + + // Use the cached geometry if it exists + pending.push( cached.promise ); + + } else { + + var geometryPromise; + + if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { + + // Use DRACO geometry if available + geometryPromise = createDracoPrimitive( primitive ); + + } else { + + // Otherwise create a new geometry + geometryPromise = addPrimitiveAttributes( new BufferGeometry(), primitive, parser ); + + } + + // Cache this geometry + cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise }; + + pending.push( geometryPromise ); + + } + + } + + return Promise.all( pending ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes + * @param {number} meshIndex + * @return {Promise} + */ + GLTFParser.prototype.loadMesh = function ( meshIndex ) { + + var parser = this; + var json = this.json; + var extensions = this.extensions; + + var meshDef = json.meshes[ meshIndex ]; + var primitives = meshDef.primitives; + + var pending = []; + + for ( var i = 0, il = primitives.length; i < il; i ++ ) { + + var material = primitives[ i ].material === undefined + ? createDefaultMaterial( this.cache ) + : this.getDependency( 'material', primitives[ i ].material ); + + pending.push( material ); + + } + + pending.push( parser.loadGeometries( primitives ) ); + + return Promise.all( pending ).then( function ( results ) { + + var materials = results.slice( 0, results.length - 1 ); + var geometries = results[ results.length - 1 ]; + + var meshes = []; + + for ( var i = 0, il = geometries.length; i < il; i ++ ) { + + var geometry = geometries[ i ]; + var primitive = primitives[ i ]; + + // 1. create Mesh + + var mesh; + + var material = materials[ i ]; + + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || + primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || + primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || + primitive.mode === undefined ) { + + // .isSkinnedMesh isn't in glTF spec. See ._markDefs() + mesh = meshDef.isSkinnedMesh === true + ? new SkinnedMesh( geometry, material ) + : new Mesh( geometry, material ); + + if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) { + + // we normalize floating point skin weight array to fix malformed assets (see #15319) + // it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs + mesh.normalizeSkinWeights(); + + } + + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { + + mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleStripDrawMode ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { + + mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleFanDrawMode ); + + } + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { + + mesh = new LineSegments( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { + + mesh = new Line( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { + + mesh = new LineLoop( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { + + mesh = new Points( geometry, material ); + + } else { + + throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode ); + + } + + if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) { + + updateMorphTargets( mesh, meshDef ); + + } + + mesh.name = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) ); + + assignExtrasToUserData( mesh, meshDef ); + + if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive ); + + parser.assignFinalMaterial( mesh ); + + meshes.push( mesh ); + + } + + if ( meshes.length === 1 ) { + + return meshes[ 0 ]; + + } + + var group = new Group(); + + for ( var i = 0, il = meshes.length; i < il; i ++ ) { + + group.add( meshes[ i ] ); + + } + + return group; + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras + * @param {number} cameraIndex + * @return {Promise} + */ + GLTFParser.prototype.loadCamera = function ( cameraIndex ) { + + var camera; + var cameraDef = this.json.cameras[ cameraIndex ]; + var params = cameraDef[ cameraDef.type ]; + + if ( ! params ) { + + console.warn( 'THREE.GLTFLoader: Missing camera parameters.' ); + return; + + } + + if ( cameraDef.type === 'perspective' ) { + + camera = new PerspectiveCamera( MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 ); + + } else if ( cameraDef.type === 'orthographic' ) { + + camera = new OrthographicCamera( - params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar ); + + } + + if ( cameraDef.name ) camera.name = this.createUniqueName( cameraDef.name ); + + assignExtrasToUserData( camera, cameraDef ); + + return Promise.resolve( camera ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins + * @param {number} skinIndex + * @return {Promise} + */ + GLTFParser.prototype.loadSkin = function ( skinIndex ) { + + var skinDef = this.json.skins[ skinIndex ]; + + var skinEntry = { joints: skinDef.joints }; + + if ( skinDef.inverseBindMatrices === undefined ) { + + return Promise.resolve( skinEntry ); + + } + + return this.getDependency( 'accessor', skinDef.inverseBindMatrices ).then( function ( accessor ) { + + skinEntry.inverseBindMatrices = accessor; + + return skinEntry; + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations + * @param {number} animationIndex + * @return {Promise} + */ + GLTFParser.prototype.loadAnimation = function ( animationIndex ) { + + var json = this.json; + + var animationDef = json.animations[ animationIndex ]; + + var pendingNodes = []; + var pendingInputAccessors = []; + var pendingOutputAccessors = []; + var pendingSamplers = []; + var pendingTargets = []; + + for ( var i = 0, il = animationDef.channels.length; i < il; i ++ ) { + + var channel = animationDef.channels[ i ]; + var sampler = animationDef.samplers[ channel.sampler ]; + var target = channel.target; + var name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated. + var input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; + var output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; + + pendingNodes.push( this.getDependency( 'node', name ) ); + pendingInputAccessors.push( this.getDependency( 'accessor', input ) ); + pendingOutputAccessors.push( this.getDependency( 'accessor', output ) ); + pendingSamplers.push( sampler ); + pendingTargets.push( target ); + + } + + return Promise.all( [ + + Promise.all( pendingNodes ), + Promise.all( pendingInputAccessors ), + Promise.all( pendingOutputAccessors ), + Promise.all( pendingSamplers ), + Promise.all( pendingTargets ) + + ] ).then( function ( dependencies ) { + + var nodes = dependencies[ 0 ]; + var inputAccessors = dependencies[ 1 ]; + var outputAccessors = dependencies[ 2 ]; + var samplers = dependencies[ 3 ]; + var targets = dependencies[ 4 ]; + + var tracks = []; + + for ( var i = 0, il = nodes.length; i < il; i ++ ) { + + var node = nodes[ i ]; + var inputAccessor = inputAccessors[ i ]; + var outputAccessor = outputAccessors[ i ]; + var sampler = samplers[ i ]; + var target = targets[ i ]; + + if ( node === undefined ) continue; + + node.updateMatrix(); + node.matrixAutoUpdate = true; + + var TypedKeyframeTrack; + + switch ( PATH_PROPERTIES[ target.path ] ) { + + case PATH_PROPERTIES.weights: + + TypedKeyframeTrack = NumberKeyframeTrack; + break; + + case PATH_PROPERTIES.rotation: + + TypedKeyframeTrack = QuaternionKeyframeTrack; + break; + + case PATH_PROPERTIES.position: + case PATH_PROPERTIES.scale: + default: + + TypedKeyframeTrack = VectorKeyframeTrack; + break; + + } + + var targetName = node.name ? node.name : node.uuid; + + var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear; + + var targetNames = []; + + if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { + + // Node may be a Group (glTF mesh with several primitives) or a Mesh. + node.traverse( function ( object ) { + + if ( object.isMesh === true && object.morphTargetInfluences ) { + + targetNames.push( object.name ? object.name : object.uuid ); + + } + + } ); + + } else { + + targetNames.push( targetName ); + + } + + var outputArray = outputAccessor.array; + + if ( outputAccessor.normalized ) { + + var scale; + + if ( outputArray.constructor === Int8Array ) { + + scale = 1 / 127; + + } else if ( outputArray.constructor === Uint8Array ) { + + scale = 1 / 255; + + } else if ( outputArray.constructor == Int16Array ) { + + scale = 1 / 32767; + + } else if ( outputArray.constructor === Uint16Array ) { + + scale = 1 / 65535; + + } else { + + throw new Error( 'THREE.GLTFLoader: Unsupported output accessor component type.' ); + + } + + var scaled = new Float32Array( outputArray.length ); + + for ( var j = 0, jl = outputArray.length; j < jl; j ++ ) { + + scaled[ j ] = outputArray[ j ] * scale; + + } + + outputArray = scaled; + + } + + for ( var j = 0, jl = targetNames.length; j < jl; j ++ ) { + + var track = new TypedKeyframeTrack( + targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], + inputAccessor.array, + outputArray, + interpolation + ); + + // Override interpolation with custom factory method. + if ( sampler.interpolation === 'CUBICSPLINE' ) { + + track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) { + + // A CUBICSPLINE keyframe in glTF has three output values for each input value, + // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() + // must be divided by three to get the interpolant's sampleSize argument. + + return new GLTFCubicSplineInterpolant( this.times, this.values, this.getValueSize() / 3, result ); + + }; + + // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. + track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; + + } + + tracks.push( track ); + + } + + } + + var name = animationDef.name ? animationDef.name : 'animation_' + animationIndex; + + return new AnimationClip( name, undefined, tracks ); + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy + * @param {number} nodeIndex + * @return {Promise} + */ + GLTFParser.prototype.loadNode = function ( nodeIndex ) { + + var json = this.json; + var extensions = this.extensions; + var parser = this; + + var nodeDef = json.nodes[ nodeIndex ]; + + // reserve node's name before its dependencies, so the root has the intended name. + var nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : ''; + + return ( function () { + + var pending = []; + + if ( nodeDef.mesh !== undefined ) { + + pending.push( parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { + + var node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh ); + + // if weights are provided on the node, override weights on the mesh. + if ( nodeDef.weights !== undefined ) { + + node.traverse( function ( o ) { + + if ( ! o.isMesh ) return; + + for ( var i = 0, il = nodeDef.weights.length; i < il; i ++ ) { + + o.morphTargetInfluences[ i ] = nodeDef.weights[ i ]; + + } + + } ); + + } + + return node; + + } ) ); + + } + + if ( nodeDef.camera !== undefined ) { + + pending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) { + + return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera ); + + } ) ); + + } + + parser._invokeAll( function ( ext ) { + + return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex ); + + } ).forEach( function ( promise ) { + + pending.push( promise ); + + } ); + + return Promise.all( pending ); + + }() ).then( function ( objects ) { + + var node; + + // .isBone isn't in glTF spec. See ._markDefs + if ( nodeDef.isBone === true ) { + + node = new Bone(); + + } else if ( objects.length > 1 ) { + + node = new Group(); + + } else if ( objects.length === 1 ) { + + node = objects[ 0 ]; + + } else { + + node = new Object3D(); + + } + + if ( node !== objects[ 0 ] ) { + + for ( var i = 0, il = objects.length; i < il; i ++ ) { + + node.add( objects[ i ] ); + + } + + } + + if ( nodeDef.name ) { + + node.userData.name = nodeDef.name; + node.name = nodeName; + + } + + assignExtrasToUserData( node, nodeDef ); + + if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); + + if ( nodeDef.matrix !== undefined ) { + + var matrix = new Matrix4(); + matrix.fromArray( nodeDef.matrix ); + node.applyMatrix4( matrix ); + + } else { + + if ( nodeDef.translation !== undefined ) { + + node.position.fromArray( nodeDef.translation ); + + } + + if ( nodeDef.rotation !== undefined ) { + + node.quaternion.fromArray( nodeDef.rotation ); + + } + + if ( nodeDef.scale !== undefined ) { + + node.scale.fromArray( nodeDef.scale ); + + } + + } + + parser.associations.set( node, { type: 'nodes', index: nodeIndex } ); + + return node; + + } ); + + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes + * @param {number} sceneIndex + * @return {Promise} + */ + GLTFParser.prototype.loadScene = function () { + + // scene node hierachy builder + + function buildNodeHierachy( nodeId, parentObject, json, parser ) { + + var nodeDef = json.nodes[ nodeId ]; + + return parser.getDependency( 'node', nodeId ).then( function ( node ) { + + if ( nodeDef.skin === undefined ) return node; + + // build skeleton here as well + + var skinEntry; + + return parser.getDependency( 'skin', nodeDef.skin ).then( function ( skin ) { + + skinEntry = skin; + + var pendingJoints = []; + + for ( var i = 0, il = skinEntry.joints.length; i < il; i ++ ) { + + pendingJoints.push( parser.getDependency( 'node', skinEntry.joints[ i ] ) ); + + } + + return Promise.all( pendingJoints ); + + } ).then( function ( jointNodes ) { + + node.traverse( function ( mesh ) { + + if ( ! mesh.isMesh ) return; + + var bones = []; + var boneInverses = []; + + for ( var j = 0, jl = jointNodes.length; j < jl; j ++ ) { + + var jointNode = jointNodes[ j ]; + + if ( jointNode ) { + + bones.push( jointNode ); + + var mat = new Matrix4(); + + if ( skinEntry.inverseBindMatrices !== undefined ) { + + mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 ); + + } + + boneInverses.push( mat ); + + } else { + + console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[ j ] ); + + } + + } + + mesh.bind( new Skeleton( bones, boneInverses ), mesh.matrixWorld ); + + } ); + + return node; + + } ); + + } ).then( function ( node ) { + + // build node hierachy + + parentObject.add( node ); + + var pending = []; + + if ( nodeDef.children ) { + + var children = nodeDef.children; + + for ( var i = 0, il = children.length; i < il; i ++ ) { + + var child = children[ i ]; + pending.push( buildNodeHierachy( child, node, json, parser ) ); + + } + + } + + return Promise.all( pending ); + + } ); + + } + + return function loadScene( sceneIndex ) { + + var json = this.json; + var extensions = this.extensions; + var sceneDef = this.json.scenes[ sceneIndex ]; + var parser = this; + + // Loader returns Group, not Scene. + // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 + var scene = new Group(); + if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); + + assignExtrasToUserData( scene, sceneDef ); + + if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); + + var nodeIds = sceneDef.nodes || []; + + var pending = []; + + for ( var i = 0, il = nodeIds.length; i < il; i ++ ) { + + pending.push( buildNodeHierachy( nodeIds[ i ], scene, json, parser ) ); + + } + + return Promise.all( pending ).then( function () { + + return scene; + + } ); + + }; + + }(); + + return GLTFLoader; + +} )(); + +export { GLTFLoader }; diff --git a/templates/webgl/vendor/OrbitControls.js b/templates/webgl/vendor/OrbitControls.js new file mode 100644 index 00000000..95269dd8 --- /dev/null +++ b/templates/webgl/vendor/OrbitControls.js @@ -0,0 +1,1229 @@ +import { + EventDispatcher, + MOUSE, + Quaternion, + Spherical, + TOUCH, + Vector2, + Vector3 +} from './three.module.min.js'; + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + +var OrbitControls = function ( object, domElement ) { + + if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + + this.object = object; + this.domElement = domElement; + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new Vector3(); + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.05; + + // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + // Mouse buttons + this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; + + // Touch fingers + this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // the target DOM element for key events + this._domElementKeyEvents = null; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.listenToKeyEvents = function ( domElement ) { + + domElement.addEventListener( 'keydown', onKeyDown ); + this._domElementKeyEvents = domElement; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + var offset = new Vector3(); + + // so camera.up is the orbit axis + var quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); + var quatInverse = quat.clone().invert(); + + var lastPosition = new Vector3(); + var lastQuaternion = new Quaternion(); + + var twoPI = 2 * Math.PI; + + return function update() { + + var position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + spherical.setFromVector3( offset ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + if ( scope.enableDamping ) { + + spherical.theta += sphericalDelta.theta * scope.dampingFactor; + spherical.phi += sphericalDelta.phi * scope.dampingFactor; + + } else { + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + } + + // restrict theta to be between desired limits + + var min = scope.minAzimuthAngle; + var max = scope.maxAzimuthAngle; + + if ( isFinite( min ) && isFinite( max ) ) { + + if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; + + if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; + + if ( min <= max ) { + + spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); + + } else { + + spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, spherical.theta ) : + Math.min( max, spherical.theta ); + + } + + } + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + // move target to panned location + + if ( scope.enableDamping === true ) { + + scope.target.addScaledVector( panOffset, scope.dampingFactor ); + + } else { + + scope.target.add( panOffset ); + + } + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( scope.target ).add( offset ); + + scope.object.lookAt( scope.target ); + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; + + // 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 || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + + scope.domElement.removeEventListener( 'touchstart', onTouchStart ); + scope.domElement.removeEventListener( 'touchend', onTouchEnd ); + scope.domElement.removeEventListener( 'touchmove', onTouchMove ); + + scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + + + if ( scope._domElementKeyEvents !== null ) { + + scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); + + } + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + var scope = this; + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start' }; + var endEvent = { type: 'end' }; + + var STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 + }; + + var state = STATE.NONE; + + var EPS = 0.000001; + + // current position in spherical coordinates + var spherical = new Spherical(); + var sphericalDelta = new Spherical(); + + var scale = 1; + var panOffset = new Vector3(); + var zoomChanged = false; + + var rotateStart = new Vector2(); + var rotateEnd = new Vector2(); + var rotateDelta = new Vector2(); + + var panStart = new Vector2(); + var panEnd = new Vector2(); + var panDelta = new Vector2(); + + var dollyStart = new Vector2(); + var dollyEnd = new Vector2(); + var dollyDelta = new Vector2(); + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + var panLeft = function () { + + var v = new Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + var panUp = function () { + + var v = new Vector3(); + + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + var pan = function () { + + var offset = new Vector3(); + + return function pan( deltaX, deltaY ) { + + var element = scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + var position = scope.object.position; + offset.copy( position ).sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + rotateEnd.set( event.clientX, event.clientY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyIn( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseUp( /*event*/ ) { + + // no-op + + } + + function handleMouseWheel( event ) { + + if ( event.deltaY < 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyOut( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + var needsUpdate = false; + + switch ( event.keyCode ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + + scope.update(); + + } + + + } + + function handleTouchStartRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + rotateStart.set( x, y ); + + } + + } + + function handleTouchStartPan( event ) { + + if ( event.touches.length == 1 ) { + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchStartDolly( event ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + function handleTouchStartDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + + if ( scope.enablePan ) handleTouchStartPan( event ); + + } + + function handleTouchStartDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + + if ( scope.enableRotate ) handleTouchStartRotate( event ); + + } + + function handleTouchMoveRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + rotateEnd.set( x, y ); + + } + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + } + + function handleTouchMovePan( event ) { + + if ( event.touches.length == 1 ) { + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panEnd.set( x, y ); + + } + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + function handleTouchMoveDolly( event ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + + dollyOut( dollyDelta.y ); + + dollyStart.copy( dollyEnd ); + + } + + function handleTouchMoveDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enablePan ) handleTouchMovePan( event ); + + } + + function handleTouchMoveDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enableRotate ) handleTouchMoveRotate( event ); + + } + + function handleTouchEnd( /*event*/ ) { + + // no-op + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseDown( event ); + break; + + // TODO touch + + } + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseMove( event ); + break; + + // TODO touch + + } + + } + + function onPointerUp( event ) { + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseUp( event ); + break; + + // TODO touch + + } + + } + + function onMouseDown( event ) { + + // Prevent the browser from scrolling. + event.preventDefault(); + + // Manually set the focus since calling preventDefault above + // prevents the browser from setting it automatically. + + scope.domElement.focus ? scope.domElement.focus() : window.focus(); + + var mouseAction; + + switch ( event.button ) { + + case 0: + + mouseAction = scope.mouseButtons.LEFT; + break; + + case 1: + + mouseAction = scope.mouseButtons.MIDDLE; + break; + + case 2: + + mouseAction = scope.mouseButtons.RIGHT; + break; + + default: + + mouseAction = - 1; + + } + + switch ( mouseAction ) { + + case MOUSE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case MOUSE.ROTATE: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } else { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } + + break; + + case MOUSE.PAN: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } else { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp ); + + scope.dispatchEvent( startEvent ); + + } + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseUp( event ) { + + scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + + if ( scope.enabled === false ) return; + + handleMouseUp( event ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; + + event.preventDefault(); + event.stopPropagation(); + + scope.dispatchEvent( startEvent ); + + handleMouseWheel( event ); + + scope.dispatchEvent( endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); // prevent scrolling + + switch ( event.touches.length ) { + + case 1: + + switch ( scope.touches.ONE ) { + + case TOUCH.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate( event ); + + state = STATE.TOUCH_ROTATE; + + break; + + case TOUCH.PAN: + + if ( scope.enablePan === false ) return; + + handleTouchStartPan( event ); + + state = STATE.TOUCH_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + case 2: + + switch ( scope.touches.TWO ) { + + case TOUCH.DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchStartDollyPan( event ); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + case TOUCH.DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchStartDollyRotate( event ); + + state = STATE.TOUCH_DOLLY_ROTATE; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( startEvent ); + + } + + } + + function onTouchMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); // prevent scrolling + event.stopPropagation(); + + switch ( state ) { + + case STATE.TOUCH_ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchMoveRotate( event ); + + scope.update(); + + break; + + case STATE.TOUCH_PAN: + + if ( scope.enablePan === false ) return; + + handleTouchMovePan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchMoveDollyPan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchMoveDollyRotate( event ); + + scope.update(); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onTouchEnd( event ) { + + if ( scope.enabled === false ) return; + + handleTouchEnd( event ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.addEventListener( 'pointerdown', onPointerDown ); + scope.domElement.addEventListener( 'wheel', onMouseWheel ); + + scope.domElement.addEventListener( 'touchstart', onTouchStart ); + scope.domElement.addEventListener( 'touchend', onTouchEnd ); + scope.domElement.addEventListener( 'touchmove', onTouchMove ); + + // force an update at start + + this.update(); + +}; + +OrbitControls.prototype = Object.create( EventDispatcher.prototype ); +OrbitControls.prototype.constructor = OrbitControls; + + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// This is very similar to OrbitControls, another set of touch behavior +// +// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - left mouse, or arrow keys / touch: one-finger move + +var MapControls = function ( object, domElement ) { + + OrbitControls.call( this, object, domElement ); + + this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up + + this.mouseButtons.LEFT = MOUSE.PAN; + this.mouseButtons.RIGHT = MOUSE.ROTATE; + + this.touches.ONE = TOUCH.PAN; + this.touches.TWO = TOUCH.DOLLY_ROTATE; + +}; + +MapControls.prototype = Object.create( EventDispatcher.prototype ); +MapControls.prototype.constructor = MapControls; + +export { OrbitControls, MapControls }; diff --git a/templates/webgl/vendor/three.module.min.js b/templates/webgl/vendor/three.module.min.js new file mode 100644 index 00000000..8b785e9f --- /dev/null +++ b/templates/webgl/vendor/three.module.min.js @@ -0,0 +1,8 @@ +/** + * Minified by jsDelivr using Terser v5.39.0. + * Original file: /npm/three@0.126.1/build/three.module.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +const REVISION="126",MOUSE={LEFT:0,MIDDLE:1,RIGHT:2,ROTATE:0,DOLLY:1,PAN:2},TOUCH={ROTATE:0,PAN:1,DOLLY_PAN:2,DOLLY_ROTATE:3},CullFaceNone=0,CullFaceBack=1,CullFaceFront=2,CullFaceFrontBack=3,BasicShadowMap=0,PCFShadowMap=1,PCFSoftShadowMap=2,VSMShadowMap=3,FrontSide=0,BackSide=1,DoubleSide=2,FlatShading=1,SmoothShading=2,NoBlending=0,NormalBlending=1,AdditiveBlending=2,SubtractiveBlending=3,MultiplyBlending=4,CustomBlending=5,AddEquation=100,SubtractEquation=101,ReverseSubtractEquation=102,MinEquation=103,MaxEquation=104,ZeroFactor=200,OneFactor=201,SrcColorFactor=202,OneMinusSrcColorFactor=203,SrcAlphaFactor=204,OneMinusSrcAlphaFactor=205,DstAlphaFactor=206,OneMinusDstAlphaFactor=207,DstColorFactor=208,OneMinusDstColorFactor=209,SrcAlphaSaturateFactor=210,NeverDepth=0,AlwaysDepth=1,LessDepth=2,LessEqualDepth=3,EqualDepth=4,GreaterEqualDepth=5,GreaterDepth=6,NotEqualDepth=7,MultiplyOperation=0,MixOperation=1,AddOperation=2,NoToneMapping=0,LinearToneMapping=1,ReinhardToneMapping=2,CineonToneMapping=3,ACESFilmicToneMapping=4,CustomToneMapping=5,UVMapping=300,CubeReflectionMapping=301,CubeRefractionMapping=302,EquirectangularReflectionMapping=303,EquirectangularRefractionMapping=304,CubeUVReflectionMapping=306,CubeUVRefractionMapping=307,RepeatWrapping=1e3,ClampToEdgeWrapping=1001,MirroredRepeatWrapping=1002,NearestFilter=1003,NearestMipmapNearestFilter=1004,NearestMipMapNearestFilter=1004,NearestMipmapLinearFilter=1005,NearestMipMapLinearFilter=1005,LinearFilter=1006,LinearMipmapNearestFilter=1007,LinearMipMapNearestFilter=1007,LinearMipmapLinearFilter=1008,LinearMipMapLinearFilter=1008,UnsignedByteType=1009,ByteType=1010,ShortType=1011,UnsignedShortType=1012,IntType=1013,UnsignedIntType=1014,FloatType=1015,HalfFloatType=1016,UnsignedShort4444Type=1017,UnsignedShort5551Type=1018,UnsignedShort565Type=1019,UnsignedInt248Type=1020,AlphaFormat=1021,RGBFormat=1022,RGBAFormat=1023,LuminanceFormat=1024,LuminanceAlphaFormat=1025,RGBEFormat=1023,DepthFormat=1026,DepthStencilFormat=1027,RedFormat=1028,RedIntegerFormat=1029,RGFormat=1030,RGIntegerFormat=1031,RGBIntegerFormat=1032,RGBAIntegerFormat=1033,RGB_S3TC_DXT1_Format=33776,RGBA_S3TC_DXT1_Format=33777,RGBA_S3TC_DXT3_Format=33778,RGBA_S3TC_DXT5_Format=33779,RGB_PVRTC_4BPPV1_Format=35840,RGB_PVRTC_2BPPV1_Format=35841,RGBA_PVRTC_4BPPV1_Format=35842,RGBA_PVRTC_2BPPV1_Format=35843,RGB_ETC1_Format=36196,RGB_ETC2_Format=37492,RGBA_ETC2_EAC_Format=37496,RGBA_ASTC_4x4_Format=37808,RGBA_ASTC_5x4_Format=37809,RGBA_ASTC_5x5_Format=37810,RGBA_ASTC_6x5_Format=37811,RGBA_ASTC_6x6_Format=37812,RGBA_ASTC_8x5_Format=37813,RGBA_ASTC_8x6_Format=37814,RGBA_ASTC_8x8_Format=37815,RGBA_ASTC_10x5_Format=37816,RGBA_ASTC_10x6_Format=37817,RGBA_ASTC_10x8_Format=37818,RGBA_ASTC_10x10_Format=37819,RGBA_ASTC_12x10_Format=37820,RGBA_ASTC_12x12_Format=37821,RGBA_BPTC_Format=36492,SRGB8_ALPHA8_ASTC_4x4_Format=37840,SRGB8_ALPHA8_ASTC_5x4_Format=37841,SRGB8_ALPHA8_ASTC_5x5_Format=37842,SRGB8_ALPHA8_ASTC_6x5_Format=37843,SRGB8_ALPHA8_ASTC_6x6_Format=37844,SRGB8_ALPHA8_ASTC_8x5_Format=37845,SRGB8_ALPHA8_ASTC_8x6_Format=37846,SRGB8_ALPHA8_ASTC_8x8_Format=37847,SRGB8_ALPHA8_ASTC_10x5_Format=37848,SRGB8_ALPHA8_ASTC_10x6_Format=37849,SRGB8_ALPHA8_ASTC_10x8_Format=37850,SRGB8_ALPHA8_ASTC_10x10_Format=37851,SRGB8_ALPHA8_ASTC_12x10_Format=37852,SRGB8_ALPHA8_ASTC_12x12_Format=37853,LoopOnce=2200,LoopRepeat=2201,LoopPingPong=2202,InterpolateDiscrete=2300,InterpolateLinear=2301,InterpolateSmooth=2302,ZeroCurvatureEnding=2400,ZeroSlopeEnding=2401,WrapAroundEnding=2402,NormalAnimationBlendMode=2500,AdditiveAnimationBlendMode=2501,TrianglesDrawMode=0,TriangleStripDrawMode=1,TriangleFanDrawMode=2,LinearEncoding=3e3,sRGBEncoding=3001,GammaEncoding=3007,RGBEEncoding=3002,LogLuvEncoding=3003,RGBM7Encoding=3004,RGBM16Encoding=3005,RGBDEncoding=3006,BasicDepthPacking=3200,RGBADepthPacking=3201,TangentSpaceNormalMap=0,ObjectSpaceNormalMap=1,ZeroStencilOp=0,KeepStencilOp=7680,ReplaceStencilOp=7681,IncrementStencilOp=7682,DecrementStencilOp=7683,IncrementWrapStencilOp=34055,DecrementWrapStencilOp=34056,InvertStencilOp=5386,NeverStencilFunc=512,LessStencilFunc=513,EqualStencilFunc=514,LessEqualStencilFunc=515,GreaterStencilFunc=516,NotEqualStencilFunc=517,GreaterEqualStencilFunc=518,AlwaysStencilFunc=519,StaticDrawUsage=35044,DynamicDrawUsage=35048,StreamDrawUsage=35040,StaticReadUsage=35045,DynamicReadUsage=35049,StreamReadUsage=35041,StaticCopyUsage=35046,DynamicCopyUsage=35050,StreamCopyUsage=35042,GLSL1="100",GLSL3="300 es";function EventDispatcher(){}Object.assign(EventDispatcher.prototype,{addEventListener:function(e,t){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[e]&&(n[e]=[]),-1===n[e].indexOf(t)&&n[e].push(t)},hasEventListener:function(e,t){if(void 0===this._listeners)return!1;const n=this._listeners;return void 0!==n[e]&&-1!==n[e].indexOf(t)},removeEventListener:function(e,t){if(void 0===this._listeners)return;const n=this._listeners[e];if(void 0!==n){const e=n.indexOf(t);-1!==e&&n.splice(e,1)}},dispatchEvent:function(e){if(void 0===this._listeners)return;const t=this._listeners[e.type];if(void 0!==t){e.target=this;const n=t.slice(0);for(let t=0,r=n.length;t>8&255]+_lut[e>>16&255]+_lut[e>>24&255]+"-"+_lut[255&t]+_lut[t>>8&255]+"-"+_lut[t>>16&15|64]+_lut[t>>24&255]+"-"+_lut[63&n|128]+_lut[n>>8&255]+"-"+_lut[n>>16&255]+_lut[n>>24&255]+_lut[255&r]+_lut[r>>8&255]+_lut[r>>16&255]+_lut[r>>24&255]).toUpperCase()},clamp:function(e,t,n){return Math.max(t,Math.min(n,e))},euclideanModulo:function(e,t){return(e%t+t)%t},mapLinear:function(e,t,n,r,i){return r+(e-t)*(i-r)/(n-t)},lerp:function(e,t,n){return(1-n)*e+n*t},damp:function(e,t,n,r){return MathUtils.lerp(e,t,1-Math.exp(-n*r))},pingpong:function(e,t=1){return t-Math.abs(MathUtils.euclideanModulo(e,2*t)-t)},smoothstep:function(e,t,n){return e<=t?0:e>=n?1:(e=(e-t)/(n-t))*e*(3-2*e)},smootherstep:function(e,t,n){return e<=t?0:e>=n?1:(e=(e-t)/(n-t))*e*e*(e*(6*e-15)+10)},randInt:function(e,t){return e+Math.floor(Math.random()*(t-e+1))},randFloat:function(e,t){return e+Math.random()*(t-e)},randFloatSpread:function(e){return e*(.5-Math.random())},seededRandom:function(e){return void 0!==e&&(_seed=e%2147483647),_seed=16807*_seed%2147483647,(_seed-1)/2147483646},degToRad:function(e){return e*MathUtils.DEG2RAD},radToDeg:function(e){return e*MathUtils.RAD2DEG},isPowerOfTwo:function(e){return!(e&e-1)&&0!==e},ceilPowerOfTwo:function(e){return Math.pow(2,Math.ceil(Math.log(e)/Math.LN2))},floorPowerOfTwo:function(e){return Math.pow(2,Math.floor(Math.log(e)/Math.LN2))},setQuaternionFromProperEuler:function(e,t,n,r,i){const a=Math.cos,o=Math.sin,s=a(n/2),l=o(n/2),c=a((t+r)/2),h=o((t+r)/2),u=a((t-r)/2),d=o((t-r)/2),p=a((r-t)/2),m=o((r-t)/2);switch(i){case"XYX":e.set(s*h,l*u,l*d,s*c);break;case"YZY":e.set(l*d,s*h,l*u,s*c);break;case"ZXZ":e.set(l*u,l*d,s*h,s*c);break;case"XZX":e.set(s*h,l*m,l*p,s*c);break;case"YXY":e.set(l*p,s*h,l*m,s*c);break;case"ZYZ":e.set(l*m,l*p,s*h,s*c);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+i)}}};class Vector2{constructor(e=0,t=0){this.x=e,this.y=t}get width(){return this.x}set width(e){this.x=e}get height(){return this.y}set height(e){this.y=e}set(e,t){return this.x=e,this.y=t,this}setScalar(e){return this.x=e,this.y=e,this}setX(e){return this.x=e,this}setY(e){return this.y=e,this}setComponent(e,t){switch(e){case 0:this.x=t;break;case 1:this.y=t;break;default:throw new Error("index is out of range: "+e)}return this}getComponent(e){switch(e){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+e)}}clone(){return new this.constructor(this.x,this.y)}copy(e){return this.x=e.x,this.y=e.y,this}add(e,t){return void 0!==t?(console.warn("THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(e,t)):(this.x+=e.x,this.y+=e.y,this)}addScalar(e){return this.x+=e,this.y+=e,this}addVectors(e,t){return this.x=e.x+t.x,this.y=e.y+t.y,this}addScaledVector(e,t){return this.x+=e.x*t,this.y+=e.y*t,this}sub(e,t){return void 0!==t?(console.warn("THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(e,t)):(this.x-=e.x,this.y-=e.y,this)}subScalar(e){return this.x-=e,this.y-=e,this}subVectors(e,t){return this.x=e.x-t.x,this.y=e.y-t.y,this}multiply(e){return this.x*=e.x,this.y*=e.y,this}multiplyScalar(e){return this.x*=e,this.y*=e,this}divide(e){return this.x/=e.x,this.y/=e.y,this}divideScalar(e){return this.multiplyScalar(1/e)}applyMatrix3(e){const t=this.x,n=this.y,r=e.elements;return this.x=r[0]*t+r[3]*n+r[6],this.y=r[1]*t+r[4]*n+r[7],this}min(e){return this.x=Math.min(this.x,e.x),this.y=Math.min(this.y,e.y),this}max(e){return this.x=Math.max(this.x,e.x),this.y=Math.max(this.y,e.y),this}clamp(e,t){return this.x=Math.max(e.x,Math.min(t.x,this.x)),this.y=Math.max(e.y,Math.min(t.y,this.y)),this}clampScalar(e,t){return this.x=Math.max(e,Math.min(t,this.x)),this.y=Math.max(e,Math.min(t,this.y)),this}clampLength(e,t){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(e,Math.min(t,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=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(e){return this.x*e.x+this.y*e.y}cross(e){return this.x*e.y-this.y*e.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}distanceTo(e){return Math.sqrt(this.distanceToSquared(e))}distanceToSquared(e){const t=this.x-e.x,n=this.y-e.y;return t*t+n*n}manhattanDistanceTo(e){return Math.abs(this.x-e.x)+Math.abs(this.y-e.y)}setLength(e){return this.normalize().multiplyScalar(e)}lerp(e,t){return this.x+=(e.x-this.x)*t,this.y+=(e.y-this.y)*t,this}lerpVectors(e,t,n){return this.x=e.x+(t.x-e.x)*n,this.y=e.y+(t.y-e.y)*n,this}equals(e){return e.x===this.x&&e.y===this.y}fromArray(e,t=0){return this.x=e[t],this.y=e[t+1],this}toArray(e=[],t=0){return e[t]=this.x,e[t+1]=this.y,e}fromBufferAttribute(e,t,n){return void 0!==n&&console.warn("THREE.Vector2: offset has been removed from .fromBufferAttribute()."),this.x=e.getX(t),this.y=e.getY(t),this}rotateAround(e,t){const n=Math.cos(t),r=Math.sin(t),i=this.x-e.x,a=this.y-e.y;return this.x=i*n-a*r+e.x,this.y=i*r+a*n+e.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}}Vector2.prototype.isVector2=!0;class Matrix3{constructor(){this.elements=[1,0,0,0,1,0,0,0,1],arguments.length>0&&console.error("THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.")}set(e,t,n,r,i,a,o,s,l){const c=this.elements;return c[0]=e,c[1]=r,c[2]=o,c[3]=t,c[4]=i,c[5]=s,c[6]=n,c[7]=a,c[8]=l,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(e){const t=this.elements,n=e.elements;return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],this}extractBasis(e,t,n){return e.setFromMatrix3Column(this,0),t.setFromMatrix3Column(this,1),n.setFromMatrix3Column(this,2),this}setFromMatrix4(e){const t=e.elements;return this.set(t[0],t[4],t[8],t[1],t[5],t[9],t[2],t[6],t[10]),this}multiply(e){return this.multiplyMatrices(this,e)}premultiply(e){return this.multiplyMatrices(e,this)}multiplyMatrices(e,t){const n=e.elements,r=t.elements,i=this.elements,a=n[0],o=n[3],s=n[6],l=n[1],c=n[4],h=n[7],u=n[2],d=n[5],p=n[8],m=r[0],f=r[3],g=r[6],v=r[1],_=r[4],y=r[7],x=r[2],b=r[5],M=r[8];return i[0]=a*m+o*v+s*x,i[3]=a*f+o*_+s*b,i[6]=a*g+o*y+s*M,i[1]=l*m+c*v+h*x,i[4]=l*f+c*_+h*b,i[7]=l*g+c*y+h*M,i[2]=u*m+d*v+p*x,i[5]=u*f+d*_+p*b,i[8]=u*g+d*y+p*M,this}multiplyScalar(e){const t=this.elements;return t[0]*=e,t[3]*=e,t[6]*=e,t[1]*=e,t[4]*=e,t[7]*=e,t[2]*=e,t[5]*=e,t[8]*=e,this}determinant(){const e=this.elements,t=e[0],n=e[1],r=e[2],i=e[3],a=e[4],o=e[5],s=e[6],l=e[7],c=e[8];return t*a*c-t*o*l-n*i*c+n*o*s+r*i*l-r*a*s}invert(){const e=this.elements,t=e[0],n=e[1],r=e[2],i=e[3],a=e[4],o=e[5],s=e[6],l=e[7],c=e[8],h=c*a-o*l,u=o*s-c*i,d=l*i-a*s,p=t*h+n*u+r*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const m=1/p;return e[0]=h*m,e[1]=(r*l-c*n)*m,e[2]=(o*n-r*a)*m,e[3]=u*m,e[4]=(c*t-r*s)*m,e[5]=(r*i-o*t)*m,e[6]=d*m,e[7]=(n*s-l*t)*m,e[8]=(a*t-n*i)*m,this}transpose(){let e;const t=this.elements;return e=t[1],t[1]=t[3],t[3]=e,e=t[2],t[2]=t[6],t[6]=e,e=t[5],t[5]=t[7],t[7]=e,this}getNormalMatrix(e){return this.setFromMatrix4(e).invert().transpose()}transposeIntoArray(e){const t=this.elements;return e[0]=t[0],e[1]=t[3],e[2]=t[6],e[3]=t[1],e[4]=t[4],e[5]=t[7],e[6]=t[2],e[7]=t[5],e[8]=t[8],this}setUvTransform(e,t,n,r,i,a,o){const s=Math.cos(i),l=Math.sin(i);return this.set(n*s,n*l,-n*(s*a+l*o)+a+e,-r*l,r*s,-r*(-l*a+s*o)+o+t,0,0,1),this}scale(e,t){const n=this.elements;return n[0]*=e,n[3]*=e,n[6]*=e,n[1]*=t,n[4]*=t,n[7]*=t,this}rotate(e){const t=Math.cos(e),n=Math.sin(e),r=this.elements,i=r[0],a=r[3],o=r[6],s=r[1],l=r[4],c=r[7];return r[0]=t*i+n*s,r[3]=t*a+n*l,r[6]=t*o+n*c,r[1]=-n*i+t*s,r[4]=-n*a+t*l,r[7]=-n*o+t*c,this}translate(e,t){const n=this.elements;return n[0]+=e*n[2],n[3]+=e*n[5],n[6]+=e*n[8],n[1]+=t*n[2],n[4]+=t*n[5],n[7]+=t*n[8],this}equals(e){const t=this.elements,n=e.elements;for(let e=0;e<9;e++)if(t[e]!==n[e])return!1;return!0}fromArray(e,t=0){for(let n=0;n<9;n++)this.elements[n]=e[n+t];return this}toArray(e=[],t=0){const n=this.elements;return e[t]=n[0],e[t+1]=n[1],e[t+2]=n[2],e[t+3]=n[3],e[t+4]=n[4],e[t+5]=n[5],e[t+6]=n[6],e[t+7]=n[7],e[t+8]=n[8],e}clone(){return(new this.constructor).fromArray(this.elements)}}let _canvas;Matrix3.prototype.isMatrix3=!0;const ImageUtils={getDataURL:function(e){if(/^data:/i.test(e.src))return e.src;if("undefined"==typeof HTMLCanvasElement)return e.src;let t;if(e instanceof HTMLCanvasElement)t=e;else{void 0===_canvas&&(_canvas=document.createElementNS("http://www.w3.org/1999/xhtml","canvas")),_canvas.width=e.width,_canvas.height=e.height;const n=_canvas.getContext("2d");e instanceof ImageData?n.putImageData(e,0,0):n.drawImage(e,0,0,e.width,e.height),t=_canvas}return t.width>2048||t.height>2048?t.toDataURL("image/jpeg",.6):t.toDataURL("image/png")}};let textureId=0;class Texture extends EventDispatcher{constructor(e=Texture.DEFAULT_IMAGE,t=Texture.DEFAULT_MAPPING,n=1001,r=1001,i=1006,a=1008,o=1023,s=1009,l=1,c=3e3){super(),Object.defineProperty(this,"id",{value:textureId++}),this.uuid=MathUtils.generateUUID(),this.name="",this.image=e,this.mipmaps=[],this.mapping=t,this.wrapS=n,this.wrapT=r,this.magFilter=i,this.minFilter=a,this.anisotropy=l,this.format=o,this.internalFormat=null,this.type=s,this.offset=new Vector2(0,0),this.repeat=new Vector2(1,1),this.center=new Vector2(0,0),this.rotation=0,this.matrixAutoUpdate=!0,this.matrix=new Matrix3,this.generateMipmaps=!0,this.premultiplyAlpha=!1,this.flipY=!0,this.unpackAlignment=4,this.encoding=c,this.version=0,this.onUpdate=null}updateMatrix(){this.matrix.setUvTransform(this.offset.x,this.offset.y,this.repeat.x,this.repeat.y,this.rotation,this.center.x,this.center.y)}clone(){return(new this.constructor).copy(this)}copy(e){return this.name=e.name,this.image=e.image,this.mipmaps=e.mipmaps.slice(0),this.mapping=e.mapping,this.wrapS=e.wrapS,this.wrapT=e.wrapT,this.magFilter=e.magFilter,this.minFilter=e.minFilter,this.anisotropy=e.anisotropy,this.format=e.format,this.internalFormat=e.internalFormat,this.type=e.type,this.offset.copy(e.offset),this.repeat.copy(e.repeat),this.center.copy(e.center),this.rotation=e.rotation,this.matrixAutoUpdate=e.matrixAutoUpdate,this.matrix.copy(e.matrix),this.generateMipmaps=e.generateMipmaps,this.premultiplyAlpha=e.premultiplyAlpha,this.flipY=e.flipY,this.unpackAlignment=e.unpackAlignment,this.encoding=e.encoding,this}toJSON(e){const t=void 0===e||"string"==typeof e;if(!t&&void 0!==e.textures[this.uuid])return e.textures[this.uuid];const n={metadata:{version:4.5,type:"Texture",generator:"Texture.toJSON"},uuid:this.uuid,name:this.name,mapping:this.mapping,repeat:[this.repeat.x,this.repeat.y],offset:[this.offset.x,this.offset.y],center:[this.center.x,this.center.y],rotation:this.rotation,wrap:[this.wrapS,this.wrapT],format:this.format,type:this.type,encoding:this.encoding,minFilter:this.minFilter,magFilter:this.magFilter,anisotropy:this.anisotropy,flipY:this.flipY,premultiplyAlpha:this.premultiplyAlpha,unpackAlignment:this.unpackAlignment};if(void 0!==this.image){const r=this.image;if(void 0===r.uuid&&(r.uuid=MathUtils.generateUUID()),!t&&void 0===e.images[r.uuid]){let t;if(Array.isArray(r)){t=[];for(let e=0,n=r.length;e1)switch(this.wrapS){case 1e3:e.x=e.x-Math.floor(e.x);break;case 1001:e.x=e.x<0?0:1;break;case 1002:1===Math.abs(Math.floor(e.x)%2)?e.x=Math.ceil(e.x)-e.x:e.x=e.x-Math.floor(e.x)}if(e.y<0||e.y>1)switch(this.wrapT){case 1e3:e.y=e.y-Math.floor(e.y);break;case 1001:e.y=e.y<0?0:1;break;case 1002:1===Math.abs(Math.floor(e.y)%2)?e.y=Math.ceil(e.y)-e.y:e.y=e.y-Math.floor(e.y)}return this.flipY&&(e.y=1-e.y),e}set needsUpdate(e){!0===e&&this.version++}}function serializeImage(e){return"undefined"!=typeof HTMLImageElement&&e instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&e instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&e instanceof ImageBitmap?ImageUtils.getDataURL(e):e.data?{data:Array.prototype.slice.call(e.data),width:e.width,height:e.height,type:e.data.constructor.name}:(console.warn("THREE.Texture: Unable to serialize Texture."),{})}Texture.DEFAULT_IMAGE=void 0,Texture.DEFAULT_MAPPING=300,Texture.prototype.isTexture=!0;class Vector4{constructor(e=0,t=0,n=0,r=1){this.x=e,this.y=t,this.z=n,this.w=r}get width(){return this.z}set width(e){this.z=e}get height(){return this.w}set height(e){this.w=e}set(e,t,n,r){return this.x=e,this.y=t,this.z=n,this.w=r,this}setScalar(e){return this.x=e,this.y=e,this.z=e,this.w=e,this}setX(e){return this.x=e,this}setY(e){return this.y=e,this}setZ(e){return this.z=e,this}setW(e){return this.w=e,this}setComponent(e,t){switch(e){case 0:this.x=t;break;case 1:this.y=t;break;case 2:this.z=t;break;case 3:this.w=t;break;default:throw new Error("index is out of range: "+e)}return this}getComponent(e){switch(e){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: "+e)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(e){return this.x=e.x,this.y=e.y,this.z=e.z,this.w=void 0!==e.w?e.w:1,this}add(e,t){return void 0!==t?(console.warn("THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(e,t)):(this.x+=e.x,this.y+=e.y,this.z+=e.z,this.w+=e.w,this)}addScalar(e){return this.x+=e,this.y+=e,this.z+=e,this.w+=e,this}addVectors(e,t){return this.x=e.x+t.x,this.y=e.y+t.y,this.z=e.z+t.z,this.w=e.w+t.w,this}addScaledVector(e,t){return this.x+=e.x*t,this.y+=e.y*t,this.z+=e.z*t,this.w+=e.w*t,this}sub(e,t){return void 0!==t?(console.warn("THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(e,t)):(this.x-=e.x,this.y-=e.y,this.z-=e.z,this.w-=e.w,this)}subScalar(e){return this.x-=e,this.y-=e,this.z-=e,this.w-=e,this}subVectors(e,t){return this.x=e.x-t.x,this.y=e.y-t.y,this.z=e.z-t.z,this.w=e.w-t.w,this}multiply(e){return this.x*=e.x,this.y*=e.y,this.z*=e.z,this.w*=e.w,this}multiplyScalar(e){return this.x*=e,this.y*=e,this.z*=e,this.w*=e,this}applyMatrix4(e){const t=this.x,n=this.y,r=this.z,i=this.w,a=e.elements;return this.x=a[0]*t+a[4]*n+a[8]*r+a[12]*i,this.y=a[1]*t+a[5]*n+a[9]*r+a[13]*i,this.z=a[2]*t+a[6]*n+a[10]*r+a[14]*i,this.w=a[3]*t+a[7]*n+a[11]*r+a[15]*i,this}divideScalar(e){return this.multiplyScalar(1/e)}setAxisAngleFromQuaternion(e){this.w=2*Math.acos(e.w);const t=Math.sqrt(1-e.w*e.w);return t<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=e.x/t,this.y=e.y/t,this.z=e.z/t),this}setAxisAngleFromRotationMatrix(e){let t,n,r,i;const a=.01,o=.1,s=e.elements,l=s[0],c=s[4],h=s[8],u=s[1],d=s[5],p=s[9],m=s[2],f=s[6],g=s[10];if(Math.abs(c-u)s&&e>v?ev?s=0?1:-1,r=1-t*t;if(r>Number.EPSILON){const i=Math.sqrt(r),a=Math.atan2(i,t*n);e=Math.sin(e*a)/i,o=Math.sin(o*a)/i}const i=o*n;if(s=s*e+u*i,l=l*e+d*i,c=c*e+p*i,h=h*e+m*i,e===1-o){const e=1/Math.sqrt(s*s+l*l+c*c+h*h);s*=e,l*=e,c*=e,h*=e}}e[t]=s,e[t+1]=l,e[t+2]=c,e[t+3]=h}static multiplyQuaternionsFlat(e,t,n,r,i,a){const o=n[r],s=n[r+1],l=n[r+2],c=n[r+3],h=i[a],u=i[a+1],d=i[a+2],p=i[a+3];return e[t]=o*p+c*h+s*d-l*u,e[t+1]=s*p+c*u+l*h-o*d,e[t+2]=l*p+c*d+o*u-s*h,e[t+3]=c*p-o*h-s*u-l*d,e}get x(){return this._x}set x(e){this._x=e,this._onChangeCallback()}get y(){return this._y}set y(e){this._y=e,this._onChangeCallback()}get z(){return this._z}set z(e){this._z=e,this._onChangeCallback()}get w(){return this._w}set w(e){this._w=e,this._onChangeCallback()}set(e,t,n,r){return this._x=e,this._y=t,this._z=n,this._w=r,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(e){return this._x=e.x,this._y=e.y,this._z=e.z,this._w=e.w,this._onChangeCallback(),this}setFromEuler(e,t){if(!e||!e.isEuler)throw new Error("THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.");const n=e._x,r=e._y,i=e._z,a=e._order,o=Math.cos,s=Math.sin,l=o(n/2),c=o(r/2),h=o(i/2),u=s(n/2),d=s(r/2),p=s(i/2);switch(a){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: "+a)}return!1!==t&&this._onChangeCallback(),this}setFromAxisAngle(e,t){const n=t/2,r=Math.sin(n);return this._x=e.x*r,this._y=e.y*r,this._z=e.z*r,this._w=Math.cos(n),this._onChangeCallback(),this}setFromRotationMatrix(e){const t=e.elements,n=t[0],r=t[4],i=t[8],a=t[1],o=t[5],s=t[9],l=t[2],c=t[6],h=t[10],u=n+o+h;if(u>0){const e=.5/Math.sqrt(u+1);this._w=.25/e,this._x=(c-s)*e,this._y=(i-l)*e,this._z=(a-r)*e}else if(n>o&&n>h){const e=2*Math.sqrt(1+n-o-h);this._w=(c-s)/e,this._x=.25*e,this._y=(r+a)/e,this._z=(i+l)/e}else if(o>h){const e=2*Math.sqrt(1+o-n-h);this._w=(i-l)/e,this._x=(r+a)/e,this._y=.25*e,this._z=(s+c)/e}else{const e=2*Math.sqrt(1+h-n-o);this._w=(a-r)/e,this._x=(i+l)/e,this._y=(s+c)/e,this._z=.25*e}return this._onChangeCallback(),this}setFromUnitVectors(e,t){let n=e.dot(t)+1;return n<1e-6?(n=0,Math.abs(e.x)>Math.abs(e.z)?(this._x=-e.y,this._y=e.x,this._z=0,this._w=n):(this._x=0,this._y=-e.z,this._z=e.y,this._w=n)):(this._x=e.y*t.z-e.z*t.y,this._y=e.z*t.x-e.x*t.z,this._z=e.x*t.y-e.y*t.x,this._w=n),this.normalize()}angleTo(e){return 2*Math.acos(Math.abs(MathUtils.clamp(this.dot(e),-1,1)))}rotateTowards(e,t){const n=this.angleTo(e);if(0===n)return this;const r=Math.min(1,t/n);return this.slerp(e,r),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(e){return this._x*e._x+this._y*e._y+this._z*e._z+this._w*e._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 e=this.length();return 0===e?(this._x=0,this._y=0,this._z=0,this._w=1):(e=1/e,this._x=this._x*e,this._y=this._y*e,this._z=this._z*e,this._w=this._w*e),this._onChangeCallback(),this}multiply(e,t){return void 0!==t?(console.warn("THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead."),this.multiplyQuaternions(e,t)):this.multiplyQuaternions(this,e)}premultiply(e){return this.multiplyQuaternions(e,this)}multiplyQuaternions(e,t){const n=e._x,r=e._y,i=e._z,a=e._w,o=t._x,s=t._y,l=t._z,c=t._w;return this._x=n*c+a*o+r*l-i*s,this._y=r*c+a*s+i*o-n*l,this._z=i*c+a*l+n*s-r*o,this._w=a*c-n*o-r*s-i*l,this._onChangeCallback(),this}slerp(e,t){if(0===t)return this;if(1===t)return this.copy(e);const n=this._x,r=this._y,i=this._z,a=this._w;let o=a*e._w+n*e._x+r*e._y+i*e._z;if(o<0?(this._w=-e._w,this._x=-e._x,this._y=-e._y,this._z=-e._z,o=-o):this.copy(e),o>=1)return this._w=a,this._x=n,this._y=r,this._z=i,this;const s=1-o*o;if(s<=Number.EPSILON){const e=1-t;return this._w=e*a+t*this._w,this._x=e*n+t*this._x,this._y=e*r+t*this._y,this._z=e*i+t*this._z,this.normalize(),this._onChangeCallback(),this}const l=Math.sqrt(s),c=Math.atan2(l,o),h=Math.sin((1-t)*c)/l,u=Math.sin(t*c)/l;return this._w=a*h+this._w*u,this._x=n*h+this._x*u,this._y=r*h+this._y*u,this._z=i*h+this._z*u,this._onChangeCallback(),this}equals(e){return e._x===this._x&&e._y===this._y&&e._z===this._z&&e._w===this._w}fromArray(e,t=0){return this._x=e[t],this._y=e[t+1],this._z=e[t+2],this._w=e[t+3],this._onChangeCallback(),this}toArray(e=[],t=0){return e[t]=this._x,e[t+1]=this._y,e[t+2]=this._z,e[t+3]=this._w,e}fromBufferAttribute(e,t){return this._x=e.getX(t),this._y=e.getY(t),this._z=e.getZ(t),this._w=e.getW(t),this}_onChange(e){return this._onChangeCallback=e,this}_onChangeCallback(){}}Quaternion.prototype.isQuaternion=!0;class Vector3{constructor(e=0,t=0,n=0){this.x=e,this.y=t,this.z=n}set(e,t,n){return void 0===n&&(n=this.z),this.x=e,this.y=t,this.z=n,this}setScalar(e){return this.x=e,this.y=e,this.z=e,this}setX(e){return this.x=e,this}setY(e){return this.y=e,this}setZ(e){return this.z=e,this}setComponent(e,t){switch(e){case 0:this.x=t;break;case 1:this.y=t;break;case 2:this.z=t;break;default:throw new Error("index is out of range: "+e)}return this}getComponent(e){switch(e){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+e)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(e){return this.x=e.x,this.y=e.y,this.z=e.z,this}add(e,t){return void 0!==t?(console.warn("THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(e,t)):(this.x+=e.x,this.y+=e.y,this.z+=e.z,this)}addScalar(e){return this.x+=e,this.y+=e,this.z+=e,this}addVectors(e,t){return this.x=e.x+t.x,this.y=e.y+t.y,this.z=e.z+t.z,this}addScaledVector(e,t){return this.x+=e.x*t,this.y+=e.y*t,this.z+=e.z*t,this}sub(e,t){return void 0!==t?(console.warn("THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(e,t)):(this.x-=e.x,this.y-=e.y,this.z-=e.z,this)}subScalar(e){return this.x-=e,this.y-=e,this.z-=e,this}subVectors(e,t){return this.x=e.x-t.x,this.y=e.y-t.y,this.z=e.z-t.z,this}multiply(e,t){return void 0!==t?(console.warn("THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead."),this.multiplyVectors(e,t)):(this.x*=e.x,this.y*=e.y,this.z*=e.z,this)}multiplyScalar(e){return this.x*=e,this.y*=e,this.z*=e,this}multiplyVectors(e,t){return this.x=e.x*t.x,this.y=e.y*t.y,this.z=e.z*t.z,this}applyEuler(e){return e&&e.isEuler||console.error("THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order."),this.applyQuaternion(_quaternion.setFromEuler(e))}applyAxisAngle(e,t){return this.applyQuaternion(_quaternion.setFromAxisAngle(e,t))}applyMatrix3(e){const t=this.x,n=this.y,r=this.z,i=e.elements;return this.x=i[0]*t+i[3]*n+i[6]*r,this.y=i[1]*t+i[4]*n+i[7]*r,this.z=i[2]*t+i[5]*n+i[8]*r,this}applyNormalMatrix(e){return this.applyMatrix3(e).normalize()}applyMatrix4(e){const t=this.x,n=this.y,r=this.z,i=e.elements,a=1/(i[3]*t+i[7]*n+i[11]*r+i[15]);return this.x=(i[0]*t+i[4]*n+i[8]*r+i[12])*a,this.y=(i[1]*t+i[5]*n+i[9]*r+i[13])*a,this.z=(i[2]*t+i[6]*n+i[10]*r+i[14])*a,this}applyQuaternion(e){const t=this.x,n=this.y,r=this.z,i=e.x,a=e.y,o=e.z,s=e.w,l=s*t+a*r-o*n,c=s*n+o*t-i*r,h=s*r+i*n-a*t,u=-i*t-a*n-o*r;return this.x=l*s+u*-i+c*-o-h*-a,this.y=c*s+u*-a+h*-i-l*-o,this.z=h*s+u*-o+l*-a-c*-i,this}project(e){return this.applyMatrix4(e.matrixWorldInverse).applyMatrix4(e.projectionMatrix)}unproject(e){return this.applyMatrix4(e.projectionMatrixInverse).applyMatrix4(e.matrixWorld)}transformDirection(e){const t=this.x,n=this.y,r=this.z,i=e.elements;return this.x=i[0]*t+i[4]*n+i[8]*r,this.y=i[1]*t+i[5]*n+i[9]*r,this.z=i[2]*t+i[6]*n+i[10]*r,this.normalize()}divide(e){return this.x/=e.x,this.y/=e.y,this.z/=e.z,this}divideScalar(e){return this.multiplyScalar(1/e)}min(e){return this.x=Math.min(this.x,e.x),this.y=Math.min(this.y,e.y),this.z=Math.min(this.z,e.z),this}max(e){return this.x=Math.max(this.x,e.x),this.y=Math.max(this.y,e.y),this.z=Math.max(this.z,e.z),this}clamp(e,t){return this.x=Math.max(e.x,Math.min(t.x,this.x)),this.y=Math.max(e.y,Math.min(t.y,this.y)),this.z=Math.max(e.z,Math.min(t.z,this.z)),this}clampScalar(e,t){return this.x=Math.max(e,Math.min(t,this.x)),this.y=Math.max(e,Math.min(t,this.y)),this.z=Math.max(e,Math.min(t,this.z)),this}clampLength(e,t){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(e,Math.min(t,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=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this.z=this.z<0?Math.ceil(this.z):Math.floor(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(e){return this.x*e.x+this.y*e.y+this.z*e.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(e){return this.normalize().multiplyScalar(e)}lerp(e,t){return this.x+=(e.x-this.x)*t,this.y+=(e.y-this.y)*t,this.z+=(e.z-this.z)*t,this}lerpVectors(e,t,n){return this.x=e.x+(t.x-e.x)*n,this.y=e.y+(t.y-e.y)*n,this.z=e.z+(t.z-e.z)*n,this}cross(e,t){return void 0!==t?(console.warn("THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead."),this.crossVectors(e,t)):this.crossVectors(this,e)}crossVectors(e,t){const n=e.x,r=e.y,i=e.z,a=t.x,o=t.y,s=t.z;return this.x=r*s-i*o,this.y=i*a-n*s,this.z=n*o-r*a,this}projectOnVector(e){const t=e.lengthSq();if(0===t)return this.set(0,0,0);const n=e.dot(this)/t;return this.copy(e).multiplyScalar(n)}projectOnPlane(e){return _vector.copy(this).projectOnVector(e),this.sub(_vector)}reflect(e){return this.sub(_vector.copy(e).multiplyScalar(2*this.dot(e)))}angleTo(e){const t=Math.sqrt(this.lengthSq()*e.lengthSq());if(0===t)return Math.PI/2;const n=this.dot(e)/t;return Math.acos(MathUtils.clamp(n,-1,1))}distanceTo(e){return Math.sqrt(this.distanceToSquared(e))}distanceToSquared(e){const t=this.x-e.x,n=this.y-e.y,r=this.z-e.z;return t*t+n*n+r*r}manhattanDistanceTo(e){return Math.abs(this.x-e.x)+Math.abs(this.y-e.y)+Math.abs(this.z-e.z)}setFromSpherical(e){return this.setFromSphericalCoords(e.radius,e.phi,e.theta)}setFromSphericalCoords(e,t,n){const r=Math.sin(t)*e;return this.x=r*Math.sin(n),this.y=Math.cos(t)*e,this.z=r*Math.cos(n),this}setFromCylindrical(e){return this.setFromCylindricalCoords(e.radius,e.theta,e.y)}setFromCylindricalCoords(e,t,n){return this.x=e*Math.sin(t),this.y=n,this.z=e*Math.cos(t),this}setFromMatrixPosition(e){const t=e.elements;return this.x=t[12],this.y=t[13],this.z=t[14],this}setFromMatrixScale(e){const t=this.setFromMatrixColumn(e,0).length(),n=this.setFromMatrixColumn(e,1).length(),r=this.setFromMatrixColumn(e,2).length();return this.x=t,this.y=n,this.z=r,this}setFromMatrixColumn(e,t){return this.fromArray(e.elements,4*t)}setFromMatrix3Column(e,t){return this.fromArray(e.elements,3*t)}equals(e){return e.x===this.x&&e.y===this.y&&e.z===this.z}fromArray(e,t=0){return this.x=e[t],this.y=e[t+1],this.z=e[t+2],this}toArray(e=[],t=0){return e[t]=this.x,e[t+1]=this.y,e[t+2]=this.z,e}fromBufferAttribute(e,t,n){return void 0!==n&&console.warn("THREE.Vector3: offset has been removed from .fromBufferAttribute()."),this.x=e.getX(t),this.y=e.getY(t),this.z=e.getZ(t),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}}Vector3.prototype.isVector3=!0;const _vector=new Vector3,_quaternion=new Quaternion;class Box3{constructor(e=new Vector3(1/0,1/0,1/0),t=new Vector3(-1/0,-1/0,-1/0)){this.min=e,this.max=t}set(e,t){return this.min.copy(e),this.max.copy(t),this}setFromArray(e){let t=1/0,n=1/0,r=1/0,i=-1/0,a=-1/0,o=-1/0;for(let s=0,l=e.length;si&&(i=l),c>a&&(a=c),h>o&&(o=h)}return this.min.set(t,n,r),this.max.set(i,a,o),this}setFromBufferAttribute(e){let t=1/0,n=1/0,r=1/0,i=-1/0,a=-1/0,o=-1/0;for(let s=0,l=e.count;si&&(i=l),c>a&&(a=c),h>o&&(o=h)}return this.min.set(t,n,r),this.max.set(i,a,o),this}setFromPoints(e){this.makeEmpty();for(let t=0,n=e.length;tthis.max.x||e.ythis.max.y||e.zthis.max.z)}containsBox(e){return this.min.x<=e.min.x&&e.max.x<=this.max.x&&this.min.y<=e.min.y&&e.max.y<=this.max.y&&this.min.z<=e.min.z&&e.max.z<=this.max.z}getParameter(e,t){return void 0===t&&(console.warn("THREE.Box3: .getParameter() target is now required"),t=new Vector3),t.set((e.x-this.min.x)/(this.max.x-this.min.x),(e.y-this.min.y)/(this.max.y-this.min.y),(e.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(e){return!(e.max.xthis.max.x||e.max.ythis.max.y||e.max.zthis.max.z)}intersectsSphere(e){return this.clampPoint(e.center,_vector$1),_vector$1.distanceToSquared(e.center)<=e.radius*e.radius}intersectsPlane(e){let t,n;return e.normal.x>0?(t=e.normal.x*this.min.x,n=e.normal.x*this.max.x):(t=e.normal.x*this.max.x,n=e.normal.x*this.min.x),e.normal.y>0?(t+=e.normal.y*this.min.y,n+=e.normal.y*this.max.y):(t+=e.normal.y*this.max.y,n+=e.normal.y*this.min.y),e.normal.z>0?(t+=e.normal.z*this.min.z,n+=e.normal.z*this.max.z):(t+=e.normal.z*this.max.z,n+=e.normal.z*this.min.z),t<=-e.constant&&n>=-e.constant}intersectsTriangle(e){if(this.isEmpty())return!1;this.getCenter(_center),_extents.subVectors(this.max,_center),_v0.subVectors(e.a,_center),_v1.subVectors(e.b,_center),_v2.subVectors(e.c,_center),_f0.subVectors(_v1,_v0),_f1.subVectors(_v2,_v1),_f2.subVectors(_v0,_v2);let t=[0,-_f0.z,_f0.y,0,-_f1.z,_f1.y,0,-_f2.z,_f2.y,_f0.z,0,-_f0.x,_f1.z,0,-_f1.x,_f2.z,0,-_f2.x,-_f0.y,_f0.x,0,-_f1.y,_f1.x,0,-_f2.y,_f2.x,0];return!!satForAxes(t,_v0,_v1,_v2,_extents)&&(t=[1,0,0,0,1,0,0,0,1],!!satForAxes(t,_v0,_v1,_v2,_extents)&&(_triangleNormal.crossVectors(_f0,_f1),t=[_triangleNormal.x,_triangleNormal.y,_triangleNormal.z],satForAxes(t,_v0,_v1,_v2,_extents)))}clampPoint(e,t){return void 0===t&&(console.warn("THREE.Box3: .clampPoint() target is now required"),t=new Vector3),t.copy(e).clamp(this.min,this.max)}distanceToPoint(e){return _vector$1.copy(e).clamp(this.min,this.max).sub(e).length()}getBoundingSphere(e){return void 0===e&&console.error("THREE.Box3: .getBoundingSphere() target is now required"),this.getCenter(e.center),e.radius=.5*this.getSize(_vector$1).length(),e}intersect(e){return this.min.max(e.min),this.max.min(e.max),this.isEmpty()&&this.makeEmpty(),this}union(e){return this.min.min(e.min),this.max.max(e.max),this}applyMatrix4(e){return this.isEmpty()||(_points[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(e),_points[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(e),_points[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(e),_points[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(e),_points[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(e),_points[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(e),_points[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(e),_points[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(e),this.setFromPoints(_points)),this}translate(e){return this.min.add(e),this.max.add(e),this}equals(e){return e.min.equals(this.min)&&e.max.equals(this.max)}}Box3.prototype.isBox3=!0;const _points=[new Vector3,new Vector3,new Vector3,new Vector3,new Vector3,new Vector3,new Vector3,new Vector3],_vector$1=new Vector3,_box=new Box3,_v0=new Vector3,_v1=new Vector3,_v2=new Vector3,_f0=new Vector3,_f1=new Vector3,_f2=new Vector3,_center=new Vector3,_extents=new Vector3,_triangleNormal=new Vector3,_testAxis=new Vector3;function satForAxes(e,t,n,r,i){for(let a=0,o=e.length-3;a<=o;a+=3){_testAxis.fromArray(e,a);const o=i.x*Math.abs(_testAxis.x)+i.y*Math.abs(_testAxis.y)+i.z*Math.abs(_testAxis.z),s=t.dot(_testAxis),l=n.dot(_testAxis),c=r.dot(_testAxis);if(Math.max(-Math.max(s,l,c),Math.min(s,l,c))>o)return!1}return!0}const _box$1=new Box3;class Sphere{constructor(e=new Vector3,t=-1){this.center=e,this.radius=t}set(e,t){return this.center.copy(e),this.radius=t,this}setFromPoints(e,t){const n=this.center;void 0!==t?n.copy(t):_box$1.setFromPoints(e).getCenter(n);let r=0;for(let t=0,i=e.length;tthis.radius*this.radius&&(t.sub(this.center).normalize(),t.multiplyScalar(this.radius).add(this.center)),t}getBoundingBox(e){return void 0===e&&(console.warn("THREE.Sphere: .getBoundingBox() target is now required"),e=new Box3),this.isEmpty()?(e.makeEmpty(),e):(e.set(this.center,this.center),e.expandByScalar(this.radius),e)}applyMatrix4(e){return this.center.applyMatrix4(e),this.radius=this.radius*e.getMaxScaleOnAxis(),this}translate(e){return this.center.add(e),this}equals(e){return e.center.equals(this.center)&&e.radius===this.radius}clone(){return(new this.constructor).copy(this)}}const _vector$2=new Vector3,_segCenter=new Vector3,_segDir=new Vector3,_diff=new Vector3,_edge1=new Vector3,_edge2=new Vector3,_normal=new Vector3;class Ray{constructor(e=new Vector3,t=new Vector3(0,0,-1)){this.origin=e,this.direction=t}set(e,t){return this.origin.copy(e),this.direction.copy(t),this}copy(e){return this.origin.copy(e.origin),this.direction.copy(e.direction),this}at(e,t){return void 0===t&&(console.warn("THREE.Ray: .at() target is now required"),t=new Vector3),t.copy(this.direction).multiplyScalar(e).add(this.origin)}lookAt(e){return this.direction.copy(e).sub(this.origin).normalize(),this}recast(e){return this.origin.copy(this.at(e,_vector$2)),this}closestPointToPoint(e,t){void 0===t&&(console.warn("THREE.Ray: .closestPointToPoint() target is now required"),t=new Vector3),t.subVectors(e,this.origin);const n=t.dot(this.direction);return n<0?t.copy(this.origin):t.copy(this.direction).multiplyScalar(n).add(this.origin)}distanceToPoint(e){return Math.sqrt(this.distanceSqToPoint(e))}distanceSqToPoint(e){const t=_vector$2.subVectors(e,this.origin).dot(this.direction);return t<0?this.origin.distanceToSquared(e):(_vector$2.copy(this.direction).multiplyScalar(t).add(this.origin),_vector$2.distanceToSquared(e))}distanceSqToSegment(e,t,n,r){_segCenter.copy(e).add(t).multiplyScalar(.5),_segDir.copy(t).sub(e).normalize(),_diff.copy(this.origin).sub(_segCenter);const i=.5*e.distanceTo(t),a=-this.direction.dot(_segDir),o=_diff.dot(this.direction),s=-_diff.dot(_segDir),l=_diff.lengthSq(),c=Math.abs(1-a*a);let h,u,d,p;if(c>0)if(h=a*s-o,u=a*o-s,p=i*c,h>=0)if(u>=-p)if(u<=p){const e=1/c;h*=e,u*=e,d=h*(h+a*u+2*o)+u*(a*h+u+2*s)+l}else u=i,h=Math.max(0,-(a*u+o)),d=-h*h+u*(u+2*s)+l;else u=-i,h=Math.max(0,-(a*u+o)),d=-h*h+u*(u+2*s)+l;else u<=-p?(h=Math.max(0,-(-a*i+o)),u=h>0?-i:Math.min(Math.max(-i,-s),i),d=-h*h+u*(u+2*s)+l):u<=p?(h=0,u=Math.min(Math.max(-i,-s),i),d=u*(u+2*s)+l):(h=Math.max(0,-(a*i+o)),u=h>0?i:Math.min(Math.max(-i,-s),i),d=-h*h+u*(u+2*s)+l);else u=a>0?-i:i,h=Math.max(0,-(a*u+o)),d=-h*h+u*(u+2*s)+l;return n&&n.copy(this.direction).multiplyScalar(h).add(this.origin),r&&r.copy(_segDir).multiplyScalar(u).add(_segCenter),d}intersectSphere(e,t){_vector$2.subVectors(e.center,this.origin);const n=_vector$2.dot(this.direction),r=_vector$2.dot(_vector$2)-n*n,i=e.radius*e.radius;if(r>i)return null;const a=Math.sqrt(i-r),o=n-a,s=n+a;return o<0&&s<0?null:o<0?this.at(s,t):this.at(o,t)}intersectsSphere(e){return this.distanceSqToPoint(e.center)<=e.radius*e.radius}distanceToPlane(e){const t=e.normal.dot(this.direction);if(0===t)return 0===e.distanceToPoint(this.origin)?0:null;const n=-(this.origin.dot(e.normal)+e.constant)/t;return n>=0?n:null}intersectPlane(e,t){const n=this.distanceToPlane(e);return null===n?null:this.at(n,t)}intersectsPlane(e){const t=e.distanceToPoint(this.origin);if(0===t)return!0;return e.normal.dot(this.direction)*t<0}intersectBox(e,t){let n,r,i,a,o,s;const l=1/this.direction.x,c=1/this.direction.y,h=1/this.direction.z,u=this.origin;return l>=0?(n=(e.min.x-u.x)*l,r=(e.max.x-u.x)*l):(n=(e.max.x-u.x)*l,r=(e.min.x-u.x)*l),c>=0?(i=(e.min.y-u.y)*c,a=(e.max.y-u.y)*c):(i=(e.max.y-u.y)*c,a=(e.min.y-u.y)*c),n>a||i>r?null:((i>n||n!=n)&&(n=i),(a=0?(o=(e.min.z-u.z)*h,s=(e.max.z-u.z)*h):(o=(e.max.z-u.z)*h,s=(e.min.z-u.z)*h),n>s||o>r?null:((o>n||n!=n)&&(n=o),(s=0?n:r,t)))}intersectsBox(e){return null!==this.intersectBox(e,_vector$2)}intersectTriangle(e,t,n,r,i){_edge1.subVectors(t,e),_edge2.subVectors(n,e),_normal.crossVectors(_edge1,_edge2);let a,o=this.direction.dot(_normal);if(o>0){if(r)return null;a=1}else{if(!(o<0))return null;a=-1,o=-o}_diff.subVectors(this.origin,e);const s=a*this.direction.dot(_edge2.crossVectors(_diff,_edge2));if(s<0)return null;const l=a*this.direction.dot(_edge1.cross(_diff));if(l<0)return null;if(s+l>o)return null;const c=-a*_diff.dot(_normal);return c<0?null:this.at(c/o,i)}applyMatrix4(e){return this.origin.applyMatrix4(e),this.direction.transformDirection(e),this}equals(e){return e.origin.equals(this.origin)&&e.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}class Matrix4{constructor(){this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],arguments.length>0&&console.error("THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.")}set(e,t,n,r,i,a,o,s,l,c,h,u,d,p,m,f){const g=this.elements;return g[0]=e,g[4]=t,g[8]=n,g[12]=r,g[1]=i,g[5]=a,g[9]=o,g[13]=s,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 Matrix4).fromArray(this.elements)}copy(e){const t=this.elements,n=e.elements;return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t[9]=n[9],t[10]=n[10],t[11]=n[11],t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15],this}copyPosition(e){const t=this.elements,n=e.elements;return t[12]=n[12],t[13]=n[13],t[14]=n[14],this}setFromMatrix3(e){const t=e.elements;return this.set(t[0],t[3],t[6],0,t[1],t[4],t[7],0,t[2],t[5],t[8],0,0,0,0,1),this}extractBasis(e,t,n){return e.setFromMatrixColumn(this,0),t.setFromMatrixColumn(this,1),n.setFromMatrixColumn(this,2),this}makeBasis(e,t,n){return this.set(e.x,t.x,n.x,0,e.y,t.y,n.y,0,e.z,t.z,n.z,0,0,0,0,1),this}extractRotation(e){const t=this.elements,n=e.elements,r=1/_v1$1.setFromMatrixColumn(e,0).length(),i=1/_v1$1.setFromMatrixColumn(e,1).length(),a=1/_v1$1.setFromMatrixColumn(e,2).length();return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=0,t[4]=n[4]*i,t[5]=n[5]*i,t[6]=n[6]*i,t[7]=0,t[8]=n[8]*a,t[9]=n[9]*a,t[10]=n[10]*a,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,this}makeRotationFromEuler(e){e&&e.isEuler||console.error("THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.");const t=this.elements,n=e.x,r=e.y,i=e.z,a=Math.cos(n),o=Math.sin(n),s=Math.cos(r),l=Math.sin(r),c=Math.cos(i),h=Math.sin(i);if("XYZ"===e.order){const e=a*c,n=a*h,r=o*c,i=o*h;t[0]=s*c,t[4]=-s*h,t[8]=l,t[1]=n+r*l,t[5]=e-i*l,t[9]=-o*s,t[2]=i-e*l,t[6]=r+n*l,t[10]=a*s}else if("YXZ"===e.order){const e=s*c,n=s*h,r=l*c,i=l*h;t[0]=e+i*o,t[4]=r*o-n,t[8]=a*l,t[1]=a*h,t[5]=a*c,t[9]=-o,t[2]=n*o-r,t[6]=i+e*o,t[10]=a*s}else if("ZXY"===e.order){const e=s*c,n=s*h,r=l*c,i=l*h;t[0]=e-i*o,t[4]=-a*h,t[8]=r+n*o,t[1]=n+r*o,t[5]=a*c,t[9]=i-e*o,t[2]=-a*l,t[6]=o,t[10]=a*s}else if("ZYX"===e.order){const e=a*c,n=a*h,r=o*c,i=o*h;t[0]=s*c,t[4]=r*l-n,t[8]=e*l+i,t[1]=s*h,t[5]=i*l+e,t[9]=n*l-r,t[2]=-l,t[6]=o*s,t[10]=a*s}else if("YZX"===e.order){const e=a*s,n=a*l,r=o*s,i=o*l;t[0]=s*c,t[4]=i-e*h,t[8]=r*h+n,t[1]=h,t[5]=a*c,t[9]=-o*c,t[2]=-l*c,t[6]=n*h+r,t[10]=e-i*h}else if("XZY"===e.order){const e=a*s,n=a*l,r=o*s,i=o*l;t[0]=s*c,t[4]=-h,t[8]=l*c,t[1]=e*h+i,t[5]=a*c,t[9]=n*h-r,t[2]=r*h-n,t[6]=o*c,t[10]=i*h+e}return t[3]=0,t[7]=0,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,this}makeRotationFromQuaternion(e){return this.compose(_zero,e,_one)}lookAt(e,t,n){const r=this.elements;return _z.subVectors(e,t),0===_z.lengthSq()&&(_z.z=1),_z.normalize(),_x.crossVectors(n,_z),0===_x.lengthSq()&&(1===Math.abs(n.z)?_z.x+=1e-4:_z.z+=1e-4,_z.normalize(),_x.crossVectors(n,_z)),_x.normalize(),_y.crossVectors(_z,_x),r[0]=_x.x,r[4]=_y.x,r[8]=_z.x,r[1]=_x.y,r[5]=_y.y,r[9]=_z.y,r[2]=_x.z,r[6]=_y.z,r[10]=_z.z,this}multiply(e,t){return void 0!==t?(console.warn("THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead."),this.multiplyMatrices(e,t)):this.multiplyMatrices(this,e)}premultiply(e){return this.multiplyMatrices(e,this)}multiplyMatrices(e,t){const n=e.elements,r=t.elements,i=this.elements,a=n[0],o=n[4],s=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],y=n[11],x=n[15],b=r[0],M=r[4],w=r[8],S=r[12],T=r[1],E=r[5],A=r[9],L=r[13],C=r[2],R=r[6],P=r[10],B=r[14],D=r[3],I=r[7],G=r[11],F=r[15];return i[0]=a*b+o*T+s*C+l*D,i[4]=a*M+o*E+s*R+l*I,i[8]=a*w+o*A+s*P+l*G,i[12]=a*S+o*L+s*B+l*F,i[1]=c*b+h*T+u*C+d*D,i[5]=c*M+h*E+u*R+d*I,i[9]=c*w+h*A+u*P+d*G,i[13]=c*S+h*L+u*B+d*F,i[2]=p*b+m*T+f*C+g*D,i[6]=p*M+m*E+f*R+g*I,i[10]=p*w+m*A+f*P+g*G,i[14]=p*S+m*L+f*B+g*F,i[3]=v*b+_*T+y*C+x*D,i[7]=v*M+_*E+y*R+x*I,i[11]=v*w+_*A+y*P+x*G,i[15]=v*S+_*L+y*B+x*F,this}multiplyScalar(e){const t=this.elements;return t[0]*=e,t[4]*=e,t[8]*=e,t[12]*=e,t[1]*=e,t[5]*=e,t[9]*=e,t[13]*=e,t[2]*=e,t[6]*=e,t[10]*=e,t[14]*=e,t[3]*=e,t[7]*=e,t[11]*=e,t[15]*=e,this}determinant(){const e=this.elements,t=e[0],n=e[4],r=e[8],i=e[12],a=e[1],o=e[5],s=e[9],l=e[13],c=e[2],h=e[6],u=e[10],d=e[14];return e[3]*(+i*s*h-r*l*h-i*o*u+n*l*u+r*o*d-n*s*d)+e[7]*(+t*s*d-t*l*u+i*a*u-r*a*d+r*l*c-i*s*c)+e[11]*(+t*l*h-t*o*d-i*a*h+n*a*d+i*o*c-n*l*c)+e[15]*(-r*o*c-t*s*h+t*o*u+r*a*h-n*a*u+n*s*c)}transpose(){const e=this.elements;let t;return t=e[1],e[1]=e[4],e[4]=t,t=e[2],e[2]=e[8],e[8]=t,t=e[6],e[6]=e[9],e[9]=t,t=e[3],e[3]=e[12],e[12]=t,t=e[7],e[7]=e[13],e[13]=t,t=e[11],e[11]=e[14],e[14]=t,this}setPosition(e,t,n){const r=this.elements;return e.isVector3?(r[12]=e.x,r[13]=e.y,r[14]=e.z):(r[12]=e,r[13]=t,r[14]=n),this}invert(){const e=this.elements,t=e[0],n=e[1],r=e[2],i=e[3],a=e[4],o=e[5],s=e[6],l=e[7],c=e[8],h=e[9],u=e[10],d=e[11],p=e[12],m=e[13],f=e[14],g=e[15],v=h*f*l-m*u*l+m*s*d-o*f*d-h*s*g+o*u*g,_=p*u*l-c*f*l-p*s*d+a*f*d+c*s*g-a*u*g,y=c*m*l-p*h*l+p*o*d-a*m*d-c*o*g+a*h*g,x=p*h*s-c*m*s-p*o*u+a*m*u+c*o*f-a*h*f,b=t*v+n*_+r*y+i*x;if(0===b)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const M=1/b;return e[0]=v*M,e[1]=(m*u*i-h*f*i-m*r*d+n*f*d+h*r*g-n*u*g)*M,e[2]=(o*f*i-m*s*i+m*r*l-n*f*l-o*r*g+n*s*g)*M,e[3]=(h*s*i-o*u*i-h*r*l+n*u*l+o*r*d-n*s*d)*M,e[4]=_*M,e[5]=(c*f*i-p*u*i+p*r*d-t*f*d-c*r*g+t*u*g)*M,e[6]=(p*s*i-a*f*i-p*r*l+t*f*l+a*r*g-t*s*g)*M,e[7]=(a*u*i-c*s*i+c*r*l-t*u*l-a*r*d+t*s*d)*M,e[8]=y*M,e[9]=(p*h*i-c*m*i-p*n*d+t*m*d+c*n*g-t*h*g)*M,e[10]=(a*m*i-p*o*i+p*n*l-t*m*l-a*n*g+t*o*g)*M,e[11]=(c*o*i-a*h*i-c*n*l+t*h*l+a*n*d-t*o*d)*M,e[12]=x*M,e[13]=(c*m*r-p*h*r+p*n*u-t*m*u-c*n*f+t*h*f)*M,e[14]=(p*o*r-a*m*r-p*n*s+t*m*s+a*n*f-t*o*f)*M,e[15]=(a*h*r-c*o*r+c*n*s-t*h*s-a*n*u+t*o*u)*M,this}scale(e){const t=this.elements,n=e.x,r=e.y,i=e.z;return t[0]*=n,t[4]*=r,t[8]*=i,t[1]*=n,t[5]*=r,t[9]*=i,t[2]*=n,t[6]*=r,t[10]*=i,t[3]*=n,t[7]*=r,t[11]*=i,this}getMaxScaleOnAxis(){const e=this.elements,t=e[0]*e[0]+e[1]*e[1]+e[2]*e[2],n=e[4]*e[4]+e[5]*e[5]+e[6]*e[6],r=e[8]*e[8]+e[9]*e[9]+e[10]*e[10];return Math.sqrt(Math.max(t,n,r))}makeTranslation(e,t,n){return this.set(1,0,0,e,0,1,0,t,0,0,1,n,0,0,0,1),this}makeRotationX(e){const t=Math.cos(e),n=Math.sin(e);return this.set(1,0,0,0,0,t,-n,0,0,n,t,0,0,0,0,1),this}makeRotationY(e){const t=Math.cos(e),n=Math.sin(e);return this.set(t,0,n,0,0,1,0,0,-n,0,t,0,0,0,0,1),this}makeRotationZ(e){const t=Math.cos(e),n=Math.sin(e);return this.set(t,-n,0,0,n,t,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(e,t){const n=Math.cos(t),r=Math.sin(t),i=1-n,a=e.x,o=e.y,s=e.z,l=i*a,c=i*o;return this.set(l*a+n,l*o-r*s,l*s+r*o,0,l*o+r*s,c*o+n,c*s-r*a,0,l*s-r*o,c*s+r*a,i*s*s+n,0,0,0,0,1),this}makeScale(e,t,n){return this.set(e,0,0,0,0,t,0,0,0,0,n,0,0,0,0,1),this}makeShear(e,t,n){return this.set(1,t,n,0,e,1,n,0,e,t,1,0,0,0,0,1),this}compose(e,t,n){const r=this.elements,i=t._x,a=t._y,o=t._z,s=t._w,l=i+i,c=a+a,h=o+o,u=i*l,d=i*c,p=i*h,m=a*c,f=a*h,g=o*h,v=s*l,_=s*c,y=s*h,x=n.x,b=n.y,M=n.z;return r[0]=(1-(m+g))*x,r[1]=(d+y)*x,r[2]=(p-_)*x,r[3]=0,r[4]=(d-y)*b,r[5]=(1-(u+g))*b,r[6]=(f+v)*b,r[7]=0,r[8]=(p+_)*M,r[9]=(f-v)*M,r[10]=(1-(u+m))*M,r[11]=0,r[12]=e.x,r[13]=e.y,r[14]=e.z,r[15]=1,this}decompose(e,t,n){const r=this.elements;let i=_v1$1.set(r[0],r[1],r[2]).length();const a=_v1$1.set(r[4],r[5],r[6]).length(),o=_v1$1.set(r[8],r[9],r[10]).length();this.determinant()<0&&(i=-i),e.x=r[12],e.y=r[13],e.z=r[14],_m1.copy(this);const s=1/i,l=1/a,c=1/o;return _m1.elements[0]*=s,_m1.elements[1]*=s,_m1.elements[2]*=s,_m1.elements[4]*=l,_m1.elements[5]*=l,_m1.elements[6]*=l,_m1.elements[8]*=c,_m1.elements[9]*=c,_m1.elements[10]*=c,t.setFromRotationMatrix(_m1),n.x=i,n.y=a,n.z=o,this}makePerspective(e,t,n,r,i,a){void 0===a&&console.warn("THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.");const o=this.elements,s=2*i/(t-e),l=2*i/(n-r),c=(t+e)/(t-e),h=(n+r)/(n-r),u=-(a+i)/(a-i),d=-2*a*i/(a-i);return o[0]=s,o[4]=0,o[8]=c,o[12]=0,o[1]=0,o[5]=l,o[9]=h,o[13]=0,o[2]=0,o[6]=0,o[10]=u,o[14]=d,o[3]=0,o[7]=0,o[11]=-1,o[15]=0,this}makeOrthographic(e,t,n,r,i,a){const o=this.elements,s=1/(t-e),l=1/(n-r),c=1/(a-i),h=(t+e)*s,u=(n+r)*l,d=(a+i)*c;return o[0]=2*s,o[4]=0,o[8]=0,o[12]=-h,o[1]=0,o[5]=2*l,o[9]=0,o[13]=-u,o[2]=0,o[6]=0,o[10]=-2*c,o[14]=-d,o[3]=0,o[7]=0,o[11]=0,o[15]=1,this}equals(e){const t=this.elements,n=e.elements;for(let e=0;e<16;e++)if(t[e]!==n[e])return!1;return!0}fromArray(e,t=0){for(let n=0;n<16;n++)this.elements[n]=e[n+t];return this}toArray(e=[],t=0){const n=this.elements;return e[t]=n[0],e[t+1]=n[1],e[t+2]=n[2],e[t+3]=n[3],e[t+4]=n[4],e[t+5]=n[5],e[t+6]=n[6],e[t+7]=n[7],e[t+8]=n[8],e[t+9]=n[9],e[t+10]=n[10],e[t+11]=n[11],e[t+12]=n[12],e[t+13]=n[13],e[t+14]=n[14],e[t+15]=n[15],e}}Matrix4.prototype.isMatrix4=!0;const _v1$1=new Vector3,_m1=new Matrix4,_zero=new Vector3(0,0,0),_one=new Vector3(1,1,1),_x=new Vector3,_y=new Vector3,_z=new Vector3,_matrix=new Matrix4,_quaternion$1=new Quaternion;class Euler{constructor(e=0,t=0,n=0,r=Euler.DefaultOrder){this._x=e,this._y=t,this._z=n,this._order=r}get x(){return this._x}set x(e){this._x=e,this._onChangeCallback()}get y(){return this._y}set y(e){this._y=e,this._onChangeCallback()}get z(){return this._z}set z(e){this._z=e,this._onChangeCallback()}get order(){return this._order}set order(e){this._order=e,this._onChangeCallback()}set(e,t,n,r){return this._x=e,this._y=t,this._z=n,this._order=r||this._order,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._order)}copy(e){return this._x=e._x,this._y=e._y,this._z=e._z,this._order=e._order,this._onChangeCallback(),this}setFromRotationMatrix(e,t,n){const r=MathUtils.clamp,i=e.elements,a=i[0],o=i[4],s=i[8],l=i[1],c=i[5],h=i[9],u=i[2],d=i[6],p=i[10];switch(t=t||this._order){case"XYZ":this._y=Math.asin(r(s,-1,1)),Math.abs(s)<.9999999?(this._x=Math.atan2(-h,p),this._z=Math.atan2(-o,a)):(this._x=Math.atan2(d,c),this._z=0);break;case"YXZ":this._x=Math.asin(-r(h,-1,1)),Math.abs(h)<.9999999?(this._y=Math.atan2(s,p),this._z=Math.atan2(l,c)):(this._y=Math.atan2(-u,a),this._z=0);break;case"ZXY":this._x=Math.asin(r(d,-1,1)),Math.abs(d)<.9999999?(this._y=Math.atan2(-u,p),this._z=Math.atan2(-o,c)):(this._y=0,this._z=Math.atan2(l,a));break;case"ZYX":this._y=Math.asin(-r(u,-1,1)),Math.abs(u)<.9999999?(this._x=Math.atan2(d,p),this._z=Math.atan2(l,a)):(this._x=0,this._z=Math.atan2(-o,c));break;case"YZX":this._z=Math.asin(r(l,-1,1)),Math.abs(l)<.9999999?(this._x=Math.atan2(-h,c),this._y=Math.atan2(-u,a)):(this._x=0,this._y=Math.atan2(s,p));break;case"XZY":this._z=Math.asin(-r(o,-1,1)),Math.abs(o)<.9999999?(this._x=Math.atan2(d,c),this._y=Math.atan2(s,a)):(this._x=Math.atan2(-h,p),this._y=0);break;default:console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: "+t)}return this._order=t,!1!==n&&this._onChangeCallback(),this}setFromQuaternion(e,t,n){return _matrix.makeRotationFromQuaternion(e),this.setFromRotationMatrix(_matrix,t,n)}setFromVector3(e,t){return this.set(e.x,e.y,e.z,t||this._order)}reorder(e){return _quaternion$1.setFromEuler(this),this.setFromQuaternion(_quaternion$1,e)}equals(e){return e._x===this._x&&e._y===this._y&&e._z===this._z&&e._order===this._order}fromArray(e){return this._x=e[0],this._y=e[1],this._z=e[2],void 0!==e[3]&&(this._order=e[3]),this._onChangeCallback(),this}toArray(e=[],t=0){return e[t]=this._x,e[t+1]=this._y,e[t+2]=this._z,e[t+3]=this._order,e}toVector3(e){return e?e.set(this._x,this._y,this._z):new Vector3(this._x,this._y,this._z)}_onChange(e){return this._onChangeCallback=e,this}_onChangeCallback(){}}Euler.prototype.isEuler=!0,Euler.DefaultOrder="XYZ",Euler.RotationOrders=["XYZ","YZX","ZXY","XZY","YXZ","ZYX"];class Layers{constructor(){this.mask=1}set(e){this.mask=1<1){for(let e=0;e1){for(let e=0;e0){r.children=[];for(let t=0;t0){r.animations=[];for(let t=0;t0&&(n.geometries=t),r.length>0&&(n.materials=r),i.length>0&&(n.textures=i),o.length>0&&(n.images=o),s.length>0&&(n.shapes=s),l.length>0&&(n.skeletons=l),c.length>0&&(n.animations=c)}return n.object=r,n;function a(e){const t=[];for(const n in e){const r=e[n];delete r.metadata,t.push(r)}return t}},clone:function(e){return(new this.constructor).copy(this,e)},copy:function(e,t=!0){if(this.name=e.name,this.up.copy(e.up),this.position.copy(e.position),this.rotation.order=e.rotation.order,this.quaternion.copy(e.quaternion),this.scale.copy(e.scale),this.matrix.copy(e.matrix),this.matrixWorld.copy(e.matrixWorld),this.matrixAutoUpdate=e.matrixAutoUpdate,this.matrixWorldNeedsUpdate=e.matrixWorldNeedsUpdate,this.layers.mask=e.layers.mask,this.visible=e.visible,this.castShadow=e.castShadow,this.receiveShadow=e.receiveShadow,this.frustumCulled=e.frustumCulled,this.renderOrder=e.renderOrder,this.userData=JSON.parse(JSON.stringify(e.userData)),!0===t)for(let t=0;t1?void 0:t.copy(n).multiplyScalar(i).add(e.start)}intersectsLine(e){const t=this.distanceToPoint(e.start),n=this.distanceToPoint(e.end);return t<0&&n>0||n<0&&t>0}intersectsBox(e){return e.intersectsPlane(this)}intersectsSphere(e){return e.intersectsPlane(this)}coplanarPoint(e){return void 0===e&&(console.warn("THREE.Plane: .coplanarPoint() target is now required"),e=new Vector3),e.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(e,t){const n=t||_normalMatrix.getNormalMatrix(e),r=this.coplanarPoint(_vector1).applyMatrix4(e),i=this.normal.applyMatrix3(n).normalize();return this.constant=-r.dot(i),this}translate(e){return this.constant-=e.dot(this.normal),this}equals(e){return e.normal.equals(this.normal)&&e.constant===this.constant}clone(){return(new this.constructor).copy(this)}}Plane.prototype.isPlane=!0;const _v0$1=new Vector3,_v1$3=new Vector3,_v2$1=new Vector3,_v3=new Vector3,_vab=new Vector3,_vac=new Vector3,_vbc=new Vector3,_vap=new Vector3,_vbp=new Vector3,_vcp=new Vector3;class Triangle{constructor(e=new Vector3,t=new Vector3,n=new Vector3){this.a=e,this.b=t,this.c=n}static getNormal(e,t,n,r){void 0===r&&(console.warn("THREE.Triangle: .getNormal() target is now required"),r=new Vector3),r.subVectors(n,t),_v0$1.subVectors(e,t),r.cross(_v0$1);const i=r.lengthSq();return i>0?r.multiplyScalar(1/Math.sqrt(i)):r.set(0,0,0)}static getBarycoord(e,t,n,r,i){_v0$1.subVectors(r,t),_v1$3.subVectors(n,t),_v2$1.subVectors(e,t);const a=_v0$1.dot(_v0$1),o=_v0$1.dot(_v1$3),s=_v0$1.dot(_v2$1),l=_v1$3.dot(_v1$3),c=_v1$3.dot(_v2$1),h=a*l-o*o;if(void 0===i&&(console.warn("THREE.Triangle: .getBarycoord() target is now required"),i=new Vector3),0===h)return i.set(-2,-1,-1);const u=1/h,d=(l*s-o*c)*u,p=(a*c-o*s)*u;return i.set(1-d-p,p,d)}static containsPoint(e,t,n,r){return this.getBarycoord(e,t,n,r,_v3),_v3.x>=0&&_v3.y>=0&&_v3.x+_v3.y<=1}static getUV(e,t,n,r,i,a,o,s){return this.getBarycoord(e,t,n,r,_v3),s.set(0,0),s.addScaledVector(i,_v3.x),s.addScaledVector(a,_v3.y),s.addScaledVector(o,_v3.z),s}static isFrontFacing(e,t,n,r){return _v0$1.subVectors(n,t),_v1$3.subVectors(e,t),_v0$1.cross(_v1$3).dot(r)<0}set(e,t,n){return this.a.copy(e),this.b.copy(t),this.c.copy(n),this}setFromPointsAndIndices(e,t,n,r){return this.a.copy(e[t]),this.b.copy(e[n]),this.c.copy(e[r]),this}clone(){return(new this.constructor).copy(this)}copy(e){return this.a.copy(e.a),this.b.copy(e.b),this.c.copy(e.c),this}getArea(){return _v0$1.subVectors(this.c,this.b),_v1$3.subVectors(this.a,this.b),.5*_v0$1.cross(_v1$3).length()}getMidpoint(e){return void 0===e&&(console.warn("THREE.Triangle: .getMidpoint() target is now required"),e=new Vector3),e.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(e){return Triangle.getNormal(this.a,this.b,this.c,e)}getPlane(e){return void 0===e&&(console.warn("THREE.Triangle: .getPlane() target is now required"),e=new Plane),e.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(e,t){return Triangle.getBarycoord(e,this.a,this.b,this.c,t)}getUV(e,t,n,r,i){return Triangle.getUV(e,this.a,this.b,this.c,t,n,r,i)}containsPoint(e){return Triangle.containsPoint(e,this.a,this.b,this.c)}isFrontFacing(e){return Triangle.isFrontFacing(this.a,this.b,this.c,e)}intersectsBox(e){return e.intersectsTriangle(this)}closestPointToPoint(e,t){void 0===t&&(console.warn("THREE.Triangle: .closestPointToPoint() target is now required"),t=new Vector3);const n=this.a,r=this.b,i=this.c;let a,o;_vab.subVectors(r,n),_vac.subVectors(i,n),_vap.subVectors(e,n);const s=_vab.dot(_vap),l=_vac.dot(_vap);if(s<=0&&l<=0)return t.copy(n);_vbp.subVectors(e,r);const c=_vab.dot(_vbp),h=_vac.dot(_vbp);if(c>=0&&h<=c)return t.copy(r);const u=s*h-c*l;if(u<=0&&s>=0&&c<=0)return a=s/(s-c),t.copy(n).addScaledVector(_vab,a);_vcp.subVectors(e,i);const d=_vab.dot(_vcp),p=_vac.dot(_vcp);if(p>=0&&d<=p)return t.copy(i);const m=d*l-s*p;if(m<=0&&l>=0&&p<=0)return o=l/(l-p),t.copy(n).addScaledVector(_vac,o);const f=c*p-d*h;if(f<=0&&h-c>=0&&d-p>=0)return _vbc.subVectors(i,r),o=(h-c)/(h-c+(d-p)),t.copy(r).addScaledVector(_vbc,o);const g=1/(f+m+u);return a=m*g,o=u*g,t.copy(n).addScaledVector(_vab,a).addScaledVector(_vac,o)}equals(e){return e.a.equals(this.a)&&e.b.equals(this.b)&&e.c.equals(this.c)}}let materialId=0;function Material(){Object.defineProperty(this,"id",{value:materialId++}),this.uuid=MathUtils.generateUUID(),this.name="",this.type="Material",this.fog=!0,this.blending=1,this.side=0,this.vertexColors=!1,this.opacity=1,this.transparent=!1,this.blendSrc=204,this.blendDst=205,this.blendEquation=100,this.blendSrcAlpha=null,this.blendDstAlpha=null,this.blendEquationAlpha=null,this.depthFunc=3,this.depthTest=!0,this.depthWrite=!0,this.stencilWriteMask=255,this.stencilFunc=519,this.stencilRef=0,this.stencilFuncMask=255,this.stencilFail=7680,this.stencilZFail=7680,this.stencilZPass=7680,this.stencilWrite=!1,this.clippingPlanes=null,this.clipIntersection=!1,this.clipShadows=!1,this.shadowSide=null,this.colorWrite=!0,this.precision=null,this.polygonOffset=!1,this.polygonOffsetFactor=0,this.polygonOffsetUnits=0,this.dithering=!1,this.alphaTest=0,this.premultipliedAlpha=!1,this.visible=!0,this.toneMapped=!0,this.userData={},this.version=0}Material.prototype=Object.assign(Object.create(EventDispatcher.prototype),{constructor:Material,isMaterial:!0,onBeforeCompile:function(){},customProgramCacheKey:function(){return this.onBeforeCompile.toString()},setValues:function(e){if(void 0!==e)for(const t in e){const n=e[t];if(void 0===n){console.warn("THREE.Material: '"+t+"' parameter is undefined.");continue}if("shading"===t){console.warn("THREE."+this.type+": .shading has been removed. Use the boolean .flatShading instead."),this.flatShading=1===n;continue}const r=this[t];void 0!==r?r&&r.isColor?r.set(n):r&&r.isVector3&&n&&n.isVector3?r.copy(n):this[t]=n:console.warn("THREE."+this.type+": '"+t+"' is not a property of this material.")}},toJSON:function(e){const t=void 0===e||"string"==typeof e;t&&(e={textures:{},images:{}});const n={metadata:{version:4.5,type:"Material",generator:"Material.toJSON"}};function r(e){const t=[];for(const n in e){const r=e[n];delete r.metadata,t.push(r)}return t}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),this.sheen&&this.sheen.isColor&&(n.sheen=this.sheen.getHex()),this.emissive&&this.emissive.isColor&&(n.emissive=this.emissive.getHex()),this.emissiveIntensity&&1!==this.emissiveIntensity&&(n.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(n.specular=this.specular.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(e).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(n.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(e).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(n.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(e).uuid,n.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),this.map&&this.map.isTexture&&(n.map=this.map.toJSON(e).uuid),this.matcap&&this.matcap.isTexture&&(n.matcap=this.matcap.toJSON(e).uuid),this.alphaMap&&this.alphaMap.isTexture&&(n.alphaMap=this.alphaMap.toJSON(e).uuid),this.lightMap&&this.lightMap.isTexture&&(n.lightMap=this.lightMap.toJSON(e).uuid,n.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(n.aoMap=this.aoMap.toJSON(e).uuid,n.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(n.bumpMap=this.bumpMap.toJSON(e).uuid,n.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(n.normalMap=this.normalMap.toJSON(e).uuid,n.normalMapType=this.normalMapType,n.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(n.displacementMap=this.displacementMap.toJSON(e).uuid,n.displacementScale=this.displacementScale,n.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(n.roughnessMap=this.roughnessMap.toJSON(e).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(n.metalnessMap=this.metalnessMap.toJSON(e).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(n.emissiveMap=this.emissiveMap.toJSON(e).uuid),this.specularMap&&this.specularMap.isTexture&&(n.specularMap=this.specularMap.toJSON(e).uuid),this.envMap&&this.envMap.isTexture&&(n.envMap=this.envMap.toJSON(e).uuid,n.reflectivity=this.reflectivity,n.refractionRatio=this.refractionRatio,void 0!==this.combine&&(n.combine=this.combine),void 0!==this.envMapIntensity&&(n.envMapIntensity=this.envMapIntensity)),this.gradientMap&&this.gradientMap.isTexture&&(n.gradientMap=this.gradientMap.toJSON(e).uuid),void 0!==this.size&&(n.size=this.size),void 0!==this.sizeAttenuation&&(n.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(n.blending=this.blending),0!==this.side&&(n.side=this.side),this.vertexColors&&(n.vertexColors=!0),this.opacity<1&&(n.opacity=this.opacity),!0===this.transparent&&(n.transparent=this.transparent),n.depthFunc=this.depthFunc,n.depthTest=this.depthTest,n.depthWrite=this.depthWrite,n.stencilWrite=this.stencilWrite,n.stencilWriteMask=this.stencilWriteMask,n.stencilFunc=this.stencilFunc,n.stencilRef=this.stencilRef,n.stencilFuncMask=this.stencilFuncMask,n.stencilFail=this.stencilFail,n.stencilZFail=this.stencilZFail,n.stencilZPass=this.stencilZPass,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),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.premultipliedAlpha&&(n.premultipliedAlpha=this.premultipliedAlpha),!0===this.wireframe&&(n.wireframe=this.wireframe),this.wireframeLinewidth>1&&(n.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(n.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(n.wireframeLinejoin=this.wireframeLinejoin),!0===this.morphTargets&&(n.morphTargets=!0),!0===this.morphNormals&&(n.morphNormals=!0),!0===this.skinning&&(n.skinning=!0),!0===this.flatShading&&(n.flatShading=this.flatShading),!1===this.visible&&(n.visible=!1),!1===this.toneMapped&&(n.toneMapped=!1),"{}"!==JSON.stringify(this.userData)&&(n.userData=this.userData),t){const t=r(e.textures),i=r(e.images);t.length>0&&(n.textures=t),i.length>0&&(n.images=i)}return n},clone:function(){return(new this.constructor).copy(this)},copy:function(e){this.name=e.name,this.fog=e.fog,this.blending=e.blending,this.side=e.side,this.vertexColors=e.vertexColors,this.opacity=e.opacity,this.transparent=e.transparent,this.blendSrc=e.blendSrc,this.blendDst=e.blendDst,this.blendEquation=e.blendEquation,this.blendSrcAlpha=e.blendSrcAlpha,this.blendDstAlpha=e.blendDstAlpha,this.blendEquationAlpha=e.blendEquationAlpha,this.depthFunc=e.depthFunc,this.depthTest=e.depthTest,this.depthWrite=e.depthWrite,this.stencilWriteMask=e.stencilWriteMask,this.stencilFunc=e.stencilFunc,this.stencilRef=e.stencilRef,this.stencilFuncMask=e.stencilFuncMask,this.stencilFail=e.stencilFail,this.stencilZFail=e.stencilZFail,this.stencilZPass=e.stencilZPass,this.stencilWrite=e.stencilWrite;const t=e.clippingPlanes;let n=null;if(null!==t){const e=t.length;n=new Array(e);for(let r=0;r!==e;++r)n[r]=t[r].clone()}return this.clippingPlanes=n,this.clipIntersection=e.clipIntersection,this.clipShadows=e.clipShadows,this.shadowSide=e.shadowSide,this.colorWrite=e.colorWrite,this.precision=e.precision,this.polygonOffset=e.polygonOffset,this.polygonOffsetFactor=e.polygonOffsetFactor,this.polygonOffsetUnits=e.polygonOffsetUnits,this.dithering=e.dithering,this.alphaTest=e.alphaTest,this.premultipliedAlpha=e.premultipliedAlpha,this.visible=e.visible,this.toneMapped=e.toneMapped,this.userData=JSON.parse(JSON.stringify(e.userData)),this},dispose:function(){this.dispatchEvent({type:"dispose"})}}),Object.defineProperty(Material.prototype,"needsUpdate",{set:function(e){!0===e&&this.version++}});const _colorKeywords={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},_hslA={h:0,s:0,l:0},_hslB={h:0,s:0,l:0};function hue2rgb(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+6*(t-e)*n:n<.5?t:n<2/3?e+6*(t-e)*(2/3-n):e}function SRGBToLinear(e){return e<.04045?.0773993808*e:Math.pow(.9478672986*e+.0521327014,2.4)}function LinearToSRGB(e){return e<.0031308?12.92*e:1.055*Math.pow(e,.41666)-.055}class Color{constructor(e,t,n){return void 0===t&&void 0===n?this.set(e):this.setRGB(e,t,n)}set(e){return e&&e.isColor?this.copy(e):"number"==typeof e?this.setHex(e):"string"==typeof e&&this.setStyle(e),this}setScalar(e){return this.r=e,this.g=e,this.b=e,this}setHex(e){return e=Math.floor(e),this.r=(e>>16&255)/255,this.g=(e>>8&255)/255,this.b=(255&e)/255,this}setRGB(e,t,n){return this.r=e,this.g=t,this.b=n,this}setHSL(e,t,n){if(e=MathUtils.euclideanModulo(e,1),t=MathUtils.clamp(t,0,1),n=MathUtils.clamp(n,0,1),0===t)this.r=this.g=this.b=n;else{const r=n<=.5?n*(1+t):n+t-n*t,i=2*n-r;this.r=hue2rgb(i,r,e+1/3),this.g=hue2rgb(i,r,e),this.b=hue2rgb(i,r,e-1/3)}return this}setStyle(e){function t(t){void 0!==t&&parseFloat(t)<1&&console.warn("THREE.Color: Alpha component of "+e+" will be ignored.")}let n;if(n=/^((?:rgb|hsl)a?)\(([^\)]*)\)/.exec(e)){let e;const r=n[1],i=n[2];switch(r){case"rgb":case"rgba":if(e=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(i))return this.r=Math.min(255,parseInt(e[1],10))/255,this.g=Math.min(255,parseInt(e[2],10))/255,this.b=Math.min(255,parseInt(e[3],10))/255,t(e[4]),this;if(e=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(i))return this.r=Math.min(100,parseInt(e[1],10))/100,this.g=Math.min(100,parseInt(e[2],10))/100,this.b=Math.min(100,parseInt(e[3],10))/100,t(e[4]),this;break;case"hsl":case"hsla":if(e=/^\s*(\d*\.?\d+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(i)){const n=parseFloat(e[1])/360,r=parseInt(e[2],10)/100,i=parseInt(e[3],10)/100;return t(e[4]),this.setHSL(n,r,i)}}}else if(n=/^\#([A-Fa-f\d]+)$/.exec(e)){const e=n[1],t=e.length;if(3===t)return this.r=parseInt(e.charAt(0)+e.charAt(0),16)/255,this.g=parseInt(e.charAt(1)+e.charAt(1),16)/255,this.b=parseInt(e.charAt(2)+e.charAt(2),16)/255,this;if(6===t)return this.r=parseInt(e.charAt(0)+e.charAt(1),16)/255,this.g=parseInt(e.charAt(2)+e.charAt(3),16)/255,this.b=parseInt(e.charAt(4)+e.charAt(5),16)/255,this}return e&&e.length>0?this.setColorName(e):this}setColorName(e){const t=_colorKeywords[e];return void 0!==t?this.setHex(t):console.warn("THREE.Color: Unknown color "+e),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(e){return this.r=e.r,this.g=e.g,this.b=e.b,this}copyGammaToLinear(e,t=2){return this.r=Math.pow(e.r,t),this.g=Math.pow(e.g,t),this.b=Math.pow(e.b,t),this}copyLinearToGamma(e,t=2){const n=t>0?1/t:1;return this.r=Math.pow(e.r,n),this.g=Math.pow(e.g,n),this.b=Math.pow(e.b,n),this}convertGammaToLinear(e){return this.copyGammaToLinear(this,e),this}convertLinearToGamma(e){return this.copyLinearToGamma(this,e),this}copySRGBToLinear(e){return this.r=SRGBToLinear(e.r),this.g=SRGBToLinear(e.g),this.b=SRGBToLinear(e.b),this}copyLinearToSRGB(e){return this.r=LinearToSRGB(e.r),this.g=LinearToSRGB(e.g),this.b=LinearToSRGB(e.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(){return 255*this.r<<16^255*this.g<<8^255*this.b}getHexString(){return("000000"+this.getHex().toString(16)).slice(-6)}getHSL(e){void 0===e&&(console.warn("THREE.Color: .getHSL() target is now required"),e={h:0,s:0,l:0});const t=this.r,n=this.g,r=this.b,i=Math.max(t,n,r),a=Math.min(t,n,r);let o,s;const l=(a+i)/2;if(a===i)o=0,s=0;else{const e=i-a;switch(s=l<=.5?e/(i+a):e/(2-i-a),i){case t:o=(n-r)/e+(nt&&(t=e[n]);return t}Object.defineProperty(BufferAttribute.prototype,"needsUpdate",{set:function(e){!0===e&&this.version++}}),Object.assign(BufferAttribute.prototype,{isBufferAttribute:!0,onUploadCallback:function(){},setUsage:function(e){return this.usage=e,this},copy:function(e){return this.name=e.name,this.array=new e.array.constructor(e.array),this.itemSize=e.itemSize,this.count=e.count,this.normalized=e.normalized,this.usage=e.usage,this},copyAt:function(e,t,n){e*=this.itemSize,n*=t.itemSize;for(let r=0,i=this.itemSize;r65535?Uint32BufferAttribute:Uint16BufferAttribute)(e,1):this.index=e,this},getAttribute:function(e){return this.attributes[e]},setAttribute:function(e,t){return this.attributes[e]=t,this},deleteAttribute:function(e){return delete this.attributes[e],this},hasAttribute:function(e){return void 0!==this.attributes[e]},addGroup:function(e,t,n=0){this.groups.push({start:e,count:t,materialIndex:n})},clearGroups:function(){this.groups=[]},setDrawRange:function(e,t){this.drawRange.start=e,this.drawRange.count=t},applyMatrix4:function(e){const t=this.attributes.position;void 0!==t&&(t.applyMatrix4(e),t.needsUpdate=!0);const n=this.attributes.normal;if(void 0!==n){const t=(new Matrix3).getNormalMatrix(e);n.applyNormalMatrix(t),n.needsUpdate=!0}const r=this.attributes.tangent;return void 0!==r&&(r.transformDirection(e),r.needsUpdate=!0),null!==this.boundingBox&&this.computeBoundingBox(),null!==this.boundingSphere&&this.computeBoundingSphere(),this},rotateX:function(e){return _m1$2.makeRotationX(e),this.applyMatrix4(_m1$2),this},rotateY:function(e){return _m1$2.makeRotationY(e),this.applyMatrix4(_m1$2),this},rotateZ:function(e){return _m1$2.makeRotationZ(e),this.applyMatrix4(_m1$2),this},translate:function(e,t,n){return _m1$2.makeTranslation(e,t,n),this.applyMatrix4(_m1$2),this},scale:function(e,t,n){return _m1$2.makeScale(e,t,n),this.applyMatrix4(_m1$2),this},lookAt:function(e){return _obj.lookAt(e),_obj.updateMatrix(),this.applyMatrix4(_obj.matrix),this},center:function(){return this.computeBoundingBox(),this.boundingBox.getCenter(_offset).negate(),this.translate(_offset.x,_offset.y,_offset.z),this},setFromPoints:function(e){const t=[];for(let n=0,r=e.length;n0&&(e.userData=this.userData),void 0!==this.parameters){const t=this.parameters;for(const n in t)void 0!==t[n]&&(e[n]=t[n]);return e}e.data={attributes:{}};const t=this.index;null!==t&&(e.data.index={type:t.array.constructor.name,array:Array.prototype.slice.call(t.array)});const n=this.attributes;for(const t in n){const r=n[t],i=r.toJSON(e.data);""!==r.name&&(i.name=r.name),e.data.attributes[t]=i}const r={};let i=!1;for(const t in this.morphAttributes){const n=this.morphAttributes[t],a=[];for(let t=0,r=n.length;t0&&(r[t]=a,i=!0)}i&&(e.data.morphAttributes=r,e.data.morphTargetsRelative=this.morphTargetsRelative);const a=this.groups;a.length>0&&(e.data.groups=JSON.parse(JSON.stringify(a)));const o=this.boundingSphere;return null!==o&&(e.data.boundingSphere={center:o.center.toArray(),radius:o.radius}),e},clone:function(){return(new BufferGeometry).copy(this)},copy:function(e){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const t={};this.name=e.name;const n=e.index;null!==n&&this.setIndex(n.clone(t));const r=e.attributes;for(const e in r){const n=r[e];this.setAttribute(e,n.clone(t))}const i=e.morphAttributes;for(const e in i){const n=[],r=i[e];for(let e=0,i=r.length;en.far?null:{distance:c,point:_intersectionPointWorld.clone(),object:e}}function checkBufferGeometryIntersection(e,t,n,r,i,a,o,s,l,c,h,u){_vA.fromBufferAttribute(i,c),_vB.fromBufferAttribute(i,h),_vC.fromBufferAttribute(i,u);const d=e.morphTargetInfluences;if(t.morphTargets&&a&&d){_morphA.set(0,0,0),_morphB.set(0,0,0),_morphC.set(0,0,0);for(let e=0,t=a.length;e0){const e=t[n[0]];if(void 0!==e){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,n=e.length;t0&&console.error("THREE.Mesh.updateMorphTargets() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.")}},raycast:function(e,t){const n=this.geometry,r=this.material,i=this.matrixWorld;if(void 0===r)return;if(null===n.boundingSphere&&n.computeBoundingSphere(),_sphere.copy(n.boundingSphere),_sphere.applyMatrix4(i),!1===e.ray.intersectsSphere(_sphere))return;if(_inverseMatrix.copy(i).invert(),_ray.copy(e.ray).applyMatrix4(_inverseMatrix),null!==n.boundingBox&&!1===_ray.intersectsBox(n.boundingBox))return;let a;if(n.isBufferGeometry){const i=n.index,o=n.attributes.position,s=n.morphAttributes.position,l=n.morphTargetsRelative,c=n.attributes.uv,h=n.attributes.uv2,u=n.groups,d=n.drawRange;if(null!==i)if(Array.isArray(r))for(let n=0,p=u.length;n0?1:-1,c.push(A.x,A.y,A.z),h.push(s/f),h.push(1-a/g),T+=1}}for(let e=0;e0&&(t.defines=this.defines),t.vertexShader=this.vertexShader,t.fragmentShader=this.fragmentShader;const n={};for(const e in this.extensions)!0===this.extensions[e]&&(n[e]=!0);return Object.keys(n).length>0&&(t.extensions=n),t},Camera.prototype=Object.assign(Object.create(Object3D.prototype),{constructor:Camera,isCamera:!0,copy:function(e,t){return Object3D.prototype.copy.call(this,e,t),this.matrixWorldInverse.copy(e.matrixWorldInverse),this.projectionMatrix.copy(e.projectionMatrix),this.projectionMatrixInverse.copy(e.projectionMatrixInverse),this},getWorldDirection:function(e){void 0===e&&(console.warn("THREE.Camera: .getWorldDirection() target is now required"),e=new Vector3),this.updateWorldMatrix(!0,!1);const t=this.matrixWorld.elements;return e.set(-t[8],-t[9],-t[10]).normalize()},updateMatrixWorld:function(e){Object3D.prototype.updateMatrixWorld.call(this,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()},updateWorldMatrix:function(e,t){Object3D.prototype.updateWorldMatrix.call(this,e,t),this.matrixWorldInverse.copy(this.matrixWorld).invert()},clone:function(){return(new this.constructor).copy(this)}}),PerspectiveCamera.prototype=Object.assign(Object.create(Camera.prototype),{constructor:PerspectiveCamera,isPerspectiveCamera:!0,copy:function(e,t){return Camera.prototype.copy.call(this,e,t),this.fov=e.fov,this.zoom=e.zoom,this.near=e.near,this.far=e.far,this.focus=e.focus,this.aspect=e.aspect,this.view=null===e.view?null:Object.assign({},e.view),this.filmGauge=e.filmGauge,this.filmOffset=e.filmOffset,this},setFocalLength:function(e){const t=.5*this.getFilmHeight()/e;this.fov=2*MathUtils.RAD2DEG*Math.atan(t),this.updateProjectionMatrix()},getFocalLength:function(){const e=Math.tan(.5*MathUtils.DEG2RAD*this.fov);return.5*this.getFilmHeight()/e},getEffectiveFOV:function(){return 2*MathUtils.RAD2DEG*Math.atan(Math.tan(.5*MathUtils.DEG2RAD*this.fov)/this.zoom)},getFilmWidth:function(){return this.filmGauge*Math.min(this.aspect,1)},getFilmHeight:function(){return this.filmGauge/Math.max(this.aspect,1)},setViewOffset:function(e,t,n,r,i,a){this.aspect=e/t,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=e,this.view.fullHeight=t,this.view.offsetX=n,this.view.offsetY=r,this.view.width=i,this.view.height=a,this.updateProjectionMatrix()},clearViewOffset:function(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()},updateProjectionMatrix:function(){const e=this.near;let t=e*Math.tan(.5*MathUtils.DEG2RAD*this.fov)/this.zoom,n=2*t,r=this.aspect*n,i=-.5*r;const a=this.view;if(null!==this.view&&this.view.enabled){const e=a.fullWidth,o=a.fullHeight;i+=a.offsetX*r/e,t-=a.offsetY*n/o,r*=a.width/e,n*=a.height/o}const o=this.filmOffset;0!==o&&(i+=e*o/this.getFilmWidth()),this.projectionMatrix.makePerspective(i,i+r,t,t-n,e,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()},toJSON:function(e){const t=Object3D.prototype.toJSON.call(this,e);return t.object.fov=this.fov,t.object.zoom=this.zoom,t.object.near=this.near,t.object.far=this.far,t.object.focus=this.focus,t.object.aspect=this.aspect,null!==this.view&&(t.object.view=Object.assign({},this.view)),t.object.filmGauge=this.filmGauge,t.object.filmOffset=this.filmOffset,t}});const fov=90,aspect=1;class CubeCamera extends Object3D{constructor(e,t,n){if(super(),this.type="CubeCamera",!0!==n.isWebGLCubeRenderTarget)return void console.error("THREE.CubeCamera: The constructor now expects an instance of WebGLCubeRenderTarget as third parameter.");this.renderTarget=n;const r=new PerspectiveCamera(90,1,e,t);r.layers=this.layers,r.up.set(0,-1,0),r.lookAt(new Vector3(1,0,0)),this.add(r);const i=new PerspectiveCamera(90,1,e,t);i.layers=this.layers,i.up.set(0,-1,0),i.lookAt(new Vector3(-1,0,0)),this.add(i);const a=new PerspectiveCamera(90,1,e,t);a.layers=this.layers,a.up.set(0,0,1),a.lookAt(new Vector3(0,1,0)),this.add(a);const o=new PerspectiveCamera(90,1,e,t);o.layers=this.layers,o.up.set(0,0,-1),o.lookAt(new Vector3(0,-1,0)),this.add(o);const s=new PerspectiveCamera(90,1,e,t);s.layers=this.layers,s.up.set(0,-1,0),s.lookAt(new Vector3(0,0,1)),this.add(s);const l=new PerspectiveCamera(90,1,e,t);l.layers=this.layers,l.up.set(0,-1,0),l.lookAt(new Vector3(0,0,-1)),this.add(l)}update(e,t){null===this.parent&&this.updateMatrixWorld();const n=this.renderTarget,[r,i,a,o,s,l]=this.children,c=e.xr.enabled,h=e.getRenderTarget();e.xr.enabled=!1;const u=n.texture.generateMipmaps;n.texture.generateMipmaps=!1,e.setRenderTarget(n,0),e.render(t,r),e.setRenderTarget(n,1),e.render(t,i),e.setRenderTarget(n,2),e.render(t,a),e.setRenderTarget(n,3),e.render(t,o),e.setRenderTarget(n,4),e.render(t,s),n.texture.generateMipmaps=u,e.setRenderTarget(n,5),e.render(t,l),e.setRenderTarget(h),e.xr.enabled=c}}class CubeTexture extends Texture{constructor(e,t,n,r,i,a,o,s,l,c){super(e=void 0!==e?e:[],t=void 0!==t?t:301,n,r,i,a,o=void 0!==o?o:1022,s,l,c),this._needsFlipEnvMap=!0,this.flipY=!1}get images(){return this.image}set images(e){this.image=e}}CubeTexture.prototype.isCubeTexture=!0;class WebGLCubeRenderTarget extends WebGLRenderTarget{constructor(e,t,n){Number.isInteger(t)&&(console.warn("THREE.WebGLCubeRenderTarget: constructor signature is now WebGLCubeRenderTarget( size, options )"),t=n),super(e,e,t),t=t||{},this.texture=new CubeTexture(void 0,t.mapping,t.wrapS,t.wrapT,t.magFilter,t.minFilter,t.format,t.type,t.anisotropy,t.encoding),this.texture.generateMipmaps=void 0!==t.generateMipmaps&&t.generateMipmaps,this.texture.minFilter=void 0!==t.minFilter?t.minFilter:1006,this.texture._needsFlipEnvMap=!1}fromEquirectangularTexture(e,t){this.texture.type=t.type,this.texture.format=1023,this.texture.encoding=t.encoding,this.texture.generateMipmaps=t.generateMipmaps,this.texture.minFilter=t.minFilter,this.texture.magFilter=t.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"},r=new BoxGeometry(5,5,5),i=new ShaderMaterial({name:"CubemapFromEquirect",uniforms:cloneUniforms(n.uniforms),vertexShader:n.vertexShader,fragmentShader:n.fragmentShader,side:1,blending:0});i.uniforms.tEquirect.value=t;const a=new Mesh(r,i),o=t.minFilter;1008===t.minFilter&&(t.minFilter=1006);return new CubeCamera(1,10,this).update(e,a),t.minFilter=o,a.geometry.dispose(),a.material.dispose(),this}clear(e,t,n,r){const i=e.getRenderTarget();for(let i=0;i<6;i++)e.setRenderTarget(this,i),e.clear(t,n,r);e.setRenderTarget(i)}}WebGLCubeRenderTarget.prototype.isWebGLCubeRenderTarget=!0;class DataTexture extends Texture{constructor(e,t,n,r,i,a,o,s,l,c,h,u){super(null,a,o,s,l,c,r,i,h,u),this.image={data:e||null,width:t||1,height:n||1},this.magFilter=void 0!==l?l:1003,this.minFilter=void 0!==c?c:1003,this.generateMipmaps=!1,this.flipY=!1,this.unpackAlignment=1,this.needsUpdate=!0}}DataTexture.prototype.isDataTexture=!0;const _sphere$1=new Sphere,_vector$5=new Vector3;class Frustum{constructor(e=new Plane,t=new Plane,n=new Plane,r=new Plane,i=new Plane,a=new Plane){this.planes=[e,t,n,r,i,a]}set(e,t,n,r,i,a){const o=this.planes;return o[0].copy(e),o[1].copy(t),o[2].copy(n),o[3].copy(r),o[4].copy(i),o[5].copy(a),this}copy(e){const t=this.planes;for(let n=0;n<6;n++)t[n].copy(e.planes[n]);return this}setFromProjectionMatrix(e){const t=this.planes,n=e.elements,r=n[0],i=n[1],a=n[2],o=n[3],s=n[4],l=n[5],c=n[6],h=n[7],u=n[8],d=n[9],p=n[10],m=n[11],f=n[12],g=n[13],v=n[14],_=n[15];return t[0].setComponents(o-r,h-s,m-u,_-f).normalize(),t[1].setComponents(o+r,h+s,m+u,_+f).normalize(),t[2].setComponents(o+i,h+l,m+d,_+g).normalize(),t[3].setComponents(o-i,h-l,m-d,_-g).normalize(),t[4].setComponents(o-a,h-c,m-p,_-v).normalize(),t[5].setComponents(o+a,h+c,m+p,_+v).normalize(),this}intersectsObject(e){const t=e.geometry;return null===t.boundingSphere&&t.computeBoundingSphere(),_sphere$1.copy(t.boundingSphere).applyMatrix4(e.matrixWorld),this.intersectsSphere(_sphere$1)}intersectsSprite(e){return _sphere$1.center.set(0,0,0),_sphere$1.radius=.7071067811865476,_sphere$1.applyMatrix4(e.matrixWorld),this.intersectsSphere(_sphere$1)}intersectsSphere(e){const t=this.planes,n=e.center,r=-e.radius;for(let e=0;e<6;e++){if(t[e].distanceToPoint(n)0?e.max.x:e.min.x,_vector$5.y=r.normal.y>0?e.max.y:e.min.y,_vector$5.z=r.normal.z>0?e.max.z:e.min.z,r.distanceToPoint(_vector$5)<0)return!1}return!0}containsPoint(e){const t=this.planes;for(let n=0;n<6;n++)if(t[n].distanceToPoint(e)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}function WebGLAnimation(){let e=null,t=!1,n=null,r=null;function i(t,a){n(t,a),r=e.requestAnimationFrame(i)}return{start:function(){!0!==t&&null!==n&&(r=e.requestAnimationFrame(i),t=!0)},stop:function(){e.cancelAnimationFrame(r),t=!1},setAnimationLoop:function(e){n=e},setContext:function(t){e=t}}}function WebGLAttributes(e,t){const n=t.isWebGL2,r=new WeakMap;return{get:function(e){return e.isInterleavedBufferAttribute&&(e=e.data),r.get(e)},remove:function(t){t.isInterleavedBufferAttribute&&(t=t.data);const n=r.get(t);n&&(e.deleteBuffer(n.buffer),r.delete(t))},update:function(t,i){if(t.isGLBufferAttribute){const e=r.get(t);return void((!e||e.version=0){const a=l[t];if(void 0!==a){const t=a.normalized,i=a.itemSize,o=n.get(a);if(void 0===o)continue;const l=o.buffer,c=o.type,h=o.bytesPerElement;if(a.isInterleavedBufferAttribute){const n=a.data,o=n.stride,u=a.offset;n&&n.isInstancedInterleavedBuffer?(f(r,n.meshPerAttribute),void 0===s._maxInstanceCount&&(s._maxInstanceCount=n.meshPerAttribute*n.count)):m(r),e.bindBuffer(34962,l),v(r,i,c,t,o*h,u*h)}else a.isInstancedBufferAttribute?(f(r,a.meshPerAttribute),void 0===s._maxInstanceCount&&(s._maxInstanceCount=a.meshPerAttribute*a.count)):m(r),e.bindBuffer(34962,l),v(r,i,c,t,0,0)}else if("instanceMatrix"===t){const t=n.get(i.instanceMatrix);if(void 0===t)continue;const a=t.buffer,o=t.type;f(r+0,1),f(r+1,1),f(r+2,1),f(r+3,1),e.bindBuffer(34962,a),e.vertexAttribPointer(r+0,4,o,!1,64,0),e.vertexAttribPointer(r+1,4,o,!1,64,16),e.vertexAttribPointer(r+2,4,o,!1,64,32),e.vertexAttribPointer(r+3,4,o,!1,64,48)}else if("instanceColor"===t){const t=n.get(i.instanceColor);if(void 0===t)continue;const a=t.buffer,o=t.type;f(r,1),e.bindBuffer(34962,a),e.vertexAttribPointer(r,3,o,!1,12,0)}else if(void 0!==h){const n=h[t];if(void 0!==n)switch(n.length){case 2:e.vertexAttrib2fv(r,n);break;case 3:e.vertexAttrib3fv(r,n);break;case 4:e.vertexAttrib4fv(r,n);break;default:e.vertexAttrib1fv(r,n)}}}}g()}(i,l,u,_),null!==y&&e.bindBuffer(34963,n.get(y).buffer))},reset:_,resetDefaultState:y,dispose:function(){_();for(const e in s){const t=s[e];for(const e in t){const n=t[e];for(const e in n)u(n[e].object),delete n[e];delete t[e]}delete s[e]}},releaseStatesOfGeometry:function(e){if(void 0===s[e.id])return;const t=s[e.id];for(const e in t){const n=t[e];for(const e in n)u(n[e].object),delete n[e];delete t[e]}delete s[e.id]},releaseStatesOfProgram:function(e){for(const t in s){const n=s[t];if(void 0===n[e.id])continue;const r=n[e.id];for(const e in r)u(r[e].object),delete r[e];delete n[e.id]}},initAttributes:p,enableAttribute:m,disableUnusedAttributes:g}}function WebGLBufferRenderer(e,t,n,r){const i=r.isWebGL2;let a;this.setMode=function(e){a=e},this.render=function(t,r){e.drawArrays(a,t,r),n.update(r,a,1)},this.renderInstances=function(r,o,s){if(0===s)return;let l,c;if(i)l=e,c="drawArraysInstanced";else if(l=t.get("ANGLE_instanced_arrays"),c="drawArraysInstancedANGLE",null===l)return void console.error("THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.");l[c](a,r,o,s),n.update(o,a,s)}}function WebGLCapabilities(e,t,n){let r;function i(t){if("highp"===t){if(e.getShaderPrecisionFormat(35633,36338).precision>0&&e.getShaderPrecisionFormat(35632,36338).precision>0)return"highp";t="mediump"}return"mediump"===t&&e.getShaderPrecisionFormat(35633,36337).precision>0&&e.getShaderPrecisionFormat(35632,36337).precision>0?"mediump":"lowp"}const a="undefined"!=typeof WebGL2RenderingContext&&e instanceof WebGL2RenderingContext||"undefined"!=typeof WebGL2ComputeRenderingContext&&e instanceof WebGL2ComputeRenderingContext;let o=void 0!==n.precision?n.precision:"highp";const s=i(o);s!==o&&(console.warn("THREE.WebGLRenderer:",o,"not supported, using",s,"instead."),o=s);const l=!0===n.logarithmicDepthBuffer,c=e.getParameter(34930),h=e.getParameter(35660),u=e.getParameter(3379),d=e.getParameter(34076),p=e.getParameter(34921),m=e.getParameter(36347),f=e.getParameter(36348),g=e.getParameter(36349),v=h>0,_=a||t.has("OES_texture_float");return{isWebGL2:a,getMaxAnisotropy:function(){if(void 0!==r)return r;if(!0===t.has("EXT_texture_filter_anisotropic")){const n=t.get("EXT_texture_filter_anisotropic");r=e.getParameter(n.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else r=0;return r},getMaxPrecision:i,precision:o,logarithmicDepthBuffer:l,maxTextures:c,maxVertexTextures:h,maxTextureSize:u,maxCubemapSize:d,maxAttributes:p,maxVertexUniforms:m,maxVaryings:f,maxFragmentUniforms:g,vertexTextures:v,floatFragmentTextures:_,floatVertexTextures:v&&_,maxSamples:a?e.getParameter(36183):0}}function WebGLClipping(e){const t=this;let n=null,r=0,i=!1,a=!1;const o=new Plane,s=new Matrix3,l={value:null,needsUpdate:!1};function c(){l.value!==n&&(l.value=n,l.needsUpdate=r>0),t.numPlanes=r,t.numIntersection=0}function h(e,n,r,i){const a=null!==e?e.length:0;let c=null;if(0!==a){if(c=l.value,!0!==i||null===c){const t=r+4*a,i=n.matrixWorldInverse;s.getNormalMatrix(i),(null===c||c.length0){const o=e.getRenderTarget(),s=new WebGLCubeRenderTarget(a.height/2);return s.fromEquirectangularTexture(e,i),t.set(i,s),e.setRenderTarget(o),i.addEventListener("dispose",r),n(s.texture,i.mapping)}return null}}}return i},dispose:function(){t=new WeakMap}}}function WebGLExtensions(e){const t={};function n(n){if(void 0!==t[n])return t[n];let r;switch(n){case"WEBGL_depth_texture":r=e.getExtension("WEBGL_depth_texture")||e.getExtension("MOZ_WEBGL_depth_texture")||e.getExtension("WEBKIT_WEBGL_depth_texture");break;case"EXT_texture_filter_anisotropic":r=e.getExtension("EXT_texture_filter_anisotropic")||e.getExtension("MOZ_EXT_texture_filter_anisotropic")||e.getExtension("WEBKIT_EXT_texture_filter_anisotropic");break;case"WEBGL_compressed_texture_s3tc":r=e.getExtension("WEBGL_compressed_texture_s3tc")||e.getExtension("MOZ_WEBGL_compressed_texture_s3tc")||e.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc");break;case"WEBGL_compressed_texture_pvrtc":r=e.getExtension("WEBGL_compressed_texture_pvrtc")||e.getExtension("WEBKIT_WEBGL_compressed_texture_pvrtc");break;default:r=e.getExtension(n)}return t[n]=r,r}return{has:function(e){return null!==n(e)},init:function(e){e.isWebGL2?n("EXT_color_buffer_float"):(n("WEBGL_depth_texture"),n("OES_texture_float"),n("OES_texture_half_float"),n("OES_texture_half_float_linear"),n("OES_standard_derivatives"),n("OES_element_index_uint"),n("OES_vertex_array_object"),n("ANGLE_instanced_arrays")),n("OES_texture_float_linear"),n("EXT_color_buffer_half_float")},get:function(e){const t=n(e);return null===t&&console.warn("THREE.WebGLRenderer: "+e+" extension not supported."),t}}}function WebGLGeometries(e,t,n,r){const i={},a=new WeakMap;function o(e){const s=e.target;null!==s.index&&t.remove(s.index);for(const e in s.attributes)t.remove(s.attributes[e]);s.removeEventListener("dispose",o),delete i[s.id];const l=a.get(s);l&&(t.remove(l),a.delete(s)),r.releaseStatesOfGeometry(s),!0===s.isInstancedBufferGeometry&&delete s._maxInstanceCount,n.memory.geometries--}function s(e){const n=[],r=e.index,i=e.attributes.position;let o=0;if(null!==r){const e=r.array;o=r.version;for(let t=0,r=e.length;t65535?Uint32BufferAttribute:Uint16BufferAttribute)(n,1);s.version=o;const l=a.get(e);l&&t.remove(l),a.set(e,s)}return{get:function(e,t){return!0===i[t.id]||(t.addEventListener("dispose",o),i[t.id]=!0,n.memory.geometries++),t},update:function(e){const n=e.attributes;for(const e in n)t.update(n[e],34962);const r=e.morphAttributes;for(const e in r){const n=r[e];for(let e=0,r=n.length;e0)return e;const i=t*n;let a=arrayCacheF32[i];if(void 0===a&&(a=new Float32Array(i),arrayCacheF32[i]=a),0!==t){r.toArray(a,0);for(let r=1,i=0;r!==t;++r)i+=n,e[r].toArray(a,i)}return a}function arraysEqual(e,t){if(e.length!==t.length)return!1;for(let n=0,r=e.length;n/gm;function resolveIncludes(e){return e.replace(includePattern,includeReplacer)}function includeReplacer(e,t){const n=ShaderChunk[t];if(void 0===n)throw new Error("Can not resolve #include <"+t+">");return resolveIncludes(n)}const deprecatedUnrollLoopPattern=/#pragma unroll_loop[\s]+?for \( int i \= (\d+)\; i < (\d+)\; i \+\+ \) \{([\s\S]+?)(?=\})\}/g,unrollLoopPattern=/#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 unrollLoops(e){return e.replace(unrollLoopPattern,loopReplacer).replace(deprecatedUnrollLoopPattern,deprecatedLoopReplacer)}function deprecatedLoopReplacer(e,t,n,r){return console.warn("WebGLProgram: #pragma unroll_loop shader syntax is deprecated. Please use #pragma unroll_loop_start syntax instead."),loopReplacer(e,t,n,r)}function loopReplacer(e,t,n,r){let i="";for(let e=parseInt(t);e0?e.gammaFactor:1,p=n.isWebGL2?"":generateExtensions(n),m=generateDefines(a),f=i.createProgram();let g,v,_=n.glslVersion?"#version "+n.glslVersion+"\n":"";n.isRawShaderMaterial?(g=[m].filter(filterEmptyLine).join("\n"),g.length>0&&(g+="\n"),v=[p,m].filter(filterEmptyLine).join("\n"),v.length>0&&(v+="\n")):(g=[generatePrecision(n),"#define SHADER_NAME "+n.shaderName,m,n.instancing?"#define USE_INSTANCING":"",n.instancingColor?"#define USE_INSTANCING_COLOR":"",n.supportsVertexTextures?"#define VERTEX_TEXTURES":"","#define GAMMA_FACTOR "+d,"#define MAX_BONES "+n.maxBones,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 "+h:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMap&&n.objectSpaceNormalMap?"#define OBJECTSPACE_NORMALMAP":"",n.normalMap&&n.tangentSpaceNormalMap?"#define TANGENTSPACE_NORMALMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.displacementMap&&n.supportsVertexTextures?"#define USE_DISPLACEMENTMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.vertexTangents?"#define USE_TANGENT":"",n.vertexColors?"#define USE_COLOR":"",n.vertexUvs?"#define USE_UV":"",n.uvsVertexOnly?"#define UVS_VERTEX_ONLY":"",n.flatShading?"#define FLAT_SHADED":"",n.skinning?"#define USE_SKINNING":"",n.useVertexTexture?"#define BONE_TEXTURE":"",n.morphTargets?"#define USE_MORPHTARGETS":"",n.morphNormals&&!1===n.flatShading?"#define USE_MORPHNORMALS":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+l:"",n.sizeAttenuation?"#define USE_SIZEATTENUATION":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","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","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_TANGENT","\tattribute vec4 tangent;","#endif","#ifdef USE_COLOR","\tattribute vec3 color;","#endif","#ifdef USE_MORPHTARGETS","\tattribute vec3 morphTarget0;","\tattribute vec3 morphTarget1;","\tattribute vec3 morphTarget2;","\tattribute vec3 morphTarget3;","\t#ifdef USE_MORPHNORMALS","\t\tattribute vec3 morphNormal0;","\t\tattribute vec3 morphNormal1;","\t\tattribute vec3 morphNormal2;","\t\tattribute vec3 morphNormal3;","\t#else","\t\tattribute vec3 morphTarget4;","\t\tattribute vec3 morphTarget5;","\t\tattribute vec3 morphTarget6;","\t\tattribute vec3 morphTarget7;","\t#endif","#endif","#ifdef USE_SKINNING","\tattribute vec4 skinIndex;","\tattribute vec4 skinWeight;","#endif","\n"].filter(filterEmptyLine).join("\n"),v=[p,generatePrecision(n),"#define SHADER_NAME "+n.shaderName,m,n.alphaTest?"#define ALPHATEST "+n.alphaTest+(n.alphaTest%1?"":".0"):"","#define GAMMA_FACTOR "+d,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.matcap?"#define USE_MATCAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+c:"",n.envMap?"#define "+h:"",n.envMap?"#define "+u:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMap&&n.objectSpaceNormalMap?"#define OBJECTSPACE_NORMALMAP":"",n.normalMap&&n.tangentSpaceNormalMap?"#define TANGENTSPACE_NORMALMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.sheen?"#define USE_SHEEN":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.vertexTangents?"#define USE_TANGENT":"",n.vertexColors||n.instancingColor?"#define USE_COLOR":"",n.vertexUvs?"#define USE_UV":"",n.uvsVertexOnly?"#define UVS_VERTEX_ONLY":"",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 "+l:"",n.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",n.physicallyCorrectLights?"#define PHYSICALLY_CORRECT_LIGHTS":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"",(n.extensionShaderTextureLOD||n.envMap)&&n.rendererExtensionShaderTextureLod?"#define TEXTURE_LOD_EXT":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",0!==n.toneMapping?"#define TONE_MAPPING":"",0!==n.toneMapping?ShaderChunk.tonemapping_pars_fragment:"",0!==n.toneMapping?getToneMappingFunction("toneMapping",n.toneMapping):"",n.dithering?"#define DITHERING":"",ShaderChunk.encodings_pars_fragment,n.map?getTexelDecodingFunction("mapTexelToLinear",n.mapEncoding):"",n.matcap?getTexelDecodingFunction("matcapTexelToLinear",n.matcapEncoding):"",n.envMap?getTexelDecodingFunction("envMapTexelToLinear",n.envMapEncoding):"",n.emissiveMap?getTexelDecodingFunction("emissiveMapTexelToLinear",n.emissiveMapEncoding):"",n.lightMap?getTexelDecodingFunction("lightMapTexelToLinear",n.lightMapEncoding):"",getTexelEncodingFunction("linearToOutputTexel",n.outputEncoding),n.depthPacking?"#define DEPTH_PACKING "+n.depthPacking:"","\n"].filter(filterEmptyLine).join("\n")),o=resolveIncludes(o),o=replaceLightNums(o,n),o=replaceClippingPlaneNums(o,n),s=resolveIncludes(s),s=replaceLightNums(s,n),s=replaceClippingPlaneNums(s,n),o=unrollLoops(o),s=unrollLoops(s),n.isWebGL2&&!0!==n.isRawShaderMaterial&&(_="#version 300 es\n",g=["#define attribute in","#define varying out","#define texture2D texture"].join("\n")+"\n"+g,v=["#define varying in",n.glslVersion===GLSL3?"":"out highp vec4 pc_fragColor;",n.glslVersion===GLSL3?"":"#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"+v);const y=_+v+s,x=WebGLShader(i,35633,_+g+o),b=WebGLShader(i,35632,y);if(i.attachShader(f,x),i.attachShader(f,b),void 0!==n.index0AttributeName?i.bindAttribLocation(f,0,n.index0AttributeName):!0===n.morphTargets&&i.bindAttribLocation(f,0,"position"),i.linkProgram(f),e.debug.checkShaderErrors){const e=i.getProgramInfoLog(f).trim(),t=i.getShaderInfoLog(x).trim(),n=i.getShaderInfoLog(b).trim();let r=!0,a=!0;if(!1===i.getProgramParameter(f,35714)){r=!1;const t=getShaderErrors(i,x,"vertex"),n=getShaderErrors(i,b,"fragment");console.error("THREE.WebGLProgram: shader error: ",i.getError(),"35715",i.getProgramParameter(f,35715),"gl.getProgramInfoLog",e,t,n)}else""!==e?console.warn("THREE.WebGLProgram: gl.getProgramInfoLog()",e):""!==t&&""!==n||(a=!1);a&&(this.diagnostics={runnable:r,programLog:e,vertexShader:{log:t,prefix:g},fragmentShader:{log:n,prefix:v}})}let M,w;return i.deleteShader(x),i.deleteShader(b),this.getUniforms=function(){return void 0===M&&(M=new WebGLUniforms(i,f)),M},this.getAttributes=function(){return void 0===w&&(w=fetchAttributeLocations(i,f)),w},this.destroy=function(){r.releaseStatesOfProgram(this),i.deleteProgram(f),this.program=void 0},this.name=n.shaderName,this.id=programIdCount++,this.cacheKey=t,this.usedTimes=1,this.program=f,this.vertexShader=x,this.fragmentShader=b,this}function WebGLPrograms(e,t,n,r,i,a){const o=[],s=r.isWebGL2,l=r.logarithmicDepthBuffer,c=r.floatVertexTextures,h=r.maxVertexUniforms,u=r.vertexTextures;let d=r.precision;const p={MeshDepthMaterial:"depth",MeshDistanceMaterial:"distanceRGBA",MeshNormalMaterial:"normal",MeshBasicMaterial:"basic",MeshLambertMaterial:"lambert",MeshPhongMaterial:"phong",MeshToonMaterial:"toon",MeshStandardMaterial:"physical",MeshPhysicalMaterial:"physical",MeshMatcapMaterial:"matcap",LineBasicMaterial:"basic",LineDashedMaterial:"dashed",PointsMaterial:"points",ShadowMaterial:"shadow",SpriteMaterial:"sprite"},m=["precision","isWebGL2","supportsVertexTextures","outputEncoding","instancing","instancingColor","map","mapEncoding","matcap","matcapEncoding","envMap","envMapMode","envMapEncoding","envMapCubeUV","lightMap","lightMapEncoding","aoMap","emissiveMap","emissiveMapEncoding","bumpMap","normalMap","objectSpaceNormalMap","tangentSpaceNormalMap","clearcoatMap","clearcoatRoughnessMap","clearcoatNormalMap","displacementMap","specularMap","roughnessMap","metalnessMap","gradientMap","alphaMap","combine","vertexColors","vertexTangents","vertexUvs","uvsVertexOnly","fog","useFog","fogExp2","flatShading","sizeAttenuation","logarithmicDepthBuffer","skinning","maxBones","useVertexTexture","morphTargets","morphNormals","maxMorphTargets","maxMorphNormals","premultipliedAlpha","numDirLights","numPointLights","numSpotLights","numHemiLights","numRectAreaLights","numDirLightShadows","numPointLightShadows","numSpotLightShadows","shadowMapEnabled","shadowMapType","toneMapping","physicallyCorrectLights","alphaTest","doubleSided","flipSided","numClippingPlanes","numClipIntersection","depthPacking","dithering","sheen","transmissionMap"];function f(e){let t;return e&&e.isTexture?t=e.encoding:e&&e.isWebGLRenderTarget?(console.warn("THREE.WebGLPrograms.getTextureEncodingFromMap: don't use render targets as textures. Use their .texture property instead."),t=e.texture.encoding):t=3e3,t}return{getParameters:function(i,o,m,g,v){const _=g.fog,y=i.isMeshStandardMaterial?g.environment:null,x=t.get(i.envMap||y),b=p[i.type],M=v.isSkinnedMesh?function(e){const t=e.skeleton.bones;if(c)return 1024;{const e=h,n=Math.floor((e-20)/4),r=Math.min(n,t.length);return r0,maxBones:M,useVertexTexture:c,morphTargets:i.morphTargets,morphNormals:i.morphNormals,maxMorphTargets:e.maxMorphTargets,maxMorphNormals:e.maxMorphNormals,numDirLights:o.directional.length,numPointLights:o.point.length,numSpotLights:o.spot.length,numRectAreaLights:o.rectArea.length,numHemiLights:o.hemi.length,numDirLightShadows:o.directionalShadowMap.length,numPointLightShadows:o.pointShadowMap.length,numSpotLightShadows:o.spotShadowMap.length,numClippingPlanes:a.numPlanes,numClipIntersection:a.numIntersection,dithering:i.dithering,shadowMapEnabled:e.shadowMap.enabled&&m.length>0,shadowMapType:e.shadowMap.type,toneMapping:i.toneMapped?e.toneMapping:0,physicallyCorrectLights:e.physicallyCorrectLights,premultipliedAlpha:i.premultipliedAlpha,alphaTest:i.alphaTest,doubleSided:2===i.side,flipSided:1===i.side,depthPacking:void 0!==i.depthPacking&&i.depthPacking,index0AttributeName:i.index0AttributeName,extensionDerivatives:i.extensions&&i.extensions.derivatives,extensionFragDepth:i.extensions&&i.extensions.fragDepth,extensionDrawBuffers:i.extensions&&i.extensions.drawBuffers,extensionShaderTextureLOD:i.extensions&&i.extensions.shaderTextureLOD,rendererExtensionFragDepth:s||n.has("EXT_frag_depth"),rendererExtensionDrawBuffers:s||n.has("WEBGL_draw_buffers"),rendererExtensionShaderTextureLod:s||n.has("EXT_shader_texture_lod"),customProgramCacheKey:i.customProgramCacheKey()}},getProgramCacheKey:function(t){const n=[];if(t.shaderID?n.push(t.shaderID):(n.push(t.fragmentShader),n.push(t.vertexShader)),void 0!==t.defines)for(const e in t.defines)n.push(e),n.push(t.defines[e]);if(!1===t.isRawShaderMaterial){for(let e=0;e1&&r.sort(e||painterSortStable),i.length>1&&i.sort(t||reversePainterSortStable)}}}function WebGLRenderLists(e){let t=new WeakMap;return{get:function(n,r){let i;return!1===t.has(n)?(i=new WebGLRenderList(e),t.set(n,[i])):r>=t.get(n).length?(i=new WebGLRenderList(e),t.get(n).push(i)):i=t.get(n)[r],i},dispose:function(){t=new WeakMap}}}function UniformsCache(){const e={};return{get:function(t){if(void 0!==e[t.id])return e[t.id];let n;switch(t.type){case"DirectionalLight":n={direction:new Vector3,color:new Color};break;case"SpotLight":n={position:new Vector3,direction:new Vector3,color:new Color,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":n={position:new Vector3,color:new Color,distance:0,decay:0};break;case"HemisphereLight":n={direction:new Vector3,skyColor:new Color,groundColor:new Color};break;case"RectAreaLight":n={color:new Color,position:new Vector3,halfWidth:new Vector3,halfHeight:new Vector3}}return e[t.id]=n,n}}}function ShadowUniformsCache(){const e={};return{get:function(t){if(void 0!==e[t.id])return e[t.id];let n;switch(t.type){case"DirectionalLight":case"SpotLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new Vector2};break;case"PointLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new Vector2,shadowCameraNear:1,shadowCameraFar:1e3}}return e[t.id]=n,n}}}let nextVersion=0;function shadowCastingLightsFirst(e,t){return(t.castShadow?1:0)-(e.castShadow?1:0)}function WebGLLights(e,t){const n=new UniformsCache,r=ShadowUniformsCache(),i={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotShadow:[],spotShadowMap:[],spotShadowMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[]};for(let e=0;e<9;e++)i.probe.push(new Vector3);const a=new Vector3,o=new Matrix4,s=new Matrix4;return{setup:function(a){let o=0,s=0,l=0;for(let e=0;e<9;e++)i.probe[e].set(0,0,0);let c=0,h=0,u=0,d=0,p=0,m=0,f=0,g=0;a.sort(shadowCastingLightsFirst);for(let e=0,t=a.length;e0&&(t.isWebGL2||!0===e.has("OES_texture_float_linear")?(i.rectAreaLTC1=UniformsLib.LTC_FLOAT_1,i.rectAreaLTC2=UniformsLib.LTC_FLOAT_2):!0===e.has("OES_texture_half_float_linear")?(i.rectAreaLTC1=UniformsLib.LTC_HALF_1,i.rectAreaLTC2=UniformsLib.LTC_HALF_2):console.error("THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.")),i.ambient[0]=o,i.ambient[1]=s,i.ambient[2]=l;const v=i.hash;v.directionalLength===c&&v.pointLength===h&&v.spotLength===u&&v.rectAreaLength===d&&v.hemiLength===p&&v.numDirectionalShadows===m&&v.numPointShadows===f&&v.numSpotShadows===g||(i.directional.length=c,i.spot.length=u,i.rectArea.length=d,i.point.length=h,i.hemi.length=p,i.directionalShadow.length=m,i.directionalShadowMap.length=m,i.pointShadow.length=f,i.pointShadowMap.length=f,i.spotShadow.length=g,i.spotShadowMap.length=g,i.directionalShadowMatrix.length=m,i.pointShadowMatrix.length=f,i.spotShadowMatrix.length=g,v.directionalLength=c,v.pointLength=h,v.spotLength=u,v.rectAreaLength=d,v.hemiLength=p,v.numDirectionalShadows=m,v.numPointShadows=f,v.numSpotShadows=g,i.version=nextVersion++)},setupView:function(e,t){let n=0,r=0,l=0,c=0,h=0;const u=t.matrixWorldInverse;for(let t=0,d=e.length;t=n.get(r).length?(a=new WebGLRenderState(e,t),n.get(r).push(a)):a=n.get(r)[i],a},dispose:function(){n=new WeakMap}}}class MeshDepthMaterial extends Material{constructor(e){super(),this.type="MeshDepthMaterial",this.depthPacking=3200,this.skinning=!1,this.morphTargets=!1,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.fog=!1,this.setValues(e)}copy(e){return super.copy(e),this.depthPacking=e.depthPacking,this.skinning=e.skinning,this.morphTargets=e.morphTargets,this.map=e.map,this.alphaMap=e.alphaMap,this.displacementMap=e.displacementMap,this.displacementScale=e.displacementScale,this.displacementBias=e.displacementBias,this.wireframe=e.wireframe,this.wireframeLinewidth=e.wireframeLinewidth,this}}MeshDepthMaterial.prototype.isMeshDepthMaterial=!0;class MeshDistanceMaterial extends Material{constructor(e){super(),this.type="MeshDistanceMaterial",this.referencePosition=new Vector3,this.nearDistance=1,this.farDistance=1e3,this.skinning=!1,this.morphTargets=!1,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.fog=!1,this.setValues(e)}copy(e){return super.copy(e),this.referencePosition.copy(e.referencePosition),this.nearDistance=e.nearDistance,this.farDistance=e.farDistance,this.skinning=e.skinning,this.morphTargets=e.morphTargets,this.map=e.map,this.alphaMap=e.alphaMap,this.displacementMap=e.displacementMap,this.displacementScale=e.displacementScale,this.displacementBias=e.displacementBias,this}}MeshDistanceMaterial.prototype.isMeshDistanceMaterial=!0;var vsm_frag="uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy ) / resolution ) );\n\tfor ( float i = -1.0; i < 1.0 ; i += SAMPLE_RATE) {\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( i, 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, i ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean * HALF_SAMPLE_RATE;\n\tsquared_mean = squared_mean * HALF_SAMPLE_RATE;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}",vsm_vert="void main() {\n\tgl_Position = vec4( position, 1.0 );\n}";function WebGLShadowMap(e,t,n){let r=new Frustum;const i=new Vector2,a=new Vector2,o=new Vector4,s=[],l=[],c={},h={0:1,1:0,2:2},u=new ShaderMaterial({defines:{SAMPLE_RATE:2/8,HALF_SAMPLE_RATE:1/8},uniforms:{shadow_pass:{value:null},resolution:{value:new Vector2},radius:{value:4}},vertexShader:vsm_vert,fragmentShader:vsm_frag}),d=u.clone();d.defines.HORIZONTAL_PASS=1;const p=new BufferGeometry;p.setAttribute("position",new BufferAttribute(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const m=new Mesh(p,u),f=this;function g(n,r){const i=t.update(m);u.uniforms.shadow_pass.value=n.map.texture,u.uniforms.resolution.value=n.mapSize,u.uniforms.radius.value=n.radius,e.setRenderTarget(n.mapPass),e.clear(),e.renderBufferDirect(r,null,i,u,m,null),d.uniforms.shadow_pass.value=n.mapPass.texture,d.uniforms.resolution.value=n.mapSize,d.uniforms.radius.value=n.radius,e.setRenderTarget(n.map),e.clear(),e.renderBufferDirect(r,null,i,d,m,null)}function v(e,t,n){const r=e|t<<1|n<<2;let i=s[r];return void 0===i&&(i=new MeshDepthMaterial({depthPacking:3201,morphTargets:e,skinning:t}),s[r]=i),i}function _(e,t,n){const r=e|t<<1|n<<2;let i=l[r];return void 0===i&&(i=new MeshDistanceMaterial({morphTargets:e,skinning:t}),l[r]=i),i}function y(t,n,r,i,a,o,s){let l=null,u=v,d=t.customDepthMaterial;if(!0===i.isPointLight&&(u=_,d=t.customDistanceMaterial),void 0===d){let e=!1;!0===r.morphTargets&&(e=n.morphAttributes&&n.morphAttributes.position&&n.morphAttributes.position.length>0);let i=!1;!0===t.isSkinnedMesh&&(!0===r.skinning?i=!0:console.warn("THREE.WebGLShadowMap: THREE.SkinnedMesh with material.skinning set to false:",t));l=u(e,i,!0===t.isInstancedMesh)}else l=d;if(e.localClippingEnabled&&!0===r.clipShadows&&0!==r.clippingPlanes.length){const e=l.uuid,t=r.uuid;let n=c[e];void 0===n&&(n={},c[e]=n);let i=n[t];void 0===i&&(i=l.clone(),n[t]=i),l=i}return l.visible=r.visible,l.wireframe=r.wireframe,l.side=3===s?null!==r.shadowSide?r.shadowSide:r.side:null!==r.shadowSide?r.shadowSide:h[r.side],l.clipShadows=r.clipShadows,l.clippingPlanes=r.clippingPlanes,l.clipIntersection=r.clipIntersection,l.wireframeLinewidth=r.wireframeLinewidth,l.linewidth=r.linewidth,!0===i.isPointLight&&!0===l.isMeshDistanceMaterial&&(l.referencePosition.setFromMatrixPosition(i.matrixWorld),l.nearDistance=a,l.farDistance=o),l}function x(n,i,a,o,s){if(!1===n.visible)return;if(n.layers.test(i.layers)&&(n.isMesh||n.isLine||n.isPoints)&&(n.castShadow||n.receiveShadow&&3===s)&&(!n.frustumCulled||r.intersectsObject(n))){n.modelViewMatrix.multiplyMatrices(a.matrixWorldInverse,n.matrixWorld);const r=t.update(n),i=n.material;if(Array.isArray(i)){const t=r.groups;for(let l=0,c=t.length;ln||i.y>n)&&(i.x>n&&(a.x=Math.floor(n/p.x),i.x=a.x*p.x,u.mapSize.x=a.x),i.y>n&&(a.y=Math.floor(n/p.y),i.y=a.y*p.y,u.mapSize.y=a.y)),null===u.map&&!u.isPointLightShadow&&3===this.type){const e={minFilter:1006,magFilter:1006,format:1023};u.map=new WebGLRenderTarget(i.x,i.y,e),u.map.texture.name=h.name+".shadowMap",u.mapPass=new WebGLRenderTarget(i.x,i.y,e),u.camera.updateProjectionMatrix()}if(null===u.map){const e={minFilter:1003,magFilter:1003,format:1023};u.map=new WebGLRenderTarget(i.x,i.y,e),u.map.texture.name=h.name+".shadowMap",u.camera.updateProjectionMatrix()}e.setRenderTarget(u.map),e.clear();const m=u.getViewportCount();for(let e=0;e=1):-1!==E.indexOf("OpenGL ES")&&(T=parseFloat(/^OpenGL ES (\d)/.exec(E)[1]),S=T>=2);let A=null,L={};const C=new Vector4,R=new Vector4;function P(t,n,r){const i=new Uint8Array(4),a=e.createTexture();e.bindTexture(t,a),e.texParameteri(t,10241,9728),e.texParameteri(t,10240,9728);for(let t=0;tr||e.height>r)&&(i=r/Math.max(e.width,e.height)),i<1||!0===t){if("undefined"!=typeof HTMLImageElement&&e instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&e instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&e instanceof ImageBitmap){const r=t?MathUtils.floorPowerOfTwo:Math.floor,a=r(i*e.width),o=r(i*e.height);void 0===p&&(p=f(a,o));const s=n?f(a,o):p;s.width=a,s.height=o;return s.getContext("2d").drawImage(e,0,0,a,o),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+e.width+"x"+e.height+") to ("+a+"x"+o+")."),s}return"data"in e&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+e.width+"x"+e.height+")."),e}return e}function v(e){return MathUtils.isPowerOfTwo(e.width)&&MathUtils.isPowerOfTwo(e.height)}function _(e,t){return e.generateMipmaps&&t&&1003!==e.minFilter&&1006!==e.minFilter}function y(t,n,i,a){e.generateMipmap(t);r.get(n).__maxMipLevel=Math.log2(Math.max(i,a))}function x(n,r,i){if(!1===s)return r;if(null!==n){if(void 0!==e[n])return e[n];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+n+"'")}let a=r;return 6403===r&&(5126===i&&(a=33326),5131===i&&(a=33325),5121===i&&(a=33321)),6407===r&&(5126===i&&(a=34837),5131===i&&(a=34843),5121===i&&(a=32849)),6408===r&&(5126===i&&(a=34836),5131===i&&(a=34842),5121===i&&(a=32856)),33325!==a&&33326!==a&&34842!==a&&34836!==a||t.get("EXT_color_buffer_float"),a}function b(e){return 1003===e||1004===e||1005===e?9728:9729}function M(t){const n=t.target;n.removeEventListener("dispose",M),function(t){const n=r.get(t);if(void 0===n.__webglInit)return;e.deleteTexture(n.__webglTexture),r.remove(t)}(n),n.isVideoTexture&&d.delete(n),o.memory.textures--}function w(t){const n=t.target;n.removeEventListener("dispose",w),function(t){const n=t.texture,i=r.get(t),a=r.get(n);if(!t)return;void 0!==a.__webglTexture&&e.deleteTexture(a.__webglTexture);t.depthTexture&&t.depthTexture.dispose();if(t.isWebGLCubeRenderTarget)for(let t=0;t<6;t++)e.deleteFramebuffer(i.__webglFramebuffer[t]),i.__webglDepthbuffer&&e.deleteRenderbuffer(i.__webglDepthbuffer[t]);else e.deleteFramebuffer(i.__webglFramebuffer),i.__webglDepthbuffer&&e.deleteRenderbuffer(i.__webglDepthbuffer),i.__webglMultisampledFramebuffer&&e.deleteFramebuffer(i.__webglMultisampledFramebuffer),i.__webglColorRenderbuffer&&e.deleteRenderbuffer(i.__webglColorRenderbuffer),i.__webglDepthRenderbuffer&&e.deleteRenderbuffer(i.__webglDepthRenderbuffer);r.remove(n),r.remove(t)}(n),o.memory.textures--}let S=0;function T(e,t){const i=r.get(e);if(e.isVideoTexture&&function(e){const t=o.render.frame;d.get(e)!==t&&(d.set(e,t),e.update())}(e),e.version>0&&i.__version!==e.version){const n=e.image;if(void 0===n)console.warn("THREE.WebGLRenderer: Texture marked for update but image is undefined");else{if(!1!==n.complete)return void P(i,e,t);console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete")}}n.activeTexture(33984+t),n.bindTexture(3553,i.__webglTexture)}function E(t,i){const o=r.get(t);t.version>0&&o.__version!==t.version?function(t,r,i){if(6!==r.image.length)return;R(t,r),n.activeTexture(33984+i),n.bindTexture(34067,t.__webglTexture),e.pixelStorei(37440,r.flipY),e.pixelStorei(37441,r.premultiplyAlpha),e.pixelStorei(3317,r.unpackAlignment),e.pixelStorei(37443,0);const o=r&&(r.isCompressedTexture||r.image[0].isCompressedTexture),l=r.image[0]&&r.image[0].isDataTexture,h=[];for(let e=0;e<6;e++)h[e]=o||l?l?r.image[e].image:r.image[e]:g(r.image[e],!1,!0,c);const u=h[0],d=v(u)||s,p=a.convert(r.format),m=a.convert(r.type),f=x(r.internalFormat,p,m);let b;if(C(34067,r,d),o){for(let e=0;e<6;e++){b=h[e].mipmaps;for(let t=0;t1||r.get(a).__currentAnisotropy)&&(e.texParameterf(n,o.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(a.anisotropy,i.getMaxAnisotropy())),r.get(a).__currentAnisotropy=a.anisotropy)}}function R(t,n){void 0===t.__webglInit&&(t.__webglInit=!0,n.addEventListener("dispose",M),t.__webglTexture=e.createTexture(),o.memory.textures++)}function P(t,r,i){let o=3553;r.isDataTexture2DArray&&(o=35866),r.isDataTexture3D&&(o=32879),R(t,r),n.activeTexture(33984+i),n.bindTexture(o,t.__webglTexture),e.pixelStorei(37440,r.flipY),e.pixelStorei(37441,r.premultiplyAlpha),e.pixelStorei(3317,r.unpackAlignment),e.pixelStorei(37443,0);const l=function(e){return!s&&(1001!==e.wrapS||1001!==e.wrapT||1003!==e.minFilter&&1006!==e.minFilter)}(r)&&!1===v(r.image),c=g(r.image,l,!1,h),u=v(c)||s,d=a.convert(r.format);let p,m=a.convert(r.type),f=x(r.internalFormat,d,m);C(o,r,u);const b=r.mipmaps;if(r.isDepthTexture)f=6402,s?f=1015===r.type?36012:1014===r.type?33190:1020===r.type?35056:33189:1015===r.type&&console.error("WebGLRenderer: Floating point depth texture requires WebGL2."),1026===r.format&&6402===f&&1012!==r.type&&1014!==r.type&&(console.warn("THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture."),r.type=1012,m=a.convert(r.type)),1027===r.format&&6402===f&&(f=34041,1020!==r.type&&(console.warn("THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture."),r.type=1020,m=a.convert(r.type))),n.texImage2D(3553,0,f,c.width,c.height,0,d,m,null);else if(r.isDataTexture)if(b.length>0&&u){for(let e=0,t=b.length;e0&&u){for(let e=0,t=b.length;e=l&&console.warn("THREE.WebGLTextures: Trying to use "+e+" texture units while this GPU supports only "+l),S+=1,e},this.resetTextureUnits=function(){S=0},this.setTexture2D=T,this.setTexture2DArray=function(e,t){const i=r.get(e);e.version>0&&i.__version!==e.version?P(i,e,t):(n.activeTexture(33984+t),n.bindTexture(35866,i.__webglTexture))},this.setTexture3D=function(e,t){const i=r.get(e);e.version>0&&i.__version!==e.version?P(i,e,t):(n.activeTexture(33984+t),n.bindTexture(32879,i.__webglTexture))},this.setTextureCube=E,this.setupRenderTarget=function(t){const i=t.texture,l=r.get(t),c=r.get(i);t.addEventListener("dispose",w),c.__webglTexture=e.createTexture(),o.memory.textures++;const h=!0===t.isWebGLCubeRenderTarget,u=!0===t.isWebGLMultisampleRenderTarget,d=i.isDataTexture3D||i.isDataTexture2DArray,p=v(t)||s;if(!s||1022!==i.format||1015!==i.type&&1016!==i.type||(i.format=1023,console.warn("THREE.WebGLRenderer: Rendering to textures with RGB format is not supported. Using RGBA format instead.")),h){l.__webglFramebuffer=[];for(let t=0;t<6;t++)l.__webglFramebuffer[t]=e.createFramebuffer()}else if(l.__webglFramebuffer=e.createFramebuffer(),u)if(s){l.__webglMultisampledFramebuffer=e.createFramebuffer(),l.__webglColorRenderbuffer=e.createRenderbuffer(),e.bindRenderbuffer(36161,l.__webglColorRenderbuffer);const n=a.convert(i.format),r=a.convert(i.type),o=x(i.internalFormat,n,r),s=G(t);e.renderbufferStorageMultisample(36161,s,o,t.width,t.height),e.bindFramebuffer(36160,l.__webglMultisampledFramebuffer),e.framebufferRenderbuffer(36160,36064,36161,l.__webglColorRenderbuffer),e.bindRenderbuffer(36161,null),t.depthBuffer&&(l.__webglDepthRenderbuffer=e.createRenderbuffer(),D(l.__webglDepthRenderbuffer,t,!0)),e.bindFramebuffer(36160,null)}else console.warn("THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.");if(h){n.bindTexture(34067,c.__webglTexture),C(34067,i,p);for(let e=0;e<6;e++)B(l.__webglFramebuffer[e],t,36064,34069+e);_(i,p)&&y(34067,i,t.width,t.height),n.bindTexture(34067,null)}else{let e=3553;if(d)if(s){e=i.isDataTexture3D?32879:35866}else console.warn("THREE.DataTexture3D and THREE.DataTexture2DArray only supported with WebGL2.");n.bindTexture(e,c.__webglTexture),C(e,i,p),B(l.__webglFramebuffer,t,36064,e),_(i,p)&&y(3553,i,t.width,t.height),n.bindTexture(3553,null)}t.depthBuffer&&I(t)},this.updateRenderTargetMipmap=function(e){const t=e.texture;if(_(t,v(e)||s)){const i=e.isWebGLCubeRenderTarget?34067:3553,a=r.get(t).__webglTexture;n.bindTexture(i,a),y(i,t,e.width,e.height),n.bindTexture(i,null)}},this.updateMultisampleRenderTarget=function(t){if(t.isWebGLMultisampleRenderTarget)if(s){const n=r.get(t);e.bindFramebuffer(36008,n.__webglMultisampledFramebuffer),e.bindFramebuffer(36009,n.__webglFramebuffer);const i=t.width,a=t.height;let o=16384;t.depthBuffer&&(o|=256),t.stencilBuffer&&(o|=1024),e.blitFramebuffer(0,0,i,a,0,0,i,a,o,9728),e.bindFramebuffer(36160,n.__webglMultisampledFramebuffer)}else console.warn("THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.")},this.safeSetTexture2D=function(e,t){e&&e.isWebGLRenderTarget&&(!1===F&&(console.warn("THREE.WebGLTextures.safeSetTexture2D: don't use render targets as textures. Use their .texture property instead."),F=!0),e=e.texture),T(e,t)},this.safeSetTextureCube=function(e,t){e&&e.isWebGLCubeRenderTarget&&(!1===N&&(console.warn("THREE.WebGLTextures.safeSetTextureCube: don't use cube render targets as textures. Use their .texture property instead."),N=!0),e=e.texture),E(e,t)}}function WebGLUtils(e,t,n){const r=n.isWebGL2;return{convert:function(e){let n;if(1009===e)return 5121;if(1017===e)return 32819;if(1018===e)return 32820;if(1019===e)return 33635;if(1010===e)return 5120;if(1011===e)return 5122;if(1012===e)return 5123;if(1013===e)return 5124;if(1014===e)return 5125;if(1015===e)return 5126;if(1016===e)return r?5131:(n=t.get("OES_texture_half_float"),null!==n?n.HALF_FLOAT_OES:null);if(1021===e)return 6406;if(1022===e)return 6407;if(1023===e)return 6408;if(1024===e)return 6409;if(1025===e)return 6410;if(1026===e)return 6402;if(1027===e)return 34041;if(1028===e)return 6403;if(1029===e)return 36244;if(1030===e)return 33319;if(1031===e)return 33320;if(1032===e)return 36248;if(1033===e)return 36249;if(33776===e||33777===e||33778===e||33779===e){if(n=t.get("WEBGL_compressed_texture_s3tc"),null===n)return null;if(33776===e)return n.COMPRESSED_RGB_S3TC_DXT1_EXT;if(33777===e)return n.COMPRESSED_RGBA_S3TC_DXT1_EXT;if(33778===e)return n.COMPRESSED_RGBA_S3TC_DXT3_EXT;if(33779===e)return n.COMPRESSED_RGBA_S3TC_DXT5_EXT}if(35840===e||35841===e||35842===e||35843===e){if(n=t.get("WEBGL_compressed_texture_pvrtc"),null===n)return null;if(35840===e)return n.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;if(35841===e)return n.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;if(35842===e)return n.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;if(35843===e)return n.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG}if(36196===e)return n=t.get("WEBGL_compressed_texture_etc1"),null!==n?n.COMPRESSED_RGB_ETC1_WEBGL:null;if((37492===e||37496===e)&&(n=t.get("WEBGL_compressed_texture_etc"),null!==n)){if(37492===e)return n.COMPRESSED_RGB8_ETC2;if(37496===e)return n.COMPRESSED_RGBA8_ETC2_EAC}return 37808===e||37809===e||37810===e||37811===e||37812===e||37813===e||37814===e||37815===e||37816===e||37817===e||37818===e||37819===e||37820===e||37821===e||37840===e||37841===e||37842===e||37843===e||37844===e||37845===e||37846===e||37847===e||37848===e||37849===e||37850===e||37851===e||37852===e||37853===e?(n=t.get("WEBGL_compressed_texture_astc"),null!==n?e:null):36492===e?(n=t.get("EXT_texture_compression_bptc"),null!==n?e:null):1020===e?r?34042:(n=t.get("WEBGL_depth_texture"),null!==n?n.UNSIGNED_INT_24_8_WEBGL:null):void 0}}}function ArrayCamera(e=[]){PerspectiveCamera.call(this),this.cameras=e}ArrayCamera.prototype=Object.assign(Object.create(PerspectiveCamera.prototype),{constructor:ArrayCamera,isArrayCamera:!0});class Group extends Object3D{constructor(){super(),this.type="Group"}}function WebXRController(){this._targetRay=null,this._grip=null,this._hand=null}function WebXRManager(e,t){const n=this;let r=null,i=1,a=null,o="local-floor",s=null;const l=[],c=new Map,h=new PerspectiveCamera;h.layers.enable(1),h.viewport=new Vector4;const u=new PerspectiveCamera;u.layers.enable(2),u.viewport=new Vector4;const d=[h,u],p=new ArrayCamera;p.layers.enable(1),p.layers.enable(2);let m=null,f=null;function g(e){const t=c.get(e.inputSource);t&&t.dispatchEvent({type:e.type,data:e.inputSource})}function v(){c.forEach((function(e,t){e.disconnect(t)})),c.clear(),m=null,f=null,e.setFramebuffer(null),e.setRenderTarget(e.getRenderTarget()),w.stop(),n.isPresenting=!1,n.dispatchEvent({type:"sessionend"})}function _(e){const t=r.inputSources;for(let e=0;e0&&be(a,e,t),o.length>0&&be(o,e,t),!0===e.isScene&&e.onAfterRender(f,e,t),null!==x&&($.updateRenderTargetMipmap(x),$.updateMultisampleRenderTarget(x)),W.buffers.depth.setTest(!0),W.buffers.depth.setMask(!0),W.buffers.color.setMask(!0),W.setPolygonOffset(!1),m.pop(),d=m.length>0?m[m.length-1]:null,p.pop(),u=p.length>0?p[p.length-1]:null},this.setFramebuffer=function(e){v!==e&&null===x&&ce.bindFramebuffer(36160,e),v=e},this.getActiveCubeFace=function(){return _},this.getActiveMipmapLevel=function(){return y},this.getRenderTarget=function(){return x},this.setRenderTarget=function(e,t=0,n=0){x=e,_=t,y=n,e&&void 0===q.get(e).__webglFramebuffer&&$.setupRenderTarget(e);let r=v,i=!1,a=!1;if(e){const n=e.texture;(n.isDataTexture3D||n.isDataTexture2DArray)&&(a=!0);const o=q.get(e).__webglFramebuffer;e.isWebGLCubeRenderTarget?(r=o[t],i=!0):r=e.isWebGLMultisampleRenderTarget?q.get(e).__webglMultisampledFramebuffer:o,S.copy(e.viewport),T.copy(e.scissor),E=e.scissorTest}else S.copy(B).multiplyScalar(C).floor(),T.copy(D).multiplyScalar(C).floor(),E=I;if(b!==r&&(ce.bindFramebuffer(36160,r),b=r),W.viewport(S),W.scissor(T),W.setScissorTest(E),i){const r=q.get(e.texture);ce.framebufferTexture2D(36160,36064,34069+t,r.__webglTexture,n)}else if(a){const r=q.get(e.texture),i=t||0;ce.framebufferTextureLayer(36160,36064,r.__webglTexture,n||0,i)}},this.readRenderTargetPixels=function(e,t,n,r,i,a,o){if(!e||!e.isWebGLRenderTarget)return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let s=q.get(e).__webglFramebuffer;if(e.isWebGLCubeRenderTarget&&void 0!==o&&(s=s[o]),s){let o=!1;s!==b&&(ce.bindFramebuffer(36160,s),o=!0);try{const o=e.texture,s=o.format,l=o.type;if(1023!==s&&se.convert(s)!==ce.getParameter(35739))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");const c=1016===l&&(H.has("EXT_color_buffer_half_float")||k.isWebGL2&&H.has("EXT_color_buffer_float"));if(!(1009===l||se.convert(l)===ce.getParameter(35738)||1015===l&&(k.isWebGL2||H.has("OES_texture_float")||H.has("WEBGL_color_buffer_float"))||c))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");36053===ce.checkFramebufferStatus(36160)?t>=0&&t<=e.width-r&&n>=0&&n<=e.height-i&&ce.readPixels(t,n,r,i,se.convert(s),se.convert(l),a):console.error("THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.")}finally{o&&ce.bindFramebuffer(36160,b)}}},this.copyFramebufferToTexture=function(e,t,n=0){const r=Math.pow(2,-n),i=Math.floor(t.image.width*r),a=Math.floor(t.image.height*r),o=se.convert(t.format);$.setTexture2D(t,0),ce.copyTexImage2D(3553,n,o,e.x,e.y,i,a,0),W.unbindTexture()},this.copyTextureToTexture=function(e,t,n,r=0){const i=t.image.width,a=t.image.height,o=se.convert(n.format),s=se.convert(n.type);$.setTexture2D(n,0),ce.pixelStorei(37440,n.flipY),ce.pixelStorei(37441,n.premultiplyAlpha),ce.pixelStorei(3317,n.unpackAlignment),t.isDataTexture?ce.texSubImage2D(3553,r,e.x,e.y,i,a,o,s,t.image.data):t.isCompressedTexture?ce.compressedTexSubImage2D(3553,r,e.x,e.y,t.mipmaps[0].width,t.mipmaps[0].height,o,t.mipmaps[0].data):ce.texSubImage2D(3553,r,e.x,e.y,o,s,t.image),0===r&&n.generateMipmaps&&ce.generateMipmap(3553),W.unbindTexture()},this.copyTextureToTexture3D=function(e,t,n,r,i=0){if(f.isWebGL1Renderer)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.");const{width:a,height:o,data:s}=n.image,l=se.convert(r.format),c=se.convert(r.type);let h;if(r.isDataTexture3D)$.setTexture3D(r,0),h=32879;else{if(!r.isDataTexture2DArray)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.");$.setTexture2DArray(r,0),h=35866}ce.pixelStorei(37440,r.flipY),ce.pixelStorei(37441,r.premultiplyAlpha),ce.pixelStorei(3317,r.unpackAlignment);const u=ce.getParameter(3314),d=ce.getParameter(32878),p=ce.getParameter(3316),m=ce.getParameter(3315),g=ce.getParameter(32877);ce.pixelStorei(3314,a),ce.pixelStorei(32878,o),ce.pixelStorei(3316,e.min.x),ce.pixelStorei(3315,e.min.y),ce.pixelStorei(32877,e.min.z),ce.texSubImage3D(h,i,t.x,t.y,t.z,e.max.x-e.min.x+1,e.max.y-e.min.y+1,e.max.z-e.min.z+1,l,c,s),ce.pixelStorei(3314,u),ce.pixelStorei(32878,d),ce.pixelStorei(3316,p),ce.pixelStorei(3315,m),ce.pixelStorei(32877,g),0===i&&r.generateMipmaps&&ce.generateMipmap(h),W.unbindTexture()},this.initTexture=function(e){$.setTexture2D(e,0),W.unbindTexture()},this.resetState=function(){W.reset(),le.reset()},"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}Group.prototype.isGroup=!0,Object.assign(WebXRController.prototype,{constructor:WebXRController,getHandSpace:function(){return null===this._hand&&(this._hand=new Group,this._hand.matrixAutoUpdate=!1,this._hand.visible=!1,this._hand.joints={},this._hand.inputState={pinching:!1}),this._hand},getTargetRaySpace:function(){return null===this._targetRay&&(this._targetRay=new Group,this._targetRay.matrixAutoUpdate=!1,this._targetRay.visible=!1),this._targetRay},getGripSpace:function(){return null===this._grip&&(this._grip=new Group,this._grip.matrixAutoUpdate=!1,this._grip.visible=!1),this._grip},dispatchEvent:function(e){return null!==this._targetRay&&this._targetRay.dispatchEvent(e),null!==this._grip&&this._grip.dispatchEvent(e),null!==this._hand&&this._hand.dispatchEvent(e),this},disconnect:function(e){return this.dispatchEvent({type:"disconnected",data:e}),null!==this._targetRay&&(this._targetRay.visible=!1),null!==this._grip&&(this._grip.visible=!1),null!==this._hand&&(this._hand.visible=!1),this},update:function(e,t,n){let r=null,i=null,a=null;const o=this._targetRay,s=this._grip,l=this._hand;if(e&&"visible-blurred"!==t.session.visibilityState)if(l&&e.hand){a=!0;for(const r of e.hand.values()){const e=t.getJointPose(r,n);if(void 0===l.joints[r.jointName]){const e=new Group;e.matrixAutoUpdate=!1,e.visible=!1,l.joints[r.jointName]=e,l.add(e)}const i=l.joints[r.jointName];null!==e&&(i.matrix.fromArray(e.transform.matrix),i.matrix.decompose(i.position,i.rotation,i.scale),i.jointRadius=e.radius),i.visible=null!==e}const r=l.joints["index-finger-tip"],i=l.joints["thumb-tip"],o=r.position.distanceTo(i.position),s=.02,c=.005;l.inputState.pinching&&o>s+c?(l.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:e.handedness,target:this})):!l.inputState.pinching&&o<=s-c&&(l.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:e.handedness,target:this}))}else null!==o&&(r=t.getPose(e.targetRaySpace,n),null!==r&&(o.matrix.fromArray(r.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale))),null!==s&&e.gripSpace&&(i=t.getPose(e.gripSpace,n),null!==i&&(s.matrix.fromArray(i.transform.matrix),s.matrix.decompose(s.position,s.rotation,s.scale)));return null!==o&&(o.visible=null!==r),null!==s&&(s.visible=null!==i),null!==l&&(l.visible=null!==a),this}}),Object.assign(WebXRManager.prototype,EventDispatcher.prototype);class WebGL1Renderer extends WebGLRenderer{}WebGL1Renderer.prototype.isWebGL1Renderer=!0;class FogExp2{constructor(e,t){this.name="",this.color=new Color(e),this.density=void 0!==t?t:25e-5}clone(){return new FogExp2(this.color,this.density)}toJSON(){return{type:"FogExp2",color:this.color.getHex(),density:this.density}}}FogExp2.prototype.isFogExp2=!0;class Fog{constructor(e,t,n){this.name="",this.color=new Color(e),this.near=void 0!==t?t:1,this.far=void 0!==n?n:1e3}clone(){return new Fog(this.color,this.near,this.far)}toJSON(){return{type:"Fog",color:this.color.getHex(),near:this.near,far:this.far}}}Fog.prototype.isFog=!0;class Scene extends Object3D{constructor(){super(),this.type="Scene",this.background=null,this.environment=null,this.fog=null,this.overrideMaterial=null,this.autoUpdate=!0,"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}copy(e,t){return super.copy(e,t),null!==e.background&&(this.background=e.background.clone()),null!==e.environment&&(this.environment=e.environment.clone()),null!==e.fog&&(this.fog=e.fog.clone()),null!==e.overrideMaterial&&(this.overrideMaterial=e.overrideMaterial.clone()),this.autoUpdate=e.autoUpdate,this.matrixAutoUpdate=e.matrixAutoUpdate,this}toJSON(e){const t=super.toJSON(e);return null!==this.background&&(t.object.background=this.background.toJSON(e)),null!==this.environment&&(t.object.environment=this.environment.toJSON(e)),null!==this.fog&&(t.object.fog=this.fog.toJSON()),t}}function InterleavedBuffer(e,t){this.array=e,this.stride=t,this.count=void 0!==e?e.length/t:0,this.usage=35044,this.updateRange={offset:0,count:-1},this.version=0,this.uuid=MathUtils.generateUUID()}Scene.prototype.isScene=!0,Object.defineProperty(InterleavedBuffer.prototype,"needsUpdate",{set:function(e){!0===e&&this.version++}}),Object.assign(InterleavedBuffer.prototype,{isInterleavedBuffer:!0,onUploadCallback:function(){},setUsage:function(e){return this.usage=e,this},copy:function(e){return this.array=new e.array.constructor(e.array),this.count=e.count,this.stride=e.stride,this.usage=e.usage,this},copyAt:function(e,t,n){e*=this.stride,n*=t.stride;for(let r=0,i=this.stride;re.far||t.push({distance:s,point:_intersectPoint.clone(),uv:Triangle.getUV(_intersectPoint,_vA$1,_vB$1,_vC$1,_uvA$1,_uvB$1,_uvC$1,new Vector2),face:null,object:this})}copy(e){return super.copy(e),void 0!==e.center&&this.center.copy(e.center),this.material=e.material,this}}function transformVertex(e,t,n,r,i,a){_alignedPosition.subVectors(e,n).addScalar(.5).multiply(r),void 0!==i?(_rotatedPosition.x=a*_alignedPosition.x-i*_alignedPosition.y,_rotatedPosition.y=i*_alignedPosition.x+a*_alignedPosition.y):_rotatedPosition.copy(_alignedPosition),e.copy(t),e.x+=_rotatedPosition.x,e.y+=_rotatedPosition.y,e.applyMatrix4(_viewWorldMatrix)}Sprite.prototype.isSprite=!0;const _v1$4=new Vector3,_v2$2=new Vector3;class LOD extends Object3D{constructor(){super(),this._currentLevel=0,this.type="LOD",Object.defineProperties(this,{levels:{enumerable:!0,value:[]},isLOD:{value:!0}}),this.autoUpdate=!0}copy(e){super.copy(e,!1);const t=e.levels;for(let e=0,n=t.length;e0){let n,r;for(n=1,r=t.length;n0){_v1$4.setFromMatrixPosition(this.matrixWorld);const n=e.ray.origin.distanceTo(_v1$4);this.getObjectForDistance(n).raycast(e,t)}}update(e){const t=this.levels;if(t.length>1){_v1$4.setFromMatrixPosition(e.matrixWorld),_v2$2.setFromMatrixPosition(this.matrixWorld);const n=_v1$4.distanceTo(_v2$2)/e.zoom;let r,i;for(t[0].object.visible=!0,r=1,i=t.length;r=t[r].distance;r++)t[r-1].object.visible=!1,t[r].object.visible=!0;for(this._currentLevel=r-1;ro)continue;h.applyMatrix4(this.matrixWorld);const d=e.ray.origin.distanceTo(h);de.far||t.push({distance:d,point:c.clone().applyMatrix4(this.matrixWorld),index:r,face:null,faceIndex:null,object:this})}}else for(let n=0,r=i.count-1;no)continue;h.applyMatrix4(this.matrixWorld);const r=e.ray.origin.distanceTo(h);re.far||t.push({distance:r,point:c.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}else n.isGeometry&&console.error("THREE.Line.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.")},updateMorphTargets:function(){const e=this.geometry;if(e.isBufferGeometry){const t=e.morphAttributes,n=Object.keys(t);if(n.length>0){const e=t[n[0]];if(void 0!==e){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,n=e.length;t0&&console.error("THREE.Line.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.")}}});const _start$1=new Vector3,_end$1=new Vector3;function LineSegments(e,t){Line.call(this,e,t),this.type="LineSegments"}LineSegments.prototype=Object.assign(Object.create(Line.prototype),{constructor:LineSegments,isLineSegments:!0,computeLineDistances:function(){const e=this.geometry;if(e.isBufferGeometry)if(null===e.index){const t=e.attributes.position,n=[];for(let e=0,r=t.count;ei.far)return;a.push({distance:l,distanceToRay:Math.sqrt(s),point:n,index:t,face:null,object:o})}}Points.prototype=Object.assign(Object.create(Object3D.prototype),{constructor:Points,isPoints:!0,copy:function(e){return Object3D.prototype.copy.call(this,e),this.material=e.material,this.geometry=e.geometry,this},raycast:function(e,t){const n=this.geometry,r=this.matrixWorld,i=e.params.Points.threshold;if(null===n.boundingSphere&&n.computeBoundingSphere(),_sphere$3.copy(n.boundingSphere),_sphere$3.applyMatrix4(r),_sphere$3.radius+=i,!1===e.ray.intersectsSphere(_sphere$3))return;_inverseMatrix$2.copy(r).invert(),_ray$2.copy(e.ray).applyMatrix4(_inverseMatrix$2);const a=i/((this.scale.x+this.scale.y+this.scale.z)/3),o=a*a;if(n.isBufferGeometry){const i=n.index,a=n.attributes.position;if(null!==i){const n=i.array;for(let i=0,s=n.length;i0){const e=t[n[0]];if(void 0!==e){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,n=e.length;t0&&console.error("THREE.Points.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.")}}});class VideoTexture extends Texture{constructor(e,t,n,r,i,a,o,s,l){super(e,t,n,r,i,a,o,s,l),this.format=void 0!==o?o:1022,this.minFilter=void 0!==a?a:1006,this.magFilter=void 0!==i?i:1006,this.generateMipmaps=!1;const c=this;"requestVideoFrameCallback"in e&&e.requestVideoFrameCallback((function t(){c.needsUpdate=!0,e.requestVideoFrameCallback(t)}))}clone(){return new this.constructor(this.image).copy(this)}update(){const e=this.image;!1==="requestVideoFrameCallback"in e&&e.readyState>=e.HAVE_CURRENT_DATA&&(this.needsUpdate=!0)}}VideoTexture.prototype.isVideoTexture=!0;class CompressedTexture extends Texture{constructor(e,t,n,r,i,a,o,s,l,c,h,u){super(null,a,o,s,l,c,r,i,h,u),this.image={width:t,height:n},this.mipmaps=e,this.flipY=!1,this.generateMipmaps=!1}}CompressedTexture.prototype.isCompressedTexture=!0;class CanvasTexture extends Texture{constructor(e,t,n,r,i,a,o,s,l){super(e,t,n,r,i,a,o,s,l),this.needsUpdate=!0}}CanvasTexture.prototype.isCanvasTexture=!0;class DepthTexture extends Texture{constructor(e,t,n,r,i,a,o,s,l,c){if(1026!==(c=void 0!==c?c:1026)&&1027!==c)throw new Error("DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat");void 0===n&&1026===c&&(n=1012),void 0===n&&1027===c&&(n=1020),super(null,r,i,a,o,s,c,n,l),this.image={width:e,height:t},this.magFilter=void 0!==o?o:1003,this.minFilter=void 0!==s?s:1003,this.flipY=!1,this.generateMipmaps=!1}}DepthTexture.prototype.isDepthTexture=!0;class CircleGeometry extends BufferGeometry{constructor(e=1,t=8,n=0,r=2*Math.PI){super(),this.type="CircleGeometry",this.parameters={radius:e,segments:t,thetaStart:n,thetaLength:r},t=Math.max(3,t);const i=[],a=[],o=[],s=[],l=new Vector3,c=new Vector2;a.push(0,0,0),o.push(0,0,1),s.push(.5,.5);for(let i=0,h=3;i<=t;i++,h+=3){const u=n+i/t*r;l.x=e*Math.cos(u),l.y=e*Math.sin(u),a.push(l.x,l.y,l.z),o.push(0,0,1),c.x=(a[h]/e+1)/2,c.y=(a[h+1]/e+1)/2,s.push(c.x,c.y)}for(let e=1;e<=t;e++)i.push(e,e+1,0);this.setIndex(i),this.setAttribute("position",new Float32BufferAttribute(a,3)),this.setAttribute("normal",new Float32BufferAttribute(o,3)),this.setAttribute("uv",new Float32BufferAttribute(s,2))}}class CylinderGeometry extends BufferGeometry{constructor(e=1,t=1,n=1,r=8,i=1,a=!1,o=0,s=2*Math.PI){super(),this.type="CylinderGeometry",this.parameters={radiusTop:e,radiusBottom:t,height:n,radialSegments:r,heightSegments:i,openEnded:a,thetaStart:o,thetaLength:s};const l=this;r=Math.floor(r),i=Math.floor(i);const c=[],h=[],u=[],d=[];let p=0;const m=[],f=n/2;let g=0;function v(n){const i=p,a=new Vector2,m=new Vector3;let v=0;const _=!0===n?e:t,y=!0===n?1:-1;for(let e=1;e<=r;e++)h.push(0,f*y,0),u.push(0,y,0),d.push(.5,.5),p++;const x=p;for(let e=0;e<=r;e++){const t=e/r*s+o,n=Math.cos(t),i=Math.sin(t);m.x=_*i,m.y=f*y,m.z=_*n,h.push(m.x,m.y,m.z),u.push(0,y,0),a.x=.5*n+.5,a.y=.5*i*y+.5,d.push(a.x,a.y),p++}for(let e=0;e0&&v(!0),t>0&&v(!1)),this.setIndex(c),this.setAttribute("position",new Float32BufferAttribute(h,3)),this.setAttribute("normal",new Float32BufferAttribute(u,3)),this.setAttribute("uv",new Float32BufferAttribute(d,2))}}class ConeGeometry extends CylinderGeometry{constructor(e=1,t=1,n=8,r=1,i=!1,a=0,o=2*Math.PI){super(0,e,t,n,r,i,a,o),this.type="ConeGeometry",this.parameters={radius:e,height:t,radialSegments:n,heightSegments:r,openEnded:i,thetaStart:a,thetaLength:o}}}class PolyhedronGeometry extends BufferGeometry{constructor(e,t,n=1,r=0){super(),this.type="PolyhedronGeometry",this.parameters={vertices:e,indices:t,radius:n,detail:r};const i=[],a=[];function o(e,t,n,r){const i=r+1,a=[];for(let r=0;r<=i;r++){a[r]=[];const o=e.clone().lerp(n,r/i),s=t.clone().lerp(n,r/i),l=i-r;for(let e=0;e<=l;e++)a[r][e]=0===e&&r===i?o:o.clone().lerp(s,e/l)}for(let e=0;e.9&&o<.1&&(t<.2&&(a[e+0]+=1),n<.2&&(a[e+2]+=1),r<.2&&(a[e+4]+=1))}}()}(),this.setAttribute("position",new Float32BufferAttribute(i,3)),this.setAttribute("normal",new Float32BufferAttribute(i.slice(),3)),this.setAttribute("uv",new Float32BufferAttribute(a,2)),0===r?this.computeVertexNormals():this.normalizeNormals()}}class DodecahedronGeometry extends PolyhedronGeometry{constructor(e=1,t=0){const n=(1+Math.sqrt(5))/2,r=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,-r,-n,0,-r,n,0,r,-n,0,r,n,-r,-n,0,-r,n,0,r,-n,0,r,n,0,-n,0,-r,n,0,-r,-n,0,r,n,0,r],[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],e,t),this.type="DodecahedronGeometry",this.parameters={radius:e,detail:t}}}const _v0$2=new Vector3,_v1$5=new Vector3,_normal$1=new Vector3,_triangle=new Triangle;class EdgesGeometry extends BufferGeometry{constructor(e,t){if(super(),this.type="EdgesGeometry",this.parameters={thresholdAngle:t},t=void 0!==t?t:1,!0===e.isGeometry)return void console.error("THREE.EdgesGeometry no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.");const n=Math.pow(10,4),r=Math.cos(MathUtils.DEG2RAD*t),i=e.getIndex(),a=e.getAttribute("position"),o=i?i.count:a.count,s=[0,0,0],l=["a","b","c"],c=new Array(3),h={},u=[];for(let e=0;e80*n){s=c=e[0],l=h=e[1];for(let t=n;tc&&(c=u),d>h&&(h=d);p=Math.max(c-s,h-l),p=0!==p?1/p:0}return earcutLinked(a,o,n,s,l,p),o}};function linkedList(e,t,n,r,i){let a,o;if(i===signedArea(e,t,n,r)>0)for(a=t;a=t;a-=r)o=insertNode(a,e[a],e[a+1],o);return o&&equals(o,o.next)&&(removeNode(o),o=o.next),o}function filterPoints(e,t){if(!e)return e;t||(t=e);let n,r=e;do{if(n=!1,r.steiner||!equals(r,r.next)&&0!==area(r.prev,r,r.next))r=r.next;else{if(removeNode(r),r=t=r.prev,r===r.next)break;n=!0}}while(n||r!==t);return t}function earcutLinked(e,t,n,r,i,a,o){if(!e)return;!o&&a&&indexCurve(e,r,i,a);let s,l,c=e;for(;e.prev!==e.next;)if(s=e.prev,l=e.next,a?isEarHashed(e,r,i,a):isEar(e))t.push(s.i/n),t.push(e.i/n),t.push(l.i/n),removeNode(e),e=l.next,c=l.next;else if((e=l)===c){o?1===o?earcutLinked(e=cureLocalIntersections(filterPoints(e),t,n),t,n,r,i,a,2):2===o&&splitEarcut(e,t,n,r,i,a):earcutLinked(filterPoints(e),t,n,r,i,a,1);break}}function isEar(e){const t=e.prev,n=e,r=e.next;if(area(t,n,r)>=0)return!1;let i=e.next.next;for(;i!==e.prev;){if(pointInTriangle(t.x,t.y,n.x,n.y,r.x,r.y,i.x,i.y)&&area(i.prev,i,i.next)>=0)return!1;i=i.next}return!0}function isEarHashed(e,t,n,r){const i=e.prev,a=e,o=e.next;if(area(i,a,o)>=0)return!1;const s=i.xa.x?i.x>o.x?i.x:o.x:a.x>o.x?a.x:o.x,h=i.y>a.y?i.y>o.y?i.y:o.y:a.y>o.y?a.y:o.y,u=zOrder(s,l,t,n,r),d=zOrder(c,h,t,n,r);let p=e.prevZ,m=e.nextZ;for(;p&&p.z>=u&&m&&m.z<=d;){if(p!==e.prev&&p!==e.next&&pointInTriangle(i.x,i.y,a.x,a.y,o.x,o.y,p.x,p.y)&&area(p.prev,p,p.next)>=0)return!1;if(p=p.prevZ,m!==e.prev&&m!==e.next&&pointInTriangle(i.x,i.y,a.x,a.y,o.x,o.y,m.x,m.y)&&area(m.prev,m,m.next)>=0)return!1;m=m.nextZ}for(;p&&p.z>=u;){if(p!==e.prev&&p!==e.next&&pointInTriangle(i.x,i.y,a.x,a.y,o.x,o.y,p.x,p.y)&&area(p.prev,p,p.next)>=0)return!1;p=p.prevZ}for(;m&&m.z<=d;){if(m!==e.prev&&m!==e.next&&pointInTriangle(i.x,i.y,a.x,a.y,o.x,o.y,m.x,m.y)&&area(m.prev,m,m.next)>=0)return!1;m=m.nextZ}return!0}function cureLocalIntersections(e,t,n){let r=e;do{const i=r.prev,a=r.next.next;!equals(i,a)&&intersects(i,r,r.next,a)&&locallyInside(i,a)&&locallyInside(a,i)&&(t.push(i.i/n),t.push(r.i/n),t.push(a.i/n),removeNode(r),removeNode(r.next),r=e=a),r=r.next}while(r!==e);return filterPoints(r)}function splitEarcut(e,t,n,r,i,a){let o=e;do{let e=o.next.next;for(;e!==o.prev;){if(o.i!==e.i&&isValidDiagonal(o,e)){let s=splitPolygon(o,e);return o=filterPoints(o,o.next),s=filterPoints(s,s.next),earcutLinked(o,t,n,r,i,a),void earcutLinked(s,t,n,r,i,a)}e=e.next}o=o.next}while(o!==e)}function eliminateHoles(e,t,n,r){const i=[];let a,o,s,l,c;for(a=0,o=t.length;a=n.next.y&&n.next.y!==n.y){const e=n.x+(i-n.y)*(n.next.x-n.x)/(n.next.y-n.y);if(e<=r&&e>o){if(o=e,e===r){if(i===n.y)return n;if(i===n.next.y)return n.next}a=n.x=n.x&&n.x>=l&&r!==n.x&&pointInTriangle(ia.x||n.x===a.x&§orContainsSector(a,n)))&&(a=n,u=h)),n=n.next}while(n!==s);return a}function sectorContainsSector(e,t){return area(e.prev,e,t.prev)<0&&area(t.next,e,e.next)<0}function indexCurve(e,t,n,r){let i=e;do{null===i.z&&(i.z=zOrder(i.x,i.y,t,n,r)),i.prevZ=i.prev,i.nextZ=i.next,i=i.next}while(i!==e);i.prevZ.nextZ=null,i.prevZ=null,sortLinked(i)}function sortLinked(e){let t,n,r,i,a,o,s,l,c=1;do{for(n=e,e=null,a=null,o=0;n;){for(o++,r=n,s=0,t=0;t0||l>0&&r;)0!==s&&(0===l||!r||n.z<=r.z)?(i=n,n=n.nextZ,s--):(i=r,r=r.nextZ,l--),a?a.nextZ=i:e=i,i.prevZ=a,a=i;n=r}a.nextZ=null,c*=2}while(o>1);return e}function zOrder(e,t,n,r,i){return(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=32767*(e-n)*i)|e<<8))|e<<4))|e<<2))|e<<1))|(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=32767*(t-r)*i)|t<<8))|t<<4))|t<<2))|t<<1))<<1}function getLeftmost(e){let t=e,n=e;do{(t.x=0&&(e-o)*(r-s)-(n-o)*(t-s)>=0&&(n-o)*(a-s)-(i-o)*(r-s)>=0}function isValidDiagonal(e,t){return e.next.i!==t.i&&e.prev.i!==t.i&&!intersectsPolygon(e,t)&&(locallyInside(e,t)&&locallyInside(t,e)&&middleInside(e,t)&&(area(e.prev,e,t.prev)||area(e,t.prev,t))||equals(e,t)&&area(e.prev,e,e.next)>0&&area(t.prev,t,t.next)>0)}function area(e,t,n){return(t.y-e.y)*(n.x-t.x)-(t.x-e.x)*(n.y-t.y)}function equals(e,t){return e.x===t.x&&e.y===t.y}function intersects(e,t,n,r){const i=sign(area(e,t,n)),a=sign(area(e,t,r)),o=sign(area(n,r,e)),s=sign(area(n,r,t));return i!==a&&o!==s||(!(0!==i||!onSegment(e,n,t))||(!(0!==a||!onSegment(e,r,t))||(!(0!==o||!onSegment(n,e,r))||!(0!==s||!onSegment(n,t,r)))))}function onSegment(e,t,n){return t.x<=Math.max(e.x,n.x)&&t.x>=Math.min(e.x,n.x)&&t.y<=Math.max(e.y,n.y)&&t.y>=Math.min(e.y,n.y)}function sign(e){return e>0?1:e<0?-1:0}function intersectsPolygon(e,t){let n=e;do{if(n.i!==e.i&&n.next.i!==e.i&&n.i!==t.i&&n.next.i!==t.i&&intersects(n,n.next,e,t))return!0;n=n.next}while(n!==e);return!1}function locallyInside(e,t){return area(e.prev,e,e.next)<0?area(e,t,e.next)>=0&&area(e,e.prev,t)>=0:area(e,t,e.prev)<0||area(e,e.next,t)<0}function middleInside(e,t){let n=e,r=!1;const i=(e.x+t.x)/2,a=(e.y+t.y)/2;do{n.y>a!=n.next.y>a&&n.next.y!==n.y&&i<(n.next.x-n.x)*(a-n.y)/(n.next.y-n.y)+n.x&&(r=!r),n=n.next}while(n!==e);return r}function splitPolygon(e,t){const n=new Node(e.i,e.x,e.y),r=new Node(t.i,t.x,t.y),i=e.next,a=t.prev;return e.next=t,t.prev=e,n.next=i,i.prev=n,r.next=n,n.prev=r,a.next=r,r.prev=a,r}function insertNode(e,t,n,r){const i=new Node(e,t,n);return r?(i.next=r.next,i.prev=r,r.next.prev=i,r.next=i):(i.prev=i,i.next=i),i}function removeNode(e){e.next.prev=e.prev,e.prev.next=e.next,e.prevZ&&(e.prevZ.nextZ=e.nextZ),e.nextZ&&(e.nextZ.prevZ=e.prevZ)}function Node(e,t,n){this.i=e,this.x=t,this.y=n,this.prev=null,this.next=null,this.z=null,this.prevZ=null,this.nextZ=null,this.steiner=!1}function signedArea(e,t,n,r){let i=0;for(let a=t,o=n-r;a2&&e[t-1].equals(e[0])&&e.pop()}function addContour(e,t){for(let n=0;nNumber.EPSILON){const u=Math.sqrt(h),d=Math.sqrt(l*l+c*c),p=t.x-s/u,m=t.y+o/u,f=((n.x-c/d-p)*c-(n.y+l/d-m)*l)/(o*c-s*l);r=p+o*f-e.x,i=m+s*f-e.y;const g=r*r+i*i;if(g<=2)return new Vector2(r,i);a=Math.sqrt(g/2)}else{let e=!1;o>Number.EPSILON?l>Number.EPSILON&&(e=!0):o<-Number.EPSILON?l<-Number.EPSILON&&(e=!0):Math.sign(s)===Math.sign(c)&&(e=!0),e?(r=-s,i=o,a=Math.sqrt(h)):(r=o,i=s,a=Math.sqrt(h/2))}return new Vector2(r/a,i/a)}const P=[];for(let e=0,t=E.length,n=t-1,r=e+1;e=0;e--){const t=e/p,n=h*Math.cos(t*Math.PI/2),r=u*Math.sin(t*Math.PI/2)+d;for(let e=0,t=E.length;e=0;){const r=n;let i=n-1;i<0&&(i=e.length-1);for(let e=0,n=s+2*p;e=0?(e(r-s,p,h),u.subVectors(c,h)):(e(r+s,p,h),u.subVectors(h,c)),p-s>=0?(e(r,p-s,h),d.subVectors(c,h)):(e(r,p+s,h),d.subVectors(h,c)),l.crossVectors(u,d).normalize(),a.push(l.x,l.y,l.z),o.push(r,p)}}for(let e=0;e0)&&d.push(t,i,l),(e!==n-1||s=r)){l.push(t.times[e]);for(let n=0;na.tracks[e].times[0]&&(s=a.tracks[e].times[0]);for(let e=0;e=r.times[u]){const e=u*l+s,t=e+l-s;d=AnimationUtils.arraySlice(r.values,e,t)}else{const e=r.createInterpolant(),t=s,n=l-s;e.evaluate(a),d=AnimationUtils.arraySlice(e.resultBuffer,t,n)}if("quaternion"===i){(new Quaternion).fromArray(d).normalize().conjugate().toArray(d)}const p=o.times.length;for(let e=0;e=i)break e;{const o=t[1];e=i)break t}a=n,n=0}}for(;n>>1;et;)--a;if(++a,0!==i||a!==r){i>=a&&(a=Math.max(a,1),i=a-1);const e=this.getValueSize();this.times=AnimationUtils.arraySlice(n,i,a),this.values=AnimationUtils.arraySlice(this.values,i*e,a*e)}return this}validate(){let e=!0;const t=this.getValueSize();t-Math.floor(t)!=0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),e=!1);const n=this.times,r=this.values,i=n.length;0===i&&(console.error("THREE.KeyframeTrack: Track is empty.",this),e=!1);let a=null;for(let t=0;t!==i;t++){const r=n[t];if("number"==typeof r&&isNaN(r)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,t,r),e=!1;break}if(null!==a&&a>r){console.error("THREE.KeyframeTrack: Out of order keys.",this,t,r,a),e=!1;break}a=r}if(void 0!==r&&AnimationUtils.isTypedArray(r))for(let t=0,n=r.length;t!==n;++t){const n=r[t];if(isNaN(n)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,t,n),e=!1;break}}return e}optimize(){const e=AnimationUtils.arraySlice(this.times),t=AnimationUtils.arraySlice(this.values),n=this.getValueSize(),r=2302===this.getInterpolation(),i=e.length-1;let a=1;for(let o=1;o0){e[a]=e[i];for(let e=i*n,r=a*n,o=0;o!==n;++o)t[r+o]=t[e+o];++a}return a!==e.length?(this.times=AnimationUtils.arraySlice(e,0,a),this.values=AnimationUtils.arraySlice(t,0,a*n)):(this.times=e,this.values=t),this}clone(){const e=AnimationUtils.arraySlice(this.times,0),t=AnimationUtils.arraySlice(this.values,0),n=new(0,this.constructor)(this.name,e,t);return n.createInterpolant=this.createInterpolant,n}}KeyframeTrack.prototype.TimeBufferType=Float32Array,KeyframeTrack.prototype.ValueBufferType=Float32Array,KeyframeTrack.prototype.DefaultInterpolation=2301;class BooleanKeyframeTrack extends KeyframeTrack{}BooleanKeyframeTrack.prototype.ValueTypeName="bool",BooleanKeyframeTrack.prototype.ValueBufferType=Array,BooleanKeyframeTrack.prototype.DefaultInterpolation=2300,BooleanKeyframeTrack.prototype.InterpolantFactoryMethodLinear=void 0,BooleanKeyframeTrack.prototype.InterpolantFactoryMethodSmooth=void 0;class ColorKeyframeTrack extends KeyframeTrack{}ColorKeyframeTrack.prototype.ValueTypeName="color";class NumberKeyframeTrack extends KeyframeTrack{}function QuaternionLinearInterpolant(e,t,n,r){Interpolant.call(this,e,t,n,r)}NumberKeyframeTrack.prototype.ValueTypeName="number",QuaternionLinearInterpolant.prototype=Object.assign(Object.create(Interpolant.prototype),{constructor:QuaternionLinearInterpolant,interpolate_:function(e,t,n,r){const i=this.resultBuffer,a=this.sampleValues,o=this.valueSize,s=(n-t)/(r-t);let l=e*o;for(let e=l+o;l!==e;l+=4)Quaternion.slerpFlat(i,0,a,l-o,a,l,s);return i}});class QuaternionKeyframeTrack extends KeyframeTrack{InterpolantFactoryMethodLinear(e){return new QuaternionLinearInterpolant(this.times,this.values,this.getValueSize(),e)}}QuaternionKeyframeTrack.prototype.ValueTypeName="quaternion",QuaternionKeyframeTrack.prototype.DefaultInterpolation=2301,QuaternionKeyframeTrack.prototype.InterpolantFactoryMethodSmooth=void 0;class StringKeyframeTrack extends KeyframeTrack{}StringKeyframeTrack.prototype.ValueTypeName="string",StringKeyframeTrack.prototype.ValueBufferType=Array,StringKeyframeTrack.prototype.DefaultInterpolation=2300,StringKeyframeTrack.prototype.InterpolantFactoryMethodLinear=void 0,StringKeyframeTrack.prototype.InterpolantFactoryMethodSmooth=void 0;class VectorKeyframeTrack extends KeyframeTrack{}VectorKeyframeTrack.prototype.ValueTypeName="vector";class AnimationClip{constructor(e,t=-1,n,r=2500){this.name=e,this.tracks=n,this.duration=t,this.blendMode=r,this.uuid=MathUtils.generateUUID(),this.duration<0&&this.resetDuration()}static parse(e){const t=[],n=e.tracks,r=1/(e.fps||1);for(let e=0,i=n.length;e!==i;++e)t.push(parseKeyframeTrack(n[e]).scale(r));const i=new this(e.name,e.duration,t,e.blendMode);return i.uuid=e.uuid,i}static toJSON(e){const t=[],n=e.tracks,r={name:e.name,duration:e.duration,tracks:t,uuid:e.uuid,blendMode:e.blendMode};for(let e=0,r=n.length;e!==r;++e)t.push(KeyframeTrack.toJSON(n[e]));return r}static CreateFromMorphTargetSequence(e,t,n,r){const i=t.length,a=[];for(let e=0;e1){const e=a[1];let t=r[e];t||(r[e]=t=[]),t.push(n)}}const a=[];for(const e in r)a.push(this.CreateFromMorphTargetSequence(e,r[e],t,n));return a}static parseAnimation(e,t){if(!e)return console.error("THREE.AnimationClip: No animation in JSONLoader data."),null;const n=function(e,t,n,r,i){if(0!==n.length){const a=[],o=[];AnimationUtils.flattenJSON(n,a,o,r),0!==a.length&&i.push(new e(t,a,o))}},r=[],i=e.name||"default",a=e.fps||30,o=e.blendMode;let s=e.length||-1;const l=e.hierarchy||[];for(let e=0;e0||0===e.search(/^data\:image\/jpeg/);i.format=r?1022:1023,i.needsUpdate=!0,void 0!==t&&t(i)}),n,r),i}}),Object.assign(Curve.prototype,{getPoint:function(){return console.warn("THREE.Curve: .getPoint() not implemented."),null},getPointAt:function(e,t){const n=this.getUtoTmapping(e);return this.getPoint(n,t)},getPoints:function(e=5){const t=[];for(let n=0;n<=e;n++)t.push(this.getPoint(n/e));return t},getSpacedPoints:function(e=5){const t=[];for(let n=0;n<=e;n++)t.push(this.getPointAt(n/e));return t},getLength:function(){const e=this.getLengths();return e[e.length-1]},getLengths:function(e){if(void 0===e&&(e=this.arcLengthDivisions),this.cacheArcLengths&&this.cacheArcLengths.length===e+1&&!this.needsUpdate)return this.cacheArcLengths;this.needsUpdate=!1;const t=[];let n,r=this.getPoint(0),i=0;t.push(0);for(let a=1;a<=e;a++)n=this.getPoint(a/e),i+=n.distanceTo(r),t.push(i),r=n;return this.cacheArcLengths=t,t},updateArcLengths:function(){this.needsUpdate=!0,this.getLengths()},getUtoTmapping:function(e,t){const n=this.getLengths();let r=0;const i=n.length;let a;a=t||e*n[i-1];let o,s=0,l=i-1;for(;s<=l;)if(r=Math.floor(s+(l-s)/2),o=n[r]-a,o<0)s=r+1;else{if(!(o>0)){l=r;break}l=r-1}if(r=l,n[r]===a)return r/(i-1);const c=n[r];return(r+(a-c)/(n[r+1]-c))/(i-1)},getTangent:function(e,t){const n=1e-4;let r=e-n,i=e+n;r<0&&(r=0),i>1&&(i=1);const a=this.getPoint(r),o=this.getPoint(i),s=t||(a.isVector2?new Vector2:new Vector3);return s.copy(o).sub(a).normalize(),s},getTangentAt:function(e,t){const n=this.getUtoTmapping(e);return this.getTangent(n,t)},computeFrenetFrames:function(e,t){const n=new Vector3,r=[],i=[],a=[],o=new Vector3,s=new Matrix4;for(let t=0;t<=e;t++){const n=t/e;r[t]=this.getTangentAt(n,new Vector3),r[t].normalize()}i[0]=new Vector3,a[0]=new Vector3;let l=Number.MAX_VALUE;const c=Math.abs(r[0].x),h=Math.abs(r[0].y),u=Math.abs(r[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),o.crossVectors(r[0],n).normalize(),i[0].crossVectors(r[0],o),a[0].crossVectors(r[0],i[0]);for(let t=1;t<=e;t++){if(i[t]=i[t-1].clone(),a[t]=a[t-1].clone(),o.crossVectors(r[t-1],r[t]),o.length()>Number.EPSILON){o.normalize();const e=Math.acos(MathUtils.clamp(r[t-1].dot(r[t]),-1,1));i[t].applyMatrix4(s.makeRotationAxis(o,e))}a[t].crossVectors(r[t],i[t])}if(!0===t){let t=Math.acos(MathUtils.clamp(i[0].dot(i[e]),-1,1));t/=e,r[0].dot(o.crossVectors(i[0],i[e]))>0&&(t=-t);for(let n=1;n<=e;n++)i[n].applyMatrix4(s.makeRotationAxis(r[n],t*n)),a[n].crossVectors(r[n],i[n])}return{tangents:r,normals:i,binormals:a}},clone:function(){return(new this.constructor).copy(this)},copy:function(e){return this.arcLengthDivisions=e.arcLengthDivisions,this},toJSON:function(){const e={metadata:{version:4.5,type:"Curve",generator:"Curve.toJSON"}};return e.arcLengthDivisions=this.arcLengthDivisions,e.type=this.type,e},fromJSON:function(e){return this.arcLengthDivisions=e.arcLengthDivisions,this}});class EllipseCurve extends Curve{constructor(e=0,t=0,n=1,r=1,i=0,a=2*Math.PI,o=!1,s=0){super(),this.type="EllipseCurve",this.aX=e,this.aY=t,this.xRadius=n,this.yRadius=r,this.aStartAngle=i,this.aEndAngle=a,this.aClockwise=o,this.aRotation=s}getPoint(e,t){const n=t||new Vector2,r=2*Math.PI;let i=this.aEndAngle-this.aStartAngle;const a=Math.abs(i)r;)i-=r;i0?0:(Math.floor(Math.abs(l)/i)+1)*i:0===c&&l===i-1&&(l=i-2,c=1),this.closed||l>0?o=r[(l-1)%i]:(tmp.subVectors(r[0],r[1]).add(r[0]),o=tmp);const h=r[l%i],u=r[(l+1)%i];if(this.closed||l+2r.length-2?r.length-1:a+1],h=r[a>r.length-3?r.length-1:a+2];return n.set(CatmullRom(o,s.x,l.x,c.x,h.x),CatmullRom(o,s.y,l.y,c.y,h.y)),n}copy(e){super.copy(e),this.points=[];for(let t=0,n=e.points.length;t=t){const e=n[r]-t,i=this.curves[r],a=i.getLength(),o=0===a?0:1-e/a;return i.getPointAt(o)}r++}return null}getLength(){const e=this.getCurveLengths();return e[e.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 e=[];let t=0;for(let n=0,r=this.curves.length;n1&&!t[t.length-1].equals(t[0])&&t.push(t[0]),t}copy(e){super.copy(e),this.curves=[];for(let t=0,n=e.curves.length;t0){const e=l.getPoint(0);e.equals(this.currentPoint)||this.lineTo(e.x,e.y)}this.curves.push(l);const c=l.getPoint(1);return this.currentPoint.copy(c),this}copy(e){return super.copy(e),this.currentPoint.copy(e.currentPoint),this}toJSON(){const e=super.toJSON();return e.currentPoint=this.currentPoint.toArray(),e}fromJSON(e){return super.fromJSON(e),this.currentPoint.fromArray(e.currentPoint),this}}class Shape extends Path{constructor(e){super(e),this.uuid=MathUtils.generateUUID(),this.type="Shape",this.holes=[]}getPointsHoles(e){const t=[];for(let n=0,r=this.holes.length;n0:r.vertexColors=e.vertexColors),void 0!==e.uniforms)for(const t in e.uniforms){const i=e.uniforms[t];switch(r.uniforms[t]={},i.type){case"t":r.uniforms[t].value=n(i.value);break;case"c":r.uniforms[t].value=(new Color).setHex(i.value);break;case"v2":r.uniforms[t].value=(new Vector2).fromArray(i.value);break;case"v3":r.uniforms[t].value=(new Vector3).fromArray(i.value);break;case"v4":r.uniforms[t].value=(new Vector4).fromArray(i.value);break;case"m3":r.uniforms[t].value=(new Matrix3).fromArray(i.value);break;case"m4":r.uniforms[t].value=(new Matrix4).fromArray(i.value);break;default:r.uniforms[t].value=i.value}}if(void 0!==e.defines&&(r.defines=e.defines),void 0!==e.vertexShader&&(r.vertexShader=e.vertexShader),void 0!==e.fragmentShader&&(r.fragmentShader=e.fragmentShader),void 0!==e.extensions)for(const t in e.extensions)r.extensions[t]=e.extensions[t];if(void 0!==e.shading&&(r.flatShading=1===e.shading),void 0!==e.size&&(r.size=e.size),void 0!==e.sizeAttenuation&&(r.sizeAttenuation=e.sizeAttenuation),void 0!==e.map&&(r.map=n(e.map)),void 0!==e.matcap&&(r.matcap=n(e.matcap)),void 0!==e.alphaMap&&(r.alphaMap=n(e.alphaMap)),void 0!==e.bumpMap&&(r.bumpMap=n(e.bumpMap)),void 0!==e.bumpScale&&(r.bumpScale=e.bumpScale),void 0!==e.normalMap&&(r.normalMap=n(e.normalMap)),void 0!==e.normalMapType&&(r.normalMapType=e.normalMapType),void 0!==e.normalScale){let t=e.normalScale;!1===Array.isArray(t)&&(t=[t,t]),r.normalScale=(new Vector2).fromArray(t)}return void 0!==e.displacementMap&&(r.displacementMap=n(e.displacementMap)),void 0!==e.displacementScale&&(r.displacementScale=e.displacementScale),void 0!==e.displacementBias&&(r.displacementBias=e.displacementBias),void 0!==e.roughnessMap&&(r.roughnessMap=n(e.roughnessMap)),void 0!==e.metalnessMap&&(r.metalnessMap=n(e.metalnessMap)),void 0!==e.emissiveMap&&(r.emissiveMap=n(e.emissiveMap)),void 0!==e.emissiveIntensity&&(r.emissiveIntensity=e.emissiveIntensity),void 0!==e.specularMap&&(r.specularMap=n(e.specularMap)),void 0!==e.envMap&&(r.envMap=n(e.envMap)),void 0!==e.envMapIntensity&&(r.envMapIntensity=e.envMapIntensity),void 0!==e.reflectivity&&(r.reflectivity=e.reflectivity),void 0!==e.refractionRatio&&(r.refractionRatio=e.refractionRatio),void 0!==e.lightMap&&(r.lightMap=n(e.lightMap)),void 0!==e.lightMapIntensity&&(r.lightMapIntensity=e.lightMapIntensity),void 0!==e.aoMap&&(r.aoMap=n(e.aoMap)),void 0!==e.aoMapIntensity&&(r.aoMapIntensity=e.aoMapIntensity),void 0!==e.gradientMap&&(r.gradientMap=n(e.gradientMap)),void 0!==e.clearcoatMap&&(r.clearcoatMap=n(e.clearcoatMap)),void 0!==e.clearcoatRoughnessMap&&(r.clearcoatRoughnessMap=n(e.clearcoatRoughnessMap)),void 0!==e.clearcoatNormalMap&&(r.clearcoatNormalMap=n(e.clearcoatNormalMap)),void 0!==e.clearcoatNormalScale&&(r.clearcoatNormalScale=(new Vector2).fromArray(e.clearcoatNormalScale)),void 0!==e.transmission&&(r.transmission=e.transmission),void 0!==e.transmissionMap&&(r.transmissionMap=n(e.transmissionMap)),r}setTextures(e){return this.textures=e,this}}const LoaderUtils={decodeText:function(e){if("undefined"!=typeof TextDecoder)return(new TextDecoder).decode(e);let t="";for(let n=0,r=e.length;n0){const n=new LoadingManager(t);i=new ImageLoader(n),i.setCrossOrigin(this.crossOrigin);for(let t=0,n=e.length;tNumber.EPSILON){if(l<0&&(n=t[a],s=-s,o=t[i],l=-l),e.yo.y)continue;if(e.y===n.y){if(e.x===n.x)return!0}else{const t=l*(e.x-n.x)-s*(e.y-n.y);if(0===t)return!0;if(t<0)continue;r=!r}}else{if(e.y!==n.y)continue;if(o.x<=e.x&&e.x<=n.x||n.x<=e.x&&e.x<=o.x)return!0}}return r}const i=ShapeUtils.isClockWise,a=this.subPaths;if(0===a.length)return[];if(!0===t)return n(a);let o,s,l;const c=[];if(1===a.length)return s=a[0],l=new Shape,l.curves=s.curves,c.push(l),c;let h=!i(a[0].getPoints());h=e?!h:h;const u=[],d=[];let p,m,f=[],g=0;d[g]=void 0,f[g]=[];for(let t=0,n=a.length;t1){let e=!1;const t=[];for(let e=0,t=d.length;e0&&(e||(f=u))}for(let e=0,t=d.length;e0){this.source.connect(this.filters[0]);for(let e=1,t=this.filters.length;e0){this.source.disconnect(this.filters[0]);for(let e=1,t=this.filters.length;e0&&this._mixBufferRegionAdditive(n,r,this._addIndex*t,1,t);for(let e=t,i=t+t;e!==i;++e)if(n[e]!==n[e+t]){o.setValue(n,r);break}}saveOriginalState(){const e=this.binding,t=this.buffer,n=this.valueSize,r=n*this._origIndex;e.getValue(t,r);for(let e=n,i=r;e!==i;++e)t[e]=t[r+e%n];this._setIdentity(),this.cumulativeWeight=0,this.cumulativeWeightAdditive=0}restoreOriginalState(){const e=3*this.valueSize;this.binding.setValue(this.buffer,e)}_setAdditiveIdentityNumeric(){const e=this._addIndex*this.valueSize,t=e+this.valueSize;for(let n=e;n=.5)for(let r=0;r!==i;++r)e[t+r]=e[n+r]}_slerp(e,t,n,r){Quaternion.slerpFlat(e,t,e,t,e,n,r)}_slerpAdditive(e,t,n,r,i){const a=this._workIndex*i;Quaternion.multiplyQuaternionsFlat(e,a,e,t,e,n),Quaternion.slerpFlat(e,t,e,t,e,a,r)}_lerp(e,t,n,r,i){const a=1-r;for(let o=0;o!==i;++o){const i=t+o;e[i]=e[i]*a+e[n+o]*r}}_lerpAdditive(e,t,n,r,i){for(let a=0;a!==i;++a){const i=t+a;e[i]=e[i]+e[n+a]*r}}}const _RESERVED_CHARS_RE="\\[\\]\\.:\\/",_reservedRe=new RegExp("[\\[\\]\\.:\\/]","g"),_wordChar="[^\\[\\]\\.:\\/]",_wordCharOrDot="[^"+"\\[\\]\\.:\\/".replace("\\.","")+"]",_directoryRe=/((?:WC+[\/:])*)/.source.replace("WC",_wordChar),_nodeRe=/(WCOD+)?/.source.replace("WCOD",_wordCharOrDot),_objectRe=/(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace("WC",_wordChar),_propertyRe=/\.(WC+)(?:\[(.+)\])?/.source.replace("WC",_wordChar),_trackRe=new RegExp("^"+_directoryRe+_nodeRe+_objectRe+_propertyRe+"$"),_supportedObjectNames=["material","materials","bones"];function Composite(e,t,n){const r=n||PropertyBinding.parseTrackName(t);this._targetGroup=e,this._bindings=e.subscribe_(t,r)}function PropertyBinding(e,t,n){this.path=t,this.parsedPath=n||PropertyBinding.parseTrackName(t),this.node=PropertyBinding.findNode(e,this.parsedPath.nodeName)||e,this.rootNode=e}Object.assign(Composite.prototype,{getValue:function(e,t){this.bind();const n=this._targetGroup.nCachedObjects_,r=this._bindings[n];void 0!==r&&r.getValue(e,t)},setValue:function(e,t){const n=this._bindings;for(let r=this._targetGroup.nCachedObjects_,i=n.length;r!==i;++r)n[r].setValue(e,t)},bind:function(){const e=this._bindings;for(let t=this._targetGroup.nCachedObjects_,n=e.length;t!==n;++t)e[t].bind()},unbind:function(){const e=this._bindings;for(let t=this._targetGroup.nCachedObjects_,n=e.length;t!==n;++t)e[t].unbind()}}),Object.assign(PropertyBinding,{Composite:Composite,create:function(e,t,n){return e&&e.isAnimationObjectGroup?new PropertyBinding.Composite(e,t,n):new PropertyBinding(e,t,n)},sanitizeNodeName:function(e){return e.replace(/\s/g,"_").replace(_reservedRe,"")},parseTrackName:function(e){const t=_trackRe.exec(e);if(!t)throw new Error("PropertyBinding: Cannot parse trackName: "+e);const n={nodeName:t[2],objectName:t[3],objectIndex:t[4],propertyName:t[5],propertyIndex:t[6]},r=n.nodeName&&n.nodeName.lastIndexOf(".");if(void 0!==r&&-1!==r){const e=n.nodeName.substring(r+1);-1!==_supportedObjectNames.indexOf(e)&&(n.nodeName=n.nodeName.substring(0,r),n.objectName=e)}if(null===n.propertyName||0===n.propertyName.length)throw new Error("PropertyBinding: can not parse propertyName from trackName: "+e);return n},findNode:function(e,t){if(!t||""===t||"."===t||-1===t||t===e.name||t===e.uuid)return e;if(e.skeleton){const n=e.skeleton.getBoneByName(t);if(void 0!==n)return n}if(e.children){const n=function(e){for(let r=0;r=i){const a=i++,c=e[a];t[c.uuid]=l,e[l]=c,t[s]=a,e[a]=o;for(let e=0,t=r;e!==t;++e){const t=n[e],r=t[a],i=t[l];t[l]=r,t[a]=i}}}this.nCachedObjects_=i}uncache(){const e=this._objects,t=this._indicesByUUID,n=this._bindings,r=n.length;let i=this.nCachedObjects_,a=e.length;for(let o=0,s=arguments.length;o!==s;++o){const s=arguments[o].uuid,l=t[s];if(void 0!==l)if(delete t[s],l0&&(t[o.uuid]=l),e[l]=o,e.pop();for(let e=0,t=r;e!==t;++e){const t=n[e];t[l]=t[i],t.pop()}}}this.nCachedObjects_=i}subscribe_(e,t){const n=this._bindingsIndicesByPath;let r=n[e];const i=this._bindings;if(void 0!==r)return i[r];const a=this._paths,o=this._parsedPaths,s=this._objects,l=s.length,c=this.nCachedObjects_,h=new Array(l);r=i.length,n[e]=r,a.push(e),o.push(t),i.push(h);for(let n=c,r=s.length;n!==r;++n){const r=s[n];h[n]=new PropertyBinding(r,e,t)}return h}unsubscribe_(e){const t=this._bindingsIndicesByPath,n=t[e];if(void 0!==n){const r=this._paths,i=this._parsedPaths,a=this._bindings,o=a.length-1,s=a[o];t[e[o]]=n,a[n]=s,a.pop(),i[n]=i[o],i.pop(),r[n]=r[o],r.pop()}}}AnimationObjectGroup.prototype.isAnimationObjectGroup=!0;class AnimationAction{constructor(e,t,n=null,r=t.blendMode){this._mixer=e,this._clip=t,this._localRoot=n,this.blendMode=r;const i=t.tracks,a=i.length,o=new Array(a),s={endingStart:2400,endingEnd:2400};for(let e=0;e!==a;++e){const t=i[e].createInterpolant(null);o[e]=t,t.settings=s}this._interpolantSettings=s,this._interpolants=o,this._propertyBindings=new Array(a),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(e){return this._startTime=e,this}setLoop(e,t){return this.loop=e,this.repetitions=t,this}setEffectiveWeight(e){return this.weight=e,this._effectiveWeight=this.enabled?e:0,this.stopFading()}getEffectiveWeight(){return this._effectiveWeight}fadeIn(e){return this._scheduleFading(e,0,1)}fadeOut(e){return this._scheduleFading(e,1,0)}crossFadeFrom(e,t,n){if(e.fadeOut(t),this.fadeIn(t),n){const n=this._clip.duration,r=e._clip.duration,i=r/n,a=n/r;e.warp(1,i,t),this.warp(a,1,t)}return this}crossFadeTo(e,t,n){return e.crossFadeFrom(this,t,n)}stopFading(){const e=this._weightInterpolant;return null!==e&&(this._weightInterpolant=null,this._mixer._takeBackControlInterpolant(e)),this}setEffectiveTimeScale(e){return this.timeScale=e,this._effectiveTimeScale=this.paused?0:e,this.stopWarping()}getEffectiveTimeScale(){return this._effectiveTimeScale}setDuration(e){return this.timeScale=this._clip.duration/e,this.stopWarping()}syncWith(e){return this.time=e.time,this.timeScale=e.timeScale,this.stopWarping()}halt(e){return this.warp(this._effectiveTimeScale,0,e)}warp(e,t,n){const r=this._mixer,i=r.time,a=this.timeScale;let o=this._timeScaleInterpolant;null===o&&(o=r._lendControlInterpolant(),this._timeScaleInterpolant=o);const s=o.parameterPositions,l=o.sampleValues;return s[0]=i,s[1]=i+n,l[0]=e/a,l[1]=t/a,this}stopWarping(){const e=this._timeScaleInterpolant;return null!==e&&(this._timeScaleInterpolant=null,this._mixer._takeBackControlInterpolant(e)),this}getMixer(){return this._mixer}getClip(){return this._clip}getRoot(){return this._localRoot||this._mixer._root}_update(e,t,n,r){if(!this.enabled)return void this._updateWeight(e);const i=this._startTime;if(null!==i){const r=(e-i)*n;if(r<0||0===n)return;this._startTime=null,t=n*r}t*=this._updateTimeScale(e);const a=this._updateTime(t),o=this._updateWeight(e);if(o>0){const e=this._interpolants,t=this._propertyBindings;if(2501===this.blendMode)for(let n=0,r=e.length;n!==r;++n)e[n].evaluate(a),t[n].accumulateAdditive(o);else for(let n=0,i=e.length;n!==i;++n)e[n].evaluate(a),t[n].accumulate(r,o)}}_updateWeight(e){let t=0;if(this.enabled){t=this.weight;const n=this._weightInterpolant;if(null!==n){const r=n.evaluate(e)[0];t*=r,e>n.parameterPositions[1]&&(this.stopFading(),0===r&&(this.enabled=!1))}}return this._effectiveWeight=t,t}_updateTimeScale(e){let t=0;if(!this.paused){t=this.timeScale;const n=this._timeScaleInterpolant;if(null!==n){t*=n.evaluate(e)[0],e>n.parameterPositions[1]&&(this.stopWarping(),0===t?this.paused=!0:this.timeScale=t)}}return this._effectiveTimeScale=t,t}_updateTime(e){const t=this._clip.duration,n=this.loop;let r=this.time+e,i=this._loopCount;const a=2202===n;if(0===e)return-1===i||!a||1&~i?r:t-r;if(2200===n){-1===i&&(this._loopCount=0,this._setEndings(!0,!0,!1));e:{if(r>=t)r=t;else{if(!(r<0)){this.time=r;break e}r=0}this.clampWhenFinished?this.paused=!0:this.enabled=!1,this.time=r,this._mixer.dispatchEvent({type:"finished",action:this,direction:e<0?-1:1})}}else{if(-1===i&&(e>=0?(i=0,this._setEndings(!0,0===this.repetitions,a)):this._setEndings(0===this.repetitions,!0,a)),r>=t||r<0){const n=Math.floor(r/t);r-=t*n,i+=Math.abs(n);const o=this.repetitions-i;if(o<=0)this.clampWhenFinished?this.paused=!0:this.enabled=!1,r=e>0?t:0,this.time=r,this._mixer.dispatchEvent({type:"finished",action:this,direction:e>0?1:-1});else{if(1===o){const t=e<0;this._setEndings(t,!t,a)}else this._setEndings(!1,!1,a);this._loopCount=i,this.time=r,this._mixer.dispatchEvent({type:"loop",action:this,loopDelta:n})}}else this.time=r;if(a&&!(1&~i))return t-r}return r}_setEndings(e,t,n){const r=this._interpolantSettings;n?(r.endingStart=2401,r.endingEnd=2401):(r.endingStart=e?this.zeroSlopeAtStart?2401:2400:2402,r.endingEnd=t?this.zeroSlopeAtEnd?2401:2400:2402)}_scheduleFading(e,t,n){const r=this._mixer,i=r.time;let a=this._weightInterpolant;null===a&&(a=r._lendControlInterpolant(),this._weightInterpolant=a);const o=a.parameterPositions,s=a.sampleValues;return o[0]=i,s[0]=t,o[1]=i+e,s[1]=n,this}}class AnimationMixer extends EventDispatcher{constructor(e){super(),this._root=e,this._initMemoryManager(),this._accuIndex=0,this.time=0,this.timeScale=1}_bindAction(e,t){const n=e._localRoot||this._root,r=e._clip.tracks,i=r.length,a=e._propertyBindings,o=e._interpolants,s=n.uuid,l=this._bindingsByRootAndName;let c=l[s];void 0===c&&(c={},l[s]=c);for(let e=0;e!==i;++e){const i=r[e],l=i.name;let h=c[l];if(void 0!==h)a[e]=h;else{if(h=a[e],void 0!==h){null===h._cacheIndex&&(++h.referenceCount,this._addInactiveBinding(h,s,l));continue}const r=t&&t._propertyBindings[e].binding.parsedPath;h=new PropertyMixer(PropertyBinding.create(n,l,r),i.ValueTypeName,i.getValueSize()),++h.referenceCount,this._addInactiveBinding(h,s,l),a[e]=h}o[e].resultBuffer=h.buffer}}_activateAction(e){if(!this._isActiveAction(e)){if(null===e._cacheIndex){const t=(e._localRoot||this._root).uuid,n=e._clip.uuid,r=this._actionsByClip[n];this._bindAction(e,r&&r.knownActions[0]),this._addInactiveAction(e,n,t)}const t=e._propertyBindings;for(let e=0,n=t.length;e!==n;++e){const n=t[e];0==n.useCount++&&(this._lendBinding(n),n.saveOriginalState())}this._lendAction(e)}}_deactivateAction(e){if(this._isActiveAction(e)){const t=e._propertyBindings;for(let e=0,n=t.length;e!==n;++e){const n=t[e];0==--n.useCount&&(n.restoreOriginalState(),this._takeBackBinding(n))}this._takeBackAction(e)}}_initMemoryManager(){this._actions=[],this._nActiveActions=0,this._actionsByClip={},this._bindings=[],this._nActiveBindings=0,this._bindingsByRootAndName={},this._controlInterpolants=[],this._nActiveControlInterpolants=0;const e=this;this.stats={actions:{get total(){return e._actions.length},get inUse(){return e._nActiveActions}},bindings:{get total(){return e._bindings.length},get inUse(){return e._nActiveBindings}},controlInterpolants:{get total(){return e._controlInterpolants.length},get inUse(){return e._nActiveControlInterpolants}}}}_isActiveAction(e){const t=e._cacheIndex;return null!==t&&t=0;--t)e[t].stop();return this}update(e){e*=this.timeScale;const t=this._actions,n=this._nActiveActions,r=this.time+=e,i=Math.sign(e),a=this._accuIndex^=1;for(let o=0;o!==n;++o){t[o]._update(r,e,i,a)}const o=this._bindings,s=this._nActiveBindings;for(let e=0;e!==s;++e)o[e].apply(a);return this}setTime(e){this.time=0;for(let e=0;ethis.max.x||e.ythis.max.y)}containsBox(e){return this.min.x<=e.min.x&&e.max.x<=this.max.x&&this.min.y<=e.min.y&&e.max.y<=this.max.y}getParameter(e,t){return void 0===t&&(console.warn("THREE.Box2: .getParameter() target is now required"),t=new Vector2),t.set((e.x-this.min.x)/(this.max.x-this.min.x),(e.y-this.min.y)/(this.max.y-this.min.y))}intersectsBox(e){return!(e.max.xthis.max.x||e.max.ythis.max.y)}clampPoint(e,t){return void 0===t&&(console.warn("THREE.Box2: .clampPoint() target is now required"),t=new Vector2),t.copy(e).clamp(this.min,this.max)}distanceToPoint(e){return _vector$8.copy(e).clamp(this.min,this.max).sub(e).length()}intersect(e){return this.min.max(e.min),this.max.min(e.max),this}union(e){return this.min.min(e.min),this.max.max(e.max),this}translate(e){return this.min.add(e),this.max.add(e),this}equals(e){return e.min.equals(this.min)&&e.max.equals(this.max)}}Box2.prototype.isBox2=!0;const _startP=new Vector3,_startEnd=new Vector3;class Line3{constructor(e=new Vector3,t=new Vector3){this.start=e,this.end=t}set(e,t){return this.start.copy(e),this.end.copy(t),this}copy(e){return this.start.copy(e.start),this.end.copy(e.end),this}getCenter(e){return void 0===e&&(console.warn("THREE.Line3: .getCenter() target is now required"),e=new Vector3),e.addVectors(this.start,this.end).multiplyScalar(.5)}delta(e){return void 0===e&&(console.warn("THREE.Line3: .delta() target is now required"),e=new Vector3),e.subVectors(this.end,this.start)}distanceSq(){return this.start.distanceToSquared(this.end)}distance(){return this.start.distanceTo(this.end)}at(e,t){return void 0===t&&(console.warn("THREE.Line3: .at() target is now required"),t=new Vector3),this.delta(t).multiplyScalar(e).add(this.start)}closestPointToPointParameter(e,t){_startP.subVectors(e,this.start),_startEnd.subVectors(this.end,this.start);const n=_startEnd.dot(_startEnd);let r=_startEnd.dot(_startP)/n;return t&&(r=MathUtils.clamp(r,0,1)),r}closestPointToPoint(e,t,n){const r=this.closestPointToPointParameter(e,t);return void 0===n&&(console.warn("THREE.Line3: .closestPointToPoint() target is now required"),n=new Vector3),this.delta(n).multiplyScalar(r).add(this.start)}applyMatrix4(e){return this.start.applyMatrix4(e),this.end.applyMatrix4(e),this}equals(e){return e.start.equals(this.start)&&e.end.equals(this.end)}clone(){return(new this.constructor).copy(this)}}function ImmediateRenderObject(e){Object3D.call(this),this.material=e,this.render=function(){},this.hasPositions=!1,this.hasNormals=!1,this.hasColors=!1,this.hasUvs=!1,this.positionArray=null,this.normalArray=null,this.colorArray=null,this.uvArray=null,this.count=0}ImmediateRenderObject.prototype=Object.create(Object3D.prototype),ImmediateRenderObject.prototype.constructor=ImmediateRenderObject,ImmediateRenderObject.prototype.isImmediateRenderObject=!0;const _vector$9=new Vector3;class SpotLightHelper extends Object3D{constructor(e,t){super(),this.light=e,this.light.updateMatrixWorld(),this.matrix=e.matrixWorld,this.matrixAutoUpdate=!1,this.color=t;const n=new BufferGeometry,r=[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 e=0,t=1,n=32;e.99999)this.quaternion.set(0,0,0,1);else if(e.y<-.99999)this.quaternion.set(1,0,0,0);else{_axis.set(e.z,0,-e.x).normalize();const t=Math.acos(e.y);this.quaternion.setFromAxisAngle(_axis,t)}}setLength(e,t=.2*e,n=.2*t){this.line.scale.set(1,Math.max(1e-4,e-t),1),this.line.updateMatrix(),this.cone.scale.set(n,t,n),this.cone.position.y=e,this.cone.updateMatrix()}setColor(e){this.line.material.color.set(e),this.cone.material.color.set(e)}copy(e){return super.copy(e,!1),this.line.copy(e.line),this.cone.copy(e.cone),this}}class AxesHelper extends LineSegments{constructor(e=1){const t=[0,0,0,e,0,0,0,0,0,0,e,0,0,0,0,0,0,e],n=new BufferGeometry;n.setAttribute("position",new Float32BufferAttribute(t,3)),n.setAttribute("color",new Float32BufferAttribute([1,0,0,1,.6,0,0,1,0,.6,1,0,0,0,1,0,.6,1],3));super(n,new LineBasicMaterial({vertexColors:!0,toneMapped:!1})),this.type="AxesHelper"}}const _floatView=new Float32Array(1),_int32View=new Int32Array(_floatView.buffer),DataUtils={toHalfFloat:function(e){_floatView[0]=e;const t=_int32View[0];let n=t>>16&32768,r=t>>12&2047;const i=t>>23&255;return i<103?n:i>142?(n|=31744,n|=(255==i?0:1)&&8388607&t,n):i<113?(r|=2048,n|=(r>>114-i)+(r>>113-i&1),n):(n|=i-112<<10|r>>1,n+=1&r,n)}},LOD_MIN=4,LOD_MAX=8,SIZE_MAX=Math.pow(2,8),EXTRA_LOD_SIGMA=[.125,.215,.35,.446,.526,.582],TOTAL_LODS=5+EXTRA_LOD_SIGMA.length,MAX_SAMPLES=20,ENCODINGS={[LinearEncoding]:0,[sRGBEncoding]:1,[RGBEEncoding]:2,[RGBM7Encoding]:3,[RGBM16Encoding]:4,[RGBDEncoding]:5,[GammaEncoding]:6},backgroundMaterial=new MeshBasicMaterial({side:1,depthWrite:!1,depthTest:!1}),backgroundBox=new Mesh(new BoxGeometry,backgroundMaterial),_flatCamera=new OrthographicCamera,{_lodPlanes:_lodPlanes,_sizeLods:_sizeLods,_sigmas:_sigmas}=_createPlanes(),_clearColor=new Color;let _oldTarget=null;const PHI=(1+Math.sqrt(5))/2,INV_PHI=1/PHI,_axisDirections=[new Vector3(1,1,1),new Vector3(-1,1,1),new Vector3(1,1,-1),new Vector3(-1,1,-1),new Vector3(0,PHI,INV_PHI),new Vector3(0,PHI,-INV_PHI),new Vector3(INV_PHI,0,PHI),new Vector3(-INV_PHI,0,PHI),new Vector3(PHI,INV_PHI,0),new Vector3(-PHI,INV_PHI,0)];function convertLinearToRGBE(e){const t=Math.max(e.r,e.g,e.b),n=Math.min(Math.max(Math.ceil(Math.log2(t)),-128),127);e.multiplyScalar(Math.pow(2,-n));return(n+128)/255}class PMREMGenerator{constructor(e){this._renderer=e,this._pingPongRenderTarget=null,this._blurMaterial=_getBlurShader(20),this._equirectShader=null,this._cubemapShader=null,this._compileMaterial(this._blurMaterial)}fromScene(e,t=0,n=.1,r=100){_oldTarget=this._renderer.getRenderTarget();const i=this._allocateTargets();return this._sceneToCubeUV(e,n,r,i),t>0&&this._blur(i,0,0,t),this._applyPMREM(i),this._cleanup(i),i}fromEquirectangular(e){return this._fromTexture(e)}fromCubemap(e){return this._fromTexture(e)}compileCubemapShader(){null===this._cubemapShader&&(this._cubemapShader=_getCubemapShader(),this._compileMaterial(this._cubemapShader))}compileEquirectangularShader(){null===this._equirectShader&&(this._equirectShader=_getEquirectShader(),this._compileMaterial(this._equirectShader))}dispose(){this._blurMaterial.dispose(),null!==this._cubemapShader&&this._cubemapShader.dispose(),null!==this._equirectShader&&this._equirectShader.dispose();for(let e=0;e<_lodPlanes.length;e++)_lodPlanes[e].dispose()}_cleanup(e){this._pingPongRenderTarget.dispose(),this._renderer.setRenderTarget(_oldTarget),e.scissorTest=!1,_setViewport(e,0,0,e.width,e.height)}_fromTexture(e){_oldTarget=this._renderer.getRenderTarget();const t=this._allocateTargets(e);return this._textureToCubeUV(e,t),this._applyPMREM(t),this._cleanup(t),t}_allocateTargets(e){const t={magFilter:1003,minFilter:1003,generateMipmaps:!1,type:1009,format:1023,encoding:_isLDR(e)?e.encoding:3002,depthBuffer:!1},n=_createRenderTarget(t);return n.depthBuffer=!e,this._pingPongRenderTarget=_createRenderTarget(t),n}_compileMaterial(e){const t=new Mesh(_lodPlanes[0],e);this._renderer.compile(t,_flatCamera)}_sceneToCubeUV(e,t,n,r){const i=new PerspectiveCamera(90,1,t,n),a=[1,-1,1,1,1,1],o=[1,1,1,-1,-1,-1],s=this._renderer,l=s.autoClear,c=s.outputEncoding,h=s.toneMapping;s.getClearColor(_clearColor),s.toneMapping=0,s.outputEncoding=3e3,s.autoClear=!1;let u=!1;const d=e.background;if(d){if(d.isColor){backgroundMaterial.color.copy(d).convertSRGBToLinear(),e.background=null;const t=convertLinearToRGBE(backgroundMaterial.color);backgroundMaterial.opacity=t,u=!0}}else{backgroundMaterial.color.copy(_clearColor).convertSRGBToLinear();const e=convertLinearToRGBE(backgroundMaterial.color);backgroundMaterial.opacity=e,u=!0}for(let t=0;t<6;t++){const n=t%3;0==n?(i.up.set(0,a[t],0),i.lookAt(o[t],0,0)):1==n?(i.up.set(0,0,a[t]),i.lookAt(0,o[t],0)):(i.up.set(0,a[t],0),i.lookAt(0,0,o[t])),_setViewport(r,n*SIZE_MAX,t>2?SIZE_MAX:0,SIZE_MAX,SIZE_MAX),s.setRenderTarget(r),u&&s.render(backgroundBox,i),s.render(e,i)}s.toneMapping=h,s.outputEncoding=c,s.autoClear=l}_textureToCubeUV(e,t){const n=this._renderer;e.isCubeTexture?null==this._cubemapShader&&(this._cubemapShader=_getCubemapShader()):null==this._equirectShader&&(this._equirectShader=_getEquirectShader());const r=e.isCubeTexture?this._cubemapShader:this._equirectShader,i=new Mesh(_lodPlanes[0],r),a=r.uniforms;a.envMap.value=e,e.isCubeTexture||a.texelSize.value.set(1/e.image.width,1/e.image.height),a.inputEncoding.value=ENCODINGS[e.encoding],a.outputEncoding.value=ENCODINGS[t.texture.encoding],_setViewport(t,0,0,3*SIZE_MAX,2*SIZE_MAX),n.setRenderTarget(t),n.render(i,_flatCamera)}_applyPMREM(e){const t=this._renderer,n=t.autoClear;t.autoClear=!1;for(let t=1;t20&&console.warn(`sigmaRadians, ${i}, 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 e=0;e<20;++e){const t=e/p,n=Math.exp(-t*t/2);f.push(n),0==e?g+=n:e4?r-8+4:0),3*v,2*v),s.setRenderTarget(t),s.render(c,_flatCamera)}}function _isLDR(e){return void 0!==e&&1009===e.type&&(3e3===e.encoding||3001===e.encoding||3007===e.encoding)}function _createPlanes(){const e=[],t=[],n=[];let r=8;for(let i=0;i4?o=EXTRA_LOD_SIGMA[i-8+4-1]:0==i&&(o=0),n.push(o);const s=1/(a-1),l=-s/2,c=1+s/2,h=[l,l,c,l,c,c,l,l,c,c,l,c],u=6,d=6,p=3,m=2,f=1,g=new Float32Array(p*d*u),v=new Float32Array(m*d*u),_=new Float32Array(f*d*u);for(let e=0;e2?0:-1,r=[t,n,0,t+2/3,n,0,t+2/3,n+1,0,t,n,0,t+2/3,n+1,0,t,n+1,0];g.set(r,p*d*e),v.set(h,m*d*e);const i=[e,e,e,e,e,e];_.set(i,f*d*e)}const y=new BufferGeometry;y.setAttribute("position",new BufferAttribute(g,p)),y.setAttribute("uv",new BufferAttribute(v,m)),y.setAttribute("faceIndex",new BufferAttribute(_,f)),e.push(y),r>4&&r--}return{_lodPlanes:e,_sizeLods:t,_sigmas:n}}function _createRenderTarget(e){const t=new WebGLRenderTarget(3*SIZE_MAX,3*SIZE_MAX,e);return t.texture.mapping=306,t.texture.name="PMREM.cubeUv",t.scissorTest=!0,t}function _setViewport(e,t,n,r,i){e.viewport.set(t,n,r,i),e.scissor.set(t,n,r,i)}function _getBlurShader(e){const t=new Float32Array(e),n=new Vector3(0,1,0);return new RawShaderMaterial({name:"SphericalGaussianBlur",defines:{n:e},uniforms:{envMap:{value:null},samples:{value:1},weights:{value:t},latitudinal:{value:!1},dTheta:{value:0},mipInt:{value:0},poleAxis:{value:n},inputEncoding:{value:ENCODINGS[3e3]},outputEncoding:{value:ENCODINGS[3e3]}},vertexShader:_getCommonVertexShader(),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${_getEncodings()}\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\tgl_FragColor = linearToOutputTexel( gl_FragColor );\n\n\t\t\t}\n\t\t`,blending:0,depthTest:!1,depthWrite:!1})}function _getEquirectShader(){const e=new Vector2(1,1);return new RawShaderMaterial({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null},texelSize:{value:e},inputEncoding:{value:ENCODINGS[3e3]},outputEncoding:{value:ENCODINGS[3e3]}},vertexShader:_getCommonVertexShader(),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 vec2 texelSize;\n\n\t\t\t${_getEncodings()}\n\n\t\t\t#include \n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\n\t\t\t\tvec3 outputDirection = normalize( vOutputDirection );\n\t\t\t\tvec2 uv = equirectUv( outputDirection );\n\n\t\t\t\tvec2 f = fract( uv / texelSize - 0.5 );\n\t\t\t\tuv -= f * texelSize;\n\t\t\t\tvec3 tl = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;\n\t\t\t\tuv.x += texelSize.x;\n\t\t\t\tvec3 tr = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;\n\t\t\t\tuv.y += texelSize.y;\n\t\t\t\tvec3 br = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;\n\t\t\t\tuv.x -= texelSize.x;\n\t\t\t\tvec3 bl = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;\n\n\t\t\t\tvec3 tm = mix( tl, tr, f.x );\n\t\t\t\tvec3 bm = mix( bl, br, f.x );\n\t\t\t\tgl_FragColor.rgb = mix( tm, bm, f.y );\n\n\t\t\t\tgl_FragColor = linearToOutputTexel( gl_FragColor );\n\n\t\t\t}\n\t\t`,blending:0,depthTest:!1,depthWrite:!1})}function _getCubemapShader(){return new RawShaderMaterial({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},inputEncoding:{value:ENCODINGS[3e3]},outputEncoding:{value:ENCODINGS[3e3]}},vertexShader:_getCommonVertexShader(),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 samplerCube envMap;\n\n\t\t\t${_getEncodings()}\n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb = envMapTexelToLinear( textureCube( envMap, vec3( - vOutputDirection.x, vOutputDirection.yz ) ) ).rgb;\n\t\t\t\tgl_FragColor = linearToOutputTexel( gl_FragColor );\n\n\t\t\t}\n\t\t`,blending:0,depthTest:!1,depthWrite:!1})}function _getCommonVertexShader(){return"\n\n\t\tprecision mediump float;\n\t\tprecision mediump int;\n\n\t\tattribute vec3 position;\n\t\tattribute vec2 uv;\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 _getEncodings(){return"\n\n\t\tuniform int inputEncoding;\n\t\tuniform int outputEncoding;\n\n\t\t#include \n\n\t\tvec4 inputTexelToLinear( vec4 value ) {\n\n\t\t\tif ( inputEncoding == 0 ) {\n\n\t\t\t\treturn value;\n\n\t\t\t} else if ( inputEncoding == 1 ) {\n\n\t\t\t\treturn sRGBToLinear( value );\n\n\t\t\t} else if ( inputEncoding == 2 ) {\n\n\t\t\t\treturn RGBEToLinear( value );\n\n\t\t\t} else if ( inputEncoding == 3 ) {\n\n\t\t\t\treturn RGBMToLinear( value, 7.0 );\n\n\t\t\t} else if ( inputEncoding == 4 ) {\n\n\t\t\t\treturn RGBMToLinear( value, 16.0 );\n\n\t\t\t} else if ( inputEncoding == 5 ) {\n\n\t\t\t\treturn RGBDToLinear( value, 256.0 );\n\n\t\t\t} else {\n\n\t\t\t\treturn GammaToLinear( value, 2.2 );\n\n\t\t\t}\n\n\t\t}\n\n\t\tvec4 linearToOutputTexel( vec4 value ) {\n\n\t\t\tif ( outputEncoding == 0 ) {\n\n\t\t\t\treturn value;\n\n\t\t\t} else if ( outputEncoding == 1 ) {\n\n\t\t\t\treturn LinearTosRGB( value );\n\n\t\t\t} else if ( outputEncoding == 2 ) {\n\n\t\t\t\treturn LinearToRGBE( value );\n\n\t\t\t} else if ( outputEncoding == 3 ) {\n\n\t\t\t\treturn LinearToRGBM( value, 7.0 );\n\n\t\t\t} else if ( outputEncoding == 4 ) {\n\n\t\t\t\treturn LinearToRGBM( value, 16.0 );\n\n\t\t\t} else if ( outputEncoding == 5 ) {\n\n\t\t\t\treturn LinearToRGBD( value, 256.0 );\n\n\t\t\t} else {\n\n\t\t\t\treturn LinearToGamma( value, 2.2 );\n\n\t\t\t}\n\n\t\t}\n\n\t\tvec4 envMapTexelToLinear( vec4 color ) {\n\n\t\t\treturn inputTexelToLinear( color );\n\n\t\t}\n\t"}const LineStrip=0,LinePieces=1,NoColors=0,FaceColors=1,VertexColors=2;function MeshFaceMaterial(e){return console.warn("THREE.MeshFaceMaterial has been removed. Use an Array instead."),e}function MultiMaterial(e=[]){return console.warn("THREE.MultiMaterial has been removed. Use an Array instead."),e.isMultiMaterial=!0,e.materials=e,e.clone=function(){return e.slice()},e}function PointCloud(e,t){return console.warn("THREE.PointCloud has been renamed to THREE.Points."),new Points(e,t)}function Particle(e){return console.warn("THREE.Particle has been renamed to THREE.Sprite."),new Sprite(e)}function ParticleSystem(e,t){return console.warn("THREE.ParticleSystem has been renamed to THREE.Points."),new Points(e,t)}function PointCloudMaterial(e){return console.warn("THREE.PointCloudMaterial has been renamed to THREE.PointsMaterial."),new PointsMaterial(e)}function ParticleBasicMaterial(e){return console.warn("THREE.ParticleBasicMaterial has been renamed to THREE.PointsMaterial."),new PointsMaterial(e)}function ParticleSystemMaterial(e){return console.warn("THREE.ParticleSystemMaterial has been renamed to THREE.PointsMaterial."),new PointsMaterial(e)}function Vertex(e,t,n){return console.warn("THREE.Vertex has been removed. Use THREE.Vector3 instead."),new Vector3(e,t,n)}function DynamicBufferAttribute(e,t){return console.warn("THREE.DynamicBufferAttribute has been removed. Use new THREE.BufferAttribute().setUsage( THREE.DynamicDrawUsage ) instead."),new BufferAttribute(e,t).setUsage(35048)}function Int8Attribute(e,t){return console.warn("THREE.Int8Attribute has been removed. Use new THREE.Int8BufferAttribute() instead."),new Int8BufferAttribute(e,t)}function Uint8Attribute(e,t){return console.warn("THREE.Uint8Attribute has been removed. Use new THREE.Uint8BufferAttribute() instead."),new Uint8BufferAttribute(e,t)}function Uint8ClampedAttribute(e,t){return console.warn("THREE.Uint8ClampedAttribute has been removed. Use new THREE.Uint8ClampedBufferAttribute() instead."),new Uint8ClampedBufferAttribute(e,t)}function Int16Attribute(e,t){return console.warn("THREE.Int16Attribute has been removed. Use new THREE.Int16BufferAttribute() instead."),new Int16BufferAttribute(e,t)}function Uint16Attribute(e,t){return console.warn("THREE.Uint16Attribute has been removed. Use new THREE.Uint16BufferAttribute() instead."),new Uint16BufferAttribute(e,t)}function Int32Attribute(e,t){return console.warn("THREE.Int32Attribute has been removed. Use new THREE.Int32BufferAttribute() instead."),new Int32BufferAttribute(e,t)}function Uint32Attribute(e,t){return console.warn("THREE.Uint32Attribute has been removed. Use new THREE.Uint32BufferAttribute() instead."),new Uint32BufferAttribute(e,t)}function Float32Attribute(e,t){return console.warn("THREE.Float32Attribute has been removed. Use new THREE.Float32BufferAttribute() instead."),new Float32BufferAttribute(e,t)}function Float64Attribute(e,t){return console.warn("THREE.Float64Attribute has been removed. Use new THREE.Float64BufferAttribute() instead."),new Float64BufferAttribute(e,t)}function AxisHelper(e){return console.warn("THREE.AxisHelper has been renamed to THREE.AxesHelper."),new AxesHelper(e)}function BoundingBoxHelper(e,t){return console.warn("THREE.BoundingBoxHelper has been deprecated. Creating a THREE.BoxHelper instead."),new BoxHelper(e,t)}function EdgesHelper(e,t){return console.warn("THREE.EdgesHelper has been removed. Use THREE.EdgesGeometry instead."),new LineSegments(new EdgesGeometry(e.geometry),new LineBasicMaterial({color:void 0!==t?t:16777215}))}function WireframeHelper(e,t){return console.warn("THREE.WireframeHelper has been removed. Use THREE.WireframeGeometry instead."),new LineSegments(new WireframeGeometry(e.geometry),new LineBasicMaterial({color:void 0!==t?t:16777215}))}function XHRLoader(e){return console.warn("THREE.XHRLoader has been renamed to THREE.FileLoader."),new FileLoader(e)}function BinaryTextureLoader(e){return console.warn("THREE.BinaryTextureLoader has been renamed to THREE.DataTextureLoader."),new DataTextureLoader(e)}function WebGLRenderTargetCube(e,t,n){return console.warn("THREE.WebGLRenderTargetCube( width, height, options ) is now WebGLCubeRenderTarget( size, options )."),new WebGLCubeRenderTarget(e,n)}function CanvasRenderer(){console.error("THREE.CanvasRenderer has been removed")}function JSONLoader(){console.error("THREE.JSONLoader has been removed.")}Curve.create=function(e,t){return console.log("THREE.Curve.create() has been deprecated"),e.prototype=Object.create(Curve.prototype),e.prototype.constructor=e,e.prototype.getPoint=t,e},Path.prototype.fromPoints=function(e){return console.warn("THREE.Path: .fromPoints() has been renamed to .setFromPoints()."),this.setFromPoints(e)},GridHelper.prototype.setColors=function(){console.error("THREE.GridHelper: setColors() has been deprecated, pass them in the constructor instead.")},SkeletonHelper.prototype.update=function(){console.error("THREE.SkeletonHelper: update() no longer needs to be called.")},Loader.prototype.extractUrlBase=function(e){return console.warn("THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead."),LoaderUtils.extractUrlBase(e)},Loader.Handlers={add:function(){console.error("THREE.Loader: Handlers.add() has been removed. Use LoadingManager.addHandler() instead.")},get:function(){console.error("THREE.Loader: Handlers.get() has been removed. Use LoadingManager.getHandler() instead.")}},Box2.prototype.center=function(e){return console.warn("THREE.Box2: .center() has been renamed to .getCenter()."),this.getCenter(e)},Box2.prototype.empty=function(){return console.warn("THREE.Box2: .empty() has been renamed to .isEmpty()."),this.isEmpty()},Box2.prototype.isIntersectionBox=function(e){return console.warn("THREE.Box2: .isIntersectionBox() has been renamed to .intersectsBox()."),this.intersectsBox(e)},Box2.prototype.size=function(e){return console.warn("THREE.Box2: .size() has been renamed to .getSize()."),this.getSize(e)},Box3.prototype.center=function(e){return console.warn("THREE.Box3: .center() has been renamed to .getCenter()."),this.getCenter(e)},Box3.prototype.empty=function(){return console.warn("THREE.Box3: .empty() has been renamed to .isEmpty()."),this.isEmpty()},Box3.prototype.isIntersectionBox=function(e){return console.warn("THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox()."),this.intersectsBox(e)},Box3.prototype.isIntersectionSphere=function(e){return console.warn("THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere()."),this.intersectsSphere(e)},Box3.prototype.size=function(e){return console.warn("THREE.Box3: .size() has been renamed to .getSize()."),this.getSize(e)},Sphere.prototype.empty=function(){return console.warn("THREE.Sphere: .empty() has been renamed to .isEmpty()."),this.isEmpty()},Frustum.prototype.setFromMatrix=function(e){return console.warn("THREE.Frustum: .setFromMatrix() has been renamed to .setFromProjectionMatrix()."),this.setFromProjectionMatrix(e)},Line3.prototype.center=function(e){return console.warn("THREE.Line3: .center() has been renamed to .getCenter()."),this.getCenter(e)},MathUtils.random16=function(){return console.warn("THREE.Math: .random16() has been deprecated. Use Math.random() instead."),Math.random()},MathUtils.nearestPowerOfTwo=function(e){return console.warn("THREE.Math: .nearestPowerOfTwo() has been renamed to .floorPowerOfTwo()."),MathUtils.floorPowerOfTwo(e)},MathUtils.nextPowerOfTwo=function(e){return console.warn("THREE.Math: .nextPowerOfTwo() has been renamed to .ceilPowerOfTwo()."),MathUtils.ceilPowerOfTwo(e)},Matrix3.prototype.flattenToArrayOffset=function(e,t){return console.warn("THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead."),this.toArray(e,t)},Matrix3.prototype.multiplyVector3=function(e){return console.warn("THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead."),e.applyMatrix3(this)},Matrix3.prototype.multiplyVector3Array=function(){console.error("THREE.Matrix3: .multiplyVector3Array() has been removed.")},Matrix3.prototype.applyToBufferAttribute=function(e){return console.warn("THREE.Matrix3: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix3( matrix ) instead."),e.applyMatrix3(this)},Matrix3.prototype.applyToVector3Array=function(){console.error("THREE.Matrix3: .applyToVector3Array() has been removed.")},Matrix3.prototype.getInverse=function(e){return console.warn("THREE.Matrix3: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead."),this.copy(e).invert()},Matrix4.prototype.extractPosition=function(e){return console.warn("THREE.Matrix4: .extractPosition() has been renamed to .copyPosition()."),this.copyPosition(e)},Matrix4.prototype.flattenToArrayOffset=function(e,t){return console.warn("THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead."),this.toArray(e,t)},Matrix4.prototype.getPosition=function(){return console.warn("THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead."),(new Vector3).setFromMatrixColumn(this,3)},Matrix4.prototype.setRotationFromQuaternion=function(e){return console.warn("THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion()."),this.makeRotationFromQuaternion(e)},Matrix4.prototype.multiplyToArray=function(){console.warn("THREE.Matrix4: .multiplyToArray() has been removed.")},Matrix4.prototype.multiplyVector3=function(e){return console.warn("THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead."),e.applyMatrix4(this)},Matrix4.prototype.multiplyVector4=function(e){return console.warn("THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead."),e.applyMatrix4(this)},Matrix4.prototype.multiplyVector3Array=function(){console.error("THREE.Matrix4: .multiplyVector3Array() has been removed.")},Matrix4.prototype.rotateAxis=function(e){console.warn("THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead."),e.transformDirection(this)},Matrix4.prototype.crossVector=function(e){return console.warn("THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead."),e.applyMatrix4(this)},Matrix4.prototype.translate=function(){console.error("THREE.Matrix4: .translate() has been removed.")},Matrix4.prototype.rotateX=function(){console.error("THREE.Matrix4: .rotateX() has been removed.")},Matrix4.prototype.rotateY=function(){console.error("THREE.Matrix4: .rotateY() has been removed.")},Matrix4.prototype.rotateZ=function(){console.error("THREE.Matrix4: .rotateZ() has been removed.")},Matrix4.prototype.rotateByAxis=function(){console.error("THREE.Matrix4: .rotateByAxis() has been removed.")},Matrix4.prototype.applyToBufferAttribute=function(e){return console.warn("THREE.Matrix4: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix4( matrix ) instead."),e.applyMatrix4(this)},Matrix4.prototype.applyToVector3Array=function(){console.error("THREE.Matrix4: .applyToVector3Array() has been removed.")},Matrix4.prototype.makeFrustum=function(e,t,n,r,i,a){return console.warn("THREE.Matrix4: .makeFrustum() has been removed. Use .makePerspective( left, right, top, bottom, near, far ) instead."),this.makePerspective(e,t,r,n,i,a)},Matrix4.prototype.getInverse=function(e){return console.warn("THREE.Matrix4: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead."),this.copy(e).invert()},Plane.prototype.isIntersectionLine=function(e){return console.warn("THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine()."),this.intersectsLine(e)},Quaternion.prototype.multiplyVector3=function(e){return console.warn("THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead."),e.applyQuaternion(this)},Quaternion.prototype.inverse=function(){return console.warn("THREE.Quaternion: .inverse() has been renamed to invert()."),this.invert()},Ray.prototype.isIntersectionBox=function(e){return console.warn("THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox()."),this.intersectsBox(e)},Ray.prototype.isIntersectionPlane=function(e){return console.warn("THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane()."),this.intersectsPlane(e)},Ray.prototype.isIntersectionSphere=function(e){return console.warn("THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere()."),this.intersectsSphere(e)},Triangle.prototype.area=function(){return console.warn("THREE.Triangle: .area() has been renamed to .getArea()."),this.getArea()},Triangle.prototype.barycoordFromPoint=function(e,t){return console.warn("THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord()."),this.getBarycoord(e,t)},Triangle.prototype.midpoint=function(e){return console.warn("THREE.Triangle: .midpoint() has been renamed to .getMidpoint()."),this.getMidpoint(e)},Triangle.prototypenormal=function(e){return console.warn("THREE.Triangle: .normal() has been renamed to .getNormal()."),this.getNormal(e)},Triangle.prototype.plane=function(e){return console.warn("THREE.Triangle: .plane() has been renamed to .getPlane()."),this.getPlane(e)},Triangle.barycoordFromPoint=function(e,t,n,r,i){return console.warn("THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord()."),Triangle.getBarycoord(e,t,n,r,i)},Triangle.normal=function(e,t,n,r){return console.warn("THREE.Triangle: .normal() has been renamed to .getNormal()."),Triangle.getNormal(e,t,n,r)},Shape.prototype.extractAllPoints=function(e){return console.warn("THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead."),this.extractPoints(e)},Shape.prototype.extrude=function(e){return console.warn("THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead."),new ExtrudeGeometry(this,e)},Shape.prototype.makeGeometry=function(e){return console.warn("THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead."),new ShapeGeometry(this,e)},Vector2.prototype.fromAttribute=function(e,t,n){return console.warn("THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute()."),this.fromBufferAttribute(e,t,n)},Vector2.prototype.distanceToManhattan=function(e){return console.warn("THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo()."),this.manhattanDistanceTo(e)},Vector2.prototype.lengthManhattan=function(){return console.warn("THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength()."),this.manhattanLength()},Vector3.prototype.setEulerFromRotationMatrix=function(){console.error("THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.")},Vector3.prototype.setEulerFromQuaternion=function(){console.error("THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.")},Vector3.prototype.getPositionFromMatrix=function(e){return console.warn("THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition()."),this.setFromMatrixPosition(e)},Vector3.prototype.getScaleFromMatrix=function(e){return console.warn("THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale()."),this.setFromMatrixScale(e)},Vector3.prototype.getColumnFromMatrix=function(e,t){return console.warn("THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn()."),this.setFromMatrixColumn(t,e)},Vector3.prototype.applyProjection=function(e){return console.warn("THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead."),this.applyMatrix4(e)},Vector3.prototype.fromAttribute=function(e,t,n){return console.warn("THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute()."),this.fromBufferAttribute(e,t,n)},Vector3.prototype.distanceToManhattan=function(e){return console.warn("THREE.Vector3: .distanceToManhattan() has been renamed to .manhattanDistanceTo()."),this.manhattanDistanceTo(e)},Vector3.prototype.lengthManhattan=function(){return console.warn("THREE.Vector3: .lengthManhattan() has been renamed to .manhattanLength()."),this.manhattanLength()},Vector4.prototype.fromAttribute=function(e,t,n){return console.warn("THREE.Vector4: .fromAttribute() has been renamed to .fromBufferAttribute()."),this.fromBufferAttribute(e,t,n)},Vector4.prototype.lengthManhattan=function(){return console.warn("THREE.Vector4: .lengthManhattan() has been renamed to .manhattanLength()."),this.manhattanLength()},Object3D.prototype.getChildByName=function(e){return console.warn("THREE.Object3D: .getChildByName() has been renamed to .getObjectByName()."),this.getObjectByName(e)},Object3D.prototype.renderDepth=function(){console.warn("THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.")},Object3D.prototype.translate=function(e,t){return console.warn("THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead."),this.translateOnAxis(t,e)},Object3D.prototype.getWorldRotation=function(){console.error("THREE.Object3D: .getWorldRotation() has been removed. Use THREE.Object3D.getWorldQuaternion( target ) instead.")},Object3D.prototype.applyMatrix=function(e){return console.warn("THREE.Object3D: .applyMatrix() has been renamed to .applyMatrix4()."),this.applyMatrix4(e)},Object.defineProperties(Object3D.prototype,{eulerOrder:{get:function(){return console.warn("THREE.Object3D: .eulerOrder is now .rotation.order."),this.rotation.order},set:function(e){console.warn("THREE.Object3D: .eulerOrder is now .rotation.order."),this.rotation.order=e}},useQuaternion:{get:function(){console.warn("THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.")},set:function(){console.warn("THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.")}}}),Mesh.prototype.setDrawMode=function(){console.error("THREE.Mesh: .setDrawMode() has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.")},Object.defineProperties(Mesh.prototype,{drawMode:{get:function(){return console.error("THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode."),0},set:function(){console.error("THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.")}}}),Object.defineProperties(LOD.prototype,{objects:{get:function(){return console.warn("THREE.LOD: .objects has been renamed to .levels."),this.levels}}}),Object.defineProperty(Skeleton.prototype,"useVertexTexture",{get:function(){console.warn("THREE.Skeleton: useVertexTexture has been removed.")},set:function(){console.warn("THREE.Skeleton: useVertexTexture has been removed.")}}),SkinnedMesh.prototype.initBones=function(){console.error("THREE.SkinnedMesh: initBones() has been removed.")},Object.defineProperty(Curve.prototype,"__arcLengthDivisions",{get:function(){return console.warn("THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions."),this.arcLengthDivisions},set:function(e){console.warn("THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions."),this.arcLengthDivisions=e}}),PerspectiveCamera.prototype.setLens=function(e,t){console.warn("THREE.PerspectiveCamera.setLens is deprecated. Use .setFocalLength and .filmGauge for a photographic setup."),void 0!==t&&(this.filmGauge=t),this.setFocalLength(e)},Object.defineProperties(Light.prototype,{onlyShadow:{set:function(){console.warn("THREE.Light: .onlyShadow has been removed.")}},shadowCameraFov:{set:function(e){console.warn("THREE.Light: .shadowCameraFov is now .shadow.camera.fov."),this.shadow.camera.fov=e}},shadowCameraLeft:{set:function(e){console.warn("THREE.Light: .shadowCameraLeft is now .shadow.camera.left."),this.shadow.camera.left=e}},shadowCameraRight:{set:function(e){console.warn("THREE.Light: .shadowCameraRight is now .shadow.camera.right."),this.shadow.camera.right=e}},shadowCameraTop:{set:function(e){console.warn("THREE.Light: .shadowCameraTop is now .shadow.camera.top."),this.shadow.camera.top=e}},shadowCameraBottom:{set:function(e){console.warn("THREE.Light: .shadowCameraBottom is now .shadow.camera.bottom."),this.shadow.camera.bottom=e}},shadowCameraNear:{set:function(e){console.warn("THREE.Light: .shadowCameraNear is now .shadow.camera.near."),this.shadow.camera.near=e}},shadowCameraFar:{set:function(e){console.warn("THREE.Light: .shadowCameraFar is now .shadow.camera.far."),this.shadow.camera.far=e}},shadowCameraVisible:{set:function(){console.warn("THREE.Light: .shadowCameraVisible has been removed. Use new THREE.CameraHelper( light.shadow.camera ) instead.")}},shadowBias:{set:function(e){console.warn("THREE.Light: .shadowBias is now .shadow.bias."),this.shadow.bias=e}},shadowDarkness:{set:function(){console.warn("THREE.Light: .shadowDarkness has been removed.")}},shadowMapWidth:{set:function(e){console.warn("THREE.Light: .shadowMapWidth is now .shadow.mapSize.width."),this.shadow.mapSize.width=e}},shadowMapHeight:{set:function(e){console.warn("THREE.Light: .shadowMapHeight is now .shadow.mapSize.height."),this.shadow.mapSize.height=e}}}),Object.defineProperties(BufferAttribute.prototype,{length:{get:function(){return console.warn("THREE.BufferAttribute: .length has been deprecated. Use .count instead."),this.array.length}},dynamic:{get:function(){return console.warn("THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead."),35048===this.usage},set:function(){console.warn("THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead."),this.setUsage(35048)}}}),BufferAttribute.prototype.setDynamic=function(e){return console.warn("THREE.BufferAttribute: .setDynamic() has been deprecated. Use .setUsage() instead."),this.setUsage(!0===e?35048:35044),this},BufferAttribute.prototype.copyIndicesArray=function(){console.error("THREE.BufferAttribute: .copyIndicesArray() has been removed.")},BufferAttribute.prototype.setArray=function(){console.error("THREE.BufferAttribute: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers")},BufferGeometry.prototype.addIndex=function(e){console.warn("THREE.BufferGeometry: .addIndex() has been renamed to .setIndex()."),this.setIndex(e)},BufferGeometry.prototype.addAttribute=function(e,t){return console.warn("THREE.BufferGeometry: .addAttribute() has been renamed to .setAttribute()."),t&&t.isBufferAttribute||t&&t.isInterleavedBufferAttribute?"index"===e?(console.warn("THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute."),this.setIndex(t),this):this.setAttribute(e,t):(console.warn("THREE.BufferGeometry: .addAttribute() now expects ( name, attribute )."),this.setAttribute(e,new BufferAttribute(arguments[1],arguments[2])))},BufferGeometry.prototype.addDrawCall=function(e,t,n){void 0!==n&&console.warn("THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset."),console.warn("THREE.BufferGeometry: .addDrawCall() is now .addGroup()."),this.addGroup(e,t)},BufferGeometry.prototype.clearDrawCalls=function(){console.warn("THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups()."),this.clearGroups()},BufferGeometry.prototype.computeOffsets=function(){console.warn("THREE.BufferGeometry: .computeOffsets() has been removed.")},BufferGeometry.prototype.removeAttribute=function(e){return console.warn("THREE.BufferGeometry: .removeAttribute() has been renamed to .deleteAttribute()."),this.deleteAttribute(e)},BufferGeometry.prototype.applyMatrix=function(e){return console.warn("THREE.BufferGeometry: .applyMatrix() has been renamed to .applyMatrix4()."),this.applyMatrix4(e)},Object.defineProperties(BufferGeometry.prototype,{drawcalls:{get:function(){return console.error("THREE.BufferGeometry: .drawcalls has been renamed to .groups."),this.groups}},offsets:{get:function(){return console.warn("THREE.BufferGeometry: .offsets has been renamed to .groups."),this.groups}}}),Object.defineProperties(InstancedBufferGeometry.prototype,{maxInstancedCount:{get:function(){return console.warn("THREE.InstancedBufferGeometry: .maxInstancedCount has been renamed to .instanceCount."),this.instanceCount},set:function(e){console.warn("THREE.InstancedBufferGeometry: .maxInstancedCount has been renamed to .instanceCount."),this.instanceCount=e}}}),Object.defineProperties(Raycaster.prototype,{linePrecision:{get:function(){return console.warn("THREE.Raycaster: .linePrecision has been deprecated. Use .params.Line.threshold instead."),this.params.Line.threshold},set:function(e){console.warn("THREE.Raycaster: .linePrecision has been deprecated. Use .params.Line.threshold instead."),this.params.Line.threshold=e}}}),Object.defineProperties(InterleavedBuffer.prototype,{dynamic:{get:function(){return console.warn("THREE.InterleavedBuffer: .length has been deprecated. Use .usage instead."),35048===this.usage},set:function(e){console.warn("THREE.InterleavedBuffer: .length has been deprecated. Use .usage instead."),this.setUsage(e)}}}),InterleavedBuffer.prototype.setDynamic=function(e){return console.warn("THREE.InterleavedBuffer: .setDynamic() has been deprecated. Use .setUsage() instead."),this.setUsage(!0===e?35048:35044),this},InterleavedBuffer.prototype.setArray=function(){console.error("THREE.InterleavedBuffer: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers")},ExtrudeGeometry.prototype.getArrays=function(){console.error("THREE.ExtrudeGeometry: .getArrays() has been removed.")},ExtrudeGeometry.prototype.addShapeList=function(){console.error("THREE.ExtrudeGeometry: .addShapeList() has been removed.")},ExtrudeGeometry.prototype.addShape=function(){console.error("THREE.ExtrudeGeometry: .addShape() has been removed.")},Scene.prototype.dispose=function(){console.error("THREE.Scene: .dispose() has been removed.")},Object.defineProperties(Uniform.prototype,{dynamic:{set:function(){console.warn("THREE.Uniform: .dynamic has been removed. Use object.onBeforeRender() instead.")}},onUpdate:{value:function(){return console.warn("THREE.Uniform: .onUpdate() has been removed. Use object.onBeforeRender() instead."),this}}}),Object.defineProperties(Material.prototype,{wrapAround:{get:function(){console.warn("THREE.Material: .wrapAround has been removed.")},set:function(){console.warn("THREE.Material: .wrapAround has been removed.")}},overdraw:{get:function(){console.warn("THREE.Material: .overdraw has been removed.")},set:function(){console.warn("THREE.Material: .overdraw has been removed.")}},wrapRGB:{get:function(){return console.warn("THREE.Material: .wrapRGB has been removed."),new Color}},shading:{get:function(){console.error("THREE."+this.type+": .shading has been removed. Use the boolean .flatShading instead.")},set:function(e){console.warn("THREE."+this.type+": .shading has been removed. Use the boolean .flatShading instead."),this.flatShading=1===e}},stencilMask:{get:function(){return console.warn("THREE."+this.type+": .stencilMask has been removed. Use .stencilFuncMask instead."),this.stencilFuncMask},set:function(e){console.warn("THREE."+this.type+": .stencilMask has been removed. Use .stencilFuncMask instead."),this.stencilFuncMask=e}}}),Object.defineProperties(MeshPhongMaterial.prototype,{metal:{get:function(){return console.warn("THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead."),!1},set:function(){console.warn("THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead")}}}),Object.defineProperties(MeshPhysicalMaterial.prototype,{transparency:{get:function(){return console.warn("THREE.MeshPhysicalMaterial: .transparency has been renamed to .transmission."),this.transmission},set:function(e){console.warn("THREE.MeshPhysicalMaterial: .transparency has been renamed to .transmission."),this.transmission=e}}}),Object.defineProperties(ShaderMaterial.prototype,{derivatives:{get:function(){return console.warn("THREE.ShaderMaterial: .derivatives has been moved to .extensions.derivatives."),this.extensions.derivatives},set:function(e){console.warn("THREE. ShaderMaterial: .derivatives has been moved to .extensions.derivatives."),this.extensions.derivatives=e}}}),WebGLRenderer.prototype.clearTarget=function(e,t,n,r){console.warn("THREE.WebGLRenderer: .clearTarget() has been deprecated. Use .setRenderTarget() and .clear() instead."),this.setRenderTarget(e),this.clear(t,n,r)},WebGLRenderer.prototype.animate=function(e){console.warn("THREE.WebGLRenderer: .animate() is now .setAnimationLoop()."),this.setAnimationLoop(e)},WebGLRenderer.prototype.getCurrentRenderTarget=function(){return console.warn("THREE.WebGLRenderer: .getCurrentRenderTarget() is now .getRenderTarget()."),this.getRenderTarget()},WebGLRenderer.prototype.getMaxAnisotropy=function(){return console.warn("THREE.WebGLRenderer: .getMaxAnisotropy() is now .capabilities.getMaxAnisotropy()."),this.capabilities.getMaxAnisotropy()},WebGLRenderer.prototype.getPrecision=function(){return console.warn("THREE.WebGLRenderer: .getPrecision() is now .capabilities.precision."),this.capabilities.precision},WebGLRenderer.prototype.resetGLState=function(){return console.warn("THREE.WebGLRenderer: .resetGLState() is now .state.reset()."),this.state.reset()},WebGLRenderer.prototype.supportsFloatTextures=function(){return console.warn("THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( 'OES_texture_float' )."),this.extensions.get("OES_texture_float")},WebGLRenderer.prototype.supportsHalfFloatTextures=function(){return console.warn("THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( 'OES_texture_half_float' )."),this.extensions.get("OES_texture_half_float")},WebGLRenderer.prototype.supportsStandardDerivatives=function(){return console.warn("THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( 'OES_standard_derivatives' )."),this.extensions.get("OES_standard_derivatives")},WebGLRenderer.prototype.supportsCompressedTextureS3TC=function(){return console.warn("THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( 'WEBGL_compressed_texture_s3tc' )."),this.extensions.get("WEBGL_compressed_texture_s3tc")},WebGLRenderer.prototype.supportsCompressedTexturePVRTC=function(){return console.warn("THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( 'WEBGL_compressed_texture_pvrtc' )."),this.extensions.get("WEBGL_compressed_texture_pvrtc")},WebGLRenderer.prototype.supportsBlendMinMax=function(){return console.warn("THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( 'EXT_blend_minmax' )."),this.extensions.get("EXT_blend_minmax")},WebGLRenderer.prototype.supportsVertexTextures=function(){return console.warn("THREE.WebGLRenderer: .supportsVertexTextures() is now .capabilities.vertexTextures."),this.capabilities.vertexTextures},WebGLRenderer.prototype.supportsInstancedArrays=function(){return console.warn("THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( 'ANGLE_instanced_arrays' )."),this.extensions.get("ANGLE_instanced_arrays")},WebGLRenderer.prototype.enableScissorTest=function(e){console.warn("THREE.WebGLRenderer: .enableScissorTest() is now .setScissorTest()."),this.setScissorTest(e)},WebGLRenderer.prototype.initMaterial=function(){console.warn("THREE.WebGLRenderer: .initMaterial() has been removed.")},WebGLRenderer.prototype.addPrePlugin=function(){console.warn("THREE.WebGLRenderer: .addPrePlugin() has been removed.")},WebGLRenderer.prototype.addPostPlugin=function(){console.warn("THREE.WebGLRenderer: .addPostPlugin() has been removed.")},WebGLRenderer.prototype.updateShadowMap=function(){console.warn("THREE.WebGLRenderer: .updateShadowMap() has been removed.")},WebGLRenderer.prototype.setFaceCulling=function(){console.warn("THREE.WebGLRenderer: .setFaceCulling() has been removed.")},WebGLRenderer.prototype.allocTextureUnit=function(){console.warn("THREE.WebGLRenderer: .allocTextureUnit() has been removed.")},WebGLRenderer.prototype.setTexture=function(){console.warn("THREE.WebGLRenderer: .setTexture() has been removed.")},WebGLRenderer.prototype.setTexture2D=function(){console.warn("THREE.WebGLRenderer: .setTexture2D() has been removed.")},WebGLRenderer.prototype.setTextureCube=function(){console.warn("THREE.WebGLRenderer: .setTextureCube() has been removed.")},WebGLRenderer.prototype.getActiveMipMapLevel=function(){return console.warn("THREE.WebGLRenderer: .getActiveMipMapLevel() is now .getActiveMipmapLevel()."),this.getActiveMipmapLevel()},Object.defineProperties(WebGLRenderer.prototype,{shadowMapEnabled:{get:function(){return this.shadowMap.enabled},set:function(e){console.warn("THREE.WebGLRenderer: .shadowMapEnabled is now .shadowMap.enabled."),this.shadowMap.enabled=e}},shadowMapType:{get:function(){return this.shadowMap.type},set:function(e){console.warn("THREE.WebGLRenderer: .shadowMapType is now .shadowMap.type."),this.shadowMap.type=e}},shadowMapCullFace:{get:function(){console.warn("THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.")},set:function(){console.warn("THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.")}},context:{get:function(){return console.warn("THREE.WebGLRenderer: .context has been removed. Use .getContext() instead."),this.getContext()}},vr:{get:function(){return console.warn("THREE.WebGLRenderer: .vr has been renamed to .xr"),this.xr}},gammaInput:{get:function(){return console.warn("THREE.WebGLRenderer: .gammaInput has been removed. Set the encoding for textures via Texture.encoding instead."),!1},set:function(){console.warn("THREE.WebGLRenderer: .gammaInput has been removed. Set the encoding for textures via Texture.encoding instead.")}},gammaOutput:{get:function(){return console.warn("THREE.WebGLRenderer: .gammaOutput has been removed. Set WebGLRenderer.outputEncoding instead."),!1},set:function(e){console.warn("THREE.WebGLRenderer: .gammaOutput has been removed. Set WebGLRenderer.outputEncoding instead."),this.outputEncoding=!0===e?3001:3e3}},toneMappingWhitePoint:{get:function(){return console.warn("THREE.WebGLRenderer: .toneMappingWhitePoint has been removed."),1},set:function(){console.warn("THREE.WebGLRenderer: .toneMappingWhitePoint has been removed.")}}}),Object.defineProperties(WebGLShadowMap.prototype,{cullFace:{get:function(){console.warn("THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.")},set:function(){console.warn("THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.")}},renderReverseSided:{get:function(){console.warn("THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.")},set:function(){console.warn("THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.")}},renderSingleSided:{get:function(){console.warn("THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.")},set:function(){console.warn("THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.")}}}),Object.defineProperties(WebGLRenderTarget.prototype,{wrapS:{get:function(){return console.warn("THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS."),this.texture.wrapS},set:function(e){console.warn("THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS."),this.texture.wrapS=e}},wrapT:{get:function(){return console.warn("THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT."),this.texture.wrapT},set:function(e){console.warn("THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT."),this.texture.wrapT=e}},magFilter:{get:function(){return console.warn("THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter."),this.texture.magFilter},set:function(e){console.warn("THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter."),this.texture.magFilter=e}},minFilter:{get:function(){return console.warn("THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter."),this.texture.minFilter},set:function(e){console.warn("THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter."),this.texture.minFilter=e}},anisotropy:{get:function(){return console.warn("THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy."),this.texture.anisotropy},set:function(e){console.warn("THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy."),this.texture.anisotropy=e}},offset:{get:function(){return console.warn("THREE.WebGLRenderTarget: .offset is now .texture.offset."),this.texture.offset},set:function(e){console.warn("THREE.WebGLRenderTarget: .offset is now .texture.offset."),this.texture.offset=e}},repeat:{get:function(){return console.warn("THREE.WebGLRenderTarget: .repeat is now .texture.repeat."),this.texture.repeat},set:function(e){console.warn("THREE.WebGLRenderTarget: .repeat is now .texture.repeat."),this.texture.repeat=e}},format:{get:function(){return console.warn("THREE.WebGLRenderTarget: .format is now .texture.format."),this.texture.format},set:function(e){console.warn("THREE.WebGLRenderTarget: .format is now .texture.format."),this.texture.format=e}},type:{get:function(){return console.warn("THREE.WebGLRenderTarget: .type is now .texture.type."),this.texture.type},set:function(e){console.warn("THREE.WebGLRenderTarget: .type is now .texture.type."),this.texture.type=e}},generateMipmaps:{get:function(){return console.warn("THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps."),this.texture.generateMipmaps},set:function(e){console.warn("THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps."),this.texture.generateMipmaps=e}}}),Object.defineProperties(Audio.prototype,{load:{value:function(e){console.warn("THREE.Audio: .load has been deprecated. Use THREE.AudioLoader instead.");const t=this;return(new AudioLoader).load(e,(function(e){t.setBuffer(e)})),this}},startTime:{set:function(){console.warn("THREE.Audio: .startTime is now .play( delay ).")}}}),AudioAnalyser.prototype.getData=function(){return console.warn("THREE.AudioAnalyser: .getData() is now .getFrequencyData()."),this.getFrequencyData()},CubeCamera.prototype.updateCubeMap=function(e,t){return console.warn("THREE.CubeCamera: .updateCubeMap() is now .update()."),this.update(e,t)},CubeCamera.prototype.clear=function(e,t,n,r){return console.warn("THREE.CubeCamera: .clear() is now .renderTarget.clear()."),this.renderTarget.clear(e,t,n,r)},ImageUtils.crossOrigin=void 0,ImageUtils.loadTexture=function(e,t,n,r){console.warn("THREE.ImageUtils.loadTexture has been deprecated. Use THREE.TextureLoader() instead.");const i=new TextureLoader;i.setCrossOrigin(this.crossOrigin);const a=i.load(e,n,void 0,r);return t&&(a.mapping=t),a},ImageUtils.loadTextureCube=function(e,t,n,r){console.warn("THREE.ImageUtils.loadTextureCube has been deprecated. Use THREE.CubeTextureLoader() instead.");const i=new CubeTextureLoader;i.setCrossOrigin(this.crossOrigin);const a=i.load(e,n,void 0,r);return t&&(a.mapping=t),a},ImageUtils.loadCompressedTexture=function(){console.error("THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.")},ImageUtils.loadCompressedTextureCube=function(){console.error("THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.")};const SceneUtils={createMultiMaterialObject:function(){console.error("THREE.SceneUtils has been moved to /examples/jsm/utils/SceneUtils.js")},detach:function(){console.error("THREE.SceneUtils has been moved to /examples/jsm/utils/SceneUtils.js")},attach:function(){console.error("THREE.SceneUtils has been moved to /examples/jsm/utils/SceneUtils.js")}};function LensFlare(){console.error("THREE.LensFlare has been moved to /examples/jsm/objects/Lensflare.js")}"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("register",{detail:{revision:"126"}})),"undefined"!=typeof window&&(window.__THREE__?console.warn("WARNING: Multiple instances of Three.js being imported."):window.__THREE__="126");export{ACESFilmicToneMapping,AddEquation,AddOperation,AdditiveAnimationBlendMode,AdditiveBlending,AlphaFormat,AlwaysDepth,AlwaysStencilFunc,AmbientLight,AmbientLightProbe,AnimationClip,AnimationLoader,AnimationMixer,AnimationObjectGroup,AnimationUtils,ArcCurve,ArrayCamera,ArrowHelper,Audio,AudioAnalyser,AudioContext,AudioListener,AudioLoader,AxesHelper,AxisHelper,BackSide,BasicDepthPacking,BasicShadowMap,BinaryTextureLoader,Bone,BooleanKeyframeTrack,BoundingBoxHelper,Box2,Box3,Box3Helper,BoxGeometry as BoxBufferGeometry,BoxGeometry,BoxHelper,BufferAttribute,BufferGeometry,BufferGeometryLoader,ByteType,Cache,Camera,CameraHelper,CanvasRenderer,CanvasTexture,CatmullRomCurve3,CineonToneMapping,CircleGeometry as CircleBufferGeometry,CircleGeometry,ClampToEdgeWrapping,Clock,Color,ColorKeyframeTrack,CompressedTexture,CompressedTextureLoader,ConeGeometry as ConeBufferGeometry,ConeGeometry,CubeCamera,CubeReflectionMapping,CubeRefractionMapping,CubeTexture,CubeTextureLoader,CubeUVReflectionMapping,CubeUVRefractionMapping,CubicBezierCurve,CubicBezierCurve3,CubicInterpolant,CullFaceBack,CullFaceFront,CullFaceFrontBack,CullFaceNone,Curve,CurvePath,CustomBlending,CustomToneMapping,CylinderGeometry as CylinderBufferGeometry,CylinderGeometry,Cylindrical,DataTexture,DataTexture2DArray,DataTexture3D,DataTextureLoader,DataUtils,DecrementStencilOp,DecrementWrapStencilOp,DefaultLoadingManager,DepthFormat,DepthStencilFormat,DepthTexture,DirectionalLight,DirectionalLightHelper,DiscreteInterpolant,DodecahedronGeometry as DodecahedronBufferGeometry,DodecahedronGeometry,DoubleSide,DstAlphaFactor,DstColorFactor,DynamicBufferAttribute,DynamicCopyUsage,DynamicDrawUsage,DynamicReadUsage,EdgesGeometry,EdgesHelper,EllipseCurve,EqualDepth,EqualStencilFunc,EquirectangularReflectionMapping,EquirectangularRefractionMapping,Euler,EventDispatcher,ExtrudeGeometry as ExtrudeBufferGeometry,ExtrudeGeometry,FaceColors,FileLoader,FlatShading,Float16BufferAttribute,Float32Attribute,Float32BufferAttribute,Float64Attribute,Float64BufferAttribute,FloatType,Fog,FogExp2,Font,FontLoader,FrontSide,Frustum,GLBufferAttribute,GLSL1,GLSL3,GammaEncoding,GreaterDepth,GreaterEqualDepth,GreaterEqualStencilFunc,GreaterStencilFunc,GridHelper,Group,HalfFloatType,HemisphereLight,HemisphereLightHelper,HemisphereLightProbe,IcosahedronGeometry as IcosahedronBufferGeometry,IcosahedronGeometry,ImageBitmapLoader,ImageLoader,ImageUtils,ImmediateRenderObject,IncrementStencilOp,IncrementWrapStencilOp,InstancedBufferAttribute,InstancedBufferGeometry,InstancedInterleavedBuffer,InstancedMesh,Int16Attribute,Int16BufferAttribute,Int32Attribute,Int32BufferAttribute,Int8Attribute,Int8BufferAttribute,IntType,InterleavedBuffer,InterleavedBufferAttribute,Interpolant,InterpolateDiscrete,InterpolateLinear,InterpolateSmooth,InvertStencilOp,JSONLoader,KeepStencilOp,KeyframeTrack,LOD,LatheGeometry as LatheBufferGeometry,LatheGeometry,Layers,LensFlare,LessDepth,LessEqualDepth,LessEqualStencilFunc,LessStencilFunc,Light,LightProbe,Line,Line3,LineBasicMaterial,LineCurve,LineCurve3,LineDashedMaterial,LineLoop,LinePieces,LineSegments,LineStrip,LinearEncoding,LinearFilter,LinearInterpolant,LinearMipMapLinearFilter,LinearMipMapNearestFilter,LinearMipmapLinearFilter,LinearMipmapNearestFilter,LinearToneMapping,Loader,LoaderUtils,LoadingManager,LogLuvEncoding,LoopOnce,LoopPingPong,LoopRepeat,LuminanceAlphaFormat,LuminanceFormat,MOUSE,Material,MaterialLoader,MathUtils as Math,MathUtils,Matrix3,Matrix4,MaxEquation,Mesh,MeshBasicMaterial,MeshDepthMaterial,MeshDistanceMaterial,MeshFaceMaterial,MeshLambertMaterial,MeshMatcapMaterial,MeshNormalMaterial,MeshPhongMaterial,MeshPhysicalMaterial,MeshStandardMaterial,MeshToonMaterial,MinEquation,MirroredRepeatWrapping,MixOperation,MultiMaterial,MultiplyBlending,MultiplyOperation,NearestFilter,NearestMipMapLinearFilter,NearestMipMapNearestFilter,NearestMipmapLinearFilter,NearestMipmapNearestFilter,NeverDepth,NeverStencilFunc,NoBlending,NoColors,NoToneMapping,NormalAnimationBlendMode,NormalBlending,NotEqualDepth,NotEqualStencilFunc,NumberKeyframeTrack,Object3D,ObjectLoader,ObjectSpaceNormalMap,OctahedronGeometry as OctahedronBufferGeometry,OctahedronGeometry,OneFactor,OneMinusDstAlphaFactor,OneMinusDstColorFactor,OneMinusSrcAlphaFactor,OneMinusSrcColorFactor,OrthographicCamera,PCFShadowMap,PCFSoftShadowMap,PMREMGenerator,ParametricGeometry as ParametricBufferGeometry,ParametricGeometry,Particle,ParticleBasicMaterial,ParticleSystem,ParticleSystemMaterial,Path,PerspectiveCamera,Plane,PlaneGeometry as PlaneBufferGeometry,PlaneGeometry,PlaneHelper,PointCloud,PointCloudMaterial,PointLight,PointLightHelper,Points,PointsMaterial,PolarGridHelper,PolyhedronGeometry as PolyhedronBufferGeometry,PolyhedronGeometry,PositionalAudio,PropertyBinding,PropertyMixer,QuadraticBezierCurve,QuadraticBezierCurve3,Quaternion,QuaternionKeyframeTrack,QuaternionLinearInterpolant,REVISION,RGBADepthPacking,RGBAFormat,RGBAIntegerFormat,RGBA_ASTC_10x10_Format,RGBA_ASTC_10x5_Format,RGBA_ASTC_10x6_Format,RGBA_ASTC_10x8_Format,RGBA_ASTC_12x10_Format,RGBA_ASTC_12x12_Format,RGBA_ASTC_4x4_Format,RGBA_ASTC_5x4_Format,RGBA_ASTC_5x5_Format,RGBA_ASTC_6x5_Format,RGBA_ASTC_6x6_Format,RGBA_ASTC_8x5_Format,RGBA_ASTC_8x6_Format,RGBA_ASTC_8x8_Format,RGBA_BPTC_Format,RGBA_ETC2_EAC_Format,RGBA_PVRTC_2BPPV1_Format,RGBA_PVRTC_4BPPV1_Format,RGBA_S3TC_DXT1_Format,RGBA_S3TC_DXT3_Format,RGBA_S3TC_DXT5_Format,RGBDEncoding,RGBEEncoding,RGBEFormat,RGBFormat,RGBIntegerFormat,RGBM16Encoding,RGBM7Encoding,RGB_ETC1_Format,RGB_ETC2_Format,RGB_PVRTC_2BPPV1_Format,RGB_PVRTC_4BPPV1_Format,RGB_S3TC_DXT1_Format,RGFormat,RGIntegerFormat,RawShaderMaterial,Ray,Raycaster,RectAreaLight,RedFormat,RedIntegerFormat,ReinhardToneMapping,RepeatWrapping,ReplaceStencilOp,ReverseSubtractEquation,RingGeometry as RingBufferGeometry,RingGeometry,SRGB8_ALPHA8_ASTC_10x10_Format,SRGB8_ALPHA8_ASTC_10x5_Format,SRGB8_ALPHA8_ASTC_10x6_Format,SRGB8_ALPHA8_ASTC_10x8_Format,SRGB8_ALPHA8_ASTC_12x10_Format,SRGB8_ALPHA8_ASTC_12x12_Format,SRGB8_ALPHA8_ASTC_4x4_Format,SRGB8_ALPHA8_ASTC_5x4_Format,SRGB8_ALPHA8_ASTC_5x5_Format,SRGB8_ALPHA8_ASTC_6x5_Format,SRGB8_ALPHA8_ASTC_6x6_Format,SRGB8_ALPHA8_ASTC_8x5_Format,SRGB8_ALPHA8_ASTC_8x6_Format,SRGB8_ALPHA8_ASTC_8x8_Format,Scene,SceneUtils,ShaderChunk,ShaderLib,ShaderMaterial,ShadowMaterial,Shape,ShapeGeometry as ShapeBufferGeometry,ShapeGeometry,ShapePath,ShapeUtils,ShortType,Skeleton,SkeletonHelper,SkinnedMesh,SmoothShading,Sphere,SphereGeometry as SphereBufferGeometry,SphereGeometry,Spherical,SphericalHarmonics3,SplineCurve,SpotLight,SpotLightHelper,Sprite,SpriteMaterial,SrcAlphaFactor,SrcAlphaSaturateFactor,SrcColorFactor,StaticCopyUsage,StaticDrawUsage,StaticReadUsage,StereoCamera,StreamCopyUsage,StreamDrawUsage,StreamReadUsage,StringKeyframeTrack,SubtractEquation,SubtractiveBlending,TOUCH,TangentSpaceNormalMap,TetrahedronGeometry as TetrahedronBufferGeometry,TetrahedronGeometry,TextGeometry as TextBufferGeometry,TextGeometry,Texture,TextureLoader,TorusGeometry as TorusBufferGeometry,TorusGeometry,TorusKnotGeometry as TorusKnotBufferGeometry,TorusKnotGeometry,Triangle,TriangleFanDrawMode,TriangleStripDrawMode,TrianglesDrawMode,TubeGeometry as TubeBufferGeometry,TubeGeometry,UVMapping,Uint16Attribute,Uint16BufferAttribute,Uint32Attribute,Uint32BufferAttribute,Uint8Attribute,Uint8BufferAttribute,Uint8ClampedAttribute,Uint8ClampedBufferAttribute,Uniform,UniformsLib,UniformsUtils,UnsignedByteType,UnsignedInt248Type,UnsignedIntType,UnsignedShort4444Type,UnsignedShort5551Type,UnsignedShort565Type,UnsignedShortType,VSMShadowMap,Vector2,Vector3,Vector4,VectorKeyframeTrack,Vertex,VertexColors,VideoTexture,WebGL1Renderer,WebGLCubeRenderTarget,WebGLMultisampleRenderTarget,WebGLRenderTarget,WebGLRenderTargetCube,WebGLRenderer,WebGLUtils,WireframeGeometry,WireframeHelper,WrapAroundEnding,XHRLoader,ZeroCurvatureEnding,ZeroFactor,ZeroSlopeEnding,ZeroStencilOp,sRGBEncoding}; +//# sourceMappingURL=/sm/2dc86ba69a1151ce9189734833e15730f4156be669c383721b928da886b73937.map \ No newline at end of file diff --git a/templates/webgl/viewer.js b/templates/webgl/viewer.js new file mode 100644 index 00000000..333e0be3 --- /dev/null +++ b/templates/webgl/viewer.js @@ -0,0 +1,2085 @@ +const statusEl = document.getElementById("status"); +const canvas = document.getElementById("scene-canvas"); + +function setStatus(message, level = "warn") { + if (!statusEl) return; + statusEl.textContent = message; + statusEl.className = `status ${level}`; +} + +function rowMajorToMatrix4(THREE, m) { + const mat = new THREE.Matrix4(); + mat.set( + m[0], m[1], m[2], m[3], + m[4], m[5], m[6], m[7], + m[8], m[9], m[10], m[11], + m[12], m[13], m[14], m[15], + ); + return mat; +} + +function pandaRowMajorToMatrix4(THREE, m) { + const mat = new THREE.Matrix4(); + // Panda's matrix data uses row-vector convention (translation on last row). + // Three.js expects column-vector convention (translation on last column). + mat.set( + m[0], m[4], m[8], m[12], + m[1], m[5], m[9], m[13], + m[2], m[6], m[10], m[14], + m[3], m[7], m[11], m[15], + ); + return mat; +} + +function convertNodeMatrix(THREE, sourceMatRowMajor, basis, basisInv, matrixConvention = "panda_row_vector_row_major") { + const src = matrixConvention === "panda_row_vector_row_major" + ? pandaRowMajorToMatrix4(THREE, sourceMatRowMajor) + : rowMajorToMatrix4(THREE, sourceMatRowMajor); + return basis.clone().multiply(src).multiply(basisInv); +} + +function toColorArray(color, fallback = [1, 1, 1]) { + if (!Array.isArray(color) || color.length < 3) return fallback; + return [Number(color[0]) || 0, Number(color[1]) || 0, Number(color[2]) || 0]; +} + +function applyMaterialOverride(THREE, root, override) { + if (!override) return; + + root.traverse((obj) => { + if (!obj.isMesh || !obj.material) return; + + const list = Array.isArray(obj.material) ? obj.material : [obj.material]; + for (const mat of list) { + if (mat.color && Array.isArray(override.base_color)) { + const [r, g, b] = override.base_color; + mat.color.setRGB(r ?? 1, g ?? 1, b ?? 1); + } + if (Object.prototype.hasOwnProperty.call(override, "roughness") && "roughness" in mat) { + mat.roughness = Number(override.roughness); + } + if (Object.prototype.hasOwnProperty.call(override, "metallic") && "metalness" in mat) { + mat.metalness = Number(override.metallic); + } + if (Object.prototype.hasOwnProperty.call(override, "opacity")) { + const opacity = THREE.MathUtils.clamp(Number(override.opacity), 0, 1); + const isTransparent = opacity < 0.999; + mat.opacity = opacity; + mat.transparent = isTransparent; + // Prevent "see-through solid mesh" when source GLTF had transparent pipeline state. + mat.depthWrite = !isTransparent; + mat.depthTest = true; + mat.blending = isTransparent ? THREE.NormalBlending : THREE.NoBlending; + if (!isTransparent && "alphaTest" in mat) { + mat.alphaTest = 0; + } + if (!isTransparent && "transmission" in mat) { + mat.transmission = 0; + } + } + mat.needsUpdate = true; + } + }); +} + +function textureSlotByStage(stageName) { + const key = String(stageName || "").toLowerCase(); + if (key.includes("normal")) return "normalMap"; + if (key.includes("rough")) return "roughnessMap"; + if (key.includes("metal")) return "metalnessMap"; + if (key.includes("emission") || key.includes("emissive")) return "emissiveMap"; + if (key.includes("ao")) return "aoMap"; + if (key.includes("alpha") || key.includes("opacity")) return "alphaMap"; + return "map"; +} + +function applyTextureOverrides(THREE, root, textureOverrides, textureLoader) { + if (!Array.isArray(textureOverrides) || textureOverrides.length === 0) return; + + const texBySlot = new Map(); + for (const item of textureOverrides) { + if (!item || !item.uri) continue; + const slot = textureSlotByStage(item.stage); + if (texBySlot.has(slot)) continue; + + try { + const tex = textureLoader.load(item.uri); + tex.flipY = false; + texBySlot.set(slot, tex); + } catch (err) { + console.warn("Texture load failed:", item.uri, err); + } + } + + if (texBySlot.size === 0) return; + + root.traverse((obj) => { + if (!obj.isMesh || !obj.material) return; + + const list = Array.isArray(obj.material) ? obj.material : [obj.material]; + for (const mat of list) { + for (const [slot, tex] of texBySlot.entries()) { + if (slot in mat) { + mat[slot] = tex; + } + } + mat.needsUpdate = true; + } + }); +} + +function pathKey(parts) { + if (!Array.isArray(parts)) return ""; + return parts.map((v) => String(v)).join("/"); +} + +function normalizeNamePath(parts) { + if (!Array.isArray(parts)) return []; + const out = []; + for (const raw of parts) { + const s = String(raw ?? "").trim(); + if (!s) continue; + if (out.length > 0 && out[out.length - 1] === s) continue; + out.push(s); + } + return out; +} + +function stripOccurrenceSuffix(segment) { + return String(segment ?? "").replace(/#\d+$/, ""); +} + +function normalizeNamePathLoose(parts) { + return normalizeNamePath(parts).map((s) => stripOccurrenceSuffix(s)); +} + +function stripBlenderDuplicateSuffix(segment) { + return String(segment ?? "").replace(/\.\d{3}$/, ""); +} + +function normalizeNamePathVeryLoose(parts) { + return normalizeNamePathLoose(parts).map((s) => stripBlenderDuplicateSuffix(s)); +} + +function canonicalizeNameSegment(segment) { + const noOcc = stripOccurrenceSuffix(segment).toLowerCase(); + return noOcc.replace(/[^a-z0-9]+/g, ""); +} + +function normalizeNamePathCanonical(parts) { + return normalizeNamePath(parts) + .map((s) => canonicalizeNameSegment(s)) + .filter((s) => !!s); +} + +function matrixSignatureRowMajor(values, digits = 5) { + if (!Array.isArray(values) || values.length !== 16) return ""; + const out = []; + for (let i = 0; i < 16; i += 1) { + const num = Number(values[i]); + if (!Number.isFinite(num)) return ""; + out.push(num.toFixed(digits)); + } + return out.join(","); +} + +function isSuffixPath(shorter, longer) { + if (!Array.isArray(shorter) || !Array.isArray(longer)) return false; + if (shorter.length > longer.length) return false; + const offset = longer.length - shorter.length; + for (let i = 0; i < shorter.length; i += 1) { + if (shorter[i] !== longer[offset + i]) return false; + } + return true; +} + +function suffixMatchScore(hintParts, candidateParts) { + if (!Array.isArray(hintParts) || !Array.isArray(candidateParts)) return 0; + let i = hintParts.length - 1; + let j = candidateParts.length - 1; + let score = 0; + while (i >= 0 && j >= 0) { + if (String(hintParts[i]) !== String(candidateParts[j])) break; + score += 1; + i -= 1; + j -= 1; + } + return score; +} + +function isIdentityRowMajorMatrix(m, eps = 1e-6) { + if (!Array.isArray(m) || m.length !== 16) return false; + const identity = [ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + ]; + for (let i = 0; i < 16; i += 1) { + if (Math.abs(Number(m[i]) - identity[i]) > eps) return false; + } + return true; +} + +function applySubnodeOverrides(THREE, root, overrides, basis, basisInv, matrixConvention) { + if (!root || !overrides || typeof overrides !== "object") return 0; + const preferName = String(overrides.source || "").toLowerCase() === "ssbo"; + const matrixSpace = String(overrides.matrix_space || (preferName ? "model_root" : "local_parent")).toLowerCase(); + const modelRootSpace = matrixSpace === "model_root"; + + const byIndex = new Map(); + const byName = new Map(); + const byNameNormalized = new Map(); + const byNameLoose = new Map(); + const byNameVeryLoose = new Map(); + const byNameCanonical = new Map(); + const overrideTerminalLooseCount = new Map(); + const overrideTerminalLooseFirst = new Map(); + const overrideTerminalLooseUnique = new Map(); + const overrideTerminalCanonicalCount = new Map(); + const overrideTerminalCanonicalFirst = new Map(); + const overrideTerminalCanonicalUnique = new Map(); + const tempParentInv = new THREE.Matrix4(); + const rootWorldInv = new THREE.Matrix4(); + const tempModelRoot = new THREE.Matrix4(); + const matrixMatchEps = 0.25; + const matrixTieEps = 1e-5; + const maxDepthDiffForMatrixFallback = 3; + const depthHintsByMatrixSig = new Map(); + const terminalHintsByMatrixSig = new Map(); // very loose terminal hints + const strictTerminalHintsByMatrixSig = new Map(); // keep .001 + const canonicalTerminalHintsByMatrixSig = new Map(); // punctuation-insensitive + keep digits + const strictPathHintsByMatrixSig = new Map(); // strip only #occurrence + const veryLoosePathHintsByMatrixSig = new Map(); // strip #occurrence + .001 + const canonicalPathHintsByMatrixSig = new Map(); // punctuation-insensitive + keep digits + + const makeOverrideEntry = (item, path) => { + if (!item || !Array.isArray(path) || !Array.isArray(item.matrix_local_row_major)) return null; + if (item.matrix_local_row_major.length !== 16) return null; + const matrixSig = matrixSignatureRowMajor(item.matrix_local_row_major); + const baseMatrix = Array.isArray(item.base_matrix_model_root_row_major) && item.base_matrix_model_root_row_major.length === 16 + ? item.base_matrix_model_root_row_major + : null; + const depth = Array.isArray(path) ? path.length : 0; + return { + path, + matrix: item.matrix_local_row_major, + baseMatrix, + matrixSig, + expectedDepth: depth, + depthHints: depth > 0 ? [depth] : [], + terminalHints: [], + terminalHintsStrict: [], + terminalHintsCanonical: [], + pathHintsStrict: [], + pathHintsVeryLoose: [], + pathHintsCanonical: [], + }; + }; + + for (const item of Array.isArray(overrides.by_index) ? overrides.by_index : []) { + const entry = makeOverrideEntry(item, Array.isArray(item?.path) ? item.path : null); + if (!entry) continue; + byIndex.set(pathKey(entry.path), entry); + } + + for (const item of Array.isArray(overrides.by_name) ? overrides.by_name : []) { + const entry = makeOverrideEntry(item, Array.isArray(item?.path) ? item.path : null); + if (!entry) continue; + + const rawKey = pathKey(entry.path); + byName.set(rawKey, entry); + const normParts = normalizeNamePath(item.path); + const normKey = pathKey(normParts); + if (normKey && !byNameNormalized.has(normKey)) { + byNameNormalized.set(normKey, entry); + } + const looseParts = normalizeNamePathLoose(item.path); + const looseKey = pathKey(looseParts); + if (looseKey && !byNameLoose.has(looseKey)) { + byNameLoose.set(looseKey, entry); + } + const veryLooseParts = normalizeNamePathVeryLoose(item.path); + const veryLooseKey = pathKey(veryLooseParts); + if (veryLooseKey && !byNameVeryLoose.has(veryLooseKey)) { + byNameVeryLoose.set(veryLooseKey, entry); + } + const canonicalParts = normalizeNamePathCanonical(item.path); + const canonicalKey = pathKey(canonicalParts); + if (canonicalKey && !byNameCanonical.has(canonicalKey)) { + byNameCanonical.set(canonicalKey, entry); + } + const terminalLoose = looseParts.length > 0 ? looseParts[looseParts.length - 1] : ""; + const terminalStrict = looseParts.length > 0 ? looseParts[looseParts.length - 1] : ""; + const terminalCanonical = canonicalParts.length > 0 ? canonicalParts[canonicalParts.length - 1] : ""; + if (terminalLoose) { + overrideTerminalLooseCount.set( + terminalLoose, + (overrideTerminalLooseCount.get(terminalLoose) ?? 0) + 1, + ); + if (!overrideTerminalLooseFirst.has(terminalLoose)) { + overrideTerminalLooseFirst.set(terminalLoose, entry); + } + } + if (terminalCanonical) { + overrideTerminalCanonicalCount.set( + terminalCanonical, + (overrideTerminalCanonicalCount.get(terminalCanonical) ?? 0) + 1, + ); + if (!overrideTerminalCanonicalFirst.has(terminalCanonical)) { + overrideTerminalCanonicalFirst.set(terminalCanonical, entry); + } + } + + const depth = Array.isArray(item.path) ? item.path.length : 0; + if (entry.matrixSig && depth > 0) { + const depthList = depthHintsByMatrixSig.get(entry.matrixSig) || []; + depthList.push(depth); + depthHintsByMatrixSig.set(entry.matrixSig, depthList); + } + const terminalVeryLoose = veryLooseParts.length > 0 ? veryLooseParts[veryLooseParts.length - 1] : ""; + if (entry.matrixSig && terminalVeryLoose) { + const termSet = terminalHintsByMatrixSig.get(entry.matrixSig) || new Set(); + termSet.add(terminalVeryLoose); + terminalHintsByMatrixSig.set(entry.matrixSig, termSet); + } + if (entry.matrixSig && terminalStrict) { + const termSetStrict = strictTerminalHintsByMatrixSig.get(entry.matrixSig) || new Set(); + termSetStrict.add(terminalStrict); + strictTerminalHintsByMatrixSig.set(entry.matrixSig, termSetStrict); + } + if (entry.matrixSig && terminalCanonical) { + const termSetCanonical = canonicalTerminalHintsByMatrixSig.get(entry.matrixSig) || new Set(); + termSetCanonical.add(terminalCanonical); + canonicalTerminalHintsByMatrixSig.set(entry.matrixSig, termSetCanonical); + } + if (entry.matrixSig && looseParts.length > 0) { + const strictPaths = strictPathHintsByMatrixSig.get(entry.matrixSig) || []; + strictPaths.push(looseParts); + strictPathHintsByMatrixSig.set(entry.matrixSig, strictPaths); + } + if (entry.matrixSig && veryLooseParts.length > 0) { + const veryLoosePaths = veryLoosePathHintsByMatrixSig.get(entry.matrixSig) || []; + veryLoosePaths.push(veryLooseParts); + veryLoosePathHintsByMatrixSig.set(entry.matrixSig, veryLoosePaths); + } + if (entry.matrixSig && canonicalParts.length > 0) { + const canonicalPaths = canonicalPathHintsByMatrixSig.get(entry.matrixSig) || []; + canonicalPaths.push(canonicalParts); + canonicalPathHintsByMatrixSig.set(entry.matrixSig, canonicalPaths); + } + } + + for (const entry of byIndex.values()) { + if (!entry) continue; + if (entry.matrixSig && depthHintsByMatrixSig.has(entry.matrixSig)) { + const extra = depthHintsByMatrixSig.get(entry.matrixSig) || []; + entry.depthHints = Array.from(new Set([...(entry.depthHints || []), ...extra])); + if (entry.depthHints.length > 0) { + entry.expectedDepth = Math.min(...entry.depthHints); + } + } + if (entry.matrixSig && terminalHintsByMatrixSig.has(entry.matrixSig)) { + entry.terminalHints = Array.from(terminalHintsByMatrixSig.get(entry.matrixSig) || []); + } + if (entry.matrixSig && strictTerminalHintsByMatrixSig.has(entry.matrixSig)) { + entry.terminalHintsStrict = Array.from(strictTerminalHintsByMatrixSig.get(entry.matrixSig) || []); + } + if (entry.matrixSig && canonicalTerminalHintsByMatrixSig.has(entry.matrixSig)) { + entry.terminalHintsCanonical = Array.from(canonicalTerminalHintsByMatrixSig.get(entry.matrixSig) || []); + } + if (entry.matrixSig && strictPathHintsByMatrixSig.has(entry.matrixSig)) { + entry.pathHintsStrict = strictPathHintsByMatrixSig.get(entry.matrixSig) || []; + } + if (entry.matrixSig && veryLoosePathHintsByMatrixSig.has(entry.matrixSig)) { + entry.pathHintsVeryLoose = veryLoosePathHintsByMatrixSig.get(entry.matrixSig) || []; + } + if (entry.matrixSig && canonicalPathHintsByMatrixSig.has(entry.matrixSig)) { + entry.pathHintsCanonical = canonicalPathHintsByMatrixSig.get(entry.matrixSig) || []; + } + } + + for (const [name, count] of overrideTerminalLooseCount.entries()) { + if (count === 1 && overrideTerminalLooseFirst.has(name)) { + overrideTerminalLooseUnique.set(name, overrideTerminalLooseFirst.get(name)); + } + } + for (const [name, count] of overrideTerminalCanonicalCount.entries()) { + if (count === 1 && overrideTerminalCanonicalFirst.has(name)) { + overrideTerminalCanonicalUnique.set(name, overrideTerminalCanonicalFirst.get(name)); + } + } + + const byNameEntries = Array.from(byName.keys()).map((k) => { + const rawParts = k ? k.split("/") : []; + const looseParts = normalizeNamePathLoose(rawParts); + const veryLooseParts = normalizeNamePathVeryLoose(rawParts); + const canonicalParts = normalizeNamePathCanonical(rawParts); + return { + rawParts, + normParts: normalizeNamePath(rawParts), + looseParts, + veryLooseParts, + canonicalParts, + entry: byName.get(k), + }; + }); + + if (byIndex.size === 0 && byName.size === 0) return 0; + + const nodeEntries = []; + const nodeTerminalLooseCount = new Map(); + const nodeTerminalCanonicalCount = new Map(); + const collectNodes = (node, indexPath, namePath) => { + if (indexPath.length > 0) { + const normalizedNameParts = normalizeNamePath(namePath); + const looseNameParts = normalizeNamePathLoose(namePath); + const veryLooseNameParts = normalizeNamePathVeryLoose(namePath); + const canonicalNameParts = normalizeNamePathCanonical(namePath); + const terminalLoose = looseNameParts.length > 0 ? looseNameParts[looseNameParts.length - 1] : ""; + const terminalCanonical = canonicalNameParts.length > 0 ? canonicalNameParts[canonicalNameParts.length - 1] : ""; + if (terminalLoose) { + nodeTerminalLooseCount.set( + terminalLoose, + (nodeTerminalLooseCount.get(terminalLoose) ?? 0) + 1, + ); + } + if (terminalCanonical) { + nodeTerminalCanonicalCount.set( + terminalCanonical, + (nodeTerminalCanonicalCount.get(terminalCanonical) ?? 0) + 1, + ); + } + nodeEntries.push({ + node, + indexPath, + namePath, + normalizedNameParts, + looseNameParts, + veryLooseNameParts, + canonicalNameParts, + terminalLoose, + terminalCanonical, + modelRootMatrix: null, + }); + } + + const nameCount = new Map(); + for (let i = 0; i < node.children.length; i += 1) { + const child = node.children[i]; + const childName = String(child?.name ?? ""); + const occur = nameCount.get(childName) ?? 0; + nameCount.set(childName, occur + 1); + collectNodes( + child, + indexPath.concat(i), + namePath.concat(`${childName}#${occur}`), + ); + } + }; + collectNodes(root, [], []); + + // Ensure parent.matrixWorld is valid before resolving model-root-space overrides. + root.updateMatrixWorld(true); + rootWorldInv.copy(root.matrixWorld).invert(); + for (const nodeEntry of nodeEntries) { + nodeEntry.modelRootMatrix = tempModelRoot.copy(rootWorldInv).multiply(nodeEntry.node.matrixWorld).clone(); + } + + let applied = 0; + let matrixFallbackApplied = 0; + const appliedDebug = []; + const usedOverrideEntries = new Set(); + const usedOverrideSigs = new Set(); + const usedNodes = new Set(); + + const applyOverrideToNode = (node, overrideEntry, nodeEntry = null) => { + if (!overrideEntry || !Array.isArray(overrideEntry.matrix) || overrideEntry.matrix.length !== 16) return ""; + if (isIdentityRowMajorMatrix(overrideEntry.matrix)) return ""; + + const convertedMat = convertNodeMatrix( + THREE, + overrideEntry.matrix, + basis, + basisInv, + matrixConvention, + ); + + // Some Panda paths include an extra Geom wrapper level (same-name duplicate), + // but GLTFLoader may collapse it. In this case absolute target matrix can map to + // a slightly different runtime node. Use model-space delta replay to avoid jumps. + const overrideDepth = Array.isArray(overrideEntry.path) ? overrideEntry.path.length : 0; + const runtimeDepth = Array.isArray(nodeEntry?.namePath) ? nodeEntry.namePath.length : 0; + const collapsedWrapperDepth = overrideDepth > runtimeDepth; + let targetNode = node; + let targetNodeModel = nodeEntry?.modelRootMatrix + ? nodeEntry.modelRootMatrix.clone() + : tempModelRoot.copy(rootWorldInv).multiply(node.matrixWorld).clone(); + + if (collapsedWrapperDepth && Array.isArray(overrideEntry.path) && overrideEntry.path.length > 0) { + const expectedTail = canonicalizeNameSegment(overrideEntry.path[overrideEntry.path.length - 1]); + const sameNameChildren = (Array.isArray(node.children) ? node.children : []).filter( + (c) => canonicalizeNameSegment(c?.name ?? "") === expectedTail, + ); + if (sameNameChildren.length === 1) { + targetNode = sameNameChildren[0]; + targetNode.updateMatrixWorld(true); + targetNodeModel = tempModelRoot.copy(rootWorldInv).multiply(targetNode.matrixWorld).clone(); + } + } + if ( + modelRootSpace + && collapsedWrapperDepth + && Array.isArray(overrideEntry.baseMatrix) + && overrideEntry.baseMatrix.length === 16 + ) { + const baseConverted = convertNodeMatrix( + THREE, + overrideEntry.baseMatrix, + basis, + basisInv, + matrixConvention, + ); + + const deltaModel = convertedMat.clone().multiply(baseConverted.clone().invert()); + if ( + targetNode + && targetNode.isSkinnedMesh + && targetNode.skeleton + && Array.isArray(targetNode.skeleton.bones) + && targetNode.skeleton.bones.length > 0 + ) { + const bones = targetNode.skeleton.bones; + const boneSet = new Set(bones); + const rootBones = bones.filter((b) => !b.parent || !boneSet.has(b.parent)); + if (rootBones.length > 0) { + const tx = targetNodeModel.elements[12]; + const ty = targetNodeModel.elements[13]; + const tz = targetNodeModel.elements[14]; + let bestRoot = rootBones[0]; + let bestDist = Number.POSITIVE_INFINITY; + for (const b of rootBones) { + const bModel = tempModelRoot.copy(rootWorldInv).multiply(b.matrixWorld); + const dx = Number(bModel.elements[12]) - Number(tx); + const dy = Number(bModel.elements[13]) - Number(ty); + const dz = Number(bModel.elements[14]) - Number(tz); + const dist = (dx * dx) + (dy * dy) + (dz * dz); + if (dist < bestDist) { + bestDist = dist; + bestRoot = b; + } + } + + const bestRootModel = tempModelRoot.copy(rootWorldInv).multiply(bestRoot.matrixWorld).clone(); + const rootTargetModel = deltaModel.clone().multiply(bestRootModel); + let rootLocal = rootTargetModel; + if (bestRoot.parent) { + tempParentInv.copy(bestRoot.parent.matrixWorld).invert(); + rootLocal = tempParentInv.multiply(rootTargetModel); + } + bestRoot.matrixAutoUpdate = false; + bestRoot.matrix.copy(rootLocal); + bestRoot.matrix.decompose(bestRoot.position, bestRoot.quaternion, bestRoot.scale); + bestRoot.updateMatrixWorld(true); + return "skeleton_delta"; + } + } + + const targetModel = deltaModel.multiply(targetNodeModel); + let localDeltaMat = targetModel; + if (targetNode.parent) { + tempParentInv.copy(targetNode.parent.matrixWorld).invert(); + localDeltaMat = tempParentInv.multiply(targetModel); + } + targetNode.matrixAutoUpdate = false; + targetNode.matrix.copy(localDeltaMat); + targetNode.matrix.decompose(targetNode.position, targetNode.quaternion, targetNode.scale); + targetNode.updateMatrixWorld(false); + if (nodeEntry && targetNode === node) { + nodeEntry.modelRootMatrix = targetModel.clone(); + } + return targetNode === node ? "matrix_delta" : "matrix_delta_child"; + } + + let localMat = convertedMat; + + // SSBO edits are exported in model-root space (same as Panda's obj_np.getMat(model_root)). + if (modelRootSpace && targetNode.parent) { + tempParentInv.copy(targetNode.parent.matrixWorld).invert(); + localMat = tempParentInv.multiply(convertedMat); + } + + targetNode.matrixAutoUpdate = false; + targetNode.matrix.copy(localMat); + targetNode.matrix.decompose(targetNode.position, targetNode.quaternion, targetNode.scale); + targetNode.updateMatrixWorld(false); + return targetNode === node ? "matrix" : "matrix_child"; + }; + + const matrixMaxAbsDiff = (a, b) => { + if (!a || !b) return Number.POSITIVE_INFINITY; + const ae = a.elements; + const be = b.elements; + let diff = 0; + for (let i = 0; i < 16; i += 1) { + const d = Math.abs(Number(ae[i]) - Number(be[i])); + if (d > diff) diff = d; + } + return diff; + }; + + for (const entry of nodeEntries) { + const { + node, + indexPath, + namePath, + normalizedNameParts, + looseNameParts, + veryLooseNameParts, + canonicalNameParts, + terminalLoose, + terminalCanonical, + } = entry; + const indexKey = pathKey(indexPath); + const nameKey = pathKey(namePath); + const normalizedNameKey = pathKey(normalizedNameParts); + const looseNameKey = pathKey(looseNameParts); + const veryLooseNameKey = pathKey(veryLooseNameParts); + const canonicalNameKey = pathKey(canonicalNameParts); + + let matchedOverrideEntry = preferName + ? ( + byName.get(nameKey) + || byNameNormalized.get(normalizedNameKey) + || byNameLoose.get(looseNameKey) + || byNameVeryLoose.get(veryLooseNameKey) + || byNameCanonical.get(canonicalNameKey) + || byIndex.get(indexKey) + ) + : ( + byIndex.get(indexKey) + || byName.get(nameKey) + || byNameNormalized.get(normalizedNameKey) + || byNameLoose.get(looseNameKey) + || byNameVeryLoose.get(veryLooseNameKey) + || byNameCanonical.get(canonicalNameKey) + ); + + // Fallback A: tolerate wrappers when occurrence indices still match. + if (!matchedOverrideEntry && normalizedNameParts.length > 0) { + for (const info of byNameEntries) { + if (!info || !info.entry) continue; + if (isSuffixPath(info.normParts, normalizedNameParts) || isSuffixPath(normalizedNameParts, info.normParts)) { + matchedOverrideEntry = info.entry; + break; + } + } + } + + // Fallback B: ignore occurrence suffix like "#0" when matching. + if (!matchedOverrideEntry && looseNameParts.length > 0) { + for (const info of byNameEntries) { + if (!info || !info.entry) continue; + if (isSuffixPath(info.looseParts, looseNameParts) || isSuffixPath(looseNameParts, info.looseParts)) { + matchedOverrideEntry = info.entry; + break; + } + } + } + + // Fallback C1: ignore both '#occurrence' and Blender '.001/.002' suffixes. + if (!matchedOverrideEntry && veryLooseNameParts.length > 0) { + for (const info of byNameEntries) { + if (!info || !info.entry) continue; + if (isSuffixPath(info.veryLooseParts, veryLooseNameParts) || isSuffixPath(veryLooseNameParts, info.veryLooseParts)) { + matchedOverrideEntry = info.entry; + break; + } + } + } + + // Fallback C2: punctuation-insensitive path matching while keeping numeric tails. + if (!matchedOverrideEntry && canonicalNameParts.length > 0) { + for (const info of byNameEntries) { + if (!info || !info.entry) continue; + if (isSuffixPath(info.canonicalParts, canonicalNameParts) || isSuffixPath(canonicalNameParts, info.canonicalParts)) { + matchedOverrideEntry = info.entry; + break; + } + } + } + + // Fallback C: unique terminal-name match (only when both sides are unique). + if (!matchedOverrideEntry && terminalLoose) { + const nodeTerminalCount = nodeTerminalLooseCount.get(terminalLoose) ?? 0; + if (nodeTerminalCount === 1 && overrideTerminalLooseUnique.has(terminalLoose)) { + matchedOverrideEntry = overrideTerminalLooseUnique.get(terminalLoose); + } + } + + // Fallback C3: punctuation-insensitive unique terminal match. + if (!matchedOverrideEntry && terminalCanonical) { + const nodeTerminalCount = nodeTerminalCanonicalCount.get(terminalCanonical) ?? 0; + if (nodeTerminalCount === 1 && overrideTerminalCanonicalUnique.has(terminalCanonical)) { + matchedOverrideEntry = overrideTerminalCanonicalUnique.get(terminalCanonical); + } + } + + const applyMode = applyOverrideToNode(node, matchedOverrideEntry, entry); + if (applyMode) { + usedOverrideEntries.add(matchedOverrideEntry); + if (matchedOverrideEntry.matrixSig) usedOverrideSigs.add(matchedOverrideEntry.matrixSig); + const indexEntry = byIndex.get(indexKey); + if (indexEntry) { + usedOverrideEntries.add(indexEntry); + if (indexEntry.matrixSig) usedOverrideSigs.add(indexEntry.matrixSig); + } + usedNodes.add(node); + applied += 1; + if (appliedDebug.length < 8) { + appliedDebug.push({ + runtimePath: pathKey(namePath), + runtimeIndex: pathKey(indexPath), + overridePath: Array.isArray(matchedOverrideEntry.path) ? pathKey(matchedOverrideEntry.path) : "", + via: applyMode, + }); + } + } + } + + // Fallback D: matrix-based matching for SSBO when names/index do not align. + const unmatchedIndexEntries = Array.from(byIndex.values()).filter( + (v) => ( + !usedOverrideEntries.has(v) + && !(v.matrixSig && usedOverrideSigs.has(v.matrixSig)) + && Array.isArray(v.baseMatrix) + && v.baseMatrix.length === 16 + ), + ); + for (const overrideEntry of unmatchedIndexEntries) { + const baseConverted = convertNodeMatrix( + THREE, + overrideEntry.baseMatrix, + basis, + basisInv, + matrixConvention, + ); + + let best = null; + let bestErr = Number.POSITIVE_INFINITY; + let bestDepthErr = Number.POSITIVE_INFINITY; + let tieCount = 0; + let tieCandidates = []; + const depthHints = Array.isArray(overrideEntry.depthHints) && overrideEntry.depthHints.length > 0 + ? overrideEntry.depthHints + : [Number(overrideEntry.expectedDepth ?? 0)]; + for (const nodeEntry of nodeEntries) { + if (!nodeEntry || !nodeEntry.node || usedNodes.has(nodeEntry.node)) continue; + const nodeDepth = Number(nodeEntry.indexPath?.length ?? 0); + let depthErr = Number.POSITIVE_INFINITY; + for (const hint of depthHints) { + const v = Math.abs(nodeDepth - Number(hint)); + if (v < depthErr) depthErr = v; + } + if (depthErr > maxDepthDiffForMatrixFallback) continue; + + const err = matrixMaxAbsDiff(nodeEntry.modelRootMatrix, baseConverted); + const better = + depthErr < bestDepthErr || + (depthErr === bestDepthErr && err < bestErr - matrixTieEps); + const tie = + depthErr === bestDepthErr && + Math.abs(err - bestErr) <= matrixTieEps; + + if (better) { + bestErr = err; + bestDepthErr = depthErr; + best = nodeEntry; + tieCount = 1; + tieCandidates = [nodeEntry]; + } else if (tie) { + tieCount += 1; + tieCandidates.push(nodeEntry); + } + } + + if (best && tieCount > 1) { + const strictPathHints = Array.isArray(overrideEntry.pathHintsStrict) ? overrideEntry.pathHintsStrict : []; + const veryLoosePathHints = Array.isArray(overrideEntry.pathHintsVeryLoose) ? overrideEntry.pathHintsVeryLoose : []; + const canonicalPathHints = Array.isArray(overrideEntry.pathHintsCanonical) ? overrideEntry.pathHintsCanonical : []; + const termHintsStrict = Array.isArray(overrideEntry.terminalHintsStrict) ? overrideEntry.terminalHintsStrict : []; + const termHints = Array.isArray(overrideEntry.terminalHints) ? overrideEntry.terminalHints : []; + const termHintsCanonical = Array.isArray(overrideEntry.terminalHintsCanonical) ? overrideEntry.terminalHintsCanonical : []; + const ranked = tieCandidates.map((nodeEntry) => { + const strictTail = Array.isArray(nodeEntry.looseNameParts) && nodeEntry.looseNameParts.length > 0 + ? nodeEntry.looseNameParts[nodeEntry.looseNameParts.length - 1] + : ""; + const veryLooseTail = Array.isArray(nodeEntry.veryLooseNameParts) && nodeEntry.veryLooseNameParts.length > 0 + ? nodeEntry.veryLooseNameParts[nodeEntry.veryLooseNameParts.length - 1] + : ""; + const canonicalTail = Array.isArray(nodeEntry.canonicalNameParts) && nodeEntry.canonicalNameParts.length > 0 + ? nodeEntry.canonicalNameParts[nodeEntry.canonicalNameParts.length - 1] + : ""; + let strictPathScore = 0; + for (const hint of strictPathHints) { + strictPathScore = Math.max(strictPathScore, suffixMatchScore(hint, nodeEntry.looseNameParts || [])); + } + let veryLoosePathScore = 0; + for (const hint of veryLoosePathHints) { + veryLoosePathScore = Math.max(veryLoosePathScore, suffixMatchScore(hint, nodeEntry.veryLooseNameParts || [])); + } + let canonicalPathScore = 0; + for (const hint of canonicalPathHints) { + canonicalPathScore = Math.max(canonicalPathScore, suffixMatchScore(hint, nodeEntry.canonicalNameParts || [])); + } + const strictTailHit = (strictTail && termHintsStrict.includes(strictTail)) ? 1 : 0; + const veryLooseTailHit = (veryLooseTail && termHints.includes(veryLooseTail)) ? 1 : 0; + const canonicalTailHit = (canonicalTail && termHintsCanonical.includes(canonicalTail)) ? 1 : 0; + return { + nodeEntry, + strictPathScore, + canonicalPathScore, + veryLoosePathScore, + strictTailHit, + canonicalTailHit, + veryLooseTailHit, + }; + }); + + ranked.sort((a, b) => { + if (b.strictPathScore !== a.strictPathScore) return b.strictPathScore - a.strictPathScore; + if (b.strictTailHit !== a.strictTailHit) return b.strictTailHit - a.strictTailHit; + if (b.canonicalPathScore !== a.canonicalPathScore) return b.canonicalPathScore - a.canonicalPathScore; + if (b.canonicalTailHit !== a.canonicalTailHit) return b.canonicalTailHit - a.canonicalTailHit; + if (b.veryLoosePathScore !== a.veryLoosePathScore) return b.veryLoosePathScore - a.veryLoosePathScore; + if (b.veryLooseTailHit !== a.veryLooseTailHit) return b.veryLooseTailHit - a.veryLooseTailHit; + return 0; + }); + + if (ranked.length > 0) { + const top = ranked[0]; + const second = ranked.length > 1 ? ranked[1] : null; + const topTuple = [ + top.strictPathScore, + top.strictTailHit, + top.canonicalPathScore, + top.canonicalTailHit, + top.veryLoosePathScore, + top.veryLooseTailHit, + ].join("|"); + const secondTuple = second + ? [ + second.strictPathScore, + second.strictTailHit, + second.canonicalPathScore, + second.canonicalTailHit, + second.veryLoosePathScore, + second.veryLooseTailHit, + ].join("|") + : ""; + const topHasSignal = ( + top.strictPathScore > 0 + || top.strictTailHit > 0 + || top.canonicalPathScore > 0 + || top.canonicalTailHit > 0 + || top.veryLoosePathScore > 0 + || top.veryLooseTailHit > 0 + ); + const uniqueTop = !second || topTuple !== secondTuple; + if (topHasSignal && uniqueTop) { + best = top.nodeEntry; + tieCount = 1; + } + } + } + + if (best && tieCount > 1) { + console.warn("[WebGLPack] Matrix fallback ambiguous, skip override:", { + path: overrideEntry.path, + depthHints, + bestDepthErr, + bestErr, + tieCount, + }); + continue; + } + + const fallbackMode = (best && bestErr <= matrixMatchEps) + ? applyOverrideToNode(best.node, overrideEntry, best) + : ""; + if (fallbackMode) { + usedOverrideEntries.add(overrideEntry); + if (overrideEntry.matrixSig) usedOverrideSigs.add(overrideEntry.matrixSig); + usedNodes.add(best.node); + applied += 1; + matrixFallbackApplied += 1; + if (appliedDebug.length < 8) { + appliedDebug.push({ + runtimePath: pathKey(best.namePath || []), + runtimeIndex: pathKey(best.indexPath || []), + overridePath: Array.isArray(overrideEntry.path) ? pathKey(overrideEntry.path) : "", + via: `matrix_fallback:${fallbackMode}`, + }); + } + } + } + + const unusedIndex = Array.from(byIndex.entries()) + .filter(([, entry]) => !usedOverrideEntries.has(entry) && !(entry.matrixSig && usedOverrideSigs.has(entry.matrixSig))) + .map(([key]) => key); + const unusedName = Array.from(byName.entries()) + .filter(([, entry]) => !usedOverrideEntries.has(entry) && !(entry.matrixSig && usedOverrideSigs.has(entry.matrixSig))) + .map(([key]) => key); + + if (unusedIndex.length > 0 || unusedName.length > 0) { + console.warn("[WebGLPack] Subnode overrides partially unmatched:", { + rootName: root.name || "(unnamed)", + applied, + matrixFallbackApplied, + totalIndexOverrides: byIndex.size, + totalNameOverrides: byName.size, + unmatchedIndex: unusedIndex.slice(0, 20), + unmatchedName: unusedName.slice(0, 20), + }); + console.info( + "[WebGLPack] Sample runtime node paths:", + nodeEntries.slice(0, 20).map((v) => pathKey(v.namePath)), + ); + } else { + console.info("[WebGLPack] Subnode overrides matched:", { + rootName: root.name || "(unnamed)", + applied, + matrixFallbackApplied, + totalIndexOverrides: byIndex.size, + totalNameOverrides: byName.size, + matrixSpace, + appliedSample: appliedDebug, + }); + } + + return applied; +} + +function directionToThree(THREE, direction, basis) { + const d = new THREE.Vector3( + Number(direction?.[0] ?? 0), + Number(direction?.[1] ?? 0), + Number(direction?.[2] ?? -1), + ); + d.applyMatrix4(basis); + if (d.lengthSq() < 1e-6) d.set(0, 0, -1); + return d.normalize(); +} + +function applyShadowFlags(root, enabled) { + if (!root || !root.traverse) return; + root.traverse((obj) => { + if (obj.isMesh) { + obj.castShadow = !!enabled; + obj.receiveShadow = !!enabled; + } + }); +} + +function normalizeColorInput(color, fallback = [1, 1, 1]) { + const out = toColorArray(color, fallback); + if (out.some((v) => Number(v) > 1.0)) { + return out.map((v) => Number(v) / 255.0); + } + return out; +} + +function mapToneMappingOperator(THREE, operator) { + const op = String(operator || "").trim().toLowerCase(); + if (op === "none") return THREE.NoToneMapping; + if (op === "reinhard") return THREE.ReinhardToneMapping; + if (op === "exponential" || op === "exponential2") return THREE.CineonToneMapping; + // RenderPipeline's optimized/uncharted2 are filmic-like; ACES is the closest built-in curve. + return THREE.ACESFilmicToneMapping; +} + +function clampPositive(value, fallback) { + const v = toFiniteNumber(value, fallback); + return Number.isFinite(v) && v > 0 ? v : fallback; +} + +function applyRenderPipelineApproximation(THREE, renderer, scene, profile) { + const cfg = (profile && typeof profile === "object") ? profile : {}; + const tone = (cfg.tone_mapping && typeof cfg.tone_mapping === "object") ? cfg.tone_mapping : {}; + const shadows = (cfg.shadows && typeof cfg.shadows === "object") ? cfg.shadows : {}; + const fog = (cfg.fog && typeof cfg.fog === "object") ? cfg.fog : {}; + const bloom = (cfg.bloom && typeof cfg.bloom === "object") ? cfg.bloom : {}; + + // Keep lighting calibration closer to the editor defaults. + renderer.physicallyCorrectLights = false; + const useToneMapping = toBoolean(tone.web_use_tone_mapping, false); + if (useToneMapping) { + renderer.toneMapping = mapToneMappingOperator(THREE, tone.operator); + const webExposureBoost = Math.max(0.5, Math.min(3.0, toFiniteNumber(tone.web_exposure_boost, 1.0))); + renderer.toneMappingExposure = clampPositive(tone.exposure_scale, 1.0) * webExposureBoost; + } else { + renderer.toneMapping = THREE.NoToneMapping; + renderer.toneMappingExposure = 1.0; + } + + const shadowEnabled = toBoolean(shadows.enabled, false) && toBoolean(shadows.web_enable, false); + const shadowRes = Math.max(256, Math.min(4096, Math.round(clampPositive(shadows.resolution, 1024)))); + renderer.shadowMap.enabled = shadowEnabled; + renderer.shadowMap.type = toBoolean(shadows.use_pcf, true) ? THREE.PCFSoftShadowMap : THREE.PCFShadowMap; + + let fogApplied = false; + if (toBoolean(fog.enabled, false)) { + const fogColor = normalizeColorInput(fog.color, [0.55, 0.6, 0.7]); + const fogIntensity = Math.max(0.0, toFiniteNumber(fog.intensity, 0)); + const fogRamp = Math.max(1.0, toFiniteNumber(fog.ramp_size, 2000)); + const density = Math.max(0.00001, Math.min(0.2, (fogIntensity / fogRamp) * 0.2)); + scene.fog = new THREE.FogExp2(new THREE.Color(fogColor[0], fogColor[1], fogColor[2]), density); + fogApplied = true; + } else { + scene.fog = null; + } + + return { + shadowEnabled, + shadowResolution: shadowRes, + toneMappingEnabled: useToneMapping, + fogApplied, + bloom: { + enabled: toBoolean(bloom.enabled, false), + strength: Math.max(0.0, toFiniteNumber(bloom.strength, 0)), + vendorAvailable: toBoolean(bloom.vendor_available, false), + mipmaps: Math.max(2, Math.min(10, Math.round(clampPositive(bloom.mipmaps, 6)))), + }, + }; +} + +async function tryCreateBloomComposer(THREE, renderer, scene, camera, bloomConfig) { + const bloomEnabled = toBoolean(bloomConfig?.enabled, false); + const bloomStrength = Math.max(0.0, toFiniteNumber(bloomConfig?.strength, 0)); + const vendorAvailable = toBoolean(bloomConfig?.vendorAvailable, true); + if (!bloomEnabled || bloomStrength <= 1e-4) { + return { composer: null, enabled: false, reason: "disabled" }; + } + if (!vendorAvailable) { + return { composer: null, enabled: false, reason: "vendor_missing" }; + } + + try { + const [{ EffectComposer }, { RenderPass }, { UnrealBloomPass }] = await Promise.all([ + import("../vendor/EffectComposer.js"), + import("../vendor/RenderPass.js"), + import("../vendor/UnrealBloomPass.js"), + ]); + + const composer = new EffectComposer(renderer); + composer.addPass(new RenderPass(scene, camera)); + + const bloomPass = new UnrealBloomPass( + new THREE.Vector2(window.innerWidth, window.innerHeight), + bloomStrength, + 0.35, + 0.9, + ); + composer.addPass(bloomPass); + return { composer, enabled: true, reason: "ok" }; + } catch (err) { + console.warn("[WebGLPack] Bloom pass unavailable, fallback to base render:", err); + return { composer: null, enabled: false, reason: "module_missing" }; + } +} + +function applySkyboxConfig(THREE, scene, env, textureLoader) { + const sky = (env && typeof env.skybox === "object") ? env.skybox : null; + if (!sky || !toBoolean(sky.enabled, false) || !sky.uri) { + return Promise.resolve({ enabled: false, reason: "disabled", update: null }); + } + + const skyType = String(sky.type || "equirectangular").toLowerCase(); + if (skyType !== "equirectangular" && skyType !== "skydome") { + console.warn("[WebGLPack] Unsupported skybox type:", skyType); + return Promise.resolve({ enabled: false, reason: "unsupported_type", update: null }); + } + + return new Promise((resolve) => { + textureLoader.load( + sky.uri, + (tex) => { + if ("colorSpace" in tex) { + tex.colorSpace = THREE.SRGBColorSpace; + } else if ("encoding" in tex) { + tex.encoding = THREE.sRGBEncoding; + } + tex.wrapS = THREE.RepeatWrapping; + tex.wrapT = THREE.ClampToEdgeWrapping; + + if (skyType === "equirectangular") { + tex.mapping = THREE.EquirectangularReflectionMapping; + scene.background = tex; + if (toBoolean(sky.apply_environment, true)) { + scene.environment = tex; + } + resolve({ enabled: true, reason: "ok", update: null }); + return; + } + + // RenderPipeline default sky uses skydome projection (u = atan(x, y), v = z in Panda Z-up). + // In Web viewer (Three Y-up), equivalent direction is: + // u = atan(dir.x, -dir.z), v = dir.y. + const skyRadius = Math.max(1000, Number(sky.radius ?? 20000)); + const clipLowerHemisphere = toBoolean(sky.clip_lower_hemisphere, true); + const lowerColor = toColorArray(sky.lower_hemisphere_color, [0.08, 0.10, 0.13]); + const horizonBlend = THREE.MathUtils.clamp(Number(sky.horizon_blend ?? 0.06), 0.001, 0.35); + const horizonSampleV = THREE.MathUtils.clamp(Number(sky.horizon_sample_v ?? 0.01), 0.0, 0.2); + const lowerTintStrength = THREE.MathUtils.clamp(Number(sky.lower_tint_strength ?? 0.65), 0.0, 1.0); + const skyGeom = new THREE.SphereGeometry(skyRadius, 32, 16); + const skyMat = new THREE.ShaderMaterial({ + uniforms: { + uSkyTex: { value: tex }, + uRotationU: { value: Number(sky.rotation_u ?? 0.0) }, + uClipLowerHemisphere: { value: clipLowerHemisphere ? 1.0 : 0.0 }, + uLowerColor: { value: new THREE.Color(lowerColor[0], lowerColor[1], lowerColor[2]) }, + uHorizonBlend: { value: horizonBlend }, + uHorizonSampleV: { value: horizonSampleV }, + uLowerTintStrength: { value: lowerTintStrength }, + }, + vertexShader: ` + uniform float uRotationU; + varying vec2 vSkyUv; + varying float vDirY; + const float PI = 3.1415926535897932384626433832795; + void main() { + vec3 dir = normalize(position.xyz); + vDirY = dir.y; + float u = (atan(dir.x, -dir.z) + PI) / (2.0 * PI); + vSkyUv = vec2(fract(u + uRotationU), clamp(dir.y, 0.0, 1.0)); + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + uniform sampler2D uSkyTex; + uniform float uClipLowerHemisphere; + uniform vec3 uLowerColor; + uniform float uHorizonBlend; + uniform float uHorizonSampleV; + uniform float uLowerTintStrength; + varying vec2 vSkyUv; + varying float vDirY; + + void main() { + vec3 skyCol = texture2D(uSkyTex, vSkyUv).rgb; + if (uClipLowerHemisphere > 0.5) { + vec3 horizonCol = texture2D(uSkyTex, vec2(vSkyUv.x, uHorizonSampleV)).rgb; + vec3 lowerCol = mix(uLowerColor, horizonCol, uLowerTintStrength); + float t = smoothstep(-uHorizonBlend, uHorizonBlend, vDirY); + vec3 col = mix(lowerCol, skyCol, t); + gl_FragColor = vec4(col, 1.0); + } else { + gl_FragColor = vec4(skyCol, 1.0); + } + } + `, + side: THREE.BackSide, + depthWrite: false, + fog: false, + transparent: false, + }); + skyMat.toneMapped = false; + + const skydome = new THREE.Mesh(skyGeom, skyMat); + skydome.name = "WebGLPackSkydome"; + skydome.matrixAutoUpdate = true; + skydome.frustumCulled = false; + scene.add(skydome); + + if (!toBoolean(sky.apply_environment, false)) { + scene.environment = null; + } + + const update = (camera) => { + if (!camera) return; + skydome.position.copy(camera.position); + }; + resolve({ enabled: true, reason: "ok", update }); + }, + undefined, + (err) => { + console.warn("[WebGLPack] Skybox load failed:", sky.uri, err); + resolve({ enabled: false, reason: "load_failed", update: null }); + }, + ); + }); +} + +function normalizeAnimationName(name) { + return canonicalizeNameSegment(String(name ?? "")); +} + +function pickAnimationClip(clips, requestedName) { + if (!Array.isArray(clips) || clips.length === 0) return null; + const requested = String(requestedName ?? "").trim(); + if (!requested) return clips[0]; + + let clip = clips.find((c) => String(c?.name ?? "") === requested); + if (clip) return clip; + + const reqLower = requested.toLowerCase(); + clip = clips.find((c) => String(c?.name ?? "").toLowerCase() === reqLower); + if (clip) return clip; + + const reqNorm = normalizeAnimationName(requested); + if (reqNorm) { + clip = clips.find((c) => normalizeAnimationName(c?.name ?? "") === reqNorm); + if (clip) return clip; + + clip = clips.find((c) => { + const n = normalizeAnimationName(c?.name ?? ""); + return n && (n.includes(reqNorm) || reqNorm.includes(n)); + }); + if (clip) return clip; + } + + return clips[0]; +} + +function setupModelAnimation(THREE, root, gltf, animationConfig, nodeName) { + const config = animationConfig && typeof animationConfig === "object" ? animationConfig : {}; + if (config.enabled === false) { + return { mixer: null, clipName: "", mode: "stop", started: false }; + } + + const clips = Array.isArray(gltf?.animations) ? gltf.animations : []; + if (clips.length === 0) { + if (config.clip_name || config.mode) { + console.warn("[WebGLPack] Animation requested but no clips found:", nodeName || "(unnamed)"); + } + return { mixer: null, clipName: "", mode: "stop", started: false }; + } + + const mode = String(config.mode || "loop").toLowerCase(); + const clip = pickAnimationClip(clips, config.clip_name); + if (!clip) { + return { mixer: null, clipName: "", mode, started: false }; + } + + const mixer = new THREE.AnimationMixer(root); + const action = mixer.clipAction(clip); + const speedRaw = Number(config.speed ?? 1.0); + const speed = Number.isFinite(speedRaw) && Math.abs(speedRaw) > 1e-4 ? speedRaw : 1.0; + + action.enabled = true; + action.clampWhenFinished = (mode === "play"); + if (mode === "play") { + action.setLoop(THREE.LoopOnce, 1); + } else { + action.setLoop(THREE.LoopRepeat, Infinity); + } + action.setEffectiveTimeScale(speed); + + const shouldPlay = Boolean(config.autoplay ?? (mode !== "stop")); + const startTimeRaw = Number(config.start_time ?? 0); + const duration = Math.max(0.0001, Number(clip.duration || 0.0001)); + const startTime = Number.isFinite(startTimeRaw) ? (startTimeRaw % duration) : 0; + + action.play(); + action.time = startTime; + if (!shouldPlay || mode === "stop" || mode === "pause") { + action.paused = true; + } + + return { + mixer, + clipName: String(clip.name || ""), + mode, + started: shouldPlay && mode !== "stop" && mode !== "pause", + }; +} + +function toFiniteNumber(value, fallback) { + const v = Number(value); + return Number.isFinite(v) ? v : fallback; +} + +function toBoolean(value, fallback = true) { + if (typeof value === "boolean") return value; + if (typeof value === "number") return value !== 0; + if (typeof value === "string") { + const t = value.trim().toLowerCase(); + if (["1", "true", "yes", "on"].includes(t)) return true; + if (["0", "false", "no", "off"].includes(t)) return false; + } + return fallback; +} + +function parseColorVec4(raw, fallback = [1, 1, 1, 1]) { + if (Array.isArray(raw) && raw.length >= 3) { + return [ + toFiniteNumber(raw[0], fallback[0]), + toFiniteNumber(raw[1], fallback[1]), + toFiniteNumber(raw[2], fallback[2]), + toFiniteNumber(raw[3], fallback[3]), + ]; + } + if (raw && typeof raw === "object") { + return [ + toFiniteNumber(raw.r, fallback[0]), + toFiniteNumber(raw.g, fallback[1]), + toFiniteNumber(raw.b, fallback[2]), + toFiniteNumber(raw.a, fallback[3]), + ]; + } + return fallback.slice(); +} + +function normalizeScriptName(name) { + const text = String(name ?? "").trim().replace(/\.py$/i, ""); + return canonicalizeNameSegment(text); +} + +function markObjectTransformDirty(obj) { + if (!obj || !obj.isObject3D) return; + obj.updateMatrix(); + obj.updateMatrixWorld(false); +} + +function applyPandaAxisDeltaToThreeLocal(obj, axis, delta) { + const ax = String(axis || "x").toLowerCase(); + if (ax === "x") { + obj.position.x += delta; + } else if (ax === "y") { + obj.position.z -= delta; + } else { + obj.position.y += delta; + } +} + +function ensureUniqueMaterialsForObject(root) { + if (!root || !root.traverse) return; + root.traverse((obj) => { + if (!obj.isMesh || !obj.material) return; + if (Array.isArray(obj.material)) { + obj.material = obj.material.map((mat) => { + if (!mat) return mat; + if (mat.userData?.__webglPackUniqueClone) return mat; + const cloned = mat.clone(); + cloned.userData = cloned.userData || {}; + cloned.userData.__webglPackUniqueClone = true; + return cloned; + }); + return; + } + const mat = obj.material; + if (!mat.userData?.__webglPackUniqueClone) { + const cloned = mat.clone(); + cloned.userData = cloned.userData || {}; + cloned.userData.__webglPackUniqueClone = true; + obj.material = cloned; + } + }); +} + +function applyColorToObject(root, rgba) { + if (!root || !root.traverse) return; + const r = toFiniteNumber(rgba?.[0], 1); + const g = toFiniteNumber(rgba?.[1], 1); + const b = toFiniteNumber(rgba?.[2], 1); + const a = Math.min(1, Math.max(0, toFiniteNumber(rgba?.[3], 1))); + + root.traverse((obj) => { + if (!obj.isMesh || !obj.material) return; + const mats = Array.isArray(obj.material) ? obj.material : [obj.material]; + for (const mat of mats) { + if (!mat) continue; + if (mat.color) { + mat.color.setRGB(r, g, b); + } + if ("opacity" in mat) { + mat.opacity = a; + const transparent = a < 0.999; + mat.transparent = transparent; + mat.depthWrite = !transparent; + if (!transparent && "alphaTest" in mat) mat.alphaTest = 0; + } + mat.needsUpdate = true; + } + }); +} + +function hueToRgb(h) { + const hue = ((h % 1) + 1) % 1; + const i = Math.floor(hue * 6); + const f = hue * 6 - i; + const p = 0; + const q = 1 - f; + const t = f; + switch (i % 6) { + case 0: return [1, t, p]; + case 1: return [q, 1, p]; + case 2: return [p, 1, t]; + case 3: return [p, q, 1]; + case 4: return [t, p, 1]; + default: return [1, p, q]; + } +} + +function buildNodeNameLookup(nodeMap) { + const byCanonicalName = new Map(); + for (const obj of nodeMap.values()) { + const key = canonicalizeNameSegment(obj?.name ?? ""); + if (!key) continue; + if (!byCanonicalName.has(key)) { + byCanonicalName.set(key, []); + } + byCanonicalName.get(key).push(obj); + } + return byCanonicalName; +} + +function findDescendantByNameChain(root, chain) { + if (!root || !Array.isArray(chain) || chain.length === 0) return null; + let candidates = [root]; + for (const segment of chain) { + const token = canonicalizeNameSegment(segment ?? ""); + if (!token) continue; + const next = []; + for (const candidate of candidates) { + for (const child of candidate.children || []) { + const childToken = canonicalizeNameSegment(child?.name ?? ""); + if (childToken === token) { + next.push(child); + } + } + } + if (next.length === 0) return null; + candidates = next; + } + return candidates[0] || null; +} + +function resolveScriptNodeRef(refValue, nodeMap, nodeNameLookup) { + let ref = refValue; + if (ref && typeof ref === "object" && ref.__node_ref__ && typeof ref.__node_ref__ === "object") { + ref = ref.__node_ref__; + } + if (typeof ref === "string") { + const byId = nodeMap.get(ref); + if (byId) return byId; + const token = canonicalizeNameSegment(ref); + const byName = token ? (nodeNameLookup.get(token) || []) : []; + return byName.length > 0 ? byName[0] : null; + } + if (!ref || typeof ref !== "object") return null; + + const nodeId = String(ref.node_id ?? "").trim(); + if (nodeId && nodeMap.has(nodeId)) { + return nodeMap.get(nodeId); + } + + const ancestorId = String(ref.ancestor_node_id ?? "").trim(); + if (ancestorId && nodeMap.has(ancestorId) && Array.isArray(ref.child_name_chain)) { + const ancestor = nodeMap.get(ancestorId); + const desc = findDescendantByNameChain(ancestor, ref.child_name_chain); + if (desc) return desc; + } + + const nodeName = String(ref.node_name ?? "").trim(); + if (nodeName) { + const token = canonicalizeNameSegment(nodeName); + const byName = token ? (nodeNameLookup.get(token) || []) : []; + if (byName.length > 0) return byName[0]; + } + + return null; +} + +function createScriptState(THREE, scriptCfg, object3d, nodeMap, nodeNameLookup, ownerName) { + const scriptName = String(scriptCfg?.name ?? "").trim(); + if (!scriptName) return null; + const key = normalizeScriptName(scriptName); + const params = (scriptCfg?.params && typeof scriptCfg.params === "object") ? scriptCfg.params : {}; + const enabled = toBoolean(scriptCfg?.enabled, true); + + if (key === "moverscript" || key === "mover") { + const state = { + name: scriptName, + key, + enabled, + axis: String(params.move_axis ?? "x").toLowerCase(), + moveDistance: Math.max(0, Math.abs(toFiniteNumber(params.move_distance, 5.0))), + moveSpeed: toFiniteNumber(params.move_speed, 2.0), + currentDirection: toFiniteNumber(params.current_direction, 1) >= 0 ? 1 : -1, + currentDistance: Math.max(0, Math.abs(toFiniteNumber(params.current_distance, 0.0))), + isMoving: toBoolean(params.is_moving, true), + update(dt) { + if (!this.enabled || !this.isMoving) return; + const delta = this.moveSpeed * dt * this.currentDirection; + this.currentDistance += Math.abs(delta); + if (this.moveDistance > 1e-6 && this.currentDistance >= this.moveDistance) { + this.currentDirection *= -1; + this.currentDistance = 0; + } + applyPandaAxisDeltaToThreeLocal(object3d, this.axis, delta); + markObjectTransformDirty(object3d); + }, + }; + return state; + } + + if (key === "rotatorscript" || key === "rotator") { + return { + name: scriptName, + key, + enabled, + speedDeg: toFiniteNumber(params.rotation_speed_y, 30.0), + isRotating: toBoolean(params.is_rotating, true), + update(dt) { + if (!this.enabled || !this.isRotating) return; + object3d.rotation.y += THREE.MathUtils.degToRad(this.speedDeg * dt); + markObjectTransformDirty(object3d); + }, + }; + } + + if (key === "scalerscript" || key === "scaler") { + return { + name: scriptName, + key, + enabled, + baseScale: toFiniteNumber(params.base_scale, 1.0), + scaleAmplitude: toFiniteNumber(params.scale_amplitude, 0.3), + scaleSpeed: toFiniteNumber(params.scale_speed, 2.0), + uniformScale: toBoolean(params.uniform_scale, true), + timeAccumulator: toFiniteNumber(params.time_accumulator, 0.0), + isScaling: toBoolean(params.is_scaling, true), + update(dt) { + if (!this.enabled || !this.isScaling) return; + this.timeAccumulator += dt; + const sine = Math.sin(this.timeAccumulator * this.scaleSpeed * 2 * Math.PI); + const scaleFactor = this.baseScale + (this.scaleAmplitude * sine); + if (this.uniformScale) { + object3d.scale.setScalar(scaleFactor); + } else { + object3d.scale.y = scaleFactor; + } + markObjectTransformDirty(object3d); + }, + }; + } + + if (key === "colorchangerscript" || key === "colorchanger") { + ensureUniqueMaterialsForObject(object3d); + return { + name: scriptName, + key, + enabled, + colorSpeed: toFiniteNumber(params.color_speed, 1.0), + colorMode: String(params.color_mode ?? "rainbow").toLowerCase(), + baseColor: parseColorVec4(params.base_color, [1, 1, 1, 1]), + intensity: toFiniteNumber(params.intensity, 1.0), + timeAccumulator: toFiniteNumber(params.time_accumulator, 0.0), + isChanging: toBoolean(params.is_changing, true), + update(dt) { + if (!this.enabled || !this.isChanging) return; + this.timeAccumulator += dt; + + let rgba = this.baseColor.slice(); + if (this.colorMode === "rainbow") { + const rgb = hueToRgb(this.timeAccumulator * this.colorSpeed); + rgba = [rgb[0] * this.intensity, rgb[1] * this.intensity, rgb[2] * this.intensity, 1.0]; + } else if (this.colorMode === "pulse") { + const pulse = (Math.sin(this.timeAccumulator * this.colorSpeed * 2 * Math.PI) + 1.0) / 2.0; + const m = pulse * this.intensity; + rgba = [ + this.baseColor[0] * m, + this.baseColor[1] * m, + this.baseColor[2] * m, + this.baseColor[3], + ]; + } else if (this.colorMode === "fade") { + const fade = (Math.sin(this.timeAccumulator * this.colorSpeed * 2 * Math.PI) + 1.0) / 2.0; + rgba = [ + this.baseColor[0], + this.baseColor[1], + this.baseColor[2], + fade * this.intensity, + ]; + } else if (this.colorMode === "strobe") { + const safeSpeed = Math.max(0.0001, Math.abs(this.colorSpeed)); + const interval = 1.0 / (safeSpeed * 2.0); + const on = (Math.floor(this.timeAccumulator / interval) % 2) === 0; + rgba = on + ? [ + this.baseColor[0] * this.intensity, + this.baseColor[1] * this.intensity, + this.baseColor[2] * this.intensity, + this.baseColor[3], + ] + : [0.1, 0.1, 0.1, this.baseColor[3]]; + } + applyColorToObject(object3d, rgba); + }, + }; + } + + if (key === "bouncerscript" || key === "bouncer") { + return { + name: scriptName, + key, + enabled, + jumpHeight: toFiniteNumber(params.jump_height, 2.0), + jumpSpeed: toFiniteNumber(params.jump_speed, 3.0), + bounceType: String(params.bounce_type ?? "sine").toLowerCase(), + timeAccumulator: toFiniteNumber(params.time_accumulator, 0.0), + isBouncing: toBoolean(params.is_bouncing, true), + bounceDirection: toFiniteNumber(params.bounce_direction, 1) >= 0 ? 1 : -1, + originalHeight: toFiniteNumber(params.original_y, object3d.position.y), + update(dt) { + if (!this.enabled || !this.isBouncing) return; + this.timeAccumulator += dt * this.bounceDirection; + let offset = 0; + const sineValue = Math.sin(this.timeAccumulator * this.jumpSpeed * 2 * Math.PI); + if (this.bounceType === "sine") { + offset = sineValue * this.jumpHeight; + } else if (this.bounceType === "abs_sine") { + offset = Math.abs(sineValue) * this.jumpHeight; + } else if (this.bounceType === "square") { + offset = sineValue > 0 ? this.jumpHeight : 0; + } + object3d.position.y = this.originalHeight + offset; + markObjectTransformDirty(object3d); + }, + }; + } + + if (key === "followerscript" || key === "follower") { + const targetHint = Object.prototype.hasOwnProperty.call(params, "target") + ? params.target + : (Object.prototype.hasOwnProperty.call(params, "target_ref") ? params.target_ref : null); + return { + name: scriptName, + key, + enabled, + followSpeed: Math.max(0, toFiniteNumber(params.follow_speed, 5.0)), + followDistance: Math.max(0, toFiniteNumber(params.follow_distance, 2.0)), + isFollowing: toBoolean(params.is_following, true), + targetHint, + targetObject: null, + tempTargetPos: new THREE.Vector3(), + tempCurrentPos: new THREE.Vector3(), + tempMoveDir: new THREE.Vector3(), + update(dt) { + if (!this.enabled || !this.isFollowing) return; + if (!this.targetObject && this.targetHint) { + this.targetObject = resolveScriptNodeRef(this.targetHint, nodeMap, nodeNameLookup); + } + if (!this.targetObject) return; + + this.targetObject.getWorldPosition(this.tempTargetPos); + object3d.getWorldPosition(this.tempCurrentPos); + + this.tempMoveDir.subVectors(this.tempTargetPos, this.tempCurrentPos); + const distance = this.tempMoveDir.length(); + if (distance > this.followDistance && distance > 1e-6) { + this.tempMoveDir.normalize(); + const targetFollowPos = this.tempTargetPos.clone().addScaledVector(this.tempMoveDir, -this.followDistance); + const moveDirection = targetFollowPos.sub(this.tempCurrentPos); + const moveDistance = moveDirection.length(); + if (moveDistance > 1e-6) { + moveDirection.normalize(); + const moveAmount = Math.min(this.followSpeed * dt, moveDistance); + const newWorldPos = this.tempCurrentPos.clone().addScaledVector(moveDirection, moveAmount); + if (object3d.parent) { + object3d.parent.worldToLocal(newWorldPos); + } + object3d.position.copy(newWorldPos); + } + } + object3d.lookAt(this.tempTargetPos); + markObjectTransformDirty(object3d); + }, + }; + } + + if (key === "comboanimatorscript" || key === "comboanimator") { + return { + name: scriptName, + key, + enabled, + time: toFiniteNumber(params.time, 0.0), + isActive: toBoolean(params.is_active, true), + originalHeight: object3d.position.y, + update(dt) { + if (!this.enabled || !this.isActive) return; + this.time += dt; + object3d.rotation.y += THREE.MathUtils.degToRad(45.0 * dt); + object3d.position.y = this.originalHeight + Math.abs(Math.sin(this.time * 3.0)); + markObjectTransformDirty(object3d); + }, + }; + } + + console.warn("[WebGLPack] Unsupported script in Web viewer:", { + node: ownerName || object3d?.name || "(unnamed)", + script: scriptName, + }); + return null; +} + +function buildScriptRuntimeStates(THREE, nodes, nodeMap) { + const runtimes = []; + let unsupportedCount = 0; + const nodeNameLookup = buildNodeNameLookup(nodeMap); + + for (const node of nodes) { + const obj = nodeMap.get(node?.id); + if (!obj) continue; + const scripts = Array.isArray(node?.scripts) ? node.scripts : []; + for (const scriptCfg of scripts) { + const state = createScriptState( + THREE, + scriptCfg, + obj, + nodeMap, + nodeNameLookup, + node?.name || node?.id || "", + ); + if (state) { + runtimes.push(state); + } else { + unsupportedCount += 1; + } + } + } + + return { runtimes, unsupportedCount }; +} + +async function bootstrap() { + setStatus("Loading WebGL dependencies..."); + + let THREE; + let OrbitControls; + let GLTFLoader; + + try { + THREE = await import("../vendor/three.module.min.js"); + ({ OrbitControls } = await import("../vendor/OrbitControls.js")); + ({ GLTFLoader } = await import("../vendor/GLTFLoader.js")); + } catch (err) { + setStatus( + [ + "Failed to load local Three.js vendor files.", + "Please replace vendor placeholders with official files:", + "- vendor/three.module.min.js", + "- vendor/OrbitControls.js", + "- vendor/GLTFLoader.js", + "", + String(err), + ].join("\n"), + "error", + ); + throw err; + } + + setStatus("Loading scene manifest..."); + + const response = await fetch("../scene/scene_webgl.json", { cache: "no-cache" }); + if (!response.ok) { + throw new Error(`Failed to load scene manifest: HTTP ${response.status}`); + } + + const data = await response.json(); + const sceneNodes = Array.isArray(data.nodes) ? data.nodes : []; + + const renderer = new THREE.WebGLRenderer({ + canvas, + antialias: true, + alpha: false, + }); + renderer.setPixelRatio(window.devicePixelRatio || 1); + renderer.setSize(window.innerWidth, window.innerHeight); + renderer.outputColorSpace = THREE.SRGBColorSpace; + + const scene = new THREE.Scene(); + scene.background = new THREE.Color(0x11151c); + + const cameraData = data.camera || {}; + const camera = new THREE.PerspectiveCamera( + Number(cameraData.fov_deg ?? 80), + window.innerWidth / Math.max(1, window.innerHeight), + Number(cameraData.near ?? 0.1), + Number(cameraData.far ?? 10000), + ); + + const controls = new OrbitControls(camera, renderer.domElement); + controls.enableDamping = true; + controls.target.set(0, 0, 0); + + const basis = rowMajorToMatrix4( + THREE, + Array.isArray(data.coordinate?.basis_matrix) + ? data.coordinate.basis_matrix + : [1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1], + ); + const basisInv = basis.clone().invert(); + const matrixConvention = String(data.coordinate?.matrix_convention || "panda_row_vector_row_major"); + + if (Array.isArray(cameraData.matrix_local_row_major) && cameraData.matrix_local_row_major.length === 16) { + const camMat = convertNodeMatrix( + THREE, + cameraData.matrix_local_row_major, + basis, + basisInv, + matrixConvention, + ); + camera.matrix.copy(camMat); + camera.matrix.decompose(camera.position, camera.quaternion, camera.scale); + camera.matrixAutoUpdate = true; + camera.updateMatrix(); + } else { + camera.position.set(0, -50, 20); + camera.lookAt(0, 0, 0); + } + + const env = data.environment || {}; + const rpProfile = (env.render_pipeline && typeof env.render_pipeline === "object") + ? env.render_pipeline + : {}; + const renderProfile = applyRenderPipelineApproximation(THREE, renderer, scene, rpProfile); + const textureLoader = new THREE.TextureLoader(); + const skyboxPromise = applySkyboxConfig(THREE, scene, env, textureLoader); + + if (env.ambient_light) { + const c = toColorArray(env.ambient_light.color, [0.2, 0.2, 0.2]); + const amb = new THREE.AmbientLight(new THREE.Color(c[0], c[1], c[2]), Number(env.ambient_light.intensity ?? 1)); + scene.add(amb); + } + + if (env.directional_light) { + const c = toColorArray(env.directional_light.color, [0.8, 0.8, 0.8]); + const dirLight = new THREE.DirectionalLight( + new THREE.Color(c[0], c[1], c[2]), + Number(env.directional_light.intensity ?? 1), + ); + + const dir = directionToThree(THREE, env.directional_light.direction, basis); + dirLight.position.copy(dir.clone().multiplyScalar(-40)); + dirLight.target.position.set(0, 0, 0); + dirLight.castShadow = !!renderProfile.shadowEnabled; + if (dirLight.shadow && dirLight.shadow.mapSize) { + dirLight.shadow.mapSize.set(renderProfile.shadowResolution, renderProfile.shadowResolution); + } + + scene.add(dirLight); + scene.add(dirLight.target); + } + + const nodeMap = new Map(); + const pendingModelLoads = []; + const animationMixers = []; + const animationStates = []; + + const gltfLoader = new GLTFLoader(); + + for (const node of sceneNodes) { + let obj; + + if (node.kind === "point_light") { + const light = node.light || {}; + const c = toColorArray(light.color, [1, 1, 1]); + obj = new THREE.PointLight( + new THREE.Color(c[0], c[1], c[2]), + Number(light.intensity ?? 1), + Number(light.range ?? 0), + ); + obj.castShadow = !!renderProfile.shadowEnabled; + if (obj.shadow && obj.shadow.mapSize) { + obj.shadow.mapSize.set(renderProfile.shadowResolution, renderProfile.shadowResolution); + } + } else if (node.kind === "spot_light") { + const light = node.light || {}; + const c = toColorArray(light.color, [1, 1, 1]); + const angle = THREE.MathUtils.degToRad(Number(light.spot_angle_deg ?? 45)); + const spot = new THREE.SpotLight( + new THREE.Color(c[0], c[1], c[2]), + Number(light.intensity ?? 1), + Number(light.range ?? 0), + angle, + 1 - Number(light.inner_cone_ratio ?? 0.4), + ); + spot.target.position.set(0, 0, -1); + spot.castShadow = !!renderProfile.shadowEnabled; + if (spot.shadow && spot.shadow.mapSize) { + spot.shadow.mapSize.set(renderProfile.shadowResolution, renderProfile.shadowResolution); + } + obj = spot; + } else if (node.kind === "ground") { + const g = node.ground || {}; + const width = Number(g.width ?? 100); + const height = Number(g.height ?? 100); + const m = node.material_override || {}; + const bc = Array.isArray(m.base_color) ? m.base_color : [0.8, 0.8, 0.8, 1]; + const mat = new THREE.MeshStandardMaterial({ + color: new THREE.Color(Number(bc[0] ?? 0.8), Number(bc[1] ?? 0.8), Number(bc[2] ?? 0.8)), + roughness: Number(m.roughness ?? 1), + metalness: Number(m.metallic ?? 0), + transparent: Number(m.opacity ?? 1) < 1, + opacity: Number(m.opacity ?? 1), + side: THREE.DoubleSide, + }); + obj = new THREE.Mesh(new THREE.PlaneGeometry(width, height), mat); + obj.receiveShadow = !!renderProfile.shadowEnabled; + obj.castShadow = false; + } else { + obj = new THREE.Group(); + const modelUri = node.model?.uri; + if (modelUri) { + const p = new Promise((resolve) => { + gltfLoader.load( + modelUri, + (gltf) => { + const root = gltf.scene || (Array.isArray(gltf.scenes) ? gltf.scenes[0] : null); + if (root) { + applySubnodeOverrides( + THREE, + root, + node.subnode_overrides || null, + basis, + basisInv, + matrixConvention, + ); + applyMaterialOverride(THREE, root, node.material_override || null); + applyTextureOverrides(THREE, root, node.texture_overrides || [], textureLoader); + applyShadowFlags(root, renderProfile.shadowEnabled); + const anim = setupModelAnimation( + THREE, + root, + gltf, + node.animation || null, + node.name || node.id || "", + ); + if (anim.mixer) { + animationMixers.push(anim.mixer); + } + if (anim.clipName) { + animationStates.push({ + node: node.name || node.id || "node", + clip: anim.clipName, + mode: anim.mode, + started: anim.started, + }); + } + obj.add(root); + } + resolve(); + }, + undefined, + (err) => { + console.warn(`Failed to load model ${modelUri}:`, err); + resolve(); + }, + ); + }); + pendingModelLoads.push(p); + } + } + + obj.name = node.name || node.id || "node"; + + if (Array.isArray(node.matrix_local_row_major) && node.matrix_local_row_major.length === 16) { + const converted = convertNodeMatrix(THREE, node.matrix_local_row_major, basis, basisInv, matrixConvention); + obj.matrixAutoUpdate = false; + obj.matrix.copy(converted); + obj.matrix.decompose(obj.position, obj.quaternion, obj.scale); + } + + nodeMap.set(node.id, obj); + } + + for (const node of sceneNodes) { + const obj = nodeMap.get(node.id); + if (!obj) continue; + + const parent = node.parent_id ? nodeMap.get(node.parent_id) : null; + if (parent) { + parent.add(obj); + } else { + scene.add(obj); + } + + if (obj.isSpotLight && obj.target) { + obj.add(obj.target); + } + } + + await Promise.all(pendingModelLoads); + const skyboxState = await skyboxPromise; + const bloomSetup = await tryCreateBloomComposer( + THREE, + renderer, + scene, + camera, + renderProfile.bloom, + ); + const composer = bloomSetup.composer; + const scriptRuntime = buildScriptRuntimeStates(THREE, sceneNodes, nodeMap); + const scriptStates = scriptRuntime.runtimes; + const unsupportedScriptCount = scriptRuntime.unsupportedCount; + + const resize = () => { + const w = window.innerWidth; + const h = Math.max(1, window.innerHeight); + camera.aspect = w / h; + camera.updateProjectionMatrix(); + renderer.setSize(w, h); + if (composer && typeof composer.setSize === "function") { + composer.setSize(w, h); + } + }; + + window.addEventListener("resize", resize); + resize(); + + setStatus( + `Scene ready. Nodes: ${sceneNodes.length}, Animations: ${animationStates.length}, Scripts: ${scriptStates.length}${unsupportedScriptCount > 0 ? ` (unsupported: ${unsupportedScriptCount})` : ""}, Skybox: ${skyboxState.enabled ? "on" : "off"}, ToneMap: ${renderProfile.toneMappingEnabled ? "on" : "off"}, Shadows: ${renderProfile.shadowEnabled ? "on" : "off"}, Fog: ${renderProfile.fogApplied ? "on" : "off"}, Bloom: ${bloomSetup.enabled ? "on" : "off"}.\nUse mouse to orbit, wheel to zoom.`, + "ok", + ); + + const clock = new THREE.Clock(); + const tick = () => { + requestAnimationFrame(tick); + const dt = clock.getDelta(); + if (dt >= 0) { + for (const mixer of animationMixers) { + try { + mixer.update(dt); + } catch (err) { + // Keep render loop alive even if one mixer fails. + } + } + scene.updateMatrixWorld(true); + for (const state of scriptStates) { + try { + state.update(dt); + } catch (err) { + // Keep render loop alive even if one script fails. + } + } + controls.update(); + if (skyboxState && typeof skyboxState.update === "function") { + skyboxState.update(camera); + } + if (composer) { + composer.render(); + } else { + renderer.render(scene, camera); + } + } + }; + + tick(); +} + +bootstrap().catch((err) => { + console.error(err); + setStatus(`Viewer bootstrap failed:\n${String(err)}`, "error"); +}); diff --git a/ui/panels/app_actions.py b/ui/panels/app_actions.py index 12d1cc51..aab4a09f 100644 --- a/ui/panels/app_actions.py +++ b/ui/panels/app_actions.py @@ -309,6 +309,95 @@ class AppActions: self.add_info_message("打开打包项目对话框") self.show_build_project_dialog = True + def _on_build_webgl_package(self): + """处理“打包为 WebGL”菜单项。""" + project_manager = getattr(self, "project_manager", None) + if not project_manager: + self.add_error_message("项目管理器未初始化") + return + + current_project_path = getattr(project_manager, "current_project_path", None) + if not current_project_path: + self.add_warning_message("请先创建或打开项目,再进行WebGL打包") + return + + if not self._save_project_impl(): + self.add_error_message("打包前自动保存失败,已取消WebGL打包") + return + + initial_dir = os.path.dirname(current_project_path) if current_project_path else os.getcwd() + selected_dir = self._select_directory_system_dialog("选择 WebGL 打包输出目录", initial_dir) + if not selected_dir: + dialog_error = getattr(self, "_last_directory_dialog_error", "") + if dialog_error: + self.add_warning_message(f"系统目录选择器不可用: {dialog_error}") + self.path_browser_mode = "webgl_build" + self.path_browser_current_path = initial_dir if os.path.isdir(initial_dir) else os.getcwd() + self.path_browser_selected_path = self.path_browser_current_path + self.show_path_browser = True + self._pending_webgl_package = True + self._refresh_path_browser() + self.add_info_message("已切换到内置路径浏览器,请选择输出目录并点击确定") + else: + self.add_info_message("已取消WebGL打包") + return + + self._execute_webgl_package(selected_dir) + + def _execute_webgl_package(self, selected_dir): + """执行 WebGL 打包并反馈结果。""" + ok = self.project_manager.buildWebGLPackage(selected_dir) + report = getattr(self.project_manager, "last_webgl_export_report", None) or {} + status = str(report.get("status", "failed") or "failed") + out_dir = str(report.get("output_dir", "") or "") + report_path = os.path.join(out_dir, "reports", "export_report.json") if out_dir else "" + + if ok: + missing_count = len(report.get("missing_assets", []) or []) + unsupported_count = len(report.get("unsupported_assets", []) or []) + if status == "partial": + self.add_warning_message( + f"WebGL打包部分成功: 缺失资源 {missing_count},不支持资源 {unsupported_count}" + ) + else: + self.add_success_message("WebGL打包成功") + + if out_dir: + self.add_info_message(f"输出目录: {out_dir}") + if report_path: + self.add_info_message(f"报告: {report_path}") + return True + + self.add_error_message("WebGL打包失败") + if report_path: + self.add_warning_message(f"请检查报告: {report_path}") + return False + + def _select_directory_system_dialog(self, title, initial_dir=""): + """打开系统目录选择器并返回目录路径。""" + self._last_directory_dialog_error = "" + try: + import tkinter as tk + from tkinter import filedialog + + if not initial_dir or not os.path.isdir(initial_dir): + initial_dir = os.getcwd() + + root = tk.Tk() + root.withdraw() + root.attributes("-topmost", True) + selected_dir = filedialog.askdirectory( + title=title, + initialdir=initial_dir, + mustexist=False, + parent=root, + ) + root.destroy() + return os.path.normpath(selected_dir) if selected_dir else "" + except Exception as exc: + self._last_directory_dialog_error = str(exc) + return "" + def _build_project_impl(self, output_path): """执行项目打包。""" if not hasattr(self, "project_manager") or not self.project_manager: diff --git a/ui/panels/dialog_panels.py b/ui/panels/dialog_panels.py index 8e32a978..7fd8b67f 100644 --- a/ui/panels/dialog_panels.py +++ b/ui/panels/dialog_panels.py @@ -627,6 +627,13 @@ class DialogPanels: elif self.path_browser_mode == "build_project": self.build_output_path = self.path_browser_current_path self.add_info_message(f"已选择打包位置: {self.build_output_path}") + elif self.path_browser_mode == "webgl_build": + output_dir = self.path_browser_current_path + self.add_info_message(f"已选择WebGL输出目录: {output_dir}") + if getattr(self, "_pending_webgl_package", False): + self._pending_webgl_package = False + if hasattr(self, "_execute_webgl_package"): + self._execute_webgl_package(output_dir) elif self.path_browser_mode == "import_model": # 导入模型模式:使用选择的文件路径 self.import_file_path = self.path_browser_selected_path diff --git a/ui/panels/editor_panels_top.py b/ui/panels/editor_panels_top.py index 0db4da11..81ff9130 100644 --- a/ui/panels/editor_panels_top.py +++ b/ui/panels/editor_panels_top.py @@ -189,6 +189,8 @@ class EditorPanelsTopMixin: self.app._on_save_as_project() if imgui.menu_item("打包项目", "", False, True)[1]: self.app._on_build_project() + if imgui.menu_item("打包为 WebGL", "", False, True)[1]: + self.app._on_build_webgl_package() imgui.separator() if imgui.menu_item("退出", "Alt+F4", False, True)[1]: self.app._on_exit() @@ -544,4 +546,3 @@ class EditorPanelsTopMixin: ) imgui.pop_style_var(2) - diff --git a/ui/panels/panel_delegates.py b/ui/panels/panel_delegates.py index c4d35573..94431cd6 100644 --- a/ui/panels/panel_delegates.py +++ b/ui/panels/panel_delegates.py @@ -540,6 +540,12 @@ class PanelDelegates: def _on_build_project(self, *args, **kwargs): return self.app_actions._on_build_project(*args, **kwargs) + def _on_build_webgl_package(self, *args, **kwargs): + return self.app_actions._on_build_webgl_package(*args, **kwargs) + + def _execute_webgl_package(self, *args, **kwargs): + return self.app_actions._execute_webgl_package(*args, **kwargs) + def _build_project_impl(self, *args, **kwargs): return self.app_actions._build_project_impl(*args, **kwargs) @@ -954,4 +960,3 @@ class PanelDelegates: def loadScript(self, script_path): return self.runtime_actions.loadScript(script_path) -