diff --git a/src/components/World/World.js b/src/components/World/World.js index 43518e77..f60490bc 100644 --- a/src/components/World/World.js +++ b/src/components/World/World.js @@ -1,35 +1,23 @@ // from https://medium.com/nicasource/building-an-interactive-web-portfolio-with-vue-three-js-part-three-implementing-three-js-452cb375ef80 import * as TWEEN from "@tweenjs/tween.js"; -import axios from "axios"; import * as THREE from "three"; -import { GLTFLoader } from "three/addons/loaders/GLTFLoader"; -import * as SkeletonUtils from "three/addons/utils/SkeletonUtils.js"; -import { AppString } from "@/constants/app"; import { createCamera } from "./components/camera.js"; import { createLights } from "./components/lights.js"; import { createScene } from "./components/scene.js"; +import { loadLandmarks } from "./components/objects/landmarks.js"; import { createTerrain } from "./components/objects/terrain.js"; import { Loop } from "./systems/Loop.js"; import { Resizer } from "./systems/Resizer.js"; import { createControls } from "./systems/controls.js"; import { createRenderer } from "./systems/renderer.js"; -const ANIMATION_DURATION_SECS = 10; -const BASE32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; const COLOR1 = "#dddddd"; const COLOR2 = "#0055aa"; const PLATFORM_BORDER = 10; const PLATFORM_EDGE_FOR_UNKNOWNS = 10; -const PLATFORM_SIZE = 100; - -function createLight() { - const light = new THREE.SpotLight(0xffffff, 0, 0, Math.PI / 8, 0.5, 0); - // eslint-disable-next-line @typescript-eslint/no-empty-function - light.tick = () => {}; - return light; -} +const PLATFORM_SIZE = 100; // note that the loadLandmarks calculations may still assume 100 class World { constructor(container, vue) { @@ -72,7 +60,7 @@ class World { this.scene.add(light, terrain); - this.loadClaims(vue); + loadLandmarks(vue, this, this.scene, this.loop); requestAnimationFrame(this.update); @@ -95,120 +83,6 @@ class World { requestAnimationFrame(this.update); } - async loadClaims(vue) { - const endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER; - try { - const url = - endorserApiServer + "/api/v2/report/claims?claimType=GiveAction"; - const headers = { "Content-Type": "application/json" }; - const resp = await axios.get(url, { headers: headers }); - if (resp.status === 200) { - const minDate = resp.data.data[resp.data.data.length - 1].issuedAt; - const maxDate = resp.data.data[0].issuedAt; - const minTimeMillis = new Date(minDate).getTime(); - const fullTimeMillis = new Date(maxDate).getTime() - minTimeMillis; - // ratio of animation time to real time - const fakeRealRatio = (ANIMATION_DURATION_SECS * 1000) / fullTimeMillis; - - // load plant model first because it takes a second - const loader = new GLTFLoader(); - // choose the right plant - const modelLoc = "/models/lupine_plant/scene.gltf", // push with pokies - modScale = 0.1; - //const modelLoc = "/models/round_bush/scene.gltf", // green & pink - // modScale = 1; - //const modelLoc = "/models/coreopsis-flower.glb", // 3 flowers - // modScale = 2; - //const modelLoc = "/models/a_bush/scene.gltf", // purple leaves - // modScale = 15, - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const parentWorld = this; - loader.load( - modelLoc, - function (gltf) { - gltf.scene.scale.set(0, 0, 0); - for (const claim of resp.data.data) { - const newPlant = SkeletonUtils.clone(gltf.scene); - const randomness = claim.id.substring(10); - const x = - (100 * BASE32.indexOf(randomness.substring(0, 1))) / 32 - 50; - const z = - (100 * BASE32.indexOf(randomness.substring(8, 9))) / 32 - 50; - newPlant.position.set(x, 0, z); - - parentWorld.scene.add(newPlant); - const timeDelayMillis = - fakeRealRatio * - (new Date(claim.issuedAt).getTime() - minTimeMillis); - new TWEEN.Tween(newPlant.scale) - .delay(timeDelayMillis) - .to({ x: modScale, y: modScale, z: modScale }, 5000) - .start(); - parentWorld.bushes = [...parentWorld.bushes, newPlant]; - } - }, - undefined, - function (error) { - console.error(error); - } - ); - - // calculate when lights shine on appearing claim area - for (const claim of resp.data.data) { - // claim is a GiveServerRecord (see endorserServer.ts) - - // compute location for this claim - const randomness = claim.id.substring(10); - const x = - (100 * BASE32.indexOf(randomness.substring(0, 1))) / 32 - 50; - const z = - (100 * BASE32.indexOf(randomness.substring(8, 9))) / 32 - 50; - const light = createLight(); - light.position.set(x, 20, z); - light.target.position.set(x, 0, z); - this.loop.updatables.push(light); - this.scene.add(light); - this.scene.add(light.target); - - // now figure out the timing and shine a light - const timeDelayMillis = - fakeRealRatio * - (new Date(claim.issuedAt).getTime() - minTimeMillis); - new TWEEN.Tween(light) - .delay(timeDelayMillis) - .to({ intensity: 100 }, 10) - .chain( - new TWEEN.Tween(light.position) - .to({ y: 5 }, 5000) - .onComplete(() => { - this.scene.remove(light); - light.dispose(); - }) - ) - .start(); - this.lights = [...this.lights, light]; - } - } else { - console.log( - "Got bad server response status & data of", - resp.status, - resp.data - ); - vue.setAlert( - "Error With Server", - "There was an error retrieving your claims from the server." - ); - } - } catch (error) { - console.log("Got exception contacting server:", error); - vue.setAlert( - "Error With Server", - "There was a problem retrieving your claims from the server." - ); - } - } - render() { // draw a single frame this.renderer.render(this.scene, this.camera); diff --git a/src/components/World/components/objects/landmarks.js b/src/components/World/components/objects/landmarks.js new file mode 100644 index 00000000..9326a820 --- /dev/null +++ b/src/components/World/components/objects/landmarks.js @@ -0,0 +1,127 @@ +import axios from "axios"; +import * as THREE from "three"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader"; +import * as SkeletonUtils from "three/addons/utils/SkeletonUtils"; +import * as TWEEN from "@tweenjs/tween.js"; +import { AppString } from "@/constants/app"; + +const ANIMATION_DURATION_SECS = 10; +const BASE32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; + +export async function loadLandmarks(vue, world, scene, loop) { + const endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER; + try { + const url = + endorserApiServer + "/api/v2/report/claims?claimType=GiveAction"; + const headers = { "Content-Type": "application/json" }; + const resp = await axios.get(url, { headers: headers }); + if (resp.status === 200) { + const minDate = resp.data.data[resp.data.data.length - 1].issuedAt; + const maxDate = resp.data.data[0].issuedAt; + const minTimeMillis = new Date(minDate).getTime(); + const fullTimeMillis = new Date(maxDate).getTime() - minTimeMillis; + // ratio of animation time to real time + const fakeRealRatio = (ANIMATION_DURATION_SECS * 1000) / fullTimeMillis; + + // load plant model first because it takes a second + const loader = new GLTFLoader(); + // choose the right plant + const modelLoc = "/models/lupine_plant/scene.gltf", // push with pokies + modScale = 0.1; + //const modelLoc = "/models/round_bush/scene.gltf", // green & pink + // modScale = 1; + //const modelLoc = "/models/coreopsis-flower.glb", // 3 flowers + // modScale = 2; + //const modelLoc = "/models/a_bush/scene.gltf", // purple leaves + // modScale = 15, + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const parentWorld = world; + loader.load( + modelLoc, + function (gltf) { + gltf.scene.scale.set(0, 0, 0); + for (const claim of resp.data.data) { + const newPlant = SkeletonUtils.clone(gltf.scene); + const randomness = claim.id.substring(10); + const x = + (100 * BASE32.indexOf(randomness.substring(0, 1))) / 32 - 50; + const z = + (100 * BASE32.indexOf(randomness.substring(8, 9))) / 32 - 50; + newPlant.position.set(x, 0, z); + + parentWorld.scene.add(newPlant); + const timeDelayMillis = + fakeRealRatio * + (new Date(claim.issuedAt).getTime() - minTimeMillis); + new TWEEN.Tween(newPlant.scale) + .delay(timeDelayMillis) + .to({ x: modScale, y: modScale, z: modScale }, 5000) + .start(); + parentWorld.bushes = [...parentWorld.bushes, newPlant]; + } + }, + undefined, + function (error) { + console.error(error); + } + ); + + // calculate when lights shine on appearing claim area + for (const claim of resp.data.data) { + // claim is a GiveServerRecord (see endorserServer.ts) + + // compute location for this claim + const randomness = claim.id.substring(10); + const x = (100 * BASE32.indexOf(randomness.substring(0, 1))) / 32 - 50; + const z = (100 * BASE32.indexOf(randomness.substring(8, 9))) / 32 - 50; + const light = createLight(); + light.position.set(x, 20, z); + light.target.position.set(x, 0, z); + loop.updatables.push(light); + scene.add(light); + scene.add(light.target); + + // now figure out the timing and shine a light + const timeDelayMillis = + fakeRealRatio * (new Date(claim.issuedAt).getTime() - minTimeMillis); + new TWEEN.Tween(light) + .delay(timeDelayMillis) + .to({ intensity: 100 }, 10) + .chain( + new TWEEN.Tween(light.position) + .to({ y: 5 }, 5000) + .onComplete(() => { + scene.remove(light); + light.dispose(); + }) + ) + .start(); + world.lights = [...world.lights, light]; + } + } else { + console.log( + "Got bad server response status & data of", + resp.status, + resp.data + ); + vue.setAlert( + "Error With Server", + "There was an error retrieving your claims from the server." + ); + } + } catch (error) { + console.log("Got exception contacting server:", error); + vue.setAlert( + "Error With Server", + "There was a problem retrieving your claims from the server." + ); + } +} + +function createLight() { + const light = new THREE.SpotLight(0xffffff, 0, 0, Math.PI / 8, 0.5, 0); + // eslint-disable-next-line @typescript-eslint/no-empty-function + light.tick = () => {}; + return light; +}