|
@@ -0,0 +1,298 @@
|
|
|
+import { Component, Ref, Vue } from "vue-property-decorator";
|
|
|
+import * as THREE from "three";
|
|
|
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
|
|
+import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
|
|
|
+import { DataTexture } from "three";
|
|
|
+import { USDZInstance } from "three-usdz-loader/lib/USDZInstance";
|
|
|
+import { USDZLoader } from "three-usdz-loader";
|
|
|
+import { useRoute } from "vue-router";
|
|
|
+
|
|
|
+@Component
|
|
|
+export default class Home extends Vue {
|
|
|
+ @Ref("three-container") threeContainer!: HTMLElement;
|
|
|
+ @Ref("file") fileInput!: HTMLInputElement;
|
|
|
+
|
|
|
+ scene!: THREE.Scene;
|
|
|
+ camera!: THREE.PerspectiveCamera;
|
|
|
+ renderer!: THREE.WebGLRenderer;
|
|
|
+ currentFileName!: string;
|
|
|
+ controls!: OrbitControls;
|
|
|
+
|
|
|
+ // Tells if a model is currently visible
|
|
|
+ modelIsVisible = false;
|
|
|
+
|
|
|
+ // Tells if a file is loading currently
|
|
|
+ modelIsLoading = false;
|
|
|
+
|
|
|
+ // Dialog open or closed
|
|
|
+ dialog = false;
|
|
|
+
|
|
|
+ // Loaded models
|
|
|
+ loadedModels: USDZInstance[] = [];
|
|
|
+
|
|
|
+ // USDZ loader instance. Only one should be instantiated in the DOM scope
|
|
|
+ loader!: USDZLoader;
|
|
|
+
|
|
|
+ // Simple error handling
|
|
|
+ error: string | null = null;
|
|
|
+
|
|
|
+ // Tells if the loader has loaded with success
|
|
|
+ loaderReady: boolean | null = null;
|
|
|
+
|
|
|
+ async mounted(): Promise<void> {
|
|
|
+ window.addEventListener("message", this.handleMessage)
|
|
|
+ // Setup camera
|
|
|
+ // this.camera = new THREE.PerspectiveCamera(
|
|
|
+ // 27,
|
|
|
+ // window.innerWidth / window.innerHeight,
|
|
|
+ // 1,
|
|
|
+ // 3500
|
|
|
+ // );
|
|
|
+ // this.camera.position.z = 7;
|
|
|
+ // this.camera.position.y = 7;
|
|
|
+ // this.camera.position.x = 0;
|
|
|
+
|
|
|
+ // // Setup scene
|
|
|
+ // this.scene = new THREE.Scene();
|
|
|
+ // this.scene.background = new THREE.Color(0xffffff);
|
|
|
+
|
|
|
+ // // Setup light
|
|
|
+ // const ambiantLight = new THREE.AmbientLight(0x111111);
|
|
|
+ // ambiantLight.intensity = 1;
|
|
|
+ // this.scene.add(ambiantLight);
|
|
|
+
|
|
|
+ // // Setup main scene
|
|
|
+ // this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
|
+ // this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
|
+ // this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
+ // this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
|
+ // this.renderer.shadowMap.enabled = false;
|
|
|
+ // this.renderer.shadowMap.type = THREE.VSMShadowMap;
|
|
|
+
|
|
|
+ // // Setup cubemap for reflection
|
|
|
+ // await new Promise((resolve) => {
|
|
|
+ // const pmremGenerator = new THREE.PMREMGenerator(this.renderer);
|
|
|
+ // pmremGenerator.compileCubemapShader();
|
|
|
+ // new RGBELoader().load(
|
|
|
+ // "studio_country_hall_1k.hdr",
|
|
|
+ // (texture: DataTexture) => {
|
|
|
+ // const hdrRenderTarget = pmremGenerator.fromEquirectangular(texture);
|
|
|
+
|
|
|
+ // texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
|
+ // texture.needsUpdate = true;
|
|
|
+ // window.envMap = hdrRenderTarget.texture;
|
|
|
+ // hdrRenderTarget.texture.colorSpace = THREE.LinearSRGBColorSpace;
|
|
|
+ // resolve(true);
|
|
|
+ // }
|
|
|
+ // );
|
|
|
+ // });
|
|
|
+
|
|
|
+ // //Add the canvas to the document
|
|
|
+ // this.threeContainer.appendChild(this.renderer.domElement);
|
|
|
+
|
|
|
+ // // Setup navigation
|
|
|
+ // this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
|
|
+ // this.controls.update();
|
|
|
+
|
|
|
+ // // Setup main animation update loop
|
|
|
+ // this.animate();
|
|
|
+
|
|
|
+ // // Setup the USDZ loader
|
|
|
+ // this.loader = new USDZLoader("./wasm");
|
|
|
+ // const obj = this.getQueryParams(window.location.search);
|
|
|
+ // const fileName = (obj.m || "test") + ".usdz";
|
|
|
+ // // Setup windows events
|
|
|
+ // window.addEventListener("resize", this.onWindowResize);
|
|
|
+ // this.getUrlAsFile(fileName, fileName, (file: File) => {
|
|
|
+ // console.log("File object:", file);
|
|
|
+ // this.loadFile(file);
|
|
|
+ // // 现在你可以使用 file 对象进行操作
|
|
|
+ // });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Main update loop
|
|
|
+ */
|
|
|
+ async animate(): Promise<void> {
|
|
|
+ const secs = new Date().getTime() / 1000;
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 10));
|
|
|
+
|
|
|
+ // Update all models animations (in our exemple there is only one model at a time)
|
|
|
+ for (const loadedModel of this.loadedModels) {
|
|
|
+ loadedModel.update(secs);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.renderer.render(this.scene, this.camera);
|
|
|
+ requestAnimationFrame(this.animate.bind(null));
|
|
|
+ }
|
|
|
+ handleMessage(event: MessageEvent): void {
|
|
|
+ console.log('handleMessage', event.data);
|
|
|
+ // console.log("handleMessage", window);
|
|
|
+ // window.parent && window.parent.postMessage({loading: true}, "*");
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Load a USDZ file in the scene
|
|
|
+ * @param file
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+ async loadFile(file: File): Promise<void> {
|
|
|
+ // Prevents multiple loadings in parallel
|
|
|
+ if (this.modelIsLoading) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Notice model is now loading
|
|
|
+ this.modelIsLoading = true;
|
|
|
+
|
|
|
+ // Reset any previous error
|
|
|
+ this.error = null;
|
|
|
+
|
|
|
+ // Clearup any previsouly loaded model
|
|
|
+ // We could technically load multiple files by removing this for loop
|
|
|
+ for (const el of this.loadedModels) {
|
|
|
+ el.clear();
|
|
|
+ }
|
|
|
+ this.loadedModels = [];
|
|
|
+
|
|
|
+ // Create the ThreeJs Group in which the loaded USDZ model will be placed
|
|
|
+ const group = new THREE.Group();
|
|
|
+ this.scene.add(group);
|
|
|
+
|
|
|
+ // Load file and catch any error to show the user
|
|
|
+ try {
|
|
|
+ const loadedModel = await this.loader.loadFile(file, group);
|
|
|
+ this.loadedModels.push(loadedModel);
|
|
|
+ } catch (e) {
|
|
|
+ this.error = e as string;
|
|
|
+ console.error("An error occured when trying to load the model" + e);
|
|
|
+ this.modelIsLoading = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fits the camera to match the loaded model
|
|
|
+ const allContainers = this.loadedModels.map((el: USDZInstance) => {
|
|
|
+ return el.getGroup();
|
|
|
+ });
|
|
|
+ this.fitCamera(this.camera, this.controls, allContainers);
|
|
|
+
|
|
|
+ // Notice end
|
|
|
+ this.modelIsLoading = false;
|
|
|
+ this.modelIsVisible = true;
|
|
|
+ console.log("Model loaded 渲染完毕");
|
|
|
+ window.parent && window.parent.postMessage({loading: true}, "*");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Fits the camera view to the imported objects
|
|
|
+ */
|
|
|
+ fitCamera(
|
|
|
+ camera: THREE.PerspectiveCamera,
|
|
|
+ controls: OrbitControls,
|
|
|
+ selection: THREE.Group[],
|
|
|
+ fitOffset = 1.5
|
|
|
+ ): void {
|
|
|
+ const cam = camera as THREE.PerspectiveCamera;
|
|
|
+ const size = new THREE.Vector3();
|
|
|
+ const center = new THREE.Vector3();
|
|
|
+ const box = new THREE.Box3();
|
|
|
+
|
|
|
+ box.makeEmpty();
|
|
|
+ for (const object of selection) {
|
|
|
+ box.expandByObject(object);
|
|
|
+ }
|
|
|
+
|
|
|
+ box.getSize(size);
|
|
|
+ box.getCenter(center);
|
|
|
+
|
|
|
+ const maxSize = Math.max(size.x, size.y, size.z);
|
|
|
+ const fitHeightDistance =
|
|
|
+ maxSize / (2 * Math.atan((Math.PI * cam.fov) / 360));
|
|
|
+ const fitWidthDistance = fitHeightDistance / cam.aspect;
|
|
|
+ const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance);
|
|
|
+
|
|
|
+ const direction = controls.target
|
|
|
+ .clone()
|
|
|
+ .sub(cam.position)
|
|
|
+ .normalize()
|
|
|
+ .multiplyScalar(distance);
|
|
|
+
|
|
|
+ controls.maxDistance = distance * 10;
|
|
|
+ controls.target.copy(center);
|
|
|
+
|
|
|
+ cam.near = distance / 100;
|
|
|
+ cam.far = distance * 100;
|
|
|
+ cam.updateProjectionMatrix();
|
|
|
+
|
|
|
+ camera.position.copy(controls.target).sub(direction);
|
|
|
+
|
|
|
+ controls.update();
|
|
|
+ }
|
|
|
+
|
|
|
+ onWindowResize(): void {
|
|
|
+ this.camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
+ this.camera.updateProjectionMatrix();
|
|
|
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
+ }
|
|
|
+ onChange(): void {
|
|
|
+ if (this.fileInput.files != null) {
|
|
|
+ this.handleFilesUpload(this.fileInput.files);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ onClickDragZone(): void {
|
|
|
+ this.fileInput.click();
|
|
|
+ }
|
|
|
+ getUrlAsFile(url = "test.usdz", fileName = "test.usdz", callback: any): void {
|
|
|
+ const xhr = new XMLHttpRequest();
|
|
|
+ // xhr.open("GET", "http://localhost:8888/test.usdz", true);
|
|
|
+ xhr.open("GET", "https://xinjiapo-aws-test.4dkankan.com/20241202dome/" + url, true);
|
|
|
+ xhr.setRequestHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
|
|
+ xhr.setRequestHeader("Cross-Origin-Opener-Policy", "same-origin");
|
|
|
+ xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
|
|
|
+ xhr.responseType = "blob";
|
|
|
+
|
|
|
+ xhr.onload = function () {
|
|
|
+ console.log("onload:onload", xhr.response);
|
|
|
+ if (xhr.status === 200) {
|
|
|
+ const blob = xhr.response;
|
|
|
+ const file = new File([blob], fileName, { type: blob.type });
|
|
|
+ callback(file);
|
|
|
+ } else {
|
|
|
+ console.error("Error fetching the file:", xhr.statusText);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ xhr.onerror = function () {
|
|
|
+ console.error("Error fetching the file:", xhr.statusText);
|
|
|
+ };
|
|
|
+
|
|
|
+ xhr.send();
|
|
|
+ }
|
|
|
+ dragover(event: DragEvent): void {
|
|
|
+ event.preventDefault();
|
|
|
+ }
|
|
|
+ drop(event: DragEvent): void {
|
|
|
+ event.preventDefault();
|
|
|
+ if (event.dataTransfer == null) {
|
|
|
+ console.error("Files are null");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.handleFilesUpload(event.dataTransfer.files);
|
|
|
+ }
|
|
|
+ handleFilesUpload(files: FileList): void {
|
|
|
+ console.log("handleFilesUpload", files);
|
|
|
+ this.loadFile(files[0]);
|
|
|
+ }
|
|
|
+ getQueryParams(url: string): any {
|
|
|
+ const paramArr = url.slice(url.indexOf("?") + 1).split("&");
|
|
|
+ const params = {
|
|
|
+ m: "",
|
|
|
+ };
|
|
|
+ paramArr.map((param) => {
|
|
|
+ const [key, val] = param.split("=");
|
|
|
+ if (key == "m") {
|
|
|
+ params.m = decodeURIComponent(val);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return params;
|
|
|
+ }
|
|
|
+}
|