diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d40fbb5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/types"] + path = src/types + url = ssh://git@173.199.124.46:222/trent_larson/endorser-types.git diff --git a/components/objects/Terrain.ts b/components/objects/Terrain.ts new file mode 100644 index 0000000..0b08d64 --- /dev/null +++ b/components/objects/Terrain.ts @@ -0,0 +1,28 @@ +import { PlaneGeometry, MeshLambertMaterial, Mesh, TextureLoader, Color } from "three"; + +class Terrain { + constructor(props: { width: number; height: number; color: Color }) { + const loader = new TextureLoader(); + const heightTexture = loader.load("img/textures/leafy-autumn-forest-floor.jpg"); + const geometry = new PlaneGeometry(props.width, props.height, 64, 64); + + const material = new MeshLambertMaterial({ + color: props.color, + flatShading: true, + map: heightTexture, + }); + + const plane = new Mesh(geometry, material); + plane.position.set(0, 0, 0); + plane.rotation.x -= Math.PI * 0.5; + + // Storing our original vertices position on a new attribute + plane.geometry.attributes.position.originalPosition = plane.geometry.attributes.position.array; + + plane.tick = () => {}; + + return plane; + } +} + +export { Terrain }; diff --git a/package-lock.json b/package-lock.json index 8467388..3505512 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "world-component", "version": "0.0.0", "dependencies": { + "axios": "^1.4.0", + "ramda": "^0.29.0", "three": "^0.153.0", "three-orbitcontrols-ts": "^0.1.2", "vue": "^3.3.4", @@ -596,6 +598,21 @@ "@vue/language-core": "1.8.3" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -611,6 +628,17 @@ "balanced-match": "^1.0.0" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -622,6 +650,14 @@ "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/esbuild": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", @@ -670,6 +706,38 @@ "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -722,6 +790,25 @@ "node": ">=12" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.2.tgz", @@ -792,6 +879,20 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/ramda": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", + "integrity": "sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, "node_modules/rollup": { "version": "3.25.3", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.3.tgz", diff --git a/package.json b/package.json index d7832c1..92a2cda 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "preview": "vite preview" }, "dependencies": { + "axios": "^1.4.0", + "ramda": "^0.29.0", "three": "^0.153.0", "three-orbitcontrols-ts": "^0.1.2", "vue": "^3.3.4", diff --git a/src/components/objects/Landmarks.ts b/src/components/objects/Landmarks.ts new file mode 100644 index 0000000..571f67f --- /dev/null +++ b/src/components/objects/Landmarks.ts @@ -0,0 +1,137 @@ +import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; +import { SkeletonUtils } from 'three/examples/jsm/utils/SkeletonUtils'; +import axios from 'axios'; +import TWEEN from '@tweenjs/tween.js'; +import * as THREE from 'three' + +class LandmarksLoader { + async load(vue, world, scene, loop, token) { + vue.setWorldProperty("animationDurationSeconds", ANIMATION_DURATION_SECS); + + try { + const url = apiServer + "/api/v2/report/claims?claimType=GiveAction"; + const headers = { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }; + const resp = await axios.get(url, { headers: headers }); + if (resp.status === 200) { + const landmarks = resp.data.data; + + const minDate = landmarks[landmarks.length - 1].issuedAt; + const maxDate = landmarks[0].issuedAt; + + world.setExposedWorldProperties("startTime", minDate.replace("T", " ")); + world.setExposedWorldProperties("endTime", maxDate.replace("T", " ")); + + const minTimeMillis = new Date(minDate).getTime(); + const fullTimeMillis = + maxDate > minDate ? new Date(maxDate).getTime() - minTimeMillis : 1; // avoid divide by zero + // 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 + const 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; + + // calculate positions for each claim, especially because some are random + const locations = landmarks.map((claim: GiveServerRecord) => + locForGive(claim, world.PLATFORM_SIZE, world.PLATFORM_EDGE_FOR_UNKNOWNS) + ); + + // eslint-disable-next-line @typescript-eslint/no-this-alias + loader.load( + modelLoc, + function (gltf: GLTF) { + gltf.scene.scale.set(0, 0, 0); + for (let i = 0; i < landmarks.length; i++) { + // claim is a GiveServerRecord (see endorserServer.ts) + const claim = landmarks[i]; + const newPlant = SkeletonUtils.clone(gltf.scene); + + const loc = locations[i]; + newPlant.position.set(loc.x, 0, loc.z); + + world.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(); + world.bushes = [...world.bushes, newPlant]; + } + }, + undefined, + function (error: ErrorEvent) { + console.error(error); + } + ); + + // calculate when lights shine on appearing claim area + for (let i = 0; i < landmarks.length; i++) { + // claim is a GiveServerRecord (see endorserServer.ts) + const claim = landmarks[i]; + + const loc = locations[i]; + const light = this.createLight(); + light.position.set(loc.x, 20, loc.z); + light.target.position.set(loc.x, 0, loc.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.error( + "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.error("Got exception contacting server:", error); + vue.setAlert( + "Error With Server", + "There was a problem retrieving your claims from the server." + ); + } + } + + private 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; + } + +} diff --git a/src/systems/Resizer.ts b/src/systems/Resizer.ts index aa0446d..ba5ce46 100644 --- a/src/systems/Resizer.ts +++ b/src/systems/Resizer.ts @@ -1,10 +1,10 @@ class Resizer { - constructor(container: HTMLElement, camera: any, renderer: any) { - this.setSize(container, camera, renderer); + constructor(camera: any, renderer: any) { + this.setSize(camera, renderer); window.addEventListener("resize", () => { - this.setSize(container, camera, renderer); + this.setSize(camera, renderer); this.onResize(); }); } diff --git a/src/types b/src/types new file mode 160000 index 0000000..db51253 --- /dev/null +++ b/src/types @@ -0,0 +1 @@ +Subproject commit db5125314c1a0e59c749ea01a3784f89b7dc291a diff --git a/tsconfig.json b/tsconfig.json index 5e46dfb..cd812d7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,6 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/types/endorser.d.ts"], "references": [{ "path": "./tsconfig.node.json" }] }