diff --git a/README.md b/README.md index d692f22..8e75e13 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,8 @@ See https://tea.xyz ## Other +### Reference Material + ``` // reference material from https://github.com/trentlarson/endorser-mobile/blob/8dc8e0353e0cc80ffa7ed89ded15c8b0da92726b/src/utility/idUtility.ts#L83 @@ -183,3 +185,7 @@ export const createAndStoreIdentifier = async (mnemonicPassword) => { return importAndStoreIdentifier(mnemonic, mnemonicPassword, false, []) } ``` + +## Kudos + +* [Máximo Fernández](https://medium.com/@maxfarenas) for the 3D [code](https://github.com/maxfer03/vue-three-ns) and [explanatory post](https://medium.com/nicasource/building-an-interactive-web-portfolio-with-vue-three-js-part-three-implementing-three-js-452cb375ef80) diff --git a/package-lock.json b/package-lock.json index 0e01146..0ccd820 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "readable-stream": "^4.3.0", "reflect-metadata": "^0.1.13", "register-service-worker": "^1.7.2", + "three": "^0.152.2", "vue": "^3.2.47", "vue-axios": "^3.5.2", "vue-class-component": "^8.0.0-0", @@ -54,6 +55,7 @@ }, "devDependencies": { "@types/ramda": "^0.28.23", + "@types/three": "^0.152.0", "@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/parser": "^5.57.0", "@vue/cli-plugin-babel": "~5.0.8", @@ -7741,6 +7743,12 @@ "node": ">=10.13.0" } }, + "node_modules/@tweenjs/tween.js": { + "version": "18.6.4", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", + "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==", + "dev": true + }, "node_modules/@types/bn.js": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", @@ -8006,6 +8014,25 @@ "optional": true, "peer": true }, + "node_modules/@types/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==", + "dev": true + }, + "node_modules/@types/three": { + "version": "0.152.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.152.0.tgz", + "integrity": "sha512-9QdaV5bfZEqeQi0xkXLdnoJt7lgYZbppdBAgJSWRicdtZoCYJ34nS2QkdeuzXt+UXExofk4OWqMzdX71HeDOVg==", + "dev": true, + "dependencies": { + "@tweenjs/tween.js": "~18.6.4", + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.6.9", + "lil-gui": "~0.17.0" + } + }, "node_modules/@types/trusted-types": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.2.tgz", @@ -8023,6 +8050,12 @@ "integrity": "sha512-56/MAlX5WMsPVbOg7tAxnYvNYMMWr/QJiIp6BxVSW3JJXUVzzOn64qW8TzQyMSqSUFM2+PVI4aUHcHOzIz/1tg==", "dev": true }, + "node_modules/@types/webxr": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz", + "integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.5.3.tgz", @@ -14789,6 +14822,12 @@ "optional": true, "peer": true }, + "node_modules/fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", + "dev": true + }, "node_modules/figures": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/figures/-/figures-2.0.0.tgz", @@ -18304,6 +18343,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lil-gui": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz", + "integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==", + "dev": true + }, "node_modules/lilconfig": { "version": "2.0.6", "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.0.6.tgz", @@ -25184,6 +25229,11 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/three": { + "version": "0.152.2", + "resolved": "https://registry.npmjs.org/three/-/three-0.152.2.tgz", + "integrity": "sha512-Ff9zIpSfkkqcBcpdiFo2f35vA9ZucO+N8TNacJOqaEE6DrB0eufItVMib8bK8Pcju/ZNT6a7blE1GhTpkdsILw==" + }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", diff --git a/package.json b/package.json index 879ac1d..361f908 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "readable-stream": "^4.3.0", "reflect-metadata": "^0.1.13", "register-service-worker": "^1.7.2", + "three": "^0.152.2", "vue": "^3.2.47", "vue-axios": "^3.5.2", "vue-class-component": "^8.0.0-0", @@ -54,6 +55,7 @@ }, "devDependencies": { "@types/ramda": "^0.28.23", + "@types/three": "^0.152.0", "@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/parser": "^5.57.0", "@vue/cli-plugin-babel": "~5.0.8", diff --git a/project.yaml b/project.yaml index addf166..9948067 100644 --- a/project.yaml +++ b/project.yaml @@ -6,14 +6,12 @@ - replace user-affecting console.logs with error messages (eg. catches) - contacts v1 : - - .1 remove 'copy' until it works - .5 switch to prod server - .5 Add page to show seed. - 01 Provide a way to import the non-sensitive data. - 01 Provide way to share your contact info. - .2 move all "identity" references to temporary account access - .5 make deploy for give-only features - - .5 get 'copy' to work on account page - contacts v+ : - .5 make advanced "show/hide amounts" button into a nice UI toggle @@ -33,7 +31,10 @@ - backup all data -- Next Viable Product afterward +- .5 customize favicon +- .5 make advanced features harder to access + +- Release Minimum Viable Product - Connect with phone contacts diff --git a/public/img/textures/height.png b/public/img/textures/height.png new file mode 100644 index 0000000..4cab77f Binary files /dev/null and b/public/img/textures/height.png differ diff --git a/src/components/World/World.js b/src/components/World/World.js new file mode 100644 index 0000000..43ec31e --- /dev/null +++ b/src/components/World/World.js @@ -0,0 +1,86 @@ +// from https://medium.com/nicasource/building-an-interactive-web-portfolio-with-vue-three-js-part-three-implementing-three-js-452cb375ef80 + +import { createCamera } from "./components/camera.js"; +import { createLights } from "./components/lights.js"; +import { createScene } from "./components/scene.js"; +import { createRenderer } from "./systems/renderer.js"; +import { Loop } from "./systems/Loop.js"; +import { Resizer } from "./systems/Resizer.js"; +import { createControls } from "./systems/controls.js"; + +import createTerrain from "./components/objects/terrain.js"; + +// These variables are module-scoped: we cannot access them +// from outside the module + +const color = "#42b883"; +const color2 = "#4eac82"; + +let camera; +let renderer; +let scene; +let loop; + +class World { + constructor(container) { + // Instances of camera, scene, and renderer + camera = createCamera(); + scene = createScene(color2); + renderer = createRenderer(); + + // Initializate Loop + loop = new Loop(camera, scene, renderer); + + container.append(renderer.domElement); + + // Orbit Controls + const controls = createControls(camera, renderer.domElement); + + // Light Instance, with optional light helper + const { light, lightHelper } = createLights(color); + + // Random values for terrain vertices + // We could do this on the terrain.js file, + // but if we want to have a single random + // number array for more than one terrain + // instance, then we would be in trouble. + const randomVals = []; + for (let i = 0; i < 12675; i++) { + randomVals.push(Math.random() - 0.5); + } + + // Terrain Instance + const terrain = createTerrain({ + color: color, + randVertexArr: randomVals, + }); + + loop.updatables.push(controls); + loop.updatables.push(light); + loop.updatables.push(terrain); + + scene.add(light, terrain); + + // Responsive handler + const resizer = new Resizer(container, camera, renderer); + resizer.onResize = () => { + this.render(); + }; + } + + render() { + // draw a single frame + renderer.render(scene, camera); + } + + // Animation handlers + start() { + loop.start(); + } + + stop() { + loop.stop(); + } +} + +export { World }; diff --git a/src/components/World/components/camera.js b/src/components/World/components/camera.js new file mode 100644 index 0000000..fe21c1d --- /dev/null +++ b/src/components/World/components/camera.js @@ -0,0 +1,19 @@ +import { PerspectiveCamera } from "three"; + +function createCamera() { + const camera = new PerspectiveCamera( + 35, // fov = Field Of View + 1, // aspect ratio (dummy value) + 0.1, // near clipping plane + 100 // far clipping plane + ); + + // move the camera back so we can view the scene + camera.position.set(0, 10, 30); + // eslint-disable-next-line @typescript-eslint/no-empty-function + camera.tick = (delta) => {}; + + return camera; +} + +export { createCamera }; diff --git a/src/components/World/components/lights.js b/src/components/World/components/lights.js new file mode 100644 index 0000000..d171673 --- /dev/null +++ b/src/components/World/components/lights.js @@ -0,0 +1,14 @@ +import { DirectionalLight, DirectionalLightHelper } from "three"; + +function createLights(color) { + const light = new DirectionalLight(color, 4); + const lightHelper = new DirectionalLightHelper(light, 0); + light.position.set(0, 30, 30); + + // eslint-disable-next-line @typescript-eslint/no-empty-function + light.tick = (delta) => {}; + + return { light, lightHelper }; +} + +export { createLights }; diff --git a/src/components/World/components/objects/terrain.js b/src/components/World/components/objects/terrain.js new file mode 100644 index 0000000..ff7c0de --- /dev/null +++ b/src/components/World/components/objects/terrain.js @@ -0,0 +1,60 @@ +import { + PlaneBufferGeometry, + MeshStandardMaterial, + Mesh, + TextureLoader, +} from "three"; + +export default function createTerrain(props) { + const loader = new TextureLoader(); + const height = loader.load("img/textures/height.png"); + // w h + const geometry = new PlaneBufferGeometry(150, 150, 64, 64); + + const material = new MeshStandardMaterial({ + color: props.color, + flatShading: true, + displacementMap: height, + displacementScale: 5, + }); + + 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; + + //Randomizing our vertices position + const { array } = plane.geometry.attributes.position; + for (let i = 0; i < array.length; i++) { + props.randVertexArr.push(Math.random()); + } + + plane.geometry.attributes.position.randomValues = props.randVertexArr; + + let frame = 0; + plane.tick = (delta) => { + frame += 0.01; + // destructuring of the random values, the original position and the current vertex position + const { array, originalPosition, randomValues } = + plane.geometry.attributes.position; + + // Animation for loop + // In our vertex array, we have 3 coordinates: x, y and z. We are + // only going to animate on ONE coordinate: the z coordinate. + // This means we have to omit the x and y coords, hence the i+=3 in our loop. + // Also, to access that y coordinate on each vertex, we have to add 2 to our + // current i each time. + for (let i = 0; i < array.length; i += 3) { + // Accessing the z coord + array[i + 2] = + // Try switching this numbers up, or using sine instead of cosine, see how the animation changes + originalPosition[i + 2] + Math.cos(frame + randomValues[i + 2]) * 0.002; + } + plane.geometry.attributes.position.needsUpdate = true; + }; + + return plane; +} diff --git a/src/components/World/components/scene.js b/src/components/World/components/scene.js new file mode 100644 index 0000000..43c814b --- /dev/null +++ b/src/components/World/components/scene.js @@ -0,0 +1,11 @@ +import { Color, Scene, Fog } from "three"; + +function createScene(color) { + const scene = new Scene(); + + scene.background = new Color(color); + scene.fog = new Fog(color, 60, 90); + return scene; +} + +export { createScene }; diff --git a/src/components/World/systems/Loop.js b/src/components/World/systems/Loop.js new file mode 100644 index 0000000..a4e26ca --- /dev/null +++ b/src/components/World/systems/Loop.js @@ -0,0 +1,33 @@ +import { Clock } from "three"; + +const clock = new Clock(); + +class Loop { + constructor(camera, scene, renderer) { + this.camera = camera; + this.scene = scene; + this.renderer = renderer; + this.updatables = []; + } + + start() { + this.renderer.setAnimationLoop(() => { + this.tick(); + // render a frame + this.renderer.render(this.scene, this.camera); + }); + } + + stop() { + this.renderer.setAnimationLoop(null); + } + + tick() { + const delta = clock.getDelta(); + for (const object of this.updatables) { + object.tick(delta); + } + } +} + +export { Loop }; diff --git a/src/components/World/systems/Resizer.js b/src/components/World/systems/Resizer.js new file mode 100644 index 0000000..bf41d66 --- /dev/null +++ b/src/components/World/systems/Resizer.js @@ -0,0 +1,26 @@ +const setSize = (container, camera, renderer) => { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + + renderer.setSize(window.innerWidth, window.innerHeight); + renderer.setPixelRatio(window.devicePixelRatio); +}; + +class Resizer { + constructor(container, camera, renderer) { + // set initial size on load + setSize(container, camera, renderer); + + window.addEventListener("resize", () => { + // set the size again if a resize occurs + setSize(container, camera, renderer); + // perform any custom actions + this.onResize(); + }); + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + onResize() {} +} + +export { Resizer }; diff --git a/src/components/World/systems/controls.js b/src/components/World/systems/controls.js new file mode 100644 index 0000000..d8f05e1 --- /dev/null +++ b/src/components/World/systems/controls.js @@ -0,0 +1,36 @@ +import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; +import { MathUtils } from "three"; + +function createControls(camera, canvas) { + const controls = new OrbitControls(camera, canvas); + + //enable controls? + controls.enabled = true; + controls.autoRotate = true; + controls.autoRotateSpeed = 0.2; + + // control limits + // It's recommended to set some control boundaries, + // to prevent the user from clipping with the objects. + + // y axis + controls.minPolarAngle = MathUtils.degToRad(40); // default + controls.maxPolarAngle = MathUtils.degToRad(75); + + // x axis + // controls.minAzimuthAngle = ... + // controls.maxAzimuthAngle = ... + + //smooth camera: + // remember to add to loop updatables to work + controls.enableDamping = true; + + controls.enableZoom = false; + controls.enablePan = false; + + controls.tick = () => controls.update(); + + return controls; +} + +export { createControls }; diff --git a/src/components/World/systems/renderer.js b/src/components/World/systems/renderer.js new file mode 100644 index 0000000..cc918a3 --- /dev/null +++ b/src/components/World/systems/renderer.js @@ -0,0 +1,12 @@ +import { WebGLRenderer } from "three"; + +function createRenderer() { + const renderer = new WebGLRenderer({ antialias: true }); + + // turn on the physically correct lighting model + renderer.physicallyCorrectLights = true; + + return renderer; +} + +export { createRenderer }; diff --git a/src/router/index.ts b/src/router/index.ts index f9f5f82..4ffddd2 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -121,6 +121,14 @@ const routes: Array = [ component: () => import(/* webpackChunkName: "start" */ "../views/StartView.vue"), }, + { + path: "/statistics", + name: "statistics", + component: () => + import( + /* webpackChunkName: "statistics" */ "../views/StatisticsView.vue" + ), + }, ]; /** @type {*} */ diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index f9cce60..3f04a19 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -233,6 +233,17 @@ +
+ +
+