chenlei 2 dienas atpakaļ
vecāks
revīzija
b48925b135
100 mainītis faili ar 69886 papildinājumiem un 6 dzēšanām
  1. 1 1
      packages/pc/public/modelLook/assets/index-BcLjdLAW.js
  2. 1 1
      packages/pc/public/modelLook/assets/index-P16QZIgH.js
  3. 18 2
      packages/pc/public/modelLook/index.html
  4. 271 0
      packages/pc/public/modelLook/obj-viewer.js
  5. 1860 0
      packages/pc/public/modelLook/vendor/addons/controls/OrbitControls.js
  6. 593 0
      packages/pc/public/modelLook/vendor/addons/loaders/MTLLoader.js
  7. 955 0
      packages/pc/public/modelLook/vendor/addons/loaders/OBJLoader.js
  8. 538 0
      packages/pc/public/modelLook/vendor/addons/loaders/TGALoader.js
  9. 6 0
      packages/pc/public/modelLook/vendor/three.core.min.js
  10. 6 0
      packages/pc/public/modelLook/vendor/three.module.min.js
  11. 35 2
      packages/pc/src/views/Scroll/index.vue
  12. 1 0
      packages/temp-pc/.env
  13. 30 0
      packages/temp-pc/.gitignore
  14. 105 0
      packages/temp-pc/index.html
  15. 8 0
      packages/temp-pc/jsconfig.json
  16. 36 0
      packages/temp-pc/package.json
  17. 23 0
      packages/temp-pc/postcss.config.js
  18. BIN
      packages/temp-pc/public/favicon.ico
  19. BIN
      packages/temp-pc/public/images/1000/Thumbs.db
  20. BIN
      packages/temp-pc/public/images/1000/img_gaobingbei.png
  21. BIN
      packages/temp-pc/public/images/1000/img_jindaishi.png
  22. BIN
      packages/temp-pc/public/images/1000/img_muyin.png
  23. BIN
      packages/temp-pc/public/images/1000/img_shouxinghu.png
  24. BIN
      packages/temp-pc/public/images/1000/img_yachouyue.png
  25. BIN
      packages/temp-pc/public/images/1000/img_yubi.png
  26. BIN
      packages/temp-pc/public/images/2000/Thumbs.db
  27. BIN
      packages/temp-pc/public/images/2000/img_gaobingbei.png
  28. BIN
      packages/temp-pc/public/images/2000/img_jindaishi.png
  29. BIN
      packages/temp-pc/public/images/2000/img_muyin.png
  30. BIN
      packages/temp-pc/public/images/2000/img_shouxinghu.png
  31. BIN
      packages/temp-pc/public/images/2000/img_yachouyue.png
  32. BIN
      packages/temp-pc/public/images/2000/img_yubi.png
  33. BIN
      packages/temp-pc/public/images/500/Thumbs.db
  34. BIN
      packages/temp-pc/public/images/500/img_bihua.png
  35. BIN
      packages/temp-pc/public/images/500/img_bihua_line.png
  36. BIN
      packages/temp-pc/public/images/500/img_chenhouhu.png
  37. BIN
      packages/temp-pc/public/images/500/img_chenhouhu_line.png
  38. BIN
      packages/temp-pc/public/images/500/img_gaobingbei.png
  39. BIN
      packages/temp-pc/public/images/500/img_gaobingbei_line.png
  40. BIN
      packages/temp-pc/public/images/500/img_jiaguwen.png
  41. BIN
      packages/temp-pc/public/images/500/img_jiaguwen_line.png
  42. BIN
      packages/temp-pc/public/images/500/img_jindaishi.png
  43. BIN
      packages/temp-pc/public/images/500/img_jindaishi_line.png
  44. BIN
      packages/temp-pc/public/images/500/img_muyin.png
  45. BIN
      packages/temp-pc/public/images/500/img_muyin_line.png
  46. BIN
      packages/temp-pc/public/images/500/img_shouxinghu.png
  47. BIN
      packages/temp-pc/public/images/500/img_shouxinghu_line.png
  48. BIN
      packages/temp-pc/public/images/500/img_tuzhou.png
  49. BIN
      packages/temp-pc/public/images/500/img_tuzhou_line.png
  50. BIN
      packages/temp-pc/public/images/500/img_yachouyue.png
  51. BIN
      packages/temp-pc/public/images/500/img_yachouyue_line.png
  52. BIN
      packages/temp-pc/public/images/500/img_yubi.png
  53. BIN
      packages/temp-pc/public/images/500/img_yubi_line.png
  54. BIN
      packages/temp-pc/public/images/500/img_zhujian.png
  55. BIN
      packages/temp-pc/public/images/500/img_zhujian_line.png
  56. BIN
      packages/temp-pc/public/model/021658.fbx
  57. BIN
      packages/temp-pc/public/model/3.374_红陶兽形壶.FBX
  58. 23 0
      packages/temp-pc/public/model/6.465-3/6.465-3.mtl
  59. 36647 0
      packages/temp-pc/public/model/6.465-3/6.465-3.obj
  60. BIN
      packages/temp-pc/public/model/6.465-3/6.465_m1_D.jpg
  61. BIN
      packages/temp-pc/public/model/6.465-3/6.465_m1_D.tga
  62. BIN
      packages/temp-pc/public/model/6.465-3/6.465_m2_D.jpg
  63. BIN
      packages/temp-pc/public/model/6.465-3/6.465_m2_D.tga
  64. 14 0
      packages/temp-pc/public/model/M52.40-2/M52.40-2.mtl
  65. 16422 0
      packages/temp-pc/public/model/M52.40-2/M52.40-2.obj
  66. BIN
      packages/temp-pc/public/model/M52.40-2/M52.40_D.jpg
  67. BIN
      packages/temp-pc/public/model/M52.40-2/M52.40_N.jpg
  68. BIN
      packages/temp-pc/public/model/东6.487亚醜钺.fbx
  69. 3797 0
      packages/temp-pc/public/modelLook/assets/index-BcLjdLAW.js
  70. 3797 0
      packages/temp-pc/public/modelLook/assets/index-P16QZIgH.js
  71. BIN
      packages/temp-pc/public/modelLook/assets/tinyusdz-DnE9aqql.wasm
  72. 41 0
      packages/temp-pc/public/modelLook/index.html
  73. 271 0
      packages/temp-pc/public/modelLook/obj-viewer.js
  74. 1860 0
      packages/temp-pc/public/modelLook/vendor/addons/controls/OrbitControls.js
  75. 593 0
      packages/temp-pc/public/modelLook/vendor/addons/loaders/MTLLoader.js
  76. 955 0
      packages/temp-pc/public/modelLook/vendor/addons/loaders/OBJLoader.js
  77. 538 0
      packages/temp-pc/public/modelLook/vendor/addons/loaders/TGALoader.js
  78. 6 0
      packages/temp-pc/public/modelLook/vendor/three.core.min.js
  79. 6 0
      packages/temp-pc/public/modelLook/vendor/three.module.min.js
  80. 60 0
      packages/temp-pc/src/App.vue
  81. 7 0
      packages/temp-pc/src/assets/elements.scss
  82. BIN
      packages/temp-pc/src/assets/fonts/FZJinLing.TTF
  83. BIN
      packages/temp-pc/src/assets/fonts/SOURCEHANSERIFCN-BOLD.OTF
  84. BIN
      packages/temp-pc/src/assets/fonts/SourceHanSerifCN-Light.otf
  85. BIN
      packages/temp-pc/src/assets/fonts/SourceHanSerifCN-Medium.otf
  86. BIN
      packages/temp-pc/src/assets/fonts/SourceHanSerifCN-Regular.otf
  87. BIN
      packages/temp-pc/src/assets/fonts/SourceHanSerifCN-SemiBold.otf
  88. BIN
      packages/temp-pc/src/assets/images/btn_close-min.png
  89. BIN
      packages/temp-pc/src/assets/images/btn_close.png
  90. BIN
      packages/temp-pc/src/assets/images/btn_middle_active-min.png
  91. BIN
      packages/temp-pc/src/assets/images/logo.png
  92. 111 0
      packages/temp-pc/src/assets/main.css
  93. 7 0
      packages/temp-pc/src/assets/utils.scss
  94. 17 0
      packages/temp-pc/src/main.js
  95. 27 0
      packages/temp-pc/src/router/index.js
  96. 54 0
      packages/temp-pc/src/stores/base.js
  97. 12 0
      packages/temp-pc/src/stores/counter.js
  98. 0 0
      packages/temp-pc/src/utils/constants.js
  99. 134 0
      packages/temp-pc/src/views/Collection/constants.ts
  100. 0 0
      packages/temp-pc/src/views/Collection/detail.scss

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 1
packages/pc/public/modelLook/assets/index-BcLjdLAW.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 1
packages/pc/public/modelLook/assets/index-P16QZIgH.js


+ 18 - 2
packages/pc/public/modelLook/index.html

@@ -4,7 +4,7 @@
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <meta name="color-scheme" content="light" />
-    <title>GLB 加载演示</title>
+    <title>模型加载演示</title>
     <style>
       html,
       body,
@@ -17,7 +17,23 @@
         overscroll-behavior: none;
       }
     </style>
-    <script type="module" crossorigin src="./assets/index-BcLjdLAW.js"></script>
+    <script type="importmap">
+      {
+        "imports": {
+          "three": "./vendor/three.module.min.js",
+          "three/addons/": "./vendor/addons/"
+        }
+      }
+    </script>
+    <script type="module">
+      const src = new URLSearchParams(location.search).get("src") || "";
+      const path = src.split("?")[0].split("#")[0].toLowerCase();
+      if (path.endsWith(".obj")) {
+        await import("./obj-viewer.js");
+      } else {
+        await import("./assets/index-BcLjdLAW.js");
+      }
+    </script>
   </head>
   <body>
     <div id="root"></div>

+ 271 - 0
packages/pc/public/modelLook/obj-viewer.js

@@ -0,0 +1,271 @@
+import * as THREE from "three";
+import { OrbitControls } from "three/addons/controls/OrbitControls.js";
+import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
+import { MTLLoader } from "three/addons/loaders/MTLLoader.js";
+import { TGALoader } from "three/addons/loaders/TGALoader.js";
+
+// MTL 里若引用 .tga,默认 TextureLoader 无法解码,需注册 TGALoader
+THREE.DefaultLoadingManager.addHandler(/\.tga$/i, new TGALoader());
+
+const params = new URLSearchParams(window.location.search);
+const src = params.get("src") || "";
+const progressColor = params.get("progressColor") || "#4C7F7A";
+const rawMin = Number(params.get("min"));
+const rawMax = Number(params.get("max"));
+let zoomMin = Number.isFinite(rawMin) && rawMin > 0 ? rawMin : 0.01;
+let zoomMax = Number.isFinite(rawMax) && rawMax > 0 ? rawMax : 4;
+zoomMin = Math.min(1, Math.min(zoomMin, zoomMax));
+zoomMax = Math.max(zoomMin, zoomMax);
+
+const root = document.getElementById("root");
+root.style.cssText = "width:100%;height:100%;position:relative;overflow:hidden;";
+
+const overlay = document.createElement("div");
+overlay.style.cssText =
+  "position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:9999;pointer-events:none;transition:opacity .8s ease-out;";
+overlay.innerHTML = `<div style="width:600px;height:10px;background:#fff;border-radius:5px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,.2)"><div id="obj-progress-bar" style="width:100%;transform:scaleX(0);transform-origin:left;height:100%;background:${progressColor}"></div></div>`;
+root.appendChild(overlay);
+const progressBar = overlay.querySelector("#obj-progress-bar");
+
+const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
+renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
+renderer.setSize(window.innerWidth, window.innerHeight);
+renderer.setClearColor(0x000000, 0);
+renderer.toneMapping = THREE.ACESFilmicToneMapping;
+renderer.toneMappingExposure = 1.35;
+renderer.outputColorSpace = THREE.SRGBColorSpace;
+renderer.domElement.style.cssText =
+  "width:100%;height:100%;display:block;opacity:0;transition:opacity 1s ease-in-out;touch-action:none;";
+root.appendChild(renderer.domElement);
+
+const scene = new THREE.Scene();
+const camera = new THREE.PerspectiveCamera(
+  45,
+  window.innerWidth / window.innerHeight,
+  0.01,
+  2000
+);
+camera.position.set(0, 1.2, 3);
+
+const controls = new OrbitControls(camera, renderer.domElement);
+controls.enableDamping = true;
+controls.dampingFactor = 0.08;
+controls.minDistance = 0.01;
+controls.maxDistance = 100;
+
+scene.add(new THREE.HemisphereLight(0xfff7ec, 0xc9c4bc, 1.2));
+scene.add(new THREE.AmbientLight(0xffffff, 1));
+const key = new THREE.DirectionalLight(0xffffff, 1.85);
+key.position.set(5, 10, 6);
+scene.add(key);
+const fill = new THREE.DirectionalLight(0xffffff, 0.9);
+fill.position.set(-6, 4, -3);
+scene.add(fill);
+const rim = new THREE.DirectionalLight(0xffffff, 0.5);
+rim.position.set(2, 3, -6);
+scene.add(rim);
+
+let baseDistance = 3;
+let initialCameraPos = camera.position.clone();
+let initialTarget = controls.target.clone();
+let autoRotate = false;
+let autoRotateSpeed = 1;
+let autoRotateDelay = 5000;
+let interacting = false;
+let resumeTimer = null;
+
+function setProgress(p) {
+  progressBar.style.transform = `scaleX(${Math.max(0, Math.min(1, p / 100))})`;
+}
+
+function fitObject(object) {
+  const box = new THREE.Box3().setFromObject(object);
+  const size = box.getSize(new THREE.Vector3());
+  const center = box.getCenter(new THREE.Vector3());
+  object.position.sub(center);
+
+  const maxSize = Math.max(size.x, size.y, size.z) || 1;
+  const fitDist =
+    maxSize / (2 * Math.tan((Math.PI * camera.fov) / 360)) * 1.5;
+  baseDistance = fitDist;
+  controls.minDistance = Math.max(0.01, fitDist * zoomMin);
+  controls.maxDistance = fitDist * zoomMax;
+  camera.near = Math.max(0.001, fitDist / 100);
+  camera.far = Math.max(100, fitDist * 100);
+  camera.updateProjectionMatrix();
+  camera.position.set(0, fitDist * 0.15, fitDist);
+  controls.target.set(0, 0, 0);
+  controls.update();
+  initialCameraPos = camera.position.clone();
+  initialTarget = controls.target.clone();
+}
+
+function resolveDir(url) {
+  try {
+    const u = new URL(url, window.location.href);
+    u.hash = "";
+    u.search = "";
+    const path = u.pathname;
+    const i = path.lastIndexOf("/");
+    u.pathname = i >= 0 ? path.slice(0, i + 1) : "/";
+    return u.href;
+  } catch {
+    const i = url.lastIndexOf("/");
+    return i >= 0 ? url.slice(0, i + 1) : "./";
+  }
+}
+
+async function loadObj(url) {
+  if (!url) throw new Error("缺少 src 参数");
+  setProgress(5);
+  const dir = resolveDir(url);
+
+  // 先读文本,解析 mtllib;没有材质文件时也能正常显示几何体
+  const res = await fetch(url);
+  if (!res.ok) throw new Error(`HTTP ${res.status}`);
+  const text = await res.text();
+  setProgress(55);
+
+  let materials = null;
+  const mtlMatch = text.match(/^mtllib\s+(.+)$/m);
+  if (mtlMatch) {
+    const mtlName = mtlMatch[1].trim().split(/\s+/)[0];
+    try {
+      materials = await new Promise((resolve, reject) => {
+        const loader = new MTLLoader();
+        loader.setPath(dir);
+        loader.load(
+          mtlName,
+          (mats) => {
+            mats.preload();
+            resolve(mats);
+          },
+          (e) => {
+            if (e.total) setProgress(55 + (e.loaded / e.total) * 20);
+          },
+          reject
+        );
+      });
+    } catch (err) {
+      console.warn("MTL 加载失败,将使用默认材质:", err);
+    }
+  }
+  setProgress(75);
+
+  const objLoader = new OBJLoader();
+  if (materials) objLoader.setMaterials(materials);
+  // 使用已下载文本,避免二次请求;setPath 仍用于相对纹理路径
+  objLoader.setPath(dir);
+  const object = objLoader.parse(text);
+  object.traverse((child) => {
+    if (child.isMesh) {
+      child.castShadow = false;
+      child.receiveShadow = false;
+      const mats = Array.isArray(child.material)
+        ? child.material
+        : [child.material];
+      mats.forEach((m) => {
+        if (!m) return;
+        if (m.map) m.map.colorSpace = THREE.SRGBColorSpace;
+        m.side = THREE.DoubleSide;
+        m.needsUpdate = true;
+      });
+    }
+  });
+  setProgress(100);
+  return object;
+}
+
+function scheduleAutoResume() {
+  if (!autoRotate) return;
+  clearTimeout(resumeTimer);
+  resumeTimer = setTimeout(() => {
+    interacting = false;
+  }, autoRotateDelay);
+}
+
+function animate() {
+  requestAnimationFrame(animate);
+  if (autoRotate && !interacting) {
+    controls.autoRotate = true;
+    controls.autoRotateSpeed = autoRotateSpeed;
+  } else {
+    controls.autoRotate = false;
+  }
+  controls.update();
+  renderer.render(scene, camera);
+}
+
+controls.addEventListener("start", () => {
+  interacting = true;
+  clearTimeout(resumeTimer);
+});
+controls.addEventListener("end", () => {
+  scheduleAutoResume();
+});
+
+window.addEventListener("resize", () => {
+  camera.aspect = window.innerWidth / window.innerHeight;
+  camera.updateProjectionMatrix();
+  renderer.setSize(window.innerWidth, window.innerHeight);
+});
+
+window.sceneFc = {
+  resetView() {
+    interacting = true;
+    camera.position.copy(initialCameraPos);
+    controls.target.copy(initialTarget);
+    controls.update();
+    scheduleAutoResume();
+  },
+  setAutoRotate(speed = 1, delay = 5000) {
+    autoRotateSpeed = Math.max(0.1, Math.min(8, Number(speed) || 1));
+    autoRotateDelay = Math.max(0, Number(delay) || 5000);
+    autoRotate = true;
+    interacting = false;
+  },
+  stopAutoRotate() {
+    autoRotate = false;
+    controls.autoRotate = false;
+  },
+  zoomIn(factor = 1.2) {
+    const f = Math.max(1.01, Number(factor) || 1.2);
+    const dist = camera.position.distanceTo(controls.target);
+    const next = Math.max(controls.minDistance, dist / f);
+    const dir = camera.position.clone().sub(controls.target).normalize();
+    camera.position.copy(controls.target).add(dir.multiplyScalar(next));
+    controls.update();
+  },
+  zoomOut(factor = 1.2) {
+    const f = Math.max(1.01, Number(factor) || 1.2);
+    const dist = camera.position.distanceTo(controls.target);
+    const next = Math.min(controls.maxDistance, dist * f);
+    const dir = camera.position.clone().sub(controls.target).normalize();
+    camera.position.copy(controls.target).add(dir.multiplyScalar(next));
+    controls.update();
+  },
+};
+
+animate();
+
+if (!src) {
+  overlay.style.opacity = "0";
+  const tip = document.createElement("div");
+  tip.style.cssText =
+    "position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#222;font-size:14px;";
+  tip.textContent = "请在地址栏提供 ?src=OBJ地址";
+  root.appendChild(tip);
+} else {
+  loadObj(src)
+    .then((object) => {
+      scene.add(object);
+      fitObject(object);
+      renderer.domElement.style.opacity = "1";
+      overlay.style.opacity = "0";
+    })
+    .catch((err) => {
+      console.error(err);
+      overlay.innerHTML = `<div style="color:#c00;font-size:14px;background:#fff;padding:12px 16px;border-radius:8px;">OBJ 加载失败:${err.message || err}</div>`;
+      overlay.style.pointerEvents = "auto";
+    });
+}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1860 - 0
packages/pc/public/modelLook/vendor/addons/controls/OrbitControls.js


+ 593 - 0
packages/pc/public/modelLook/vendor/addons/loaders/MTLLoader.js

@@ -0,0 +1,593 @@
+import {
+	Color,
+	ColorManagement,
+	DefaultLoadingManager,
+	FileLoader,
+	FrontSide,
+	Loader,
+	LoaderUtils,
+	MeshPhongMaterial,
+	RepeatWrapping,
+	TextureLoader,
+	Vector2,
+	SRGBColorSpace
+} from 'three';
+
+/**
+ * A loader for the MTL format.
+ *
+ * The Material Template Library format (MTL) or .MTL File Format is a companion file format
+ * to OBJ that describes surface shading (material) properties of objects within one or more
+ * OBJ files.
+ *
+ * ```js
+ * const loader = new MTLLoader();
+ * const materials = await loader.loadAsync( 'models/obj/male02/male02.mtl' );
+ *
+ * const objLoader = new OBJLoader();
+ * objLoader.setMaterials( materials );
+ * ```
+ *
+ * @augments Loader
+ * @three_import import { MTLLoader } from 'three/addons/loaders/MTLLoader.js';
+ */
+class MTLLoader extends Loader {
+
+	constructor( manager ) {
+
+		super( manager );
+
+	}
+
+	/**
+	 * Starts loading from the given URL and passes the loaded MTL asset
+	 * to the `onLoad()` callback.
+	 *
+	 * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
+	 * @param {function(MaterialCreator)} onLoad - Executed when the loading process has been finished.
+	 * @param {onProgressCallback} onProgress - Executed while the loading is in progress.
+	 * @param {onErrorCallback} onError - Executed when errors occur.
+	 */
+	load( url, onLoad, onProgress, onError ) {
+
+		const scope = this;
+
+		const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path;
+
+		const loader = new FileLoader( this.manager );
+		loader.setPath( this.path );
+		loader.setRequestHeader( this.requestHeader );
+		loader.setWithCredentials( this.withCredentials );
+		loader.load( url, function ( text ) {
+
+			try {
+
+				onLoad( scope.parse( text, path ) );
+
+			} catch ( e ) {
+
+				if ( onError ) {
+
+					onError( e );
+
+				} else {
+
+					console.error( e );
+
+				}
+
+				scope.manager.itemError( url );
+
+			}
+
+		}, onProgress, onError );
+
+	}
+
+	/**
+	 * Sets the material options.
+	 *
+	 * @param {MTLLoader~MaterialOptions} value - The material options.
+	 * @return {MTLLoader} A reference to this loader.
+	 */
+	setMaterialOptions( value ) {
+
+		this.materialOptions = value;
+		return this;
+
+	}
+
+	/**
+	 * Parses the given MTL data and returns the resulting material creator.
+	 *
+	 * @param {string} text - The raw MTL data as a string.
+	 * @param {string} path - The URL base path.
+	 * @return {MaterialCreator} The material creator.
+	 */
+	parse( text, path ) {
+
+		const lines = text.split( '\n' );
+		let info = {};
+		const delimiter_pattern = /\s+/;
+		const materialsInfo = {};
+
+		for ( let i = 0; i < lines.length; i ++ ) {
+
+			let line = lines[ i ];
+			line = line.trim();
+
+			if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
+
+				// Blank line or comment ignore
+				continue;
+
+			}
+
+			const pos = line.indexOf( ' ' );
+
+			let key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
+			key = key.toLowerCase();
+
+			let value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';
+			value = value.trim();
+
+			if ( key === 'newmtl' ) {
+
+				// New material
+
+				info = { name: value };
+				materialsInfo[ value ] = info;
+
+			} else {
+
+				if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) {
+
+					const ss = value.split( delimiter_pattern, 3 );
+					info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
+
+				} else {
+
+					info[ key ] = value;
+
+				}
+
+			}
+
+		}
+
+		const materialCreator = new MaterialCreator( this.resourcePath || path, this.materialOptions );
+		materialCreator.setCrossOrigin( this.crossOrigin );
+		materialCreator.setManager( this.manager );
+		materialCreator.setMaterials( materialsInfo );
+		return materialCreator;
+
+	}
+
+}
+
+/**
+ * Material options of `MTLLoader`.
+ *
+ * @typedef {Object} MTLLoader~MaterialOptions
+ * @property {(FrontSide|BackSide|DoubleSide)} [side=FrontSide] - Which side to apply the material.
+ * @property {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} [wrap=RepeatWrapping] - What type of wrapping to apply for textures.
+ * @property {boolean} [normalizeRGB=false] - Whether RGB colors should be normalized to `0-1` from `0-255`.
+ * @property {boolean} [ignoreZeroRGBs=false] - Ignore values of RGBs (Ka,Kd,Ks) that are all 0's.
+ */
+
+class MaterialCreator {
+
+	constructor( baseUrl = '', options = {} ) {
+
+		this.baseUrl = baseUrl;
+		this.options = options;
+		this.materialsInfo = {};
+		this.materials = {};
+		this.materialsArray = [];
+		this.nameLookup = {};
+
+		this.crossOrigin = 'anonymous';
+
+		this.side = ( this.options.side !== undefined ) ? this.options.side : FrontSide;
+		this.wrap = ( this.options.wrap !== undefined ) ? this.options.wrap : RepeatWrapping;
+
+	}
+
+	setCrossOrigin( value ) {
+
+		this.crossOrigin = value;
+		return this;
+
+	}
+
+	setManager( value ) {
+
+		this.manager = value;
+
+	}
+
+	setMaterials( materialsInfo ) {
+
+		this.materialsInfo = this.convert( materialsInfo );
+		this.materials = {};
+		this.materialsArray = [];
+		this.nameLookup = {};
+
+	}
+
+	convert( materialsInfo ) {
+
+		if ( ! this.options ) return materialsInfo;
+
+		const converted = {};
+
+		for ( const mn in materialsInfo ) {
+
+			// Convert materials info into normalized form based on options
+
+			const mat = materialsInfo[ mn ];
+
+			const covmat = {};
+
+			converted[ mn ] = covmat;
+
+			for ( const prop in mat ) {
+
+				let save = true;
+				let value = mat[ prop ];
+				const lprop = prop.toLowerCase();
+
+				switch ( lprop ) {
+
+					case 'kd':
+					case 'ka':
+					case 'ks':
+
+						// Diffuse color (color under white light) using RGB values
+
+						if ( this.options && this.options.normalizeRGB ) {
+
+							value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
+
+						}
+
+						if ( this.options && this.options.ignoreZeroRGBs ) {
+
+							if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
+
+								// ignore
+
+								save = false;
+
+							}
+
+						}
+
+						break;
+
+					default:
+
+						break;
+
+				}
+
+				if ( save ) {
+
+					covmat[ lprop ] = value;
+
+				}
+
+			}
+
+		}
+
+		return converted;
+
+	}
+
+	preload() {
+
+		for ( const mn in this.materialsInfo ) {
+
+			this.create( mn );
+
+		}
+
+	}
+
+	getIndex( materialName ) {
+
+		return this.nameLookup[ materialName ];
+
+	}
+
+	getAsArray() {
+
+		let index = 0;
+
+		for ( const mn in this.materialsInfo ) {
+
+			this.materialsArray[ index ] = this.create( mn );
+			this.nameLookup[ mn ] = index;
+			index ++;
+
+		}
+
+		return this.materialsArray;
+
+	}
+
+	create( materialName ) {
+
+		if ( this.materials[ materialName ] === undefined ) {
+
+			this.createMaterial_( materialName );
+
+		}
+
+		return this.materials[ materialName ];
+
+	}
+
+	createMaterial_( materialName ) {
+
+		// Create material
+
+		const scope = this;
+		const mat = this.materialsInfo[ materialName ];
+		const params = {
+
+			name: materialName,
+			side: this.side
+
+		};
+
+		function resolveURL( baseUrl, url ) {
+
+			if ( typeof url !== 'string' || url === '' )
+				return '';
+
+			// Absolute URL
+			if ( /^https?:\/\//i.test( url ) ) return url;
+
+			return baseUrl + url;
+
+		}
+
+		function setMapForType( mapType, value ) {
+
+			if ( params[ mapType ] ) return; // Keep the first encountered texture
+
+			const texParams = scope.getTextureParams( value, params );
+			const map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
+
+			map.repeat.copy( texParams.scale );
+			map.offset.copy( texParams.offset );
+
+			map.wrapS = scope.wrap;
+			map.wrapT = scope.wrap;
+
+			if ( mapType === 'map' || mapType === 'emissiveMap' ) {
+
+				map.colorSpace = SRGBColorSpace;
+
+			}
+
+			params[ mapType ] = map;
+
+		}
+
+		for ( const prop in mat ) {
+
+			const value = mat[ prop ];
+			let n;
+
+			if ( value === '' ) continue;
+
+			switch ( prop.toLowerCase() ) {
+
+				// Ns is material specular exponent
+
+				case 'kd':
+
+					// Diffuse color (color under white light) using RGB values
+
+					params.color = ColorManagement.colorSpaceToWorking( new Color().fromArray( value ), SRGBColorSpace );
+
+					break;
+
+				case 'ks':
+
+					// Specular color (color when light is reflected from shiny surface) using RGB values
+					params.specular = ColorManagement.colorSpaceToWorking( new Color().fromArray( value ), SRGBColorSpace );
+
+					break;
+
+				case 'ke':
+
+					// Emissive using RGB values
+					params.emissive = ColorManagement.colorSpaceToWorking( new Color().fromArray( value ), SRGBColorSpace );
+
+					break;
+
+				case 'map_kd':
+
+					// Diffuse texture map
+
+					setMapForType( 'map', value );
+
+					break;
+
+				case 'map_ks':
+
+					// Specular map
+
+					setMapForType( 'specularMap', value );
+
+					break;
+
+				case 'map_ke':
+
+					// Emissive map
+
+					setMapForType( 'emissiveMap', value );
+
+					break;
+
+				case 'norm':
+
+					setMapForType( 'normalMap', value );
+
+					break;
+
+				case 'map_bump':
+				case 'bump':
+
+					// Bump texture map
+
+					setMapForType( 'bumpMap', value );
+
+					break;
+
+				case 'disp':
+
+					// Displacement texture map
+
+					setMapForType( 'displacementMap', value );
+
+					break;
+
+				case 'map_d':
+
+					// Alpha map
+
+					setMapForType( 'alphaMap', value );
+					params.transparent = true;
+
+					break;
+
+				case 'ns':
+
+					// The specular exponent (defines the focus of the specular highlight)
+					// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
+
+					params.shininess = parseFloat( value );
+
+					break;
+
+				case 'd':
+					n = parseFloat( value );
+
+					if ( n < 1 ) {
+
+						params.opacity = n;
+						params.transparent = true;
+
+					}
+
+					break;
+
+				case 'tr':
+					n = parseFloat( value );
+
+					if ( this.options && this.options.invertTrProperty ) n = 1 - n;
+
+					if ( n > 0 ) {
+
+						params.opacity = 1 - n;
+						params.transparent = true;
+
+					}
+
+					break;
+
+				default:
+					break;
+
+			}
+
+		}
+
+		this.materials[ materialName ] = new MeshPhongMaterial( params );
+		return this.materials[ materialName ];
+
+	}
+
+	getTextureParams( value, matParams ) {
+
+		const texParams = {
+
+			scale: new Vector2( 1, 1 ),
+			offset: new Vector2( 0, 0 )
+
+		 };
+
+		const items = value.split( /\s+/ );
+		let pos;
+
+		pos = items.indexOf( '-bm' );
+
+		if ( pos >= 0 ) {
+
+			matParams.bumpScale = parseFloat( items[ pos + 1 ] );
+			items.splice( pos, 2 );
+
+		}
+
+		pos = items.indexOf( '-mm' );
+
+		if ( pos >= 0 ) {
+
+			matParams.displacementBias = parseFloat( items[ pos + 1 ] );
+			matParams.displacementScale = parseFloat( items[ pos + 2 ] );
+			items.splice( pos, 3 );
+
+		}
+
+		pos = items.indexOf( '-s' );
+
+		if ( pos >= 0 ) {
+
+			texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+			items.splice( pos, 4 ); // we expect 3 parameters here!
+
+		}
+
+		pos = items.indexOf( '-o' );
+
+		if ( pos >= 0 ) {
+
+			texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+			items.splice( pos, 4 ); // we expect 3 parameters here!
+
+		}
+
+		texParams.url = items.join( ' ' ).trim();
+		return texParams;
+
+	}
+
+	loadTexture( url, mapping, onLoad, onProgress, onError ) {
+
+		const manager = ( this.manager !== undefined ) ? this.manager : DefaultLoadingManager;
+		let loader = manager.getHandler( url );
+
+		if ( loader === null ) {
+
+			loader = new TextureLoader( manager );
+
+		}
+
+		if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
+
+		const texture = loader.load( url, onLoad, onProgress, onError );
+
+		if ( mapping !== undefined ) texture.mapping = mapping;
+
+		return texture;
+
+	}
+
+}
+
+export { MTLLoader };

+ 955 - 0
packages/pc/public/modelLook/vendor/addons/loaders/OBJLoader.js

@@ -0,0 +1,955 @@
+import {
+	BufferGeometry,
+	FileLoader,
+	Float32BufferAttribute,
+	Group,
+	LineBasicMaterial,
+	LineSegments,
+	Loader,
+	Material,
+	Mesh,
+	MeshPhongMaterial,
+	Points,
+	PointsMaterial,
+	Vector3,
+	Color,
+	SRGBColorSpace
+} from 'three';
+
+// o object_name | g group_name
+const _object_pattern = /^[og]\s*(.+)?/;
+// mtllib file_reference
+const _material_library_pattern = /^mtllib /;
+// usemtl material_name
+const _material_use_pattern = /^usemtl /;
+// usemap map_name
+const _map_use_pattern = /^usemap /;
+const _face_vertex_data_separator_pattern = /\s+/;
+
+const _vA = new Vector3();
+const _vB = new Vector3();
+const _vC = new Vector3();
+
+const _ab = new Vector3();
+const _cb = new Vector3();
+
+const _color = new Color();
+
+function ParserState() {
+
+	const state = {
+		objects: [],
+		object: {},
+
+		vertices: [],
+		normals: [],
+		colors: [],
+		uvs: [],
+
+		materials: {},
+		materialLibraries: [],
+
+		startObject: function ( name, fromDeclaration ) {
+
+			// If the current object (initial from reset) is not from a g/o declaration in the parsed
+			// file. We need to use it for the first parsed g/o to keep things in sync.
+			if ( this.object && this.object.fromDeclaration === false ) {
+
+				this.object.name = name;
+				this.object.fromDeclaration = ( fromDeclaration !== false );
+				return;
+
+			}
+
+			const previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );
+
+			if ( this.object && typeof this.object._finalize === 'function' ) {
+
+				this.object._finalize( true );
+
+			}
+
+			this.object = {
+				name: name || '',
+				fromDeclaration: ( fromDeclaration !== false ),
+
+				geometry: {
+					vertices: [],
+					normals: [],
+					colors: [],
+					uvs: [],
+					hasUVIndices: false
+				},
+				materials: [],
+				smooth: true,
+
+				startMaterial: function ( name, libraries ) {
+
+					const previous = this._finalize( false );
+
+					// New usemtl declaration overwrites an inherited material, except if faces were declared
+					// after the material, then it must be preserved for proper MultiMaterial continuation.
+					if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {
+
+						this.materials.splice( previous.index, 1 );
+
+					}
+
+					const material = {
+						index: this.materials.length,
+						name: name || '',
+						mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
+						smooth: ( previous !== undefined ? previous.smooth : this.smooth ),
+						groupStart: ( previous !== undefined ? previous.groupEnd : 0 ),
+						groupEnd: - 1,
+						groupCount: - 1,
+						inherited: false,
+
+						clone: function ( index ) {
+
+							const cloned = {
+								index: ( typeof index === 'number' ? index : this.index ),
+								name: this.name,
+								mtllib: this.mtllib,
+								smooth: this.smooth,
+								groupStart: 0,
+								groupEnd: - 1,
+								groupCount: - 1,
+								inherited: false
+							};
+							cloned.clone = this.clone.bind( cloned );
+							return cloned;
+
+						}
+					};
+
+					this.materials.push( material );
+
+					return material;
+
+				},
+
+				currentMaterial: function () {
+
+					if ( this.materials.length > 0 ) {
+
+						return this.materials[ this.materials.length - 1 ];
+
+					}
+
+					return undefined;
+
+				},
+
+				_finalize: function ( end ) {
+
+					const lastMultiMaterial = this.currentMaterial();
+					if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) {
+
+						lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
+						lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
+						lastMultiMaterial.inherited = false;
+
+					}
+
+					// Ignore objects tail materials if no face declarations followed them before a new o/g started.
+					if ( end && this.materials.length > 1 ) {
+
+						for ( let mi = this.materials.length - 1; mi >= 0; mi -- ) {
+
+							if ( this.materials[ mi ].groupCount <= 0 ) {
+
+								this.materials.splice( mi, 1 );
+
+							}
+
+						}
+
+					}
+
+					// Guarantee at least one empty material, this makes the creation later more straight forward.
+					if ( end && this.materials.length === 0 ) {
+
+						this.materials.push( {
+							name: '',
+							smooth: this.smooth
+						} );
+
+					}
+
+					return lastMultiMaterial;
+
+				}
+			};
+
+			// Inherit previous objects material.
+			// Spec tells us that a declared material must be set to all objects until a new material is declared.
+			// If a usemtl declaration is encountered while this new object is being parsed, it will
+			// overwrite the inherited material. Exception being that there was already face declarations
+			// to the inherited material, then it will be preserved for proper MultiMaterial continuation.
+
+			if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {
+
+				const declared = previousMaterial.clone( 0 );
+				declared.inherited = true;
+				this.object.materials.push( declared );
+
+			}
+
+			this.objects.push( this.object );
+
+		},
+
+		finalize: function () {
+
+			if ( this.object && typeof this.object._finalize === 'function' ) {
+
+				this.object._finalize( true );
+
+			}
+
+		},
+
+		parseVertexIndex: function ( value, len ) {
+
+			const index = parseInt( value, 10 );
+			return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+		},
+
+		parseNormalIndex: function ( value, len ) {
+
+			const index = parseInt( value, 10 );
+			return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+		},
+
+		parseUVIndex: function ( value, len ) {
+
+			const index = parseInt( value, 10 );
+			return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
+
+		},
+
+		addVertex: function ( a, b, c ) {
+
+			const src = this.vertices;
+			const dst = this.object.geometry.vertices;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+			dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+			dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+		},
+
+		addVertexPoint: function ( a ) {
+
+			const src = this.vertices;
+			const dst = this.object.geometry.vertices;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+		},
+
+		addVertexLine: function ( a ) {
+
+			const src = this.vertices;
+			const dst = this.object.geometry.vertices;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+		},
+
+		addNormal: function ( a, b, c ) {
+
+			const src = this.normals;
+			const dst = this.object.geometry.normals;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+			dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+			dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+		},
+
+		addFaceNormal: function ( a, b, c ) {
+
+			const src = this.vertices;
+			const dst = this.object.geometry.normals;
+
+			_vA.fromArray( src, a );
+			_vB.fromArray( src, b );
+			_vC.fromArray( src, c );
+
+			_cb.subVectors( _vC, _vB );
+			_ab.subVectors( _vA, _vB );
+			_cb.cross( _ab );
+
+			_cb.normalize();
+
+			dst.push( _cb.x, _cb.y, _cb.z );
+			dst.push( _cb.x, _cb.y, _cb.z );
+			dst.push( _cb.x, _cb.y, _cb.z );
+
+		},
+
+		addColor: function ( a, b, c ) {
+
+			const src = this.colors;
+			const dst = this.object.geometry.colors;
+
+			if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+			if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+			if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+		},
+
+		addUV: function ( a, b, c ) {
+
+			const src = this.uvs;
+			const dst = this.object.geometry.uvs;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ] );
+			dst.push( src[ b + 0 ], src[ b + 1 ] );
+			dst.push( src[ c + 0 ], src[ c + 1 ] );
+
+		},
+
+		addDefaultUV: function () {
+
+			const dst = this.object.geometry.uvs;
+
+			dst.push( 0, 0 );
+			dst.push( 0, 0 );
+			dst.push( 0, 0 );
+
+		},
+
+		addUVLine: function ( a ) {
+
+			const src = this.uvs;
+			const dst = this.object.geometry.uvs;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ] );
+
+		},
+
+		addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {
+
+			const vLen = this.vertices.length;
+
+			let ia = this.parseVertexIndex( a, vLen );
+			let ib = this.parseVertexIndex( b, vLen );
+			let ic = this.parseVertexIndex( c, vLen );
+
+			this.addVertex( ia, ib, ic );
+			this.addColor( ia, ib, ic );
+
+			// normals
+
+			if ( na !== undefined && na !== '' ) {
+
+				const nLen = this.normals.length;
+
+				ia = this.parseNormalIndex( na, nLen );
+				ib = this.parseNormalIndex( nb, nLen );
+				ic = this.parseNormalIndex( nc, nLen );
+
+				this.addNormal( ia, ib, ic );
+
+			} else {
+
+				this.addFaceNormal( ia, ib, ic );
+
+			}
+
+			// uvs
+
+			if ( ua !== undefined && ua !== '' ) {
+
+				const uvLen = this.uvs.length;
+
+				ia = this.parseUVIndex( ua, uvLen );
+				ib = this.parseUVIndex( ub, uvLen );
+				ic = this.parseUVIndex( uc, uvLen );
+
+				this.addUV( ia, ib, ic );
+
+				this.object.geometry.hasUVIndices = true;
+
+			} else {
+
+				// add placeholder values (for inconsistent face definitions)
+
+				this.addDefaultUV();
+
+			}
+
+		},
+
+		addPointGeometry: function ( vertices ) {
+
+			this.object.geometry.type = 'Points';
+
+			const vLen = this.vertices.length;
+
+			for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+				const index = this.parseVertexIndex( vertices[ vi ], vLen );
+
+				this.addVertexPoint( index );
+				this.addColor( index );
+
+			}
+
+		},
+
+		addLineGeometry: function ( vertices, uvs ) {
+
+			this.object.geometry.type = 'Line';
+
+			const vLen = this.vertices.length;
+			const uvLen = this.uvs.length;
+
+			for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+				this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
+
+			}
+
+			for ( let uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
+
+				this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
+
+			}
+
+		}
+
+	};
+
+	state.startObject( '', false );
+
+	return state;
+
+}
+
+
+/**
+ * A loader for the OBJ format.
+ *
+ * The [OBJ format](https://en.wikipedia.org/wiki/Wavefront_.obj_file) is a simple data-format that
+ * represents 3D geometry in a human readable format as the position of each vertex, the UV position of
+ * each texture coordinate vertex, vertex normals, and the faces that make each polygon defined as a list
+ * of vertices, and texture vertices.
+ *
+ * ```js
+ * const loader = new OBJLoader();
+ * const object = await loader.loadAsync( 'models/monster.obj' );
+ * scene.add( object );
+ * ```
+ *
+ * @augments Loader
+ * @three_import import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
+ */
+class OBJLoader extends Loader {
+
+	/**
+	 * Constructs a new OBJ loader.
+	 *
+	 * @param {LoadingManager} [manager] - The loading manager.
+	 */
+	constructor( manager ) {
+
+		super( manager );
+
+		/**
+		 * A reference to a material creator.
+		 *
+		 * @type {?MaterialCreator}
+		 * @default null
+		 */
+		this.materials = null;
+
+	}
+
+	/**
+	 * Starts loading from the given URL and passes the loaded OBJ asset
+	 * to the `onLoad()` callback.
+	 *
+	 * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
+	 * @param {function(Group)} onLoad - Executed when the loading process has been finished.
+	 * @param {onProgressCallback} onProgress - Executed while the loading is in progress.
+	 * @param {onErrorCallback} onError - Executed when errors occur.
+	 */
+	load( url, onLoad, onProgress, onError ) {
+
+		const scope = this;
+
+		const loader = new FileLoader( this.manager );
+		loader.setPath( this.path );
+		loader.setRequestHeader( this.requestHeader );
+		loader.setWithCredentials( this.withCredentials );
+		loader.load( url, function ( text ) {
+
+			try {
+
+				onLoad( scope.parse( text ) );
+
+			} catch ( e ) {
+
+				if ( onError ) {
+
+					onError( e );
+
+				} else {
+
+					console.error( e );
+
+				}
+
+				scope.manager.itemError( url );
+
+			}
+
+		}, onProgress, onError );
+
+	}
+
+	/**
+	 * Sets the material creator for this OBJ. This object is loaded via {@link MTLLoader}.
+	 *
+	 * @param {MaterialCreator} materials - An object that creates the materials for this OBJ.
+	 * @return {OBJLoader} A reference to this loader.
+	 */
+	setMaterials( materials ) {
+
+		this.materials = materials;
+
+		return this;
+
+	}
+
+	/**
+	 * Parses the given OBJ data and returns the resulting group.
+	 *
+	 * @param {string} text - The raw OBJ data as a string.
+	 * @return {Group} The parsed OBJ.
+	 */
+	parse( text ) {
+
+		const state = new ParserState();
+
+		if ( text.indexOf( '\r\n' ) !== - 1 ) {
+
+			// This is faster than String.split with regex that splits on both
+			text = text.replace( /\r\n/g, '\n' );
+
+		}
+
+		if ( text.indexOf( '\\\n' ) !== - 1 ) {
+
+			// join lines separated by a line continuation character (\)
+			text = text.replace( /\\\n/g, '' );
+
+		}
+
+		const lines = text.split( '\n' );
+		let result = [];
+
+		for ( let i = 0, l = lines.length; i < l; i ++ ) {
+
+			const line = lines[ i ].trimStart();
+
+			if ( line.length === 0 ) continue;
+
+			const lineFirstChar = line.charAt( 0 );
+
+			// @todo invoke passed in handler if any
+			if ( lineFirstChar === '#' ) continue; // skip comments
+
+			if ( lineFirstChar === 'v' ) {
+
+				const data = line.split( _face_vertex_data_separator_pattern );
+
+				switch ( data[ 0 ] ) {
+
+					case 'v':
+						state.vertices.push(
+							parseFloat( data[ 1 ] ),
+							parseFloat( data[ 2 ] ),
+							parseFloat( data[ 3 ] )
+						);
+						if ( data.length >= 7 ) {
+
+							_color.setRGB(
+								parseFloat( data[ 4 ] ),
+								parseFloat( data[ 5 ] ),
+								parseFloat( data[ 6 ] ),
+								SRGBColorSpace
+							);
+
+							state.colors.push( _color.r, _color.g, _color.b );
+
+						} else {
+
+							// if no colors are defined, add placeholders so color and vertex indices match
+
+							state.colors.push( undefined, undefined, undefined );
+
+						}
+
+						break;
+					case 'vn':
+						state.normals.push(
+							parseFloat( data[ 1 ] ),
+							parseFloat( data[ 2 ] ),
+							parseFloat( data[ 3 ] )
+						);
+						break;
+					case 'vt':
+						state.uvs.push(
+							parseFloat( data[ 1 ] ),
+							parseFloat( data[ 2 ] )
+						);
+						break;
+
+				}
+
+			} else if ( lineFirstChar === 'f' ) {
+
+				const lineData = line.slice( 1 ).trim();
+				const vertexData = lineData.split( _face_vertex_data_separator_pattern );
+				const faceVertices = [];
+
+				// Parse the face vertex data into an easy to work with format
+
+				for ( let j = 0, jl = vertexData.length; j < jl; j ++ ) {
+
+					const vertex = vertexData[ j ];
+
+					if ( vertex.length > 0 ) {
+
+						const vertexParts = vertex.split( '/' );
+						faceVertices.push( vertexParts );
+
+					}
+
+				}
+
+				// Draw an edge between the first vertex and all subsequent vertices to form an n-gon
+
+				const v1 = faceVertices[ 0 ];
+
+				for ( let j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {
+
+					const v2 = faceVertices[ j ];
+					const v3 = faceVertices[ j + 1 ];
+
+					state.addFace(
+						v1[ 0 ], v2[ 0 ], v3[ 0 ],
+						v1[ 1 ], v2[ 1 ], v3[ 1 ],
+						v1[ 2 ], v2[ 2 ], v3[ 2 ]
+					);
+
+				}
+
+			} else if ( lineFirstChar === 'l' ) {
+
+				const lineParts = line.substring( 1 ).trim().split( ' ' );
+				let lineVertices = [];
+				const lineUVs = [];
+
+				if ( line.indexOf( '/' ) === - 1 ) {
+
+					lineVertices = lineParts;
+
+				} else {
+
+					for ( let li = 0, llen = lineParts.length; li < llen; li ++ ) {
+
+						const parts = lineParts[ li ].split( '/' );
+
+						if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] );
+						if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] );
+
+					}
+
+				}
+
+				state.addLineGeometry( lineVertices, lineUVs );
+
+			} else if ( lineFirstChar === 'p' ) {
+
+				const lineData = line.slice( 1 ).trim();
+				const pointData = lineData.split( ' ' );
+
+				state.addPointGeometry( pointData );
+
+			} else if ( ( result = _object_pattern.exec( line ) ) !== null ) {
+
+				// o object_name
+				// or
+				// g group_name
+
+				// WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
+				// let name = result[ 0 ].slice( 1 ).trim();
+				const name = ( ' ' + result[ 0 ].slice( 1 ).trim() ).slice( 1 );
+
+				state.startObject( name );
+
+			} else if ( _material_use_pattern.test( line ) ) {
+
+				// material
+
+				state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );
+
+			} else if ( _material_library_pattern.test( line ) ) {
+
+				// mtl file
+
+				state.materialLibraries.push( line.substring( 7 ).trim() );
+
+			} else if ( _map_use_pattern.test( line ) ) {
+
+				// the line is parsed but ignored since the loader assumes textures are defined MTL files
+				// (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method)
+
+				console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' );
+
+			} else if ( lineFirstChar === 's' ) {
+
+				result = line.split( ' ' );
+
+				// smooth shading
+
+				// @todo Handle files that have varying smooth values for a set of faces inside one geometry,
+				// but does not define a usemtl for each face set.
+				// This should be detected and a dummy material created (later MultiMaterial and geometry groups).
+				// This requires some care to not create extra material on each smooth value for "normal" obj files.
+				// where explicit usemtl defines geometry groups.
+				// Example asset: examples/models/obj/cerberus/Cerberus.obj
+
+				/*
+					 * http://paulbourke.net/dataformats/obj/
+					 *
+					 * From chapter "Grouping" Syntax explanation "s group_number":
+					 * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
+					 * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
+					 * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
+					 * than 0."
+					 */
+				if ( result.length > 1 ) {
+
+					const value = result[ 1 ].trim().toLowerCase();
+					state.object.smooth = ( value !== '0' && value !== 'off' );
+
+				} else {
+
+					// ZBrush can produce "s" lines #11707
+					state.object.smooth = true;
+
+				}
+
+				const material = state.object.currentMaterial();
+				if ( material ) material.smooth = state.object.smooth;
+
+			} else {
+
+				// Handle null terminated files without exception
+				if ( line === '\0' ) continue;
+
+				console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' );
+
+			}
+
+		}
+
+		state.finalize();
+
+		const container = new Group();
+		container.materialLibraries = [].concat( state.materialLibraries );
+
+		const hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 );
+
+		if ( hasPrimitives === true ) {
+
+			for ( let i = 0, l = state.objects.length; i < l; i ++ ) {
+
+				const object = state.objects[ i ];
+				const geometry = object.geometry;
+				const materials = object.materials;
+				const isLine = ( geometry.type === 'Line' );
+				const isPoints = ( geometry.type === 'Points' );
+				let hasVertexColors = false;
+
+				// Skip o/g line declarations that did not follow with any faces
+				if ( geometry.vertices.length === 0 ) continue;
+
+				const buffergeometry = new BufferGeometry();
+
+				buffergeometry.setAttribute( 'position', new Float32BufferAttribute( geometry.vertices, 3 ) );
+
+				if ( geometry.normals.length > 0 ) {
+
+					buffergeometry.setAttribute( 'normal', new Float32BufferAttribute( geometry.normals, 3 ) );
+
+				}
+
+				if ( geometry.colors.length > 0 ) {
+
+					hasVertexColors = true;
+					buffergeometry.setAttribute( 'color', new Float32BufferAttribute( geometry.colors, 3 ) );
+
+				}
+
+				if ( geometry.hasUVIndices === true ) {
+
+					buffergeometry.setAttribute( 'uv', new Float32BufferAttribute( geometry.uvs, 2 ) );
+
+				}
+
+				// Create materials
+
+				const createdMaterials = [];
+
+				for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+					const sourceMaterial = materials[ mi ];
+					const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors;
+					let material = state.materials[ materialHash ];
+
+					if ( this.materials !== null ) {
+
+						material = this.materials.create( sourceMaterial.name );
+
+						// mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
+						if ( isLine && material && ! ( material instanceof LineBasicMaterial ) ) {
+
+							const materialLine = new LineBasicMaterial();
+							Material.prototype.copy.call( materialLine, material );
+							materialLine.color.copy( material.color );
+							material = materialLine;
+
+						} else if ( isPoints && material && ! ( material instanceof PointsMaterial ) ) {
+
+							const materialPoints = new PointsMaterial( { size: 10, sizeAttenuation: false } );
+							Material.prototype.copy.call( materialPoints, material );
+							materialPoints.color.copy( material.color );
+							materialPoints.map = material.map;
+							material = materialPoints;
+
+						}
+
+					}
+
+					if ( material === undefined ) {
+
+						if ( isLine ) {
+
+							material = new LineBasicMaterial();
+
+						} else if ( isPoints ) {
+
+							material = new PointsMaterial( { size: 1, sizeAttenuation: false } );
+
+						} else {
+
+							material = new MeshPhongMaterial();
+
+						}
+
+						material.name = sourceMaterial.name;
+						material.flatShading = sourceMaterial.smooth ? false : true;
+						material.vertexColors = hasVertexColors;
+
+						state.materials[ materialHash ] = material;
+
+					}
+
+					createdMaterials.push( material );
+
+				}
+
+				// Create mesh
+
+				let mesh;
+
+				if ( createdMaterials.length > 1 ) {
+
+					for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+						const sourceMaterial = materials[ mi ];
+						buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );
+
+					}
+
+					if ( isLine ) {
+
+						mesh = new LineSegments( buffergeometry, createdMaterials );
+
+					} else if ( isPoints ) {
+
+						mesh = new Points( buffergeometry, createdMaterials );
+
+					} else {
+
+						mesh = new Mesh( buffergeometry, createdMaterials );
+
+					}
+
+				} else {
+
+					if ( isLine ) {
+
+						mesh = new LineSegments( buffergeometry, createdMaterials[ 0 ] );
+
+					} else if ( isPoints ) {
+
+						mesh = new Points( buffergeometry, createdMaterials[ 0 ] );
+
+					} else {
+
+						mesh = new Mesh( buffergeometry, createdMaterials[ 0 ] );
+
+					}
+
+				}
+
+				mesh.name = object.name;
+
+				container.add( mesh );
+
+			}
+
+		} else {
+
+			// if there is only the default parser state object with no geometry data, interpret data as point cloud
+
+			if ( state.vertices.length > 0 ) {
+
+				const material = new PointsMaterial( { size: 1, sizeAttenuation: false } );
+
+				const buffergeometry = new BufferGeometry();
+
+				buffergeometry.setAttribute( 'position', new Float32BufferAttribute( state.vertices, 3 ) );
+
+				if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) {
+
+					buffergeometry.setAttribute( 'color', new Float32BufferAttribute( state.colors, 3 ) );
+					material.vertexColors = true;
+
+				}
+
+				const points = new Points( buffergeometry, material );
+				container.add( points );
+
+			}
+
+		}
+
+		return container;
+
+	}
+
+}
+
+export { OBJLoader };

+ 538 - 0
packages/pc/public/modelLook/vendor/addons/loaders/TGALoader.js

@@ -0,0 +1,538 @@
+import {
+	DataTextureLoader,
+	LinearMipmapLinearFilter
+} from 'three';
+
+/**
+ * A loader for the TGA texture format.
+ *
+ * ```js
+ * const loader = new TGALoader();
+ * const texture = await loader.loadAsync( 'textures/crate_color8.tga' );
+ * texture.colorSpace = THREE.SRGBColorSpace; // only for color textures
+ * ```
+ *
+ * @augments DataTextureLoader
+ * @three_import import { TGALoader } from 'three/addons/loaders/TGALoader.js';
+ */
+class TGALoader extends DataTextureLoader {
+
+	/**
+	 * Constructs a new TGA loader.
+	 *
+	 * @param {LoadingManager} [manager] - The loading manager.
+	 */
+	constructor( manager ) {
+
+		super( manager );
+
+	}
+
+	/**
+	 * Parses the given TGA texture data.
+	 *
+	 * @param {ArrayBuffer} buffer - The raw texture data.
+	 * @return {DataTextureLoader~TexData} An object representing the parsed texture data.
+	 */
+	parse( buffer ) {
+
+		// reference from vthibault, https://github.com/vthibault/roBrowser/blob/master/src/Loaders/Targa.js
+
+		function tgaCheckHeader( header ) {
+
+			switch ( header.image_type ) {
+
+				// check indexed type
+
+				case TGA_TYPE_INDEXED:
+				case TGA_TYPE_RLE_INDEXED:
+					if ( header.colormap_length > 256 || header.colormap_size !== 24 || header.colormap_type !== 1 ) {
+
+						throw new Error( 'THREE.TGALoader: Invalid type colormap data for indexed type.' );
+
+					}
+
+					break;
+
+					// check colormap type
+
+				case TGA_TYPE_RGB:
+				case TGA_TYPE_GREY:
+				case TGA_TYPE_RLE_RGB:
+				case TGA_TYPE_RLE_GREY:
+					if ( header.colormap_type ) {
+
+						throw new Error( 'THREE.TGALoader: Invalid type colormap data for colormap type.' );
+
+					}
+
+					break;
+
+					// What the need of a file without data ?
+
+				case TGA_TYPE_NO_DATA:
+					throw new Error( 'THREE.TGALoader: No data.' );
+
+					// Invalid type ?
+
+				default:
+					throw new Error( 'THREE.TGALoader: Invalid type ' + header.image_type );
+
+			}
+
+			// check image width and height
+
+			if ( header.width <= 0 || header.height <= 0 ) {
+
+				throw new Error( 'THREE.TGALoader: Invalid image size.' );
+
+			}
+
+			// check image pixel size
+
+			if ( header.pixel_size !== 8 && header.pixel_size !== 16 &&
+				header.pixel_size !== 24 && header.pixel_size !== 32 ) {
+
+				throw new Error( 'THREE.TGALoader: Invalid pixel size ' + header.pixel_size );
+
+			}
+
+		}
+
+		// parse tga image buffer
+
+		function tgaParse( use_rle, use_pal, header, offset, data ) {
+
+			let pixel_data,
+				palettes;
+
+			const pixel_size = header.pixel_size >> 3;
+			const pixel_total = header.width * header.height * pixel_size;
+
+			 // read palettes
+
+			 if ( use_pal ) {
+
+				 palettes = data.subarray( offset, offset += header.colormap_length * ( header.colormap_size >> 3 ) );
+
+			 }
+
+			 // read RLE
+
+			 if ( use_rle ) {
+
+				 pixel_data = new Uint8Array( pixel_total );
+
+				let c, count, i;
+				let shift = 0;
+				const pixels = new Uint8Array( pixel_size );
+
+				while ( shift < pixel_total ) {
+
+					c = data[ offset ++ ];
+					count = ( c & 0x7f ) + 1;
+
+					// RLE pixels
+
+					if ( c & 0x80 ) {
+
+						// bind pixel tmp array
+
+						for ( i = 0; i < pixel_size; ++ i ) {
+
+							pixels[ i ] = data[ offset ++ ];
+
+						}
+
+						// copy pixel array
+
+						for ( i = 0; i < count; ++ i ) {
+
+							pixel_data.set( pixels, shift + i * pixel_size );
+
+						}
+
+						shift += pixel_size * count;
+
+					} else {
+
+						// raw pixels
+
+						count *= pixel_size;
+
+						for ( i = 0; i < count; ++ i ) {
+
+							pixel_data[ shift + i ] = data[ offset ++ ];
+
+						}
+
+						shift += count;
+
+					}
+
+				}
+
+			 } else {
+
+				// raw pixels
+
+				pixel_data = data.subarray(
+					 offset, offset += ( use_pal ? header.width * header.height : pixel_total )
+				);
+
+			 }
+
+			 return {
+				pixel_data: pixel_data,
+				palettes: palettes
+			 };
+
+		}
+
+		function tgaGetImageData8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image, palettes ) {
+
+			const colormap = palettes;
+			let color, i = 0, x, y;
+			const width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
+
+					color = image[ i ];
+					imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+					imageData[ ( x + width * y ) * 4 + 2 ] = colormap[ ( color * 3 ) + 0 ];
+					imageData[ ( x + width * y ) * 4 + 1 ] = colormap[ ( color * 3 ) + 1 ];
+					imageData[ ( x + width * y ) * 4 + 0 ] = colormap[ ( color * 3 ) + 2 ];
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageData16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			let color, i = 0, x, y;
+			const width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
+
+					color = image[ i + 0 ] + ( image[ i + 1 ] << 8 );
+					imageData[ ( x + width * y ) * 4 + 0 ] = ( color & 0x7C00 ) >> 7;
+					imageData[ ( x + width * y ) * 4 + 1 ] = ( color & 0x03E0 ) >> 2;
+					imageData[ ( x + width * y ) * 4 + 2 ] = ( color & 0x001F ) << 3;
+					imageData[ ( x + width * y ) * 4 + 3 ] = ( color & 0x8000 ) ? 0 : 255;
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageData24bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			let i = 0, x, y;
+			const width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i += 3 ) {
+
+					imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+					imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
+					imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageData32bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			let i = 0, x, y;
+			const width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i += 4 ) {
+
+					imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
+					imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
+					imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 3 ];
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageDataGrey8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			let color, i = 0, x, y;
+			const width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
+
+					color = image[ i ];
+					imageData[ ( x + width * y ) * 4 + 0 ] = color;
+					imageData[ ( x + width * y ) * 4 + 1 ] = color;
+					imageData[ ( x + width * y ) * 4 + 2 ] = color;
+					imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageDataGrey16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			let i = 0, x, y;
+			const width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
+
+					imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 1 ];
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function getTgaRGBA( data, width, height, image, palette ) {
+
+			let x_start,
+				y_start,
+				x_step,
+				y_step,
+				x_end,
+				y_end;
+
+			switch ( ( header.flags & TGA_ORIGIN_MASK ) >> TGA_ORIGIN_SHIFT ) {
+
+				default:
+				case TGA_ORIGIN_UL:
+					x_start = 0;
+					x_step = 1;
+					x_end = width;
+					y_start = 0;
+					y_step = 1;
+					y_end = height;
+					break;
+
+				case TGA_ORIGIN_BL:
+					x_start = 0;
+					x_step = 1;
+					x_end = width;
+					y_start = height - 1;
+					y_step = - 1;
+					y_end = - 1;
+					break;
+
+				case TGA_ORIGIN_UR:
+					x_start = width - 1;
+					x_step = - 1;
+					x_end = - 1;
+					y_start = 0;
+					y_step = 1;
+					y_end = height;
+					break;
+
+				case TGA_ORIGIN_BR:
+					x_start = width - 1;
+					x_step = - 1;
+					x_end = - 1;
+					y_start = height - 1;
+					y_step = - 1;
+					y_end = - 1;
+					break;
+
+			}
+
+			if ( use_grey ) {
+
+				switch ( header.pixel_size ) {
+
+					case 8:
+						tgaGetImageDataGrey8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					case 16:
+						tgaGetImageDataGrey16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					default:
+						throw new Error( 'THREE.TGALoader: Format not supported.' );
+
+				}
+
+			} else {
+
+				switch ( header.pixel_size ) {
+
+					case 8:
+						tgaGetImageData8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image, palette );
+						break;
+
+					case 16:
+						tgaGetImageData16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					case 24:
+						tgaGetImageData24bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					case 32:
+						tgaGetImageData32bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					default:
+						throw new Error( 'THREE.TGALoader: Format not supported.' );
+
+				}
+
+			}
+
+			// Load image data according to specific method
+			// let func = 'tgaGetImageData' + (use_grey ? 'Grey' : '') + (header.pixel_size) + 'bits';
+			// func(data, y_start, y_step, y_end, x_start, x_step, x_end, width, image, palette );
+			return data;
+
+		}
+
+		// TGA constants
+
+		const TGA_TYPE_NO_DATA = 0,
+			TGA_TYPE_INDEXED = 1,
+			TGA_TYPE_RGB = 2,
+			TGA_TYPE_GREY = 3,
+			TGA_TYPE_RLE_INDEXED = 9,
+			TGA_TYPE_RLE_RGB = 10,
+			TGA_TYPE_RLE_GREY = 11,
+
+			TGA_ORIGIN_MASK = 0x30,
+			TGA_ORIGIN_SHIFT = 0x04,
+			TGA_ORIGIN_BL = 0x00,
+			TGA_ORIGIN_BR = 0x01,
+			TGA_ORIGIN_UL = 0x02,
+			TGA_ORIGIN_UR = 0x03;
+
+		if ( buffer.length < 19 ) throw new Error( 'THREE.TGALoader: Not enough data to contain header.' );
+
+		let offset = 0;
+
+		const content = new Uint8Array( buffer ),
+			header = {
+				id_length: content[ offset ++ ],
+				colormap_type: content[ offset ++ ],
+				image_type: content[ offset ++ ],
+				colormap_index: content[ offset ++ ] | content[ offset ++ ] << 8,
+				colormap_length: content[ offset ++ ] | content[ offset ++ ] << 8,
+				colormap_size: content[ offset ++ ],
+				origin: [
+					content[ offset ++ ] | content[ offset ++ ] << 8,
+					content[ offset ++ ] | content[ offset ++ ] << 8
+				],
+				width: content[ offset ++ ] | content[ offset ++ ] << 8,
+				height: content[ offset ++ ] | content[ offset ++ ] << 8,
+				pixel_size: content[ offset ++ ],
+				flags: content[ offset ++ ]
+			};
+
+		// check tga if it is valid format
+
+		tgaCheckHeader( header );
+
+		if ( header.id_length + offset > buffer.length ) {
+
+			throw new Error( 'THREE.TGALoader: No data.' );
+
+		}
+
+		// skip the needn't data
+
+		offset += header.id_length;
+
+		// get targa information about RLE compression and palette
+
+		let use_rle = false,
+			use_pal = false,
+			use_grey = false;
+
+		switch ( header.image_type ) {
+
+			case TGA_TYPE_RLE_INDEXED:
+				use_rle = true;
+				use_pal = true;
+				break;
+
+			case TGA_TYPE_INDEXED:
+				use_pal = true;
+				break;
+
+			case TGA_TYPE_RLE_RGB:
+				use_rle = true;
+				break;
+
+			case TGA_TYPE_RGB:
+				break;
+
+			case TGA_TYPE_RLE_GREY:
+				use_rle = true;
+				use_grey = true;
+				break;
+
+			case TGA_TYPE_GREY:
+				use_grey = true;
+				break;
+
+		}
+
+		//
+
+		const imageData = new Uint8Array( header.width * header.height * 4 );
+		const result = tgaParse( use_rle, use_pal, header, offset, content );
+		getTgaRGBA( imageData, header.width, header.height, result.pixel_data, result.palettes );
+
+		return {
+
+			data: imageData,
+			width: header.width,
+			height: header.height,
+			flipY: true,
+			generateMipmaps: true,
+			minFilter: LinearMipmapLinearFilter,
+
+		};
+
+	}
+
+}
+
+export { TGALoader };

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 6 - 0
packages/pc/public/modelLook/vendor/three.core.min.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 6 - 0
packages/pc/public/modelLook/vendor/three.module.min.js


+ 35 - 2
packages/pc/src/views/Scroll/index.vue

@@ -282,7 +282,8 @@
 </template>
 
 <script setup>
-import { ref, onBeforeUnmount, nextTick } from "vue";
+import { ref, onBeforeUnmount, nextTick, watch, computed } from "vue";
+import { useRoute } from "vue-router";
 import { useBaseStore } from "@/stores/base";
 import { storeToRefs } from "pinia";
 import { Swiper, SwiperSlide } from "swiper/vue";
@@ -318,6 +319,8 @@ import Tabbar from "@/components/Tabbar/index.vue";
 import VideoAni from "@/components/VideoAni/index.vue";
 
 const BASE_URL = import.meta.env.BASE_URL;
+const route = useRoute();
+const scrollToRight = computed(() => route.query.scroll === "true");
 const baseStore = useBaseStore();
 const { mediaUrlMap, scrollTranslate } = storeToRefs(baseStore);
 const showVideo = ref(false);
@@ -336,16 +339,46 @@ const handleSaveType = (type) => {
   model1Type.value = type + 1;
 };
 
+const scrollToRightOnInit = () => {
+  if (!swiperRef.value) return;
+  const swiper = swiperRef.value;
+  const maxTranslate =
+    typeof swiper.maxTranslate === "function" ? swiper.maxTranslate() : 0;
+  if (maxTranslate === 0) return;
+
+  if (typeof swiper.setTransition === "function") {
+    swiper.setTransition(route.query.aniTime || 20000);
+  }
+  swiper.setTranslate(maxTranslate);
+};
+
 const onSwiper = (swiper) => {
   swiperRef.value = swiper;
 
   nextTick(() => {
-    if (swiperRef.value) {
+    if (!swiperRef.value) return;
+    if (scrollToRight.value) {
+      scrollToRightOnInit();
+    } else {
       swiperRef.value.setTranslate(scrollTranslate.value || 0);
     }
   });
 };
 
+watch(
+  () => [route.query.scroll, swiperRef.value],
+  (value) => {
+    if (value === "true" && swiperRef.value) {
+      setTimeout(() => {
+        scrollToRightOnInit();
+      }, 1000);
+    }
+  },
+  {
+    immediate: true,
+  },
+);
+
 onBeforeUnmount(() => {
   if (swiperRef.value && swiperRef.value.getTranslate) {
     baseStore.setScrollTranslate(swiperRef.value.getTranslate() || 0);

+ 1 - 0
packages/temp-pc/.env

@@ -0,0 +1 @@
+VITE_PUBLIC_URL=.

+ 30 - 0
packages/temp-pc/.gitignore

@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo

+ 105 - 0
packages/temp-pc/index.html

@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" href="/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>山东博物馆</title>
+    <style>
+      * {
+        margin: 0;
+        padding: 0;
+        box-sizing: border-box;
+      }
+
+      #app-container {
+        width: 100vw;
+        height: 100vh;
+        overflow-y: auto;
+      }
+
+      #app {
+        width: 100%;
+        height: 100%;
+      }
+
+      @media (max-width: 768px) and (orientation: portrait) {
+        body:not(.in-iframe) #app-container {
+          position: fixed;
+          top: 50%;
+          left: 50%;
+          width: 100vh;
+          height: 100vw;
+          transform: translate(-50%, -50%) rotate(90deg);
+          transform-origin: center center;
+        }
+
+        body:not(.in-iframe) #app {
+          display: none;
+        }
+
+        body:not(.in-iframe) #app-iframe {
+          display: block;
+          width: 100%;
+          height: 100%;
+          border: none;
+        }
+      }
+
+      body.in-iframe #app-container {
+        width: 100%;
+        height: 100%;
+      }
+
+      body.in-iframe #app-iframe {
+        display: none;
+      }
+
+      #app-iframe {
+        display: none;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app-container">
+      <div id="app"></div>
+      <iframe id="app-iframe"></iframe>
+    </div>
+    <script>
+      if (window.self !== window.top) {
+        document.body.classList.add("in-iframe");
+      }
+
+      if (window.self === window.top) {
+        function handleOrientation() {
+          const app = document.getElementById("app");
+          const iframe = document.getElementById("app-iframe");
+
+          if (
+            window.innerWidth <= 768 &&
+            window.innerHeight > window.innerWidth
+          ) {
+            app.style.display = "none";
+            iframe.style.display = "block";
+            if (!iframe.src) {
+              const url = new URL(window.location.href);
+              url.searchParams.set("iframe", "1");
+              iframe.src = url.toString();
+            }
+          } else {
+            app.style.display = "block";
+            iframe.style.display = "none";
+          }
+        }
+
+        handleOrientation();
+
+        window.addEventListener("resize", handleOrientation);
+        window.addEventListener("orientationchange", () => {
+          setTimeout(handleOrientation, 100);
+        });
+      }
+    </script>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 8 - 0
packages/temp-pc/jsconfig.json

@@ -0,0 +1,8 @@
+{
+  "compilerOptions": {
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"]
+}

+ 36 - 0
packages/temp-pc/package.json

@@ -0,0 +1,36 @@
+{
+  "name": "@shandong/pc",
+  "version": "0.0.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@element-plus/icons-vue": "^2.3.1",
+    "@shandong/base": "workspace:*",
+    "@vueuse/core": "^12.0.0",
+    "animate.css": "^4.1.1",
+    "autoprefixer": "^10.4.20",
+    "clipboard": "^2.0.11",
+    "element-plus": "^2.9.0",
+    "pinia": "^2.2.6",
+    "postcss-px-to-viewport": "^1.1.1",
+    "swiper": "^11.1.15",
+    "three": "^0.182.0",
+    "vue": "^3.5.13",
+    "vue-lazyload-next": "^0.0.2",
+    "vue-qrcode": "^2.2.2",
+    "vue-router": "^4.4.5",
+    "vue3-smooth-dnd": "^0.0.6"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^5.2.1",
+    "sass": "^1.82.0",
+    "unplugin-auto-import": "^0.18.6",
+    "unplugin-vue-components": "^0.27.5",
+    "vite": "^6.0.1"
+  }
+}

+ 23 - 0
packages/temp-pc/postcss.config.js

@@ -0,0 +1,23 @@
+export default {
+  plugins: {
+    autoprefixer: {},
+    "postcss-px-to-viewport": {
+      unitToConvert: "px", // 需要转换的单位,默认为"px"
+      viewportWidth: 1920, // 设计稿的视口宽度
+      unitPrecision: 5, // 单位转换后保留的精度
+      propList: ["*"], // 能转化为vw的属性列表
+      viewportUnit: "vw", // 希望使用的视口单位
+      fontViewportUnit: "vw", // 字体使用的视口单位
+      selectorBlackList: [], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
+      minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
+      mediaQuery: true, // 媒体查询里的单位是否需要转换单位
+      replace: true, //  是否直接更换属性值,而不添加备用属性
+      exclude: undefined, // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
+      include: undefined, // 如果设置了include,那将只有匹配到的文件才会被转换
+      landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
+      landscapeUnit: "vw", // 横屏时使用的单位
+      landscapeWidth: 1920, // 横屏时使用的视口宽度
+      exclude: [/node_modules/],
+    },
+  },
+};

BIN
packages/temp-pc/public/favicon.ico


BIN
packages/temp-pc/public/images/1000/Thumbs.db


BIN
packages/temp-pc/public/images/1000/img_gaobingbei.png


BIN
packages/temp-pc/public/images/1000/img_jindaishi.png


BIN
packages/temp-pc/public/images/1000/img_muyin.png


BIN
packages/temp-pc/public/images/1000/img_shouxinghu.png


BIN
packages/temp-pc/public/images/1000/img_yachouyue.png


BIN
packages/temp-pc/public/images/1000/img_yubi.png


BIN
packages/temp-pc/public/images/2000/Thumbs.db


BIN
packages/temp-pc/public/images/2000/img_gaobingbei.png


BIN
packages/temp-pc/public/images/2000/img_jindaishi.png


BIN
packages/temp-pc/public/images/2000/img_muyin.png


BIN
packages/temp-pc/public/images/2000/img_shouxinghu.png


BIN
packages/temp-pc/public/images/2000/img_yachouyue.png


BIN
packages/temp-pc/public/images/2000/img_yubi.png


BIN
packages/temp-pc/public/images/500/Thumbs.db


BIN
packages/temp-pc/public/images/500/img_bihua.png


BIN
packages/temp-pc/public/images/500/img_bihua_line.png


BIN
packages/temp-pc/public/images/500/img_chenhouhu.png


BIN
packages/temp-pc/public/images/500/img_chenhouhu_line.png


BIN
packages/temp-pc/public/images/500/img_gaobingbei.png


BIN
packages/temp-pc/public/images/500/img_gaobingbei_line.png


BIN
packages/temp-pc/public/images/500/img_jiaguwen.png


BIN
packages/temp-pc/public/images/500/img_jiaguwen_line.png


BIN
packages/temp-pc/public/images/500/img_jindaishi.png


BIN
packages/temp-pc/public/images/500/img_jindaishi_line.png


BIN
packages/temp-pc/public/images/500/img_muyin.png


BIN
packages/temp-pc/public/images/500/img_muyin_line.png


BIN
packages/temp-pc/public/images/500/img_shouxinghu.png


BIN
packages/temp-pc/public/images/500/img_shouxinghu_line.png


BIN
packages/temp-pc/public/images/500/img_tuzhou.png


BIN
packages/temp-pc/public/images/500/img_tuzhou_line.png


BIN
packages/temp-pc/public/images/500/img_yachouyue.png


BIN
packages/temp-pc/public/images/500/img_yachouyue_line.png


BIN
packages/temp-pc/public/images/500/img_yubi.png


BIN
packages/temp-pc/public/images/500/img_yubi_line.png


BIN
packages/temp-pc/public/images/500/img_zhujian.png


BIN
packages/temp-pc/public/images/500/img_zhujian_line.png


BIN
packages/temp-pc/public/model/021658.fbx


BIN
packages/temp-pc/public/model/3.374_红陶兽形壶.FBX


+ 23 - 0
packages/temp-pc/public/model/6.465-3/6.465-3.mtl

@@ -0,0 +1,23 @@
+# Blender 4.2.3 LTS MTL File: 'None'
+# www.blender.org
+
+newmtl Mesh_1_Mat
+Ns 20.000006
+Kd 1.000000 1.000000 1.000000
+Ka 1.000000 1.000000 1.000000
+Ks 0.000000 0.000000 0.000000
+Ke 0.000000 0.000000 0.000000
+Ni 1.500000
+d 1.000000
+illum 1
+map_Kd 6.465_m1_D.jpg
+newmtl Mesh_2_Mat
+Ns 20.000006
+Kd 1.000000 1.000000 1.000000
+Ka 1.000000 1.000000 1.000000
+Ks 0.000000 0.000000 0.000000
+Ke 0.000000 0.000000 0.000000
+Ni 1.500000
+d 1.000000
+illum 1
+map_Kd 6.465_m2_D.jpg

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 36647 - 0
packages/temp-pc/public/model/6.465-3/6.465-3.obj


BIN
packages/temp-pc/public/model/6.465-3/6.465_m1_D.jpg


BIN
packages/temp-pc/public/model/6.465-3/6.465_m1_D.tga


BIN
packages/temp-pc/public/model/6.465-3/6.465_m2_D.jpg


BIN
packages/temp-pc/public/model/6.465-3/6.465_m2_D.tga


+ 14 - 0
packages/temp-pc/public/model/M52.40-2/M52.40-2.mtl

@@ -0,0 +1,14 @@
+# Blender 4.2.3 LTS MTL File: 'None'
+# www.blender.org
+
+newmtl 材质
+Ns 250.000000
+Kd 1.000000 1.000000 1.000000
+Ka 1.000000 1.000000 1.000000
+Ks 0.500000 0.500000 0.500000
+Ke 0.000000 0.000000 0.000000
+Ni 1.500000
+d 1.000000
+illum 2
+map_Kd M52.40_D.jpg
+map_Bump -bm 1.000000 M52.40_N.jpg

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 16422 - 0
packages/temp-pc/public/model/M52.40-2/M52.40-2.obj


BIN
packages/temp-pc/public/model/M52.40-2/M52.40_D.jpg


BIN
packages/temp-pc/public/model/M52.40-2/M52.40_N.jpg


BIN
packages/temp-pc/public/model/东6.487亚醜钺.fbx


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 3797 - 0
packages/temp-pc/public/modelLook/assets/index-BcLjdLAW.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 3797 - 0
packages/temp-pc/public/modelLook/assets/index-P16QZIgH.js


BIN
packages/temp-pc/public/modelLook/assets/tinyusdz-DnE9aqql.wasm


+ 41 - 0
packages/temp-pc/public/modelLook/index.html

@@ -0,0 +1,41 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta name="color-scheme" content="light" />
+    <title>模型加载演示</title>
+    <style>
+      html,
+      body,
+      #root {
+        height: 100%;
+        background: transparent;
+      }
+      body {
+        margin: 0;
+        overscroll-behavior: none;
+      }
+    </style>
+    <script type="importmap">
+      {
+        "imports": {
+          "three": "./vendor/three.module.min.js",
+          "three/addons/": "./vendor/addons/"
+        }
+      }
+    </script>
+    <script type="module">
+      const src = new URLSearchParams(location.search).get("src") || "";
+      const path = src.split("?")[0].split("#")[0].toLowerCase();
+      if (path.endsWith(".obj")) {
+        await import("./obj-viewer.js");
+      } else {
+        await import("./assets/index-BcLjdLAW.js");
+      }
+    </script>
+  </head>
+  <body>
+    <div id="root"></div>
+  </body>
+</html>

+ 271 - 0
packages/temp-pc/public/modelLook/obj-viewer.js

@@ -0,0 +1,271 @@
+import * as THREE from "three";
+import { OrbitControls } from "three/addons/controls/OrbitControls.js";
+import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
+import { MTLLoader } from "three/addons/loaders/MTLLoader.js";
+import { TGALoader } from "three/addons/loaders/TGALoader.js";
+
+// MTL 里若引用 .tga,默认 TextureLoader 无法解码,需注册 TGALoader
+THREE.DefaultLoadingManager.addHandler(/\.tga$/i, new TGALoader());
+
+const params = new URLSearchParams(window.location.search);
+const src = params.get("src") || "";
+const progressColor = params.get("progressColor") || "#4C7F7A";
+const rawMin = Number(params.get("min"));
+const rawMax = Number(params.get("max"));
+let zoomMin = Number.isFinite(rawMin) && rawMin > 0 ? rawMin : 0.01;
+let zoomMax = Number.isFinite(rawMax) && rawMax > 0 ? rawMax : 4;
+zoomMin = Math.min(1, Math.min(zoomMin, zoomMax));
+zoomMax = Math.max(zoomMin, zoomMax);
+
+const root = document.getElementById("root");
+root.style.cssText = "width:100%;height:100%;position:relative;overflow:hidden;";
+
+const overlay = document.createElement("div");
+overlay.style.cssText =
+  "position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:9999;pointer-events:none;transition:opacity .8s ease-out;";
+overlay.innerHTML = `<div style="width:600px;height:10px;background:#fff;border-radius:5px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,.2)"><div id="obj-progress-bar" style="width:100%;transform:scaleX(0);transform-origin:left;height:100%;background:${progressColor}"></div></div>`;
+root.appendChild(overlay);
+const progressBar = overlay.querySelector("#obj-progress-bar");
+
+const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
+renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
+renderer.setSize(window.innerWidth, window.innerHeight);
+renderer.setClearColor(0x000000, 0);
+renderer.toneMapping = THREE.ACESFilmicToneMapping;
+renderer.toneMappingExposure = 1.35;
+renderer.outputColorSpace = THREE.SRGBColorSpace;
+renderer.domElement.style.cssText =
+  "width:100%;height:100%;display:block;opacity:0;transition:opacity 1s ease-in-out;touch-action:none;";
+root.appendChild(renderer.domElement);
+
+const scene = new THREE.Scene();
+const camera = new THREE.PerspectiveCamera(
+  45,
+  window.innerWidth / window.innerHeight,
+  0.01,
+  2000
+);
+camera.position.set(0, 1.2, 3);
+
+const controls = new OrbitControls(camera, renderer.domElement);
+controls.enableDamping = true;
+controls.dampingFactor = 0.08;
+controls.minDistance = 0.01;
+controls.maxDistance = 100;
+
+scene.add(new THREE.HemisphereLight(0xfff7ec, 0xc9c4bc, 1.2));
+scene.add(new THREE.AmbientLight(0xffffff, 1));
+const key = new THREE.DirectionalLight(0xffffff, 1.85);
+key.position.set(5, 10, 6);
+scene.add(key);
+const fill = new THREE.DirectionalLight(0xffffff, 0.9);
+fill.position.set(-6, 4, -3);
+scene.add(fill);
+const rim = new THREE.DirectionalLight(0xffffff, 0.5);
+rim.position.set(2, 3, -6);
+scene.add(rim);
+
+let baseDistance = 3;
+let initialCameraPos = camera.position.clone();
+let initialTarget = controls.target.clone();
+let autoRotate = false;
+let autoRotateSpeed = 1;
+let autoRotateDelay = 5000;
+let interacting = false;
+let resumeTimer = null;
+
+function setProgress(p) {
+  progressBar.style.transform = `scaleX(${Math.max(0, Math.min(1, p / 100))})`;
+}
+
+function fitObject(object) {
+  const box = new THREE.Box3().setFromObject(object);
+  const size = box.getSize(new THREE.Vector3());
+  const center = box.getCenter(new THREE.Vector3());
+  object.position.sub(center);
+
+  const maxSize = Math.max(size.x, size.y, size.z) || 1;
+  const fitDist =
+    maxSize / (2 * Math.tan((Math.PI * camera.fov) / 360)) * 1.5;
+  baseDistance = fitDist;
+  controls.minDistance = Math.max(0.01, fitDist * zoomMin);
+  controls.maxDistance = fitDist * zoomMax;
+  camera.near = Math.max(0.001, fitDist / 100);
+  camera.far = Math.max(100, fitDist * 100);
+  camera.updateProjectionMatrix();
+  camera.position.set(0, fitDist * 0.15, fitDist);
+  controls.target.set(0, 0, 0);
+  controls.update();
+  initialCameraPos = camera.position.clone();
+  initialTarget = controls.target.clone();
+}
+
+function resolveDir(url) {
+  try {
+    const u = new URL(url, window.location.href);
+    u.hash = "";
+    u.search = "";
+    const path = u.pathname;
+    const i = path.lastIndexOf("/");
+    u.pathname = i >= 0 ? path.slice(0, i + 1) : "/";
+    return u.href;
+  } catch {
+    const i = url.lastIndexOf("/");
+    return i >= 0 ? url.slice(0, i + 1) : "./";
+  }
+}
+
+async function loadObj(url) {
+  if (!url) throw new Error("缺少 src 参数");
+  setProgress(5);
+  const dir = resolveDir(url);
+
+  // 先读文本,解析 mtllib;没有材质文件时也能正常显示几何体
+  const res = await fetch(url);
+  if (!res.ok) throw new Error(`HTTP ${res.status}`);
+  const text = await res.text();
+  setProgress(55);
+
+  let materials = null;
+  const mtlMatch = text.match(/^mtllib\s+(.+)$/m);
+  if (mtlMatch) {
+    const mtlName = mtlMatch[1].trim().split(/\s+/)[0];
+    try {
+      materials = await new Promise((resolve, reject) => {
+        const loader = new MTLLoader();
+        loader.setPath(dir);
+        loader.load(
+          mtlName,
+          (mats) => {
+            mats.preload();
+            resolve(mats);
+          },
+          (e) => {
+            if (e.total) setProgress(55 + (e.loaded / e.total) * 20);
+          },
+          reject
+        );
+      });
+    } catch (err) {
+      console.warn("MTL 加载失败,将使用默认材质:", err);
+    }
+  }
+  setProgress(75);
+
+  const objLoader = new OBJLoader();
+  if (materials) objLoader.setMaterials(materials);
+  // 使用已下载文本,避免二次请求;setPath 仍用于相对纹理路径
+  objLoader.setPath(dir);
+  const object = objLoader.parse(text);
+  object.traverse((child) => {
+    if (child.isMesh) {
+      child.castShadow = false;
+      child.receiveShadow = false;
+      const mats = Array.isArray(child.material)
+        ? child.material
+        : [child.material];
+      mats.forEach((m) => {
+        if (!m) return;
+        if (m.map) m.map.colorSpace = THREE.SRGBColorSpace;
+        m.side = THREE.DoubleSide;
+        m.needsUpdate = true;
+      });
+    }
+  });
+  setProgress(100);
+  return object;
+}
+
+function scheduleAutoResume() {
+  if (!autoRotate) return;
+  clearTimeout(resumeTimer);
+  resumeTimer = setTimeout(() => {
+    interacting = false;
+  }, autoRotateDelay);
+}
+
+function animate() {
+  requestAnimationFrame(animate);
+  if (autoRotate && !interacting) {
+    controls.autoRotate = true;
+    controls.autoRotateSpeed = autoRotateSpeed;
+  } else {
+    controls.autoRotate = false;
+  }
+  controls.update();
+  renderer.render(scene, camera);
+}
+
+controls.addEventListener("start", () => {
+  interacting = true;
+  clearTimeout(resumeTimer);
+});
+controls.addEventListener("end", () => {
+  scheduleAutoResume();
+});
+
+window.addEventListener("resize", () => {
+  camera.aspect = window.innerWidth / window.innerHeight;
+  camera.updateProjectionMatrix();
+  renderer.setSize(window.innerWidth, window.innerHeight);
+});
+
+window.sceneFc = {
+  resetView() {
+    interacting = true;
+    camera.position.copy(initialCameraPos);
+    controls.target.copy(initialTarget);
+    controls.update();
+    scheduleAutoResume();
+  },
+  setAutoRotate(speed = 1, delay = 5000) {
+    autoRotateSpeed = Math.max(0.1, Math.min(8, Number(speed) || 1));
+    autoRotateDelay = Math.max(0, Number(delay) || 5000);
+    autoRotate = true;
+    interacting = false;
+  },
+  stopAutoRotate() {
+    autoRotate = false;
+    controls.autoRotate = false;
+  },
+  zoomIn(factor = 1.2) {
+    const f = Math.max(1.01, Number(factor) || 1.2);
+    const dist = camera.position.distanceTo(controls.target);
+    const next = Math.max(controls.minDistance, dist / f);
+    const dir = camera.position.clone().sub(controls.target).normalize();
+    camera.position.copy(controls.target).add(dir.multiplyScalar(next));
+    controls.update();
+  },
+  zoomOut(factor = 1.2) {
+    const f = Math.max(1.01, Number(factor) || 1.2);
+    const dist = camera.position.distanceTo(controls.target);
+    const next = Math.min(controls.maxDistance, dist * f);
+    const dir = camera.position.clone().sub(controls.target).normalize();
+    camera.position.copy(controls.target).add(dir.multiplyScalar(next));
+    controls.update();
+  },
+};
+
+animate();
+
+if (!src) {
+  overlay.style.opacity = "0";
+  const tip = document.createElement("div");
+  tip.style.cssText =
+    "position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#222;font-size:14px;";
+  tip.textContent = "请在地址栏提供 ?src=OBJ地址";
+  root.appendChild(tip);
+} else {
+  loadObj(src)
+    .then((object) => {
+      scene.add(object);
+      fitObject(object);
+      renderer.domElement.style.opacity = "1";
+      overlay.style.opacity = "0";
+    })
+    .catch((err) => {
+      console.error(err);
+      overlay.innerHTML = `<div style="color:#c00;font-size:14px;background:#fff;padding:12px 16px;border-radius:8px;">OBJ 加载失败:${err.message || err}</div>`;
+      overlay.style.pointerEvents = "auto";
+    });
+}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1860 - 0
packages/temp-pc/public/modelLook/vendor/addons/controls/OrbitControls.js


+ 593 - 0
packages/temp-pc/public/modelLook/vendor/addons/loaders/MTLLoader.js

@@ -0,0 +1,593 @@
+import {
+	Color,
+	ColorManagement,
+	DefaultLoadingManager,
+	FileLoader,
+	FrontSide,
+	Loader,
+	LoaderUtils,
+	MeshPhongMaterial,
+	RepeatWrapping,
+	TextureLoader,
+	Vector2,
+	SRGBColorSpace
+} from 'three';
+
+/**
+ * A loader for the MTL format.
+ *
+ * The Material Template Library format (MTL) or .MTL File Format is a companion file format
+ * to OBJ that describes surface shading (material) properties of objects within one or more
+ * OBJ files.
+ *
+ * ```js
+ * const loader = new MTLLoader();
+ * const materials = await loader.loadAsync( 'models/obj/male02/male02.mtl' );
+ *
+ * const objLoader = new OBJLoader();
+ * objLoader.setMaterials( materials );
+ * ```
+ *
+ * @augments Loader
+ * @three_import import { MTLLoader } from 'three/addons/loaders/MTLLoader.js';
+ */
+class MTLLoader extends Loader {
+
+	constructor( manager ) {
+
+		super( manager );
+
+	}
+
+	/**
+	 * Starts loading from the given URL and passes the loaded MTL asset
+	 * to the `onLoad()` callback.
+	 *
+	 * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
+	 * @param {function(MaterialCreator)} onLoad - Executed when the loading process has been finished.
+	 * @param {onProgressCallback} onProgress - Executed while the loading is in progress.
+	 * @param {onErrorCallback} onError - Executed when errors occur.
+	 */
+	load( url, onLoad, onProgress, onError ) {
+
+		const scope = this;
+
+		const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path;
+
+		const loader = new FileLoader( this.manager );
+		loader.setPath( this.path );
+		loader.setRequestHeader( this.requestHeader );
+		loader.setWithCredentials( this.withCredentials );
+		loader.load( url, function ( text ) {
+
+			try {
+
+				onLoad( scope.parse( text, path ) );
+
+			} catch ( e ) {
+
+				if ( onError ) {
+
+					onError( e );
+
+				} else {
+
+					console.error( e );
+
+				}
+
+				scope.manager.itemError( url );
+
+			}
+
+		}, onProgress, onError );
+
+	}
+
+	/**
+	 * Sets the material options.
+	 *
+	 * @param {MTLLoader~MaterialOptions} value - The material options.
+	 * @return {MTLLoader} A reference to this loader.
+	 */
+	setMaterialOptions( value ) {
+
+		this.materialOptions = value;
+		return this;
+
+	}
+
+	/**
+	 * Parses the given MTL data and returns the resulting material creator.
+	 *
+	 * @param {string} text - The raw MTL data as a string.
+	 * @param {string} path - The URL base path.
+	 * @return {MaterialCreator} The material creator.
+	 */
+	parse( text, path ) {
+
+		const lines = text.split( '\n' );
+		let info = {};
+		const delimiter_pattern = /\s+/;
+		const materialsInfo = {};
+
+		for ( let i = 0; i < lines.length; i ++ ) {
+
+			let line = lines[ i ];
+			line = line.trim();
+
+			if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
+
+				// Blank line or comment ignore
+				continue;
+
+			}
+
+			const pos = line.indexOf( ' ' );
+
+			let key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
+			key = key.toLowerCase();
+
+			let value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';
+			value = value.trim();
+
+			if ( key === 'newmtl' ) {
+
+				// New material
+
+				info = { name: value };
+				materialsInfo[ value ] = info;
+
+			} else {
+
+				if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) {
+
+					const ss = value.split( delimiter_pattern, 3 );
+					info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
+
+				} else {
+
+					info[ key ] = value;
+
+				}
+
+			}
+
+		}
+
+		const materialCreator = new MaterialCreator( this.resourcePath || path, this.materialOptions );
+		materialCreator.setCrossOrigin( this.crossOrigin );
+		materialCreator.setManager( this.manager );
+		materialCreator.setMaterials( materialsInfo );
+		return materialCreator;
+
+	}
+
+}
+
+/**
+ * Material options of `MTLLoader`.
+ *
+ * @typedef {Object} MTLLoader~MaterialOptions
+ * @property {(FrontSide|BackSide|DoubleSide)} [side=FrontSide] - Which side to apply the material.
+ * @property {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} [wrap=RepeatWrapping] - What type of wrapping to apply for textures.
+ * @property {boolean} [normalizeRGB=false] - Whether RGB colors should be normalized to `0-1` from `0-255`.
+ * @property {boolean} [ignoreZeroRGBs=false] - Ignore values of RGBs (Ka,Kd,Ks) that are all 0's.
+ */
+
+class MaterialCreator {
+
+	constructor( baseUrl = '', options = {} ) {
+
+		this.baseUrl = baseUrl;
+		this.options = options;
+		this.materialsInfo = {};
+		this.materials = {};
+		this.materialsArray = [];
+		this.nameLookup = {};
+
+		this.crossOrigin = 'anonymous';
+
+		this.side = ( this.options.side !== undefined ) ? this.options.side : FrontSide;
+		this.wrap = ( this.options.wrap !== undefined ) ? this.options.wrap : RepeatWrapping;
+
+	}
+
+	setCrossOrigin( value ) {
+
+		this.crossOrigin = value;
+		return this;
+
+	}
+
+	setManager( value ) {
+
+		this.manager = value;
+
+	}
+
+	setMaterials( materialsInfo ) {
+
+		this.materialsInfo = this.convert( materialsInfo );
+		this.materials = {};
+		this.materialsArray = [];
+		this.nameLookup = {};
+
+	}
+
+	convert( materialsInfo ) {
+
+		if ( ! this.options ) return materialsInfo;
+
+		const converted = {};
+
+		for ( const mn in materialsInfo ) {
+
+			// Convert materials info into normalized form based on options
+
+			const mat = materialsInfo[ mn ];
+
+			const covmat = {};
+
+			converted[ mn ] = covmat;
+
+			for ( const prop in mat ) {
+
+				let save = true;
+				let value = mat[ prop ];
+				const lprop = prop.toLowerCase();
+
+				switch ( lprop ) {
+
+					case 'kd':
+					case 'ka':
+					case 'ks':
+
+						// Diffuse color (color under white light) using RGB values
+
+						if ( this.options && this.options.normalizeRGB ) {
+
+							value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
+
+						}
+
+						if ( this.options && this.options.ignoreZeroRGBs ) {
+
+							if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
+
+								// ignore
+
+								save = false;
+
+							}
+
+						}
+
+						break;
+
+					default:
+
+						break;
+
+				}
+
+				if ( save ) {
+
+					covmat[ lprop ] = value;
+
+				}
+
+			}
+
+		}
+
+		return converted;
+
+	}
+
+	preload() {
+
+		for ( const mn in this.materialsInfo ) {
+
+			this.create( mn );
+
+		}
+
+	}
+
+	getIndex( materialName ) {
+
+		return this.nameLookup[ materialName ];
+
+	}
+
+	getAsArray() {
+
+		let index = 0;
+
+		for ( const mn in this.materialsInfo ) {
+
+			this.materialsArray[ index ] = this.create( mn );
+			this.nameLookup[ mn ] = index;
+			index ++;
+
+		}
+
+		return this.materialsArray;
+
+	}
+
+	create( materialName ) {
+
+		if ( this.materials[ materialName ] === undefined ) {
+
+			this.createMaterial_( materialName );
+
+		}
+
+		return this.materials[ materialName ];
+
+	}
+
+	createMaterial_( materialName ) {
+
+		// Create material
+
+		const scope = this;
+		const mat = this.materialsInfo[ materialName ];
+		const params = {
+
+			name: materialName,
+			side: this.side
+
+		};
+
+		function resolveURL( baseUrl, url ) {
+
+			if ( typeof url !== 'string' || url === '' )
+				return '';
+
+			// Absolute URL
+			if ( /^https?:\/\//i.test( url ) ) return url;
+
+			return baseUrl + url;
+
+		}
+
+		function setMapForType( mapType, value ) {
+
+			if ( params[ mapType ] ) return; // Keep the first encountered texture
+
+			const texParams = scope.getTextureParams( value, params );
+			const map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
+
+			map.repeat.copy( texParams.scale );
+			map.offset.copy( texParams.offset );
+
+			map.wrapS = scope.wrap;
+			map.wrapT = scope.wrap;
+
+			if ( mapType === 'map' || mapType === 'emissiveMap' ) {
+
+				map.colorSpace = SRGBColorSpace;
+
+			}
+
+			params[ mapType ] = map;
+
+		}
+
+		for ( const prop in mat ) {
+
+			const value = mat[ prop ];
+			let n;
+
+			if ( value === '' ) continue;
+
+			switch ( prop.toLowerCase() ) {
+
+				// Ns is material specular exponent
+
+				case 'kd':
+
+					// Diffuse color (color under white light) using RGB values
+
+					params.color = ColorManagement.colorSpaceToWorking( new Color().fromArray( value ), SRGBColorSpace );
+
+					break;
+
+				case 'ks':
+
+					// Specular color (color when light is reflected from shiny surface) using RGB values
+					params.specular = ColorManagement.colorSpaceToWorking( new Color().fromArray( value ), SRGBColorSpace );
+
+					break;
+
+				case 'ke':
+
+					// Emissive using RGB values
+					params.emissive = ColorManagement.colorSpaceToWorking( new Color().fromArray( value ), SRGBColorSpace );
+
+					break;
+
+				case 'map_kd':
+
+					// Diffuse texture map
+
+					setMapForType( 'map', value );
+
+					break;
+
+				case 'map_ks':
+
+					// Specular map
+
+					setMapForType( 'specularMap', value );
+
+					break;
+
+				case 'map_ke':
+
+					// Emissive map
+
+					setMapForType( 'emissiveMap', value );
+
+					break;
+
+				case 'norm':
+
+					setMapForType( 'normalMap', value );
+
+					break;
+
+				case 'map_bump':
+				case 'bump':
+
+					// Bump texture map
+
+					setMapForType( 'bumpMap', value );
+
+					break;
+
+				case 'disp':
+
+					// Displacement texture map
+
+					setMapForType( 'displacementMap', value );
+
+					break;
+
+				case 'map_d':
+
+					// Alpha map
+
+					setMapForType( 'alphaMap', value );
+					params.transparent = true;
+
+					break;
+
+				case 'ns':
+
+					// The specular exponent (defines the focus of the specular highlight)
+					// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
+
+					params.shininess = parseFloat( value );
+
+					break;
+
+				case 'd':
+					n = parseFloat( value );
+
+					if ( n < 1 ) {
+
+						params.opacity = n;
+						params.transparent = true;
+
+					}
+
+					break;
+
+				case 'tr':
+					n = parseFloat( value );
+
+					if ( this.options && this.options.invertTrProperty ) n = 1 - n;
+
+					if ( n > 0 ) {
+
+						params.opacity = 1 - n;
+						params.transparent = true;
+
+					}
+
+					break;
+
+				default:
+					break;
+
+			}
+
+		}
+
+		this.materials[ materialName ] = new MeshPhongMaterial( params );
+		return this.materials[ materialName ];
+
+	}
+
+	getTextureParams( value, matParams ) {
+
+		const texParams = {
+
+			scale: new Vector2( 1, 1 ),
+			offset: new Vector2( 0, 0 )
+
+		 };
+
+		const items = value.split( /\s+/ );
+		let pos;
+
+		pos = items.indexOf( '-bm' );
+
+		if ( pos >= 0 ) {
+
+			matParams.bumpScale = parseFloat( items[ pos + 1 ] );
+			items.splice( pos, 2 );
+
+		}
+
+		pos = items.indexOf( '-mm' );
+
+		if ( pos >= 0 ) {
+
+			matParams.displacementBias = parseFloat( items[ pos + 1 ] );
+			matParams.displacementScale = parseFloat( items[ pos + 2 ] );
+			items.splice( pos, 3 );
+
+		}
+
+		pos = items.indexOf( '-s' );
+
+		if ( pos >= 0 ) {
+
+			texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+			items.splice( pos, 4 ); // we expect 3 parameters here!
+
+		}
+
+		pos = items.indexOf( '-o' );
+
+		if ( pos >= 0 ) {
+
+			texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+			items.splice( pos, 4 ); // we expect 3 parameters here!
+
+		}
+
+		texParams.url = items.join( ' ' ).trim();
+		return texParams;
+
+	}
+
+	loadTexture( url, mapping, onLoad, onProgress, onError ) {
+
+		const manager = ( this.manager !== undefined ) ? this.manager : DefaultLoadingManager;
+		let loader = manager.getHandler( url );
+
+		if ( loader === null ) {
+
+			loader = new TextureLoader( manager );
+
+		}
+
+		if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
+
+		const texture = loader.load( url, onLoad, onProgress, onError );
+
+		if ( mapping !== undefined ) texture.mapping = mapping;
+
+		return texture;
+
+	}
+
+}
+
+export { MTLLoader };

+ 955 - 0
packages/temp-pc/public/modelLook/vendor/addons/loaders/OBJLoader.js

@@ -0,0 +1,955 @@
+import {
+	BufferGeometry,
+	FileLoader,
+	Float32BufferAttribute,
+	Group,
+	LineBasicMaterial,
+	LineSegments,
+	Loader,
+	Material,
+	Mesh,
+	MeshPhongMaterial,
+	Points,
+	PointsMaterial,
+	Vector3,
+	Color,
+	SRGBColorSpace
+} from 'three';
+
+// o object_name | g group_name
+const _object_pattern = /^[og]\s*(.+)?/;
+// mtllib file_reference
+const _material_library_pattern = /^mtllib /;
+// usemtl material_name
+const _material_use_pattern = /^usemtl /;
+// usemap map_name
+const _map_use_pattern = /^usemap /;
+const _face_vertex_data_separator_pattern = /\s+/;
+
+const _vA = new Vector3();
+const _vB = new Vector3();
+const _vC = new Vector3();
+
+const _ab = new Vector3();
+const _cb = new Vector3();
+
+const _color = new Color();
+
+function ParserState() {
+
+	const state = {
+		objects: [],
+		object: {},
+
+		vertices: [],
+		normals: [],
+		colors: [],
+		uvs: [],
+
+		materials: {},
+		materialLibraries: [],
+
+		startObject: function ( name, fromDeclaration ) {
+
+			// If the current object (initial from reset) is not from a g/o declaration in the parsed
+			// file. We need to use it for the first parsed g/o to keep things in sync.
+			if ( this.object && this.object.fromDeclaration === false ) {
+
+				this.object.name = name;
+				this.object.fromDeclaration = ( fromDeclaration !== false );
+				return;
+
+			}
+
+			const previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );
+
+			if ( this.object && typeof this.object._finalize === 'function' ) {
+
+				this.object._finalize( true );
+
+			}
+
+			this.object = {
+				name: name || '',
+				fromDeclaration: ( fromDeclaration !== false ),
+
+				geometry: {
+					vertices: [],
+					normals: [],
+					colors: [],
+					uvs: [],
+					hasUVIndices: false
+				},
+				materials: [],
+				smooth: true,
+
+				startMaterial: function ( name, libraries ) {
+
+					const previous = this._finalize( false );
+
+					// New usemtl declaration overwrites an inherited material, except if faces were declared
+					// after the material, then it must be preserved for proper MultiMaterial continuation.
+					if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {
+
+						this.materials.splice( previous.index, 1 );
+
+					}
+
+					const material = {
+						index: this.materials.length,
+						name: name || '',
+						mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
+						smooth: ( previous !== undefined ? previous.smooth : this.smooth ),
+						groupStart: ( previous !== undefined ? previous.groupEnd : 0 ),
+						groupEnd: - 1,
+						groupCount: - 1,
+						inherited: false,
+
+						clone: function ( index ) {
+
+							const cloned = {
+								index: ( typeof index === 'number' ? index : this.index ),
+								name: this.name,
+								mtllib: this.mtllib,
+								smooth: this.smooth,
+								groupStart: 0,
+								groupEnd: - 1,
+								groupCount: - 1,
+								inherited: false
+							};
+							cloned.clone = this.clone.bind( cloned );
+							return cloned;
+
+						}
+					};
+
+					this.materials.push( material );
+
+					return material;
+
+				},
+
+				currentMaterial: function () {
+
+					if ( this.materials.length > 0 ) {
+
+						return this.materials[ this.materials.length - 1 ];
+
+					}
+
+					return undefined;
+
+				},
+
+				_finalize: function ( end ) {
+
+					const lastMultiMaterial = this.currentMaterial();
+					if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) {
+
+						lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
+						lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
+						lastMultiMaterial.inherited = false;
+
+					}
+
+					// Ignore objects tail materials if no face declarations followed them before a new o/g started.
+					if ( end && this.materials.length > 1 ) {
+
+						for ( let mi = this.materials.length - 1; mi >= 0; mi -- ) {
+
+							if ( this.materials[ mi ].groupCount <= 0 ) {
+
+								this.materials.splice( mi, 1 );
+
+							}
+
+						}
+
+					}
+
+					// Guarantee at least one empty material, this makes the creation later more straight forward.
+					if ( end && this.materials.length === 0 ) {
+
+						this.materials.push( {
+							name: '',
+							smooth: this.smooth
+						} );
+
+					}
+
+					return lastMultiMaterial;
+
+				}
+			};
+
+			// Inherit previous objects material.
+			// Spec tells us that a declared material must be set to all objects until a new material is declared.
+			// If a usemtl declaration is encountered while this new object is being parsed, it will
+			// overwrite the inherited material. Exception being that there was already face declarations
+			// to the inherited material, then it will be preserved for proper MultiMaterial continuation.
+
+			if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {
+
+				const declared = previousMaterial.clone( 0 );
+				declared.inherited = true;
+				this.object.materials.push( declared );
+
+			}
+
+			this.objects.push( this.object );
+
+		},
+
+		finalize: function () {
+
+			if ( this.object && typeof this.object._finalize === 'function' ) {
+
+				this.object._finalize( true );
+
+			}
+
+		},
+
+		parseVertexIndex: function ( value, len ) {
+
+			const index = parseInt( value, 10 );
+			return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+		},
+
+		parseNormalIndex: function ( value, len ) {
+
+			const index = parseInt( value, 10 );
+			return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+		},
+
+		parseUVIndex: function ( value, len ) {
+
+			const index = parseInt( value, 10 );
+			return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
+
+		},
+
+		addVertex: function ( a, b, c ) {
+
+			const src = this.vertices;
+			const dst = this.object.geometry.vertices;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+			dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+			dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+		},
+
+		addVertexPoint: function ( a ) {
+
+			const src = this.vertices;
+			const dst = this.object.geometry.vertices;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+		},
+
+		addVertexLine: function ( a ) {
+
+			const src = this.vertices;
+			const dst = this.object.geometry.vertices;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+		},
+
+		addNormal: function ( a, b, c ) {
+
+			const src = this.normals;
+			const dst = this.object.geometry.normals;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+			dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+			dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+		},
+
+		addFaceNormal: function ( a, b, c ) {
+
+			const src = this.vertices;
+			const dst = this.object.geometry.normals;
+
+			_vA.fromArray( src, a );
+			_vB.fromArray( src, b );
+			_vC.fromArray( src, c );
+
+			_cb.subVectors( _vC, _vB );
+			_ab.subVectors( _vA, _vB );
+			_cb.cross( _ab );
+
+			_cb.normalize();
+
+			dst.push( _cb.x, _cb.y, _cb.z );
+			dst.push( _cb.x, _cb.y, _cb.z );
+			dst.push( _cb.x, _cb.y, _cb.z );
+
+		},
+
+		addColor: function ( a, b, c ) {
+
+			const src = this.colors;
+			const dst = this.object.geometry.colors;
+
+			if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+			if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+			if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+		},
+
+		addUV: function ( a, b, c ) {
+
+			const src = this.uvs;
+			const dst = this.object.geometry.uvs;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ] );
+			dst.push( src[ b + 0 ], src[ b + 1 ] );
+			dst.push( src[ c + 0 ], src[ c + 1 ] );
+
+		},
+
+		addDefaultUV: function () {
+
+			const dst = this.object.geometry.uvs;
+
+			dst.push( 0, 0 );
+			dst.push( 0, 0 );
+			dst.push( 0, 0 );
+
+		},
+
+		addUVLine: function ( a ) {
+
+			const src = this.uvs;
+			const dst = this.object.geometry.uvs;
+
+			dst.push( src[ a + 0 ], src[ a + 1 ] );
+
+		},
+
+		addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {
+
+			const vLen = this.vertices.length;
+
+			let ia = this.parseVertexIndex( a, vLen );
+			let ib = this.parseVertexIndex( b, vLen );
+			let ic = this.parseVertexIndex( c, vLen );
+
+			this.addVertex( ia, ib, ic );
+			this.addColor( ia, ib, ic );
+
+			// normals
+
+			if ( na !== undefined && na !== '' ) {
+
+				const nLen = this.normals.length;
+
+				ia = this.parseNormalIndex( na, nLen );
+				ib = this.parseNormalIndex( nb, nLen );
+				ic = this.parseNormalIndex( nc, nLen );
+
+				this.addNormal( ia, ib, ic );
+
+			} else {
+
+				this.addFaceNormal( ia, ib, ic );
+
+			}
+
+			// uvs
+
+			if ( ua !== undefined && ua !== '' ) {
+
+				const uvLen = this.uvs.length;
+
+				ia = this.parseUVIndex( ua, uvLen );
+				ib = this.parseUVIndex( ub, uvLen );
+				ic = this.parseUVIndex( uc, uvLen );
+
+				this.addUV( ia, ib, ic );
+
+				this.object.geometry.hasUVIndices = true;
+
+			} else {
+
+				// add placeholder values (for inconsistent face definitions)
+
+				this.addDefaultUV();
+
+			}
+
+		},
+
+		addPointGeometry: function ( vertices ) {
+
+			this.object.geometry.type = 'Points';
+
+			const vLen = this.vertices.length;
+
+			for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+				const index = this.parseVertexIndex( vertices[ vi ], vLen );
+
+				this.addVertexPoint( index );
+				this.addColor( index );
+
+			}
+
+		},
+
+		addLineGeometry: function ( vertices, uvs ) {
+
+			this.object.geometry.type = 'Line';
+
+			const vLen = this.vertices.length;
+			const uvLen = this.uvs.length;
+
+			for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+				this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
+
+			}
+
+			for ( let uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
+
+				this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
+
+			}
+
+		}
+
+	};
+
+	state.startObject( '', false );
+
+	return state;
+
+}
+
+
+/**
+ * A loader for the OBJ format.
+ *
+ * The [OBJ format](https://en.wikipedia.org/wiki/Wavefront_.obj_file) is a simple data-format that
+ * represents 3D geometry in a human readable format as the position of each vertex, the UV position of
+ * each texture coordinate vertex, vertex normals, and the faces that make each polygon defined as a list
+ * of vertices, and texture vertices.
+ *
+ * ```js
+ * const loader = new OBJLoader();
+ * const object = await loader.loadAsync( 'models/monster.obj' );
+ * scene.add( object );
+ * ```
+ *
+ * @augments Loader
+ * @three_import import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
+ */
+class OBJLoader extends Loader {
+
+	/**
+	 * Constructs a new OBJ loader.
+	 *
+	 * @param {LoadingManager} [manager] - The loading manager.
+	 */
+	constructor( manager ) {
+
+		super( manager );
+
+		/**
+		 * A reference to a material creator.
+		 *
+		 * @type {?MaterialCreator}
+		 * @default null
+		 */
+		this.materials = null;
+
+	}
+
+	/**
+	 * Starts loading from the given URL and passes the loaded OBJ asset
+	 * to the `onLoad()` callback.
+	 *
+	 * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
+	 * @param {function(Group)} onLoad - Executed when the loading process has been finished.
+	 * @param {onProgressCallback} onProgress - Executed while the loading is in progress.
+	 * @param {onErrorCallback} onError - Executed when errors occur.
+	 */
+	load( url, onLoad, onProgress, onError ) {
+
+		const scope = this;
+
+		const loader = new FileLoader( this.manager );
+		loader.setPath( this.path );
+		loader.setRequestHeader( this.requestHeader );
+		loader.setWithCredentials( this.withCredentials );
+		loader.load( url, function ( text ) {
+
+			try {
+
+				onLoad( scope.parse( text ) );
+
+			} catch ( e ) {
+
+				if ( onError ) {
+
+					onError( e );
+
+				} else {
+
+					console.error( e );
+
+				}
+
+				scope.manager.itemError( url );
+
+			}
+
+		}, onProgress, onError );
+
+	}
+
+	/**
+	 * Sets the material creator for this OBJ. This object is loaded via {@link MTLLoader}.
+	 *
+	 * @param {MaterialCreator} materials - An object that creates the materials for this OBJ.
+	 * @return {OBJLoader} A reference to this loader.
+	 */
+	setMaterials( materials ) {
+
+		this.materials = materials;
+
+		return this;
+
+	}
+
+	/**
+	 * Parses the given OBJ data and returns the resulting group.
+	 *
+	 * @param {string} text - The raw OBJ data as a string.
+	 * @return {Group} The parsed OBJ.
+	 */
+	parse( text ) {
+
+		const state = new ParserState();
+
+		if ( text.indexOf( '\r\n' ) !== - 1 ) {
+
+			// This is faster than String.split with regex that splits on both
+			text = text.replace( /\r\n/g, '\n' );
+
+		}
+
+		if ( text.indexOf( '\\\n' ) !== - 1 ) {
+
+			// join lines separated by a line continuation character (\)
+			text = text.replace( /\\\n/g, '' );
+
+		}
+
+		const lines = text.split( '\n' );
+		let result = [];
+
+		for ( let i = 0, l = lines.length; i < l; i ++ ) {
+
+			const line = lines[ i ].trimStart();
+
+			if ( line.length === 0 ) continue;
+
+			const lineFirstChar = line.charAt( 0 );
+
+			// @todo invoke passed in handler if any
+			if ( lineFirstChar === '#' ) continue; // skip comments
+
+			if ( lineFirstChar === 'v' ) {
+
+				const data = line.split( _face_vertex_data_separator_pattern );
+
+				switch ( data[ 0 ] ) {
+
+					case 'v':
+						state.vertices.push(
+							parseFloat( data[ 1 ] ),
+							parseFloat( data[ 2 ] ),
+							parseFloat( data[ 3 ] )
+						);
+						if ( data.length >= 7 ) {
+
+							_color.setRGB(
+								parseFloat( data[ 4 ] ),
+								parseFloat( data[ 5 ] ),
+								parseFloat( data[ 6 ] ),
+								SRGBColorSpace
+							);
+
+							state.colors.push( _color.r, _color.g, _color.b );
+
+						} else {
+
+							// if no colors are defined, add placeholders so color and vertex indices match
+
+							state.colors.push( undefined, undefined, undefined );
+
+						}
+
+						break;
+					case 'vn':
+						state.normals.push(
+							parseFloat( data[ 1 ] ),
+							parseFloat( data[ 2 ] ),
+							parseFloat( data[ 3 ] )
+						);
+						break;
+					case 'vt':
+						state.uvs.push(
+							parseFloat( data[ 1 ] ),
+							parseFloat( data[ 2 ] )
+						);
+						break;
+
+				}
+
+			} else if ( lineFirstChar === 'f' ) {
+
+				const lineData = line.slice( 1 ).trim();
+				const vertexData = lineData.split( _face_vertex_data_separator_pattern );
+				const faceVertices = [];
+
+				// Parse the face vertex data into an easy to work with format
+
+				for ( let j = 0, jl = vertexData.length; j < jl; j ++ ) {
+
+					const vertex = vertexData[ j ];
+
+					if ( vertex.length > 0 ) {
+
+						const vertexParts = vertex.split( '/' );
+						faceVertices.push( vertexParts );
+
+					}
+
+				}
+
+				// Draw an edge between the first vertex and all subsequent vertices to form an n-gon
+
+				const v1 = faceVertices[ 0 ];
+
+				for ( let j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {
+
+					const v2 = faceVertices[ j ];
+					const v3 = faceVertices[ j + 1 ];
+
+					state.addFace(
+						v1[ 0 ], v2[ 0 ], v3[ 0 ],
+						v1[ 1 ], v2[ 1 ], v3[ 1 ],
+						v1[ 2 ], v2[ 2 ], v3[ 2 ]
+					);
+
+				}
+
+			} else if ( lineFirstChar === 'l' ) {
+
+				const lineParts = line.substring( 1 ).trim().split( ' ' );
+				let lineVertices = [];
+				const lineUVs = [];
+
+				if ( line.indexOf( '/' ) === - 1 ) {
+
+					lineVertices = lineParts;
+
+				} else {
+
+					for ( let li = 0, llen = lineParts.length; li < llen; li ++ ) {
+
+						const parts = lineParts[ li ].split( '/' );
+
+						if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] );
+						if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] );
+
+					}
+
+				}
+
+				state.addLineGeometry( lineVertices, lineUVs );
+
+			} else if ( lineFirstChar === 'p' ) {
+
+				const lineData = line.slice( 1 ).trim();
+				const pointData = lineData.split( ' ' );
+
+				state.addPointGeometry( pointData );
+
+			} else if ( ( result = _object_pattern.exec( line ) ) !== null ) {
+
+				// o object_name
+				// or
+				// g group_name
+
+				// WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
+				// let name = result[ 0 ].slice( 1 ).trim();
+				const name = ( ' ' + result[ 0 ].slice( 1 ).trim() ).slice( 1 );
+
+				state.startObject( name );
+
+			} else if ( _material_use_pattern.test( line ) ) {
+
+				// material
+
+				state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );
+
+			} else if ( _material_library_pattern.test( line ) ) {
+
+				// mtl file
+
+				state.materialLibraries.push( line.substring( 7 ).trim() );
+
+			} else if ( _map_use_pattern.test( line ) ) {
+
+				// the line is parsed but ignored since the loader assumes textures are defined MTL files
+				// (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method)
+
+				console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' );
+
+			} else if ( lineFirstChar === 's' ) {
+
+				result = line.split( ' ' );
+
+				// smooth shading
+
+				// @todo Handle files that have varying smooth values for a set of faces inside one geometry,
+				// but does not define a usemtl for each face set.
+				// This should be detected and a dummy material created (later MultiMaterial and geometry groups).
+				// This requires some care to not create extra material on each smooth value for "normal" obj files.
+				// where explicit usemtl defines geometry groups.
+				// Example asset: examples/models/obj/cerberus/Cerberus.obj
+
+				/*
+					 * http://paulbourke.net/dataformats/obj/
+					 *
+					 * From chapter "Grouping" Syntax explanation "s group_number":
+					 * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
+					 * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
+					 * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
+					 * than 0."
+					 */
+				if ( result.length > 1 ) {
+
+					const value = result[ 1 ].trim().toLowerCase();
+					state.object.smooth = ( value !== '0' && value !== 'off' );
+
+				} else {
+
+					// ZBrush can produce "s" lines #11707
+					state.object.smooth = true;
+
+				}
+
+				const material = state.object.currentMaterial();
+				if ( material ) material.smooth = state.object.smooth;
+
+			} else {
+
+				// Handle null terminated files without exception
+				if ( line === '\0' ) continue;
+
+				console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' );
+
+			}
+
+		}
+
+		state.finalize();
+
+		const container = new Group();
+		container.materialLibraries = [].concat( state.materialLibraries );
+
+		const hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 );
+
+		if ( hasPrimitives === true ) {
+
+			for ( let i = 0, l = state.objects.length; i < l; i ++ ) {
+
+				const object = state.objects[ i ];
+				const geometry = object.geometry;
+				const materials = object.materials;
+				const isLine = ( geometry.type === 'Line' );
+				const isPoints = ( geometry.type === 'Points' );
+				let hasVertexColors = false;
+
+				// Skip o/g line declarations that did not follow with any faces
+				if ( geometry.vertices.length === 0 ) continue;
+
+				const buffergeometry = new BufferGeometry();
+
+				buffergeometry.setAttribute( 'position', new Float32BufferAttribute( geometry.vertices, 3 ) );
+
+				if ( geometry.normals.length > 0 ) {
+
+					buffergeometry.setAttribute( 'normal', new Float32BufferAttribute( geometry.normals, 3 ) );
+
+				}
+
+				if ( geometry.colors.length > 0 ) {
+
+					hasVertexColors = true;
+					buffergeometry.setAttribute( 'color', new Float32BufferAttribute( geometry.colors, 3 ) );
+
+				}
+
+				if ( geometry.hasUVIndices === true ) {
+
+					buffergeometry.setAttribute( 'uv', new Float32BufferAttribute( geometry.uvs, 2 ) );
+
+				}
+
+				// Create materials
+
+				const createdMaterials = [];
+
+				for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+					const sourceMaterial = materials[ mi ];
+					const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors;
+					let material = state.materials[ materialHash ];
+
+					if ( this.materials !== null ) {
+
+						material = this.materials.create( sourceMaterial.name );
+
+						// mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
+						if ( isLine && material && ! ( material instanceof LineBasicMaterial ) ) {
+
+							const materialLine = new LineBasicMaterial();
+							Material.prototype.copy.call( materialLine, material );
+							materialLine.color.copy( material.color );
+							material = materialLine;
+
+						} else if ( isPoints && material && ! ( material instanceof PointsMaterial ) ) {
+
+							const materialPoints = new PointsMaterial( { size: 10, sizeAttenuation: false } );
+							Material.prototype.copy.call( materialPoints, material );
+							materialPoints.color.copy( material.color );
+							materialPoints.map = material.map;
+							material = materialPoints;
+
+						}
+
+					}
+
+					if ( material === undefined ) {
+
+						if ( isLine ) {
+
+							material = new LineBasicMaterial();
+
+						} else if ( isPoints ) {
+
+							material = new PointsMaterial( { size: 1, sizeAttenuation: false } );
+
+						} else {
+
+							material = new MeshPhongMaterial();
+
+						}
+
+						material.name = sourceMaterial.name;
+						material.flatShading = sourceMaterial.smooth ? false : true;
+						material.vertexColors = hasVertexColors;
+
+						state.materials[ materialHash ] = material;
+
+					}
+
+					createdMaterials.push( material );
+
+				}
+
+				// Create mesh
+
+				let mesh;
+
+				if ( createdMaterials.length > 1 ) {
+
+					for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+						const sourceMaterial = materials[ mi ];
+						buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );
+
+					}
+
+					if ( isLine ) {
+
+						mesh = new LineSegments( buffergeometry, createdMaterials );
+
+					} else if ( isPoints ) {
+
+						mesh = new Points( buffergeometry, createdMaterials );
+
+					} else {
+
+						mesh = new Mesh( buffergeometry, createdMaterials );
+
+					}
+
+				} else {
+
+					if ( isLine ) {
+
+						mesh = new LineSegments( buffergeometry, createdMaterials[ 0 ] );
+
+					} else if ( isPoints ) {
+
+						mesh = new Points( buffergeometry, createdMaterials[ 0 ] );
+
+					} else {
+
+						mesh = new Mesh( buffergeometry, createdMaterials[ 0 ] );
+
+					}
+
+				}
+
+				mesh.name = object.name;
+
+				container.add( mesh );
+
+			}
+
+		} else {
+
+			// if there is only the default parser state object with no geometry data, interpret data as point cloud
+
+			if ( state.vertices.length > 0 ) {
+
+				const material = new PointsMaterial( { size: 1, sizeAttenuation: false } );
+
+				const buffergeometry = new BufferGeometry();
+
+				buffergeometry.setAttribute( 'position', new Float32BufferAttribute( state.vertices, 3 ) );
+
+				if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) {
+
+					buffergeometry.setAttribute( 'color', new Float32BufferAttribute( state.colors, 3 ) );
+					material.vertexColors = true;
+
+				}
+
+				const points = new Points( buffergeometry, material );
+				container.add( points );
+
+			}
+
+		}
+
+		return container;
+
+	}
+
+}
+
+export { OBJLoader };

+ 538 - 0
packages/temp-pc/public/modelLook/vendor/addons/loaders/TGALoader.js

@@ -0,0 +1,538 @@
+import {
+	DataTextureLoader,
+	LinearMipmapLinearFilter
+} from 'three';
+
+/**
+ * A loader for the TGA texture format.
+ *
+ * ```js
+ * const loader = new TGALoader();
+ * const texture = await loader.loadAsync( 'textures/crate_color8.tga' );
+ * texture.colorSpace = THREE.SRGBColorSpace; // only for color textures
+ * ```
+ *
+ * @augments DataTextureLoader
+ * @three_import import { TGALoader } from 'three/addons/loaders/TGALoader.js';
+ */
+class TGALoader extends DataTextureLoader {
+
+	/**
+	 * Constructs a new TGA loader.
+	 *
+	 * @param {LoadingManager} [manager] - The loading manager.
+	 */
+	constructor( manager ) {
+
+		super( manager );
+
+	}
+
+	/**
+	 * Parses the given TGA texture data.
+	 *
+	 * @param {ArrayBuffer} buffer - The raw texture data.
+	 * @return {DataTextureLoader~TexData} An object representing the parsed texture data.
+	 */
+	parse( buffer ) {
+
+		// reference from vthibault, https://github.com/vthibault/roBrowser/blob/master/src/Loaders/Targa.js
+
+		function tgaCheckHeader( header ) {
+
+			switch ( header.image_type ) {
+
+				// check indexed type
+
+				case TGA_TYPE_INDEXED:
+				case TGA_TYPE_RLE_INDEXED:
+					if ( header.colormap_length > 256 || header.colormap_size !== 24 || header.colormap_type !== 1 ) {
+
+						throw new Error( 'THREE.TGALoader: Invalid type colormap data for indexed type.' );
+
+					}
+
+					break;
+
+					// check colormap type
+
+				case TGA_TYPE_RGB:
+				case TGA_TYPE_GREY:
+				case TGA_TYPE_RLE_RGB:
+				case TGA_TYPE_RLE_GREY:
+					if ( header.colormap_type ) {
+
+						throw new Error( 'THREE.TGALoader: Invalid type colormap data for colormap type.' );
+
+					}
+
+					break;
+
+					// What the need of a file without data ?
+
+				case TGA_TYPE_NO_DATA:
+					throw new Error( 'THREE.TGALoader: No data.' );
+
+					// Invalid type ?
+
+				default:
+					throw new Error( 'THREE.TGALoader: Invalid type ' + header.image_type );
+
+			}
+
+			// check image width and height
+
+			if ( header.width <= 0 || header.height <= 0 ) {
+
+				throw new Error( 'THREE.TGALoader: Invalid image size.' );
+
+			}
+
+			// check image pixel size
+
+			if ( header.pixel_size !== 8 && header.pixel_size !== 16 &&
+				header.pixel_size !== 24 && header.pixel_size !== 32 ) {
+
+				throw new Error( 'THREE.TGALoader: Invalid pixel size ' + header.pixel_size );
+
+			}
+
+		}
+
+		// parse tga image buffer
+
+		function tgaParse( use_rle, use_pal, header, offset, data ) {
+
+			let pixel_data,
+				palettes;
+
+			const pixel_size = header.pixel_size >> 3;
+			const pixel_total = header.width * header.height * pixel_size;
+
+			 // read palettes
+
+			 if ( use_pal ) {
+
+				 palettes = data.subarray( offset, offset += header.colormap_length * ( header.colormap_size >> 3 ) );
+
+			 }
+
+			 // read RLE
+
+			 if ( use_rle ) {
+
+				 pixel_data = new Uint8Array( pixel_total );
+
+				let c, count, i;
+				let shift = 0;
+				const pixels = new Uint8Array( pixel_size );
+
+				while ( shift < pixel_total ) {
+
+					c = data[ offset ++ ];
+					count = ( c & 0x7f ) + 1;
+
+					// RLE pixels
+
+					if ( c & 0x80 ) {
+
+						// bind pixel tmp array
+
+						for ( i = 0; i < pixel_size; ++ i ) {
+
+							pixels[ i ] = data[ offset ++ ];
+
+						}
+
+						// copy pixel array
+
+						for ( i = 0; i < count; ++ i ) {
+
+							pixel_data.set( pixels, shift + i * pixel_size );
+
+						}
+
+						shift += pixel_size * count;
+
+					} else {
+
+						// raw pixels
+
+						count *= pixel_size;
+
+						for ( i = 0; i < count; ++ i ) {
+
+							pixel_data[ shift + i ] = data[ offset ++ ];
+
+						}
+
+						shift += count;
+
+					}
+
+				}
+
+			 } else {
+
+				// raw pixels
+
+				pixel_data = data.subarray(
+					 offset, offset += ( use_pal ? header.width * header.height : pixel_total )
+				);
+
+			 }
+
+			 return {
+				pixel_data: pixel_data,
+				palettes: palettes
+			 };
+
+		}
+
+		function tgaGetImageData8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image, palettes ) {
+
+			const colormap = palettes;
+			let color, i = 0, x, y;
+			const width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
+
+					color = image[ i ];
+					imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+					imageData[ ( x + width * y ) * 4 + 2 ] = colormap[ ( color * 3 ) + 0 ];
+					imageData[ ( x + width * y ) * 4 + 1 ] = colormap[ ( color * 3 ) + 1 ];
+					imageData[ ( x + width * y ) * 4 + 0 ] = colormap[ ( color * 3 ) + 2 ];
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageData16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			let color, i = 0, x, y;
+			const width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
+
+					color = image[ i + 0 ] + ( image[ i + 1 ] << 8 );
+					imageData[ ( x + width * y ) * 4 + 0 ] = ( color & 0x7C00 ) >> 7;
+					imageData[ ( x + width * y ) * 4 + 1 ] = ( color & 0x03E0 ) >> 2;
+					imageData[ ( x + width * y ) * 4 + 2 ] = ( color & 0x001F ) << 3;
+					imageData[ ( x + width * y ) * 4 + 3 ] = ( color & 0x8000 ) ? 0 : 255;
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageData24bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			let i = 0, x, y;
+			const width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i += 3 ) {
+
+					imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+					imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
+					imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageData32bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			let i = 0, x, y;
+			const width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i += 4 ) {
+
+					imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
+					imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
+					imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 3 ];
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageDataGrey8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			let color, i = 0, x, y;
+			const width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
+
+					color = image[ i ];
+					imageData[ ( x + width * y ) * 4 + 0 ] = color;
+					imageData[ ( x + width * y ) * 4 + 1 ] = color;
+					imageData[ ( x + width * y ) * 4 + 2 ] = color;
+					imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageDataGrey16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			let i = 0, x, y;
+			const width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
+
+					imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 1 ];
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function getTgaRGBA( data, width, height, image, palette ) {
+
+			let x_start,
+				y_start,
+				x_step,
+				y_step,
+				x_end,
+				y_end;
+
+			switch ( ( header.flags & TGA_ORIGIN_MASK ) >> TGA_ORIGIN_SHIFT ) {
+
+				default:
+				case TGA_ORIGIN_UL:
+					x_start = 0;
+					x_step = 1;
+					x_end = width;
+					y_start = 0;
+					y_step = 1;
+					y_end = height;
+					break;
+
+				case TGA_ORIGIN_BL:
+					x_start = 0;
+					x_step = 1;
+					x_end = width;
+					y_start = height - 1;
+					y_step = - 1;
+					y_end = - 1;
+					break;
+
+				case TGA_ORIGIN_UR:
+					x_start = width - 1;
+					x_step = - 1;
+					x_end = - 1;
+					y_start = 0;
+					y_step = 1;
+					y_end = height;
+					break;
+
+				case TGA_ORIGIN_BR:
+					x_start = width - 1;
+					x_step = - 1;
+					x_end = - 1;
+					y_start = height - 1;
+					y_step = - 1;
+					y_end = - 1;
+					break;
+
+			}
+
+			if ( use_grey ) {
+
+				switch ( header.pixel_size ) {
+
+					case 8:
+						tgaGetImageDataGrey8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					case 16:
+						tgaGetImageDataGrey16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					default:
+						throw new Error( 'THREE.TGALoader: Format not supported.' );
+
+				}
+
+			} else {
+
+				switch ( header.pixel_size ) {
+
+					case 8:
+						tgaGetImageData8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image, palette );
+						break;
+
+					case 16:
+						tgaGetImageData16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					case 24:
+						tgaGetImageData24bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					case 32:
+						tgaGetImageData32bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					default:
+						throw new Error( 'THREE.TGALoader: Format not supported.' );
+
+				}
+
+			}
+
+			// Load image data according to specific method
+			// let func = 'tgaGetImageData' + (use_grey ? 'Grey' : '') + (header.pixel_size) + 'bits';
+			// func(data, y_start, y_step, y_end, x_start, x_step, x_end, width, image, palette );
+			return data;
+
+		}
+
+		// TGA constants
+
+		const TGA_TYPE_NO_DATA = 0,
+			TGA_TYPE_INDEXED = 1,
+			TGA_TYPE_RGB = 2,
+			TGA_TYPE_GREY = 3,
+			TGA_TYPE_RLE_INDEXED = 9,
+			TGA_TYPE_RLE_RGB = 10,
+			TGA_TYPE_RLE_GREY = 11,
+
+			TGA_ORIGIN_MASK = 0x30,
+			TGA_ORIGIN_SHIFT = 0x04,
+			TGA_ORIGIN_BL = 0x00,
+			TGA_ORIGIN_BR = 0x01,
+			TGA_ORIGIN_UL = 0x02,
+			TGA_ORIGIN_UR = 0x03;
+
+		if ( buffer.length < 19 ) throw new Error( 'THREE.TGALoader: Not enough data to contain header.' );
+
+		let offset = 0;
+
+		const content = new Uint8Array( buffer ),
+			header = {
+				id_length: content[ offset ++ ],
+				colormap_type: content[ offset ++ ],
+				image_type: content[ offset ++ ],
+				colormap_index: content[ offset ++ ] | content[ offset ++ ] << 8,
+				colormap_length: content[ offset ++ ] | content[ offset ++ ] << 8,
+				colormap_size: content[ offset ++ ],
+				origin: [
+					content[ offset ++ ] | content[ offset ++ ] << 8,
+					content[ offset ++ ] | content[ offset ++ ] << 8
+				],
+				width: content[ offset ++ ] | content[ offset ++ ] << 8,
+				height: content[ offset ++ ] | content[ offset ++ ] << 8,
+				pixel_size: content[ offset ++ ],
+				flags: content[ offset ++ ]
+			};
+
+		// check tga if it is valid format
+
+		tgaCheckHeader( header );
+
+		if ( header.id_length + offset > buffer.length ) {
+
+			throw new Error( 'THREE.TGALoader: No data.' );
+
+		}
+
+		// skip the needn't data
+
+		offset += header.id_length;
+
+		// get targa information about RLE compression and palette
+
+		let use_rle = false,
+			use_pal = false,
+			use_grey = false;
+
+		switch ( header.image_type ) {
+
+			case TGA_TYPE_RLE_INDEXED:
+				use_rle = true;
+				use_pal = true;
+				break;
+
+			case TGA_TYPE_INDEXED:
+				use_pal = true;
+				break;
+
+			case TGA_TYPE_RLE_RGB:
+				use_rle = true;
+				break;
+
+			case TGA_TYPE_RGB:
+				break;
+
+			case TGA_TYPE_RLE_GREY:
+				use_rle = true;
+				use_grey = true;
+				break;
+
+			case TGA_TYPE_GREY:
+				use_grey = true;
+				break;
+
+		}
+
+		//
+
+		const imageData = new Uint8Array( header.width * header.height * 4 );
+		const result = tgaParse( use_rle, use_pal, header, offset, content );
+		getTgaRGBA( imageData, header.width, header.height, result.pixel_data, result.palettes );
+
+		return {
+
+			data: imageData,
+			width: header.width,
+			height: header.height,
+			flipY: true,
+			generateMipmaps: true,
+			minFilter: LinearMipmapLinearFilter,
+
+		};
+
+	}
+
+}
+
+export { TGALoader };

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 6 - 0
packages/temp-pc/public/modelLook/vendor/three.core.min.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 6 - 0
packages/temp-pc/public/modelLook/vendor/three.module.min.js


+ 60 - 0
packages/temp-pc/src/App.vue

@@ -0,0 +1,60 @@
+<script setup>
+import { RouterView } from "vue-router";
+</script>
+
+<template>
+  <RouterView />
+</template>
+
+<style lang="scss">
+@use "./assets/utils.scss";
+
+:root {
+  --design-width: 1921;
+  --design-height: 971;
+}
+
+img:not(.el-image-viewer__img) {
+  width: 100%;
+  height: 100%;
+}
+
+.el-image {
+  --el-fill-color-light: transparent;
+}
+
+#app {
+  font-size: 16px;
+}
+
+.back {
+  position: fixed;
+  top: utils.vh-calc(28);
+  left: 34px;
+  width: utils.vh-calc(99);
+  height: utils.vh-calc(42);
+  background: url("./assets/images/btn_close-min.png") no-repeat center /
+    contain;
+  cursor: pointer;
+  z-index: 998;
+}
+
+.close {
+  position: fixed;
+  top: utils.vh-calc(55);
+  right: 124px;
+  width: utils.vh-calc(99);
+  height: utils.vh-calc(43);
+  background: url("./assets/images/btn_close.png") no-repeat center / contain;
+  cursor: pointer;
+  z-index: 998;
+}
+
+img[lazy] {
+  opacity: 0;
+  transition: opacity 0.2s ease-in-out;
+}
+img[lazy="loaded"] {
+  opacity: 1;
+}
+</style>

+ 7 - 0
packages/temp-pc/src/assets/elements.scss

@@ -0,0 +1,7 @@
+@forward "element-plus/theme-chalk/src/common/var.scss" with (
+  $colors: (
+    "primary": (
+      "base": #610415,
+    ),
+  )
+);

BIN
packages/temp-pc/src/assets/fonts/FZJinLing.TTF


BIN
packages/temp-pc/src/assets/fonts/SOURCEHANSERIFCN-BOLD.OTF


BIN
packages/temp-pc/src/assets/fonts/SourceHanSerifCN-Light.otf


BIN
packages/temp-pc/src/assets/fonts/SourceHanSerifCN-Medium.otf


BIN
packages/temp-pc/src/assets/fonts/SourceHanSerifCN-Regular.otf


BIN
packages/temp-pc/src/assets/fonts/SourceHanSerifCN-SemiBold.otf


BIN
packages/temp-pc/src/assets/images/btn_close-min.png


BIN
packages/temp-pc/src/assets/images/btn_close.png


BIN
packages/temp-pc/src/assets/images/btn_middle_active-min.png


BIN
packages/temp-pc/src/assets/images/logo.png


+ 111 - 0
packages/temp-pc/src/assets/main.css

@@ -0,0 +1,111 @@
+:root {
+  --z-index-normal: 1;
+  --z-index-top: 1000;
+  --z-index-popper: 2000;
+  --z-hot-popper: 3000;
+}
+
+body,
+ol,
+ul,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+th,
+td,
+dl,
+dd,
+form,
+fieldset,
+legend,
+input,
+textarea,
+select {
+  margin: 0;
+  padding: 0;
+}
+* {
+  box-sizing: border-box;
+}
+body {
+  color: #383838;
+  text-align: justify;
+  font-family: "SourceHanSerifSC-Regular";
+  -webkit-tap-highlight-color: transparent;
+}
+a {
+  color: #fff;
+  cursor: pointer;
+  text-decoration: none;
+}
+em {
+  font-style: normal;
+}
+li {
+  list-style: none;
+}
+img {
+  border: 0;
+  vertical-align: middle;
+}
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+p {
+  word-wrap: break-word;
+}
+iframe {
+  border: none;
+}
+
+@font-face {
+  font-family: "SourceHanSerifSC-Bold";
+  src: url("@/assets/fonts/SOURCEHANSERIFCN-BOLD.otf");
+}
+@font-face {
+  font-family: "SourceHanSerifSC-Regular";
+  src: url("@/assets/fonts/SOURCEHANSERIFCN-REGULAR.otf");
+}
+
+.limit-line {
+  display: -webkit-box;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  -webkit-line-clamp: 1;
+  -webkit-box-orient: vertical;
+  word-break: break-all;
+  word-wrap: break-word;
+}
+
+.line-2 {
+  -webkit-line-clamp: 2;
+}
+
+.line-3 {
+  -webkit-line-clamp: 3;
+}
+
+.hidden {
+  display: none !important;
+  visibility: hidden !important;
+}
+
+.darkGlass {
+  background-color: rgba(0, 0, 0, 0.5);
+}
+
+.message-outer {
+  position: absolute;
+  display: table;
+  height: 100%;
+  width: 100%;
+
+  * {
+    transition: all 0.3s;
+  }
+}

+ 7 - 0
packages/temp-pc/src/assets/utils.scss

@@ -0,0 +1,7 @@
+@function vh-calc($num) {
+  @return calc(100vh * ($num / var(--design-height)));
+}
+
+@function vw-calc($num) {
+  @return calc(100vw * ($num / var(--design-width)));
+}

+ 17 - 0
packages/temp-pc/src/main.js

@@ -0,0 +1,17 @@
+import "./assets/main.css";
+import "animate.css";
+
+import { createApp } from "vue";
+import { createPinia } from "pinia";
+import VueLazyloadNext from "vue-lazyload-next";
+
+import App from "./App.vue";
+import router from "./router";
+
+const app = createApp(App);
+
+app.use(createPinia());
+app.use(router);
+app.use(VueLazyloadNext);
+
+app.mount("#app");

+ 27 - 0
packages/temp-pc/src/router/index.js

@@ -0,0 +1,27 @@
+import { createRouter, createWebHashHistory } from "vue-router";
+
+const router = createRouter({
+  history: createWebHashHistory(import.meta.env.BASE_URL),
+  routes: [
+    {
+      path: "/",
+      name: "collection",
+      component: () => import("../views/Collection/list.vue"),
+    },
+    {
+      path: "/collection/:id",
+      name: "collectionDetail",
+      component: () => import("../views/Collection/detail.vue"),
+    },
+  ],
+  scrollBehavior(to, from, savedPosition) {
+    // 如果有保存的位置(浏览器前进/后退),则使用保存的位置
+    if (savedPosition) {
+      return savedPosition;
+    }
+    // 否则滚动到顶部
+    return { top: 0 };
+  },
+});
+
+export default router;

+ 54 - 0
packages/temp-pc/src/stores/base.js

@@ -0,0 +1,54 @@
+import { ref } from "vue";
+import { defineStore } from "pinia";
+import { parseUrlName } from "@shandong/base";
+import Img1 from "../views/Home/images/bg_01-min.jpg";
+import Img3 from "../views/Scroll/images/bg.jpg";
+import Img4 from "../components/Game1/images/img_neiyi-min.png";
+import Img2 from "../components/Game1/images/bg-min.jpg";
+import imgLc from "../components/Game2/images/img_lc.png";
+import imgQmy from "../components/Game2/images/img_qumy.png";
+import imgLy from "../components/Game2/images/img_yqy.png";
+import imgWsy from "../components/Game2/images/img_wsy.png";
+import imgMwy from "../components/Game2/images/img_mwy.png";
+
+const BASE_URL = import.meta.env.BASE_URL;
+const Video1 = `${BASE_URL}animations/2.mp4`;
+const Video2 = `${BASE_URL}animations/3.mp4`;
+const ImgAnimations = Array.from(
+  { length: 125 },
+  (_, index) => `${BASE_URL}animations/过场1/过场1_00${index + 147}.png`,
+);
+
+export const RESOURCE_LIST = [
+  Img1,
+  Img3,
+  Img4,
+  Img2,
+  ...ImgAnimations,
+  imgLc,
+  imgQmy,
+  imgLy,
+  imgWsy,
+  imgMwy,
+  Video1,
+  Video2,
+];
+
+export const useBaseStore = defineStore("base", () => {
+  const mediaUrlMap = ref(new Map());
+
+  RESOURCE_LIST.forEach((url) => {
+    mediaUrlMap.value.set(parseUrlName(url), url);
+  });
+
+  const setMediaUrlMap = (list) => {
+    mediaUrlMap.value = list;
+  };
+
+  const scrollTranslate = ref(0);
+  const setScrollTranslate = (t) => {
+    scrollTranslate.value = t;
+  };
+
+  return { mediaUrlMap, setMediaUrlMap, scrollTranslate, setScrollTranslate };
+});

+ 12 - 0
packages/temp-pc/src/stores/counter.js

@@ -0,0 +1,12 @@
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useCounterStore = defineStore('counter', () => {
+  const count = ref(0)
+  const doubleCount = computed(() => count.value * 2)
+  function increment() {
+    count.value++
+  }
+
+  return { count, doubleCount, increment }
+})

+ 0 - 0
packages/temp-pc/src/utils/constants.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 134 - 0
packages/temp-pc/src/views/Collection/constants.ts


+ 0 - 0
packages/temp-pc/src/views/Collection/detail.scss


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels