add beginning of visualization for statistics, unmodified from blog code

This commit is contained in:
2023-05-16 06:22:19 -06:00
parent fb7d51ac4c
commit a9844e6e78
17 changed files with 461 additions and 3 deletions

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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;
}

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -121,6 +121,14 @@ const routes: Array<RouteRecordRaw> = [
component: () =>
import(/* webpackChunkName: "start" */ "../views/StartView.vue"),
},
{
path: "/statistics",
name: "statistics",
component: () =>
import(
/* webpackChunkName: "statistics" */ "../views/StatisticsView.vue"
),
},
];
/** @type {*} */

View File

@@ -233,6 +233,17 @@
</span>
</div>
<div>
<button class="text-blue-500 px-2">
<router-link
:to="{ name: 'statistics' }"
class="block text-center py-3 px-1"
>
See Your Statistics
</router-link>
</button>
</div>
<div v-bind:class="computedAlertClassNames()">
<button
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"

View File

@@ -0,0 +1,83 @@
<template>
<!-- QUICK NAV -->
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200 z-50">
<ul class="flex text-2xl p-2 gap-2">
<!-- Home Feed -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link :to="{ name: 'home' }" class="block text-center py-3 px-1">
<fa icon="house-chimney" class="fa-fw"></fa>
</router-link>
</li>
<!-- Search -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'discover' }"
class="block text-center py-3 px-1"
>
<fa icon="magnifying-glass" class="fa-fw"></fa>
</router-link>
</li>
<!-- Projects -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'projects' }"
class="block text-center py-3 px-1"
>
<fa icon="folder-open" class="fa-fw"></fa>
</router-link>
</li>
<!-- Contacts -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'contacts' }"
class="block text-center py-3 px-1"
>
<fa icon="users" class="fa-fw"></fa>
</router-link>
</li>
<!-- Profile -->
<li class="basis-1/5 rounded-md bg-slate-400 text-white">
<router-link
:to="{ name: 'account' }"
class="block text-center py-3 px-1"
>
<fa icon="circle-user" class="fa-fw"></fa>
</router-link>
</li>
</ul>
</nav>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Your Statistics
</h1>
<div id="scene-container"></div>
<canvas ref="worldCanvas"></canvas>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { World } from "@/components/World/World.js";
@Component
export default class StatisticsView extends Vue {
mounted() {
const container = document.querySelector("#scene-container");
const world = new World(container);
world.start();
}
}
</script>
<style>
#scene-container {
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
</style>