├── .gitignore
├── favicon.ico
├── src
├── jsm
│ ├── EDM.png
│ ├── earth.jpg
│ ├── globe.png
│ ├── react.png
│ ├── spark.png
│ ├── stone.png
│ ├── envelope.png
│ ├── skrillex.png
│ ├── thunder.png
│ ├── twitter.png
│ ├── writing.png
│ ├── allSkills.png
│ ├── githubLogo.png
│ ├── lensflare0.png
│ ├── linkedInLogo.png
│ ├── terpSolutions.png
│ ├── woodTexture.jpg
│ ├── BeachBallColor.jpg
│ ├── activities_text.png
│ ├── bagholderbets-text.png
│ ├── static-portfolio.png
│ ├── home-sweet-home-text.png
│ ├── terp-solutions-text.png
│ ├── Bagholdersbetsbillboard.png
│ ├── home-sweet-home-portrait.png
│ ├── fragment.glsl
│ └── vertex.glsl
├── builds
│ └── ammo.wasm.wasm
├── resources
│ ├── preload.js
│ ├── textures.js
│ ├── surfaces.js
│ ├── eventHandlers.js
│ ├── utils.js
│ └── world.js
├── WebGL.js
└── app.js
├── portfolio_2020.gif
├── website-screenshot.png
├── .vscode
└── launch.json
├── server.js
├── webpack.config.js
├── package.json
├── README.md
├── index.html
└── style.css
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | README2.md
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/favicon.ico
--------------------------------------------------------------------------------
/src/jsm/EDM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/EDM.png
--------------------------------------------------------------------------------
/src/jsm/earth.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/earth.jpg
--------------------------------------------------------------------------------
/src/jsm/globe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/globe.png
--------------------------------------------------------------------------------
/src/jsm/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/react.png
--------------------------------------------------------------------------------
/src/jsm/spark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/spark.png
--------------------------------------------------------------------------------
/src/jsm/stone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/stone.png
--------------------------------------------------------------------------------
/portfolio_2020.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/portfolio_2020.gif
--------------------------------------------------------------------------------
/src/jsm/envelope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/envelope.png
--------------------------------------------------------------------------------
/src/jsm/skrillex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/skrillex.png
--------------------------------------------------------------------------------
/src/jsm/thunder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/thunder.png
--------------------------------------------------------------------------------
/src/jsm/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/twitter.png
--------------------------------------------------------------------------------
/src/jsm/writing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/writing.png
--------------------------------------------------------------------------------
/src/jsm/allSkills.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/allSkills.png
--------------------------------------------------------------------------------
/src/jsm/githubLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/githubLogo.png
--------------------------------------------------------------------------------
/src/jsm/lensflare0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/lensflare0.png
--------------------------------------------------------------------------------
/website-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/website-screenshot.png
--------------------------------------------------------------------------------
/src/builds/ammo.wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/builds/ammo.wasm.wasm
--------------------------------------------------------------------------------
/src/jsm/linkedInLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/linkedInLogo.png
--------------------------------------------------------------------------------
/src/jsm/terpSolutions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/terpSolutions.png
--------------------------------------------------------------------------------
/src/jsm/woodTexture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/woodTexture.jpg
--------------------------------------------------------------------------------
/src/jsm/BeachBallColor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/BeachBallColor.jpg
--------------------------------------------------------------------------------
/src/jsm/activities_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/activities_text.png
--------------------------------------------------------------------------------
/src/jsm/bagholderbets-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/bagholderbets-text.png
--------------------------------------------------------------------------------
/src/jsm/static-portfolio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/static-portfolio.png
--------------------------------------------------------------------------------
/src/jsm/home-sweet-home-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/home-sweet-home-text.png
--------------------------------------------------------------------------------
/src/jsm/terp-solutions-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/terp-solutions-text.png
--------------------------------------------------------------------------------
/src/jsm/Bagholdersbetsbillboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/Bagholdersbetsbillboard.png
--------------------------------------------------------------------------------
/src/jsm/home-sweet-home-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xFloyd/Portfolio_2020/HEAD/src/jsm/home-sweet-home-portrait.png
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "pwa-chrome",
9 | "request": "launch",
10 | "name": "Launch Chrome against localhost",
11 | "url": "http://localhost:8080",
12 | "webRoot": "${workspaceFolder}"
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const path = require("path");
3 | const port = process.env.PORT || 8080;
4 | const app = express();
5 |
6 | app.use(function (req, res, next) {
7 | if (req.header("x-forwarded-proto") !== "https") {
8 | res.redirect("https://" + req.header("host") + req.baseUrl);
9 | } else {
10 | next();
11 | }
12 | });
13 |
14 | // the __dirname is the current directory from where the script is running
15 | app.use(express.static(__dirname));
16 |
17 | // send the user to index html page inspite of the url
18 | app.get("*", (req, res) => {
19 | res.sendFile(path.resolve(__dirname, "index.html"));
20 | });
21 |
22 | app.listen(port);
23 |
--------------------------------------------------------------------------------
/src/jsm/fragment.glsl:
--------------------------------------------------------------------------------
1 | varying vec3 vColor;
2 | void main()
3 | {
4 | // Disc
5 | /*
6 | float strength = distance(gl_PointCoord, vec2(0.5));
7 | strength = step(0.5, strength);
8 | strength = 1.0 - strength;*/
9 |
10 | // Diffuse point
11 | /*
12 | float strength = distance(gl_PointCoord, vec2(0.5));
13 | strength *= 2.0;
14 | strength = 1.0 - strength;*/
15 |
16 | // Light point
17 | // Light point
18 | float strength = distance(gl_PointCoord, vec2(0.5));
19 | strength = 1.0 - strength;
20 | strength = pow(strength, 10.0);
21 |
22 | // Final color
23 | vec3 color = mix(vec3(0.0), vColor, strength);
24 | gl_FragColor = vec4(color, 1.0);
25 |
26 | // gl_FragColor = vec4(gl_PointCoord, 1.0, 1.0); // we already have access to the UV in the fragment shader with gl_PointCoord
27 |
28 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const CompressionPlugin = require('compression-webpack-plugin');
3 |
4 | module.exports = {
5 | entry: {
6 | app: ['@babel/polyfill', './src/app.js'],
7 | },
8 | output: {
9 | path: path.resolve(__dirname, 'build'),
10 | filename: 'app.bundle.js',
11 | },
12 | module: {
13 | rules: [
14 | {
15 | test: /\.js?$/,
16 | exclude: /node_modules/,
17 | loader: 'babel-loader',
18 | query: {
19 | presets: ['@babel/preset-env'],
20 | },
21 | },
22 | // Shaders
23 | {
24 | test: /\.(glsl|vs|fs|vert|frag)$/,
25 | exclude: /node_modules/,
26 | use: ['raw-loader'],
27 | },
28 | ],
29 | },
30 | plugins: [new CompressionPlugin()],
31 | devServer: {
32 | contentBase: path.join(__dirname, ''),
33 | compress: true,
34 | watchContentBase: true,
35 | port: 8080,
36 | host: '0.0.0.0', //your ip address
37 | disableHostCheck: true, //coment these out for prod
38 | },
39 | node: {
40 | fs: 'empty',
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/src/jsm/vertex.glsl:
--------------------------------------------------------------------------------
1 | uniform float uSize;
2 | attribute float aScale;
3 |
4 | varying vec3 vColor;
5 | uniform float uTime;
6 |
7 | attribute vec3 aRandomness;
8 |
9 | void main()
10 | {
11 | /**
12 | * Position
13 | */
14 | vec4 modelPosition = modelMatrix * vec4(position, 1.0);
15 |
16 | // Rotate
17 | float angle = atan(modelPosition.x, modelPosition.z);
18 | float distanceToCenter = length(modelPosition.xz);
19 | float angleOffset = (1.0 / distanceToCenter) * uTime * 0.2;
20 | angle += angleOffset;
21 | modelPosition.x = cos(angle) * distanceToCenter;
22 | modelPosition.z = sin(angle) * distanceToCenter;
23 |
24 | // Randomness
25 | modelPosition.xyz += aRandomness;
26 |
27 | vec4 viewPosition = viewMatrix * modelPosition;
28 | vec4 projectedPosition = projectionMatrix * viewPosition;
29 | gl_Position = projectedPosition;
30 |
31 | /**
32 | * Size
33 | */
34 | gl_PointSize = uSize * aScale;
35 | gl_PointSize *= (50.0 / - viewPosition.z);
36 |
37 | /**
38 | * Color
39 | */
40 | vColor = color;
41 |
42 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "portfolio_2020",
3 | "version": "1.0.0",
4 | "description": "webpack starter",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "webpack-dev-server --output-public-path=/build/ --mode development --progress --open --hot",
9 | "build": "webpack --mode production --progress",
10 | "start": "node server.js",
11 | "heroku-postbuild": "webpack -p"
12 | },
13 | "author": "0xFloyd",
14 | "license": "MIT",
15 | "devDependencies": {
16 | "@babel/core": "^7.4.3",
17 | "@babel/polyfill": "^7.4.3",
18 | "@babel/preset-env": "^7.4.3",
19 | "babel-loader": "^8.0.5",
20 | "compression-webpack-plugin": "^4.0.0",
21 | "webpack": "^4.43.0",
22 | "webpack-cli": "^3.3.11",
23 | "webpack-dev-server": "^3.11.0"
24 | },
25 | "dependencies": {
26 | "@tweenjs/tween.js": "^18.6.0",
27 | "ammo.js": "github:kripken/ammo.js",
28 | "dat.gui": "^0.7.7",
29 | "express": "^4.17.1",
30 | "raw-loader": "^4.0.2",
31 | "stats.js": "^0.17.0",
32 | "three": "^0.117.1"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/resources/preload.js:
--------------------------------------------------------------------------------
1 | export let preloadDivs = document.getElementsByClassName('preload');
2 | export let preloadOpacity = document.getElementsByClassName('preload-overlay');
3 | export let postloadDivs = document.getElementsByClassName('postload');
4 | export let startScreenDivs = document.getElementsByClassName('start-screen');
5 | export let startButton = document.getElementById('start-button');
6 | export let fadeOutDivs = document.getElementsByClassName('fadeOutDiv');
7 |
8 | export function noWebGL() {
9 | for (let i = 0; i < preloadDivs.length; i++) {
10 | preloadDivs[i].style.visibility = 'hidden'; // or
11 | preloadDivs[i].style.display = 'none';
12 | }
13 | for (let i = 0; i < postloadDivs.length; i++) {
14 | // or
15 | postloadDivs[i].style.display = 'none';
16 | }
17 | for (let i = 0; i < preloadOpacity.length; i++) {
18 | // or
19 | preloadOpacity[i].style.display = 'none';
20 | }
21 | //document.getElementById("preload-overlay").style.display = "none";
22 | var warning = WEBGL.getWebGLErrorMessage();
23 | var a = document.createElement('a');
24 | var linkText = document.createTextNode('Click here to visit my static site');
25 | a.appendChild(linkText);
26 | a.title = 'Static Site';
27 | a.href = 'https://github.com/0xFloyd/Portfolio_2020';
28 | a.style.margin = '0px auto';
29 | a.style.textAlign = 'center';
30 | document.getElementById('WEBGLcontainer').appendChild(warning);
31 | document.getElementById('WEBGLcontainer').appendChild(a);
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Portfolio 2020
2 |
3 | As a quarantine project, I wanted to learn 3D web development, and decided to revamp my portfolio into an interactive 3D world built using [Three.js](https://github.com/mrdoob/three.js) and [Ammo.js](https://github.com/kripken/ammo.js), a port of the [Bullet physics engine](https://pybullet.org/wordpress/) to JavaScript. I had an absolute blast making this!
4 |
5 | Try it out! [https://www.0xfloyd.com/](https://www.0xfloyd.com/)
6 |
7 | I wrote an article explaining the site [here](https://dev.to/0xfloyd/create-an-interactive-3d-portfolio-website-that-stands-out-to-employers-47gc)
8 |
9 | 
10 |
11 | ## Motivation
12 |
13 | While exploring [Google Experiments](https://experiments.withgoogle.com/) I discovered an amazing world of web rendering. There are so many incredible web projects out there, and I wanted to learn this technology. I was inspired by many awesome projects, but specifically examples from the [official examples/documentation](https://threejs.org/), [Lee Stemkoski](https://home.adelphi.edu/~stemkoski/) and [Three.js Fundamentals](https://threejsfundamentals.org/).
14 |
15 | ## Features
16 |
17 | - Physics engine (Ammo.js) combined with 3D rendered objects (Three.js) for real-time movement, collision detection and interaction
18 | - Desktop and Mobile Responsiveness with both keyboard and touch screen controls
19 | - Raycasting with event listeners for user touch and click interaction
20 | - FPS tracker to monitor frame rate/ rendering performance
21 | - Asset compression with webpack plugin to help with quick site load times
22 |
23 | ## Technology
24 |
25 | - Three.js (3D Graphics)
26 | - Ammo.js (Physics Engine)
27 | - JavaScript
28 | - Node.js
29 | - Express (Node.js framework)
30 | - Webpack (module/ dependency bundler)
31 | - HTML/CSS
32 | - Hosted on Heroku
33 | - Git (version control) / Github for code hosting
34 |
35 | ## Usage
36 |
37 | To use locally, clone the repository, install dependencies, run using webpack's dev server, and navigate to localhost:8080 in your browser:
38 |
39 | ```javascript
40 | npm i
41 | npm run dev
42 | ```
43 |
44 | ## License
45 |
46 | The project is licensed under the MIT License.
47 |
--------------------------------------------------------------------------------
/src/WebGL.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author alteredq / http://alteredqualia.com/
3 | * @author mrdoob / http://mrdoob.com/
4 | */
5 |
6 |
7 |
8 | var WEBGL = {
9 |
10 | isWebGLAvailable: function () {
11 |
12 | try {
13 |
14 | var canvas = document.createElement( 'canvas' );
15 | return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
16 |
17 | } catch ( e ) {
18 |
19 | return false;
20 |
21 | }
22 |
23 | },
24 |
25 | isWebGL2Available: function () {
26 |
27 | try {
28 |
29 | var canvas = document.createElement( 'canvas' );
30 | return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) );
31 |
32 | } catch ( e ) {
33 |
34 | return false;
35 |
36 | }
37 |
38 | },
39 |
40 | getWebGLErrorMessage: function () {
41 |
42 | return this.getErrorMessage( 1 );
43 |
44 | },
45 |
46 | getWebGL2ErrorMessage: function () {
47 |
48 | return this.getErrorMessage( 2 );
49 |
50 | },
51 |
52 | getErrorMessage: function ( version ) {
53 |
54 | var names = {
55 | 1: 'WebGL',
56 | 2: 'WebGL 2'
57 | };
58 |
59 | var contexts = {
60 | 1: window.WebGLRenderingContext,
61 | 2: window.WebGL2RenderingContext
62 | };
63 |
64 | var message = 'Your $0 does not seem to support $1';
65 |
66 | var element = document.createElement( 'div' );
67 | element.id = 'webglmessage';
68 | element.style.fontFamily = 'monospace';
69 | element.style.fontSize = '13px';
70 | element.style.fontWeight = 'normal';
71 | element.style.textAlign = 'center';
72 | element.style.background = '#fff';
73 | element.style.color = '#000';
74 | element.style.padding = '1.5em';
75 | element.style.width = '400px';
76 | element.style.margin = '5em auto 0';
77 |
78 | if ( contexts[ version ] ) {
79 |
80 | message = message.replace( '$0', 'graphics card' );
81 |
82 | } else {
83 |
84 | message = message.replace( '$0', 'browser' );
85 |
86 | }
87 |
88 | message = message.replace( '$1', names[ version ] );
89 |
90 | element.innerHTML = message;
91 |
92 | return element;
93 |
94 | }
95 |
96 | };
97 |
98 | export { WEBGL };
99 |
--------------------------------------------------------------------------------
/src/resources/textures.js:
--------------------------------------------------------------------------------
1 | //billboardTextures
2 | let billboardTextures = {};
3 | billboardTextures.terpSolutionsTexture = '../src/jsm/terpSolutions.png';
4 | billboardTextures.bagHolderBetsTexture = '../src/jsm/Bagholdersbetsbillboard.png';
5 | billboardTextures.homeSweetHomeTexture = '../src/jsm/home-sweet-home-portrait.png';
6 |
7 | //box textures
8 | let boxTexture = {};
9 | boxTexture.Github = '../src/jsm/githubLogo.png';
10 | boxTexture.twitter = '../src/jsm/twitter.png';
11 | boxTexture.LinkedIn = '../src/jsm/linkedInLogo.png';
12 | boxTexture.mail = '../src/jsm/envelope.png';
13 | boxTexture.globe = '../src/jsm/thunder.png';
14 | boxTexture.reactIcon = '../src/jsm/react.png';
15 | boxTexture.allSkills = '../src/jsm/allSkills.png';
16 | boxTexture.lensFlareMain = '../src/jsm/lensflare0.png';
17 | boxTexture.skrillex = '../src/jsm/skrillex.png';
18 | boxTexture.edmText = '../src/jsm/EDM.png';
19 | boxTexture.writing = '../src/jsm/writing.png';
20 |
21 | //material textures
22 | let stoneTexture = '../src/jsm/stone.png';
23 | let woodTexture = '../src/jsm/woodTexture.jpg';
24 |
25 | //text
26 | let inputText = {};
27 | inputText.terpSolutionsText = '../src/jsm/terp-solutions-text.png';
28 | inputText.activities = '../src/jsm/activities_text.png';
29 | inputText.bagholderBetsText = '../src/jsm/bagholderbets-text.png';
30 | inputText.homeSweetHomeText = '../src/jsm/home-sweet-home-text.png';
31 | inputText.staticPortfolio = '../src/jsm/static-portfolio.png';
32 |
33 | //SVG
34 | let SVG = {};
35 | SVG.reactLogo = '../src/jsm/react-svg.svg';
36 |
37 | //URLs
38 | let URL = {};
39 | URL.terpsolutions = 'https://web.archive.org/web/20200302001846/https://terpsolutions.com/';
40 | URL.bagholderBets = 'https://bagholder-bets.herokuapp.com';
41 | URL.homeSweetHomeURL = 'https://github.com/0xFloyd/home-sweet-127.0.0.1';
42 | URL.gitHub = 'https://github.com/0xFloyd/Portfolio_2020';
43 | URL.twitter = 'https://twitter.com/0xFloyd';
44 | URL.email = 'https://mailto:xfloyd.eth@gmail.com';
45 | URL.githubBagholder = 'https://github.com/0xFloyd/bagholder-bets';
46 | URL.githubHomeSweetHome = 'https://github.com/0xFloyd/home-sweet-127.0.0.1';
47 | URL.devTo = 'https://dev.to/0xfloyd/create-an-interactive-3d-portfolio-website-that-stands-out-to-employers-47gc';
48 |
49 | export { billboardTextures, boxTexture, inputText, URL, stoneTexture, woodTexture };
50 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 |
20 |
21 |
22 |
44 |
45 |
This is an interactive 3D site built with Three.js!
46 | Move the ball around with the arrow keys on the keyboard.
47 |
48 |
49 |
54 |
Loading...
55 |
56 |
69 |
70 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/src/resources/surfaces.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { scene, manager } from "./world";
3 |
4 | export function simpleText(x, y, z, inputText, fontSize) {
5 | var text_loader = new THREE.FontLoader();
6 |
7 | text_loader.load("../src/jsm/Roboto_Regular.json", function (font) {
8 | var xMid, text;
9 |
10 | var color = 0xffffff;
11 |
12 | var matLite = new THREE.MeshBasicMaterial({
13 | color: color,
14 | transparent: true,
15 | opacity: 1,
16 | side: THREE.DoubleSide,
17 | });
18 |
19 | var message = inputText;
20 |
21 | var shapes = font.generateShapes(message, fontSize);
22 |
23 | var geometry = new THREE.ShapeBufferGeometry(shapes);
24 |
25 | geometry.computeBoundingBox();
26 |
27 | xMid = -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
28 |
29 | geometry.translate(xMid, 0, 0);
30 |
31 | // make shape ( N.B. edge view not visible )
32 |
33 | text = new THREE.Mesh(geometry, matLite);
34 | text.position.z = z;
35 | text.position.y = y;
36 | text.position.x = x;
37 | text.rotation.x = -Math.PI * 0.5;
38 |
39 | scene.add(text);
40 | });
41 | }
42 |
43 | export function floatingLabel(x, y, z, inputMessage) {
44 | var text_loader = new THREE.FontLoader();
45 |
46 | text_loader.load("../src/jsm/Roboto_Regular.json", function (font) {
47 | var xMid, text;
48 |
49 | var color = 0xffffff;
50 |
51 | var matLite = new THREE.MeshBasicMaterial({
52 | color: color,
53 | transparent: true,
54 | opacity: 1,
55 | side: THREE.DoubleSide,
56 | });
57 |
58 | var message = inputMessage;
59 |
60 | var shapes = font.generateShapes(message, 1);
61 |
62 | var geometry = new THREE.ShapeBufferGeometry(shapes);
63 |
64 | geometry.computeBoundingBox();
65 |
66 | xMid = -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
67 |
68 | geometry.translate(xMid, 0, 0);
69 |
70 | // make shape ( N.B. edge view not visible )
71 |
72 | text = new THREE.Mesh(geometry, matLite);
73 | text.position.z = z;
74 | text.position.y = y;
75 | text.position.x = x;
76 | scene.add(text);
77 | });
78 | }
79 |
80 | export function allSkillsSection(
81 | x,
82 | y,
83 | z,
84 | xScale,
85 | zScale,
86 | boxTexture,
87 | URLLink = null
88 | ) {
89 | const boxScale = { x: xScale, y: 0.1, z: zScale };
90 | let quat = { x: 0, y: 0, z: 0, w: 1 };
91 | let mass = 0; //mass of zero = infinite mass
92 |
93 | var geometry = new THREE.PlaneBufferGeometry(xScale, zScale);
94 |
95 | const loader = new THREE.TextureLoader(manager);
96 | const texture = loader.load(boxTexture);
97 | texture.magFilter = THREE.LinearFilter;
98 | texture.minFilter = THREE.LinearFilter;
99 | texture.encoding = THREE.sRGBEncoding;
100 | const loadedTexture = new THREE.MeshBasicMaterial({
101 | map: texture,
102 | transparent: true,
103 | });
104 | loadedTexture.depthWrite = true;
105 | loadedTexture.depthTest = true;
106 |
107 | const linkBox = new THREE.Mesh(geometry, loadedTexture);
108 | linkBox.position.set(x, y, z);
109 | linkBox.renderOrder = 1;
110 | linkBox.rotation.x = -Math.PI * 0.5;
111 | linkBox.receiveShadow = true;
112 | linkBox.userData = { URL: URLLink };
113 | scene.add(linkBox);
114 | }
115 |
116 | export function createTextOnPlane(x, y, z, inputText, size1, size2) {
117 | // word text
118 | var activitiesGeometry = new THREE.PlaneBufferGeometry(size1, size2);
119 | const loader = new THREE.TextureLoader(manager);
120 | var activitiesTexture = loader.load(inputText);
121 | activitiesTexture.magFilter = THREE.NearestFilter;
122 | activitiesTexture.minFilter = THREE.LinearFilter;
123 | var activitiesMaterial = new THREE.MeshBasicMaterial({
124 | alphaMap: activitiesTexture,
125 | transparent: true,
126 | });
127 |
128 | activitiesMaterial.depthWrite = true;
129 | activitiesMaterial.depthTest = true;
130 | let activitiesText = new THREE.Mesh(activitiesGeometry, activitiesMaterial);
131 | activitiesText.position.x = x;
132 | activitiesText.position.y = y;
133 | activitiesText.position.z = z;
134 | activitiesText.rotation.x = -Math.PI * 0.5;
135 |
136 | activitiesText.renderOrder = 1;
137 |
138 | scene.add(activitiesText);
139 | }
140 |
--------------------------------------------------------------------------------
/src/resources/eventHandlers.js:
--------------------------------------------------------------------------------
1 | // create keyboard control event listeners
2 |
3 | export let moveDirection = { left: 0, right: 0, forward: 0, back: 0 };
4 |
5 | export function setupEventHandlers() {
6 | window.addEventListener("keydown", handleKeyDown, false);
7 | window.addEventListener("keyup", handleKeyUp, false);
8 | }
9 |
10 | function handleKeyDown(event) {
11 | let keyCode = event.keyCode;
12 |
13 | switch (keyCode) {
14 | case 87: //W: FORWARD
15 | case 38: //up arrow
16 | moveDirection.forward = 1;
17 | break;
18 |
19 | case 83: //S: BACK
20 | case 40: //down arrow
21 | moveDirection.back = 1;
22 | break;
23 |
24 | case 65: //A: LEFT
25 | case 37: //left arrow
26 | moveDirection.left = 1;
27 | break;
28 |
29 | case 68: //D: RIGHT
30 | case 39: //right arrow
31 | moveDirection.right = 1;
32 | break;
33 | }
34 | }
35 |
36 | function handleKeyUp(event) {
37 | let keyCode = event.keyCode;
38 |
39 | switch (keyCode) {
40 | case 87: //FORWARD
41 | case 38:
42 | moveDirection.forward = 0;
43 | break;
44 |
45 | case 83: //BACK
46 | case 40:
47 | moveDirection.back = 0;
48 | break;
49 |
50 | case 65: //LEFT
51 | case 37:
52 | moveDirection.left = 0;
53 | break;
54 |
55 | case 68: //RIGHT
56 | case 39:
57 | moveDirection.right = 0;
58 | break;
59 | }
60 | }
61 |
62 | export function isTouchscreenDevice() {
63 | let supportsTouch = false;
64 | if ("ontouchstart" in window)
65 | // iOS & android
66 | supportsTouch = true;
67 | else if (window.navigator.msPointerEnabled)
68 | // Win8
69 | supportsTouch = true;
70 | else if ("ontouchstart" in document.documentElement)
71 | // Controversial way to check touch support
72 | supportsTouch = true;
73 |
74 | return supportsTouch;
75 | }
76 |
77 | export function touchEvent(coordinates) {
78 | if (coordinates.x > 30) {
79 | moveDirection.right = 1;
80 | moveDirection.left = 0;
81 | } else if (coordinates.x < -30) {
82 | moveDirection.left = 1;
83 | moveDirection.right = 0;
84 | } else {
85 | moveDirection.right = 0;
86 | moveDirection.left = 0;
87 | }
88 |
89 | if (coordinates.y > 30) {
90 | moveDirection.back = 1;
91 | moveDirection.forward = 0;
92 | } else if (coordinates.y < -30) {
93 | moveDirection.forward = 1;
94 | moveDirection.back = 0;
95 | } else {
96 | moveDirection.forward = 0;
97 | moveDirection.back = 0;
98 | }
99 | }
100 |
101 | export function createJoystick(parent) {
102 | const maxDiff = 62; //how far drag can go
103 | const stick = document.createElement("div");
104 | //stick.classList.add("joystick");
105 | stick.setAttribute("id", "joystick");
106 |
107 | stick.addEventListener("mousedown", handleMouseDown);
108 | document.addEventListener("mousemove", handleMouseMove);
109 | document.addEventListener("mouseup", handleMouseUp);
110 | stick.addEventListener("touchstart", handleMouseDown);
111 | document.addEventListener("touchmove", handleMouseMove);
112 | document.addEventListener("touchend", handleMouseUp);
113 |
114 | let dragStart = null;
115 | let currentPos = { x: 0, y: 0 };
116 |
117 | function handleMouseDown(event) {
118 | event.preventDefault();
119 | stick.style.transition = "0s";
120 |
121 | if (event.changedTouches) {
122 | dragStart = {
123 | x: event.changedTouches[0].clientX,
124 | y: event.changedTouches[0].clientY,
125 | };
126 |
127 | return;
128 | }
129 | dragStart = {
130 | x: event.clientX,
131 | y: event.clientY,
132 | };
133 | }
134 |
135 | function handleMouseMove(event) {
136 | if (dragStart === null) return;
137 |
138 | //console.log("entered handleMouseMove");
139 | if (event.changedTouches) {
140 | event.clientX = event.changedTouches[0].clientX;
141 | event.clientY = event.changedTouches[0].clientY;
142 | //touchEvent(currentPos);
143 | }
144 |
145 | const xDiff = event.clientX - dragStart.x;
146 | const yDiff = event.clientY - dragStart.y;
147 | const angle = Math.atan2(yDiff, xDiff);
148 | const distance = Math.min(maxDiff, Math.hypot(xDiff, yDiff));
149 | const xNew = distance * Math.cos(angle);
150 | const yNew = distance * Math.sin(angle);
151 | stick.style.transform = `translate3d(${xNew}px, ${yNew}px, 0px)`;
152 | currentPos = { x: xNew, y: yNew };
153 | touchEvent(currentPos);
154 | }
155 |
156 | function handleMouseUp(event) {
157 | if (dragStart === null) return;
158 | stick.style.transition = ".2s";
159 | stick.style.transform = `translate3d(0px, 0px, 0px)`;
160 | dragStart = null;
161 | currentPos = { x: 0, y: 0 };
162 | moveDirection.forward = 0;
163 | moveDirection.left = 0;
164 | moveDirection.right = 0;
165 | moveDirection.back = 0;
166 | }
167 |
168 | parent.appendChild(stick);
169 | return {
170 | getPosition: () => currentPos,
171 | };
172 | }
173 |
--------------------------------------------------------------------------------
/src/resources/utils.js:
--------------------------------------------------------------------------------
1 | //start link events
2 | import * as THREE from 'three';
3 | import { camera, renderer, scene } from './world';
4 | import { cursorHoverObjects } from '../app';
5 |
6 | export const pickPosition = { x: 0, y: 0 };
7 |
8 | export function rotateCamera(ballPosition) {
9 | // current camera position
10 | var camPos = new THREE.Vector3(
11 | camera.position.x,
12 | camera.position.y,
13 | camera.position.z
14 | );
15 |
16 | // target camera position
17 | var targetPos;
18 |
19 | //1
20 | if (
21 | (ballPosition.position.x < 77 &&
22 | ballPosition.position.x > 42 &&
23 | ballPosition.position.z > -20 &&
24 | ballPosition.position.z < 40) ||
25 | (ballPosition.position.x < -2 && ballPosition.position.z < -28) ||
26 | (ballPosition.position.x < -25 &&
27 | ballPosition.position.x > -70 &&
28 | ballPosition.position.z > -10 &&
29 | ballPosition.position.z < 40)
30 | ) {
31 | targetPos = new THREE.Vector3(
32 | ballPosition.position.x,
33 | ballPosition.position.y + 50,
34 | ballPosition.position.z + 40
35 | );
36 | }
37 |
38 | //2
39 | else if (
40 | ballPosition.position.x > -3 &&
41 | ballPosition.position.x < 22 &&
42 | ballPosition.position.z > 31 &&
43 | ballPosition.position.z < 58
44 | ) {
45 | targetPos = new THREE.Vector3(
46 | ballPosition.position.x,
47 | ballPosition.position.y + 50,
48 | ballPosition.position.z + 40
49 | );
50 | }
51 |
52 | //3
53 | else if (ballPosition.position.z > 50) {
54 | targetPos = new THREE.Vector3(
55 | ballPosition.position.x,
56 | ballPosition.position.y + 10,
57 | ballPosition.position.z + 40
58 | );
59 | }
60 |
61 | // revert back to original angle
62 | else {
63 | targetPos = new THREE.Vector3(
64 | ballPosition.position.x,
65 | ballPosition.position.y + 30,
66 | ballPosition.position.z + 60
67 | );
68 | }
69 |
70 | camPos.lerp(targetPos, 0.033);
71 | camera.position.copy(camPos);
72 | camera.lookAt(ballPosition.position);
73 | }
74 |
75 | export function getCanvasRelativePosition(event) {
76 | const rect = renderer.domElement.getBoundingClientRect();
77 | return {
78 | x: ((event.clientX - rect.left) * renderer.domElement.width) / rect.width,
79 | y: ((event.clientY - rect.top) * renderer.domElement.height) / rect.height,
80 | };
81 | }
82 |
83 | export function launchClickPosition(event) {
84 | const pos = getCanvasRelativePosition(event);
85 | pickPosition.x = (pos.x / renderer.domElement.width) * 2 - 1;
86 | pickPosition.y = (pos.y / renderer.domElement.height) * -2 + 1; // note we flip Y
87 |
88 | // cast a ray through the frustum
89 | const myRaycaster = new THREE.Raycaster();
90 | myRaycaster.setFromCamera(pickPosition, camera);
91 | // get the list of objects the ray intersected
92 | const intersectedObjects = myRaycaster.intersectObjects(scene.children);
93 | if (intersectedObjects.length) {
94 | // pick the first object. It's the closest one
95 | const pickedObject = intersectedObjects[0].object;
96 | if (intersectedObjects[0].object.userData.URL)
97 | window.open(intersectedObjects[0].object.userData.URL);
98 | else {
99 | return;
100 | }
101 | }
102 | }
103 |
104 | export function launchHover(event) {
105 | event.preventDefault();
106 | var mouse = new THREE.Vector2();
107 | mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
108 | mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
109 |
110 | var raycaster = new THREE.Raycaster();
111 | raycaster.setFromCamera(mouse, camera);
112 | var intersects = raycaster.intersectObjects(cursorHoverObjects);
113 |
114 | if (intersects.length > 0) {
115 | document.getElementById('document-body').style.cursor = 'pointer';
116 | } else {
117 | document.getElementById('document-body').style.cursor = 'default';
118 | }
119 | }
120 |
121 | //deprecated camera function
122 | /*
123 | function rotateCamera(ballPosition) {
124 | //1
125 | if (
126 | (ballPosition.position.x < 77 &&
127 | ballPosition.position.x > 42 &&
128 | ballPosition.position.z > -20 &&
129 | ballPosition.position.z < 40) ||
130 | (ballPosition.position.x < -2 && ballPosition.position.z < -28) ||
131 | (ballPosition.position.x < -25 &&
132 | ballPosition.position.x > -70 &&
133 | ballPosition.position.z > -10 &&
134 | ballPosition.position.z < 40)
135 | ) {
136 | camera.position.x = ballPosition.position.x;
137 | camera.position.y = ballPosition.position.y + 50;
138 | camera.position.z = ballPosition.position.z + 40;
139 | camera.lookAt(ballPosition.position);
140 |
141 | //2
142 | } else if (
143 | ballPosition.position.x > -3 &&
144 | ballPosition.position.x < 22 &&
145 | ballPosition.position.z > 31 &&
146 | ballPosition.position.z < 58
147 | ) {
148 | camera.position.x = ballPosition.position.x;
149 | camera.position.y = ballPosition.position.y + 50;
150 | camera.position.z = ballPosition.position.z + 40;
151 | camera.lookAt(ballPosition.position);
152 |
153 | //3
154 | } else if (ballPosition.position.z > 50) {
155 | camera.position.x = ballPosition.position.x;
156 | camera.position.y = ballPosition.position.y + 10;
157 | camera.position.z = ballPosition.position.z + 40;
158 | camera.lookAt(ballPosition.position);
159 |
160 | //no change
161 | } else {
162 | camera.position.x = ballPosition.position.x;
163 | camera.position.y = ballPosition.position.y + 30;
164 | camera.position.z = ballPosition.position.z + 60;
165 | camera.lookAt(ballPosition.position);
166 | }
167 | }
168 | */
169 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | color: #fff;
4 | font-family: 'Roboto', sans-serif;
5 | font-size: 16px;
6 | overscroll-behavior: none;
7 | position: fixed;
8 | width: 100vw;
9 | height: 100vh;
10 | overflow-y: hidden;
11 | }
12 |
13 | canvas {
14 | display: block;
15 | overscroll-behavior: none;
16 | width: 100vw;
17 | height: 100vh;
18 | overflow-y: hidden;
19 | }
20 |
21 | #info {
22 | position: absolute;
23 | top: 0px;
24 | width: 100%;
25 | padding: 10px;
26 | box-sizing: border-box;
27 | text-align: center;
28 | -moz-user-select: none;
29 | -webkit-user-select: none;
30 | -ms-user-select: none;
31 | user-select: none;
32 | pointer-events: none;
33 | z-index: 1; /* TODO Solve this in HTML */
34 | }
35 |
36 | a,
37 | button,
38 | input,
39 | select {
40 | pointer-events: auto;
41 | }
42 |
43 | .dg.ac {
44 | -moz-user-select: none;
45 | -webkit-user-select: none;
46 | -ms-user-select: none;
47 | user-select: none;
48 | z-index: 2 !important; /* TODO Solve this in HTML */
49 | }
50 |
51 | #overlay {
52 | position: absolute;
53 | z-index: 2;
54 | top: 0;
55 | left: 0;
56 | width: 100%;
57 | height: 100%;
58 | display: flex;
59 | align-items: center;
60 | justify-content: center;
61 | background: rgba(0, 0, 0, 0.7);
62 | }
63 |
64 | #overlay button {
65 | background: #ffffff;
66 | border: 0;
67 | color: #000000;
68 | padding: 16px 20px;
69 | text-transform: uppercase;
70 | cursor: pointer;
71 | }
72 |
73 | #notSupported {
74 | width: 50%;
75 | margin: auto;
76 | background-color: #f00;
77 | margin-top: 20px;
78 | padding: 10px;
79 | }
80 |
81 | #tooltip {
82 | position: fixed;
83 | left: 0;
84 | top: 0;
85 | min-width: 100px;
86 | text-align: center;
87 | padding: 5px 12px;
88 | font-family: monospace;
89 | background: #ffffff;
90 | display: none;
91 | opacity: 0;
92 | box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
93 | transition: opacity 0.25s linear;
94 | border-radius: 3px;
95 | }
96 |
97 | #joystick-wrapper {
98 | border: 1px solid rgba(255, 255, 255, 0.5);
99 | width: 125px;
100 | height: 125px;
101 | position: fixed;
102 | bottom: 15px;
103 | left: 15px;
104 | text-align: center;
105 | border-radius: 100%;
106 | display: flex; /* [1] */
107 | justify-content: center; /* [2] */
108 | align-items: center;
109 | visibility: hidden;
110 | opacity: 1;
111 | }
112 |
113 | #joystick {
114 | background-color: rgba(0, 0, 0, 0.25);
115 | border-radius: 100%;
116 | cursor: pointer;
117 | height: 45%;
118 | user-select: none;
119 | width: 45%;
120 | margin: 0 auto;
121 | visibility: hidden;
122 | opacity: 1;
123 | border: 1px solid rgba(255, 255, 255, 0.5);
124 | -webkit-user-select: none; /* Chrome/Safari */
125 | -moz-user-select: none; /* Firefox */
126 | -ms-user-select: none; /* IE10+ */
127 | }
128 |
129 | .preload-overlay {
130 | height: 100%;
131 | width: 100%;
132 | position: absolute;
133 | top: 0;
134 | left: 0;
135 | background-color: black;
136 | display: flex;
137 | justify-content: center;
138 | align-items: center;
139 | z-index: 1000;
140 | opacity: 0.9;
141 | }
142 |
143 | .postload {
144 | visibility: hidden;
145 | display: none;
146 | }
147 |
148 | .bottom-webgl-text-div {
149 | position: absolute;
150 | bottom: 15vh;
151 | text-align: center;
152 | }
153 |
154 | .bottom-webgl-text {
155 | padding: 0px 0px 0px 0px;
156 | }
157 |
158 | .start-page-text {
159 | padding-right: 30px;
160 | padding-left: 30px;
161 | padding-bottom: 20px;
162 | font-size: calc(24px + (20 - 18) * (100vw - 400px) / (800 - 400));
163 | }
164 |
165 | .floyd-text {
166 | padding-right: 30px;
167 | padding-left: 30px;
168 | padding-bottom: 20px;
169 | font-size: calc(32px + (24 - 18) * (100vw - 400px) / (800 - 400));
170 | }
171 |
172 | .start-page-content-div {
173 | display: inline-block;
174 | text-align: center;
175 | justify-content: center;
176 | }
177 |
178 | #start-button {
179 | margin: 0 auto;
180 | margin-bottom: 20vh;
181 | font-size: 25px;
182 | background-color: grey;
183 | border: none;
184 | color: #fffc00;
185 | border-radius: 12px;
186 | padding: 15px 30px 15px 30px;
187 | -webkit-appearance: none;
188 | -webkit-border-radius: none;
189 | outline: none;
190 | transition: 0.25s;
191 | }
192 |
193 | #start-button:hover {
194 | cursor: pointer;
195 | transform: scale(1.15);
196 | }
197 |
198 | /*
199 | .interactive-site-text {
200 | margin: 50px 50px 50px 50px;
201 | }
202 |
203 | .joystick-directions-text {
204 | padding: 0px 30px 0px 30px;
205 | }
206 |
207 | .junior-engineer {
208 | margin-bottom: 50px;
209 | }*/
210 |
211 | .yellow-text {
212 | color: #fffc00;
213 | }
214 |
215 | #static-site-link {
216 | text-decoration: underline;
217 | color: white;
218 | }
219 |
220 | .trinity-rings-spinner,
221 | .trinity-rings-spinner * {
222 | box-sizing: border-box;
223 | }
224 |
225 | .trinity-rings-spinner {
226 | height: 500px;
227 | width: 500px;
228 | padding: 3px;
229 | position: relative;
230 | display: flex;
231 | justify-content: center;
232 | align-items: center;
233 | flex-direction: row;
234 | overflow: hidden;
235 | box-sizing: border-box;
236 | }
237 | .trinity-rings-spinner .circle {
238 | position: absolute;
239 | display: block;
240 | border-radius: 50%;
241 | border: 10px solid #fffc00;
242 | }
243 |
244 | .trinity-rings-spinner .circle:nth-child(1) {
245 | height: 350px;
246 | width: 350px;
247 | animation: trinity-rings-spinner-circle1-animation 1.5s infinite linear;
248 | border-width: 10px;
249 | opacity: 0.9;
250 | }
251 | .trinity-rings-spinner .circle:nth-child(2) {
252 | height: calc(350px * 0.65);
253 | width: calc(350px * 0.65);
254 | animation: trinity-rings-spinner-circle2-animation 1.5s infinite linear;
255 | border-width: 8px;
256 | opacity: 0.7;
257 | }
258 | .trinity-rings-spinner .circle:nth-child(3) {
259 | height: calc(350px * 0.45);
260 | width: calc(350px * 0.45);
261 | animation: trinity-rings-spinner-circle3-animation 1.5s infinite linear;
262 | border-width: 6px;
263 | opacity: 0.5;
264 | }
265 |
266 | @keyframes trinity-rings-spinner-circle1-animation {
267 | 0% {
268 | transform: rotateZ(20deg) rotateY(0deg);
269 | }
270 | 100% {
271 | transform: rotateZ(100deg) rotateY(360deg);
272 | }
273 | }
274 | @keyframes trinity-rings-spinner-circle2-animation {
275 | 0% {
276 | transform: rotateZ(100deg) rotateX(0deg);
277 | }
278 | 100% {
279 | transform: rotateZ(0deg) rotateX(360deg);
280 | }
281 | }
282 | @keyframes trinity-rings-spinner-circle3-animation {
283 | 0% {
284 | transform: rotateZ(100deg) rotateX(-360deg);
285 | }
286 | 100% {
287 | transform: rotateZ(-360deg) rotateX(360deg);
288 | }
289 | }
290 |
291 | .loading-text-div {
292 | box-sizing: border-box;
293 | position: absolute;
294 | font-size: 26px;
295 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
296 | color: #fffc00;
297 | }
298 |
299 | @keyframes blink {
300 | 50% {
301 | color: transparent;
302 | }
303 | }
304 | .loader__dot {
305 | animation: 1s blink infinite;
306 | }
307 | .loader__dot:nth-child(2) {
308 | animation-delay: 250ms;
309 | }
310 | .loader__dot:nth-child(3) {
311 | animation-delay: 500ms;
312 | }
313 |
314 | .hidden {
315 | visibility: hidden;
316 | opacity: 0;
317 | transition: visibility 0s 0.25s, opacity 0.25s linear;
318 | }
319 |
320 | .fade-out {
321 | animation: fade-out-animation 0.75s 1;
322 | /* animation-duration: 0.25s; */
323 | animation-fill-mode: forwards;
324 | }
325 |
326 | @keyframes fade-out-animation {
327 | from {
328 | opacity: 0.9;
329 | }
330 | to {
331 | opacity: 0;
332 | display: none;
333 | visibility: 'hidden';
334 | cursor: default;
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/src/resources/world.js:
--------------------------------------------------------------------------------
1 | // use Three.js to set up graphics
2 | import * as THREE from 'three';
3 | import Stats from 'stats.js';
4 | import galaxyVertexShader from '../jsm/vertex.glsl';
5 | import galaxyFragmentShader from '../jsm/fragment.glsl';
6 |
7 | //threejs variable declaration
8 | export let clock,
9 | scene,
10 | camera,
11 | renderer,
12 | stats,
13 | particleGroup,
14 | particleAttributes,
15 | particleSystemObject,
16 | lensFlareObject,
17 | galaxyClock;
18 |
19 | //generic temporary transform to begin
20 |
21 | export let manager = new THREE.LoadingManager();
22 |
23 | export function createWorld() {
24 | clock = new THREE.Clock();
25 | galaxyClock = new THREE.Clock();
26 |
27 | // init new Three.js scene
28 | scene = new THREE.Scene();
29 | scene.background = new THREE.Color(0x000000);
30 |
31 | // camera
32 | camera = new THREE.PerspectiveCamera(
33 | 45,
34 | window.innerWidth / window.innerHeight,
35 | 1,
36 | 5000
37 | );
38 | camera.position.set(0, 30, 70);
39 | //camera.lookAt(scene.position);
40 |
41 | //Add hemisphere light
42 | let hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.1);
43 | hemiLight.color.setHSL(0.6, 0.6, 0.6);
44 | hemiLight.groundColor.setHSL(0.1, 1, 0.4);
45 | hemiLight.position.set(0, 50, 0);
46 | scene.add(hemiLight);
47 |
48 | //Add directional light
49 | let dirLight = new THREE.DirectionalLight(0xffffff, 0.7);
50 | dirLight.color.setHSL(0.1, 1, 0.95);
51 | dirLight.position.set(-10, 100, 50);
52 | dirLight.position.multiplyScalar(100);
53 | scene.add(dirLight);
54 |
55 | dirLight.castShadow = true;
56 |
57 | dirLight.shadow.mapSize.width = 4096;
58 | dirLight.shadow.mapSize.height = 4096;
59 |
60 | let d = 200;
61 |
62 | dirLight.shadow.camera.left = -d;
63 | dirLight.shadow.camera.right = d;
64 | dirLight.shadow.camera.top = d;
65 | dirLight.shadow.camera.bottom = -d;
66 |
67 | dirLight.shadow.camera.far = 15000;
68 |
69 | //Setup the renderer
70 | renderer = new THREE.WebGLRenderer({ antialias: true });
71 | //renderer.setClearColor(0xbfd1e5);
72 | renderer.setPixelRatio(window.devicePixelRatio);
73 | renderer.setSize(window.innerWidth, window.innerHeight);
74 | //renderer.shadowMap.type = THREE.BasicShadowMap;
75 | document.body.appendChild(renderer.domElement);
76 |
77 | stats = new Stats();
78 | document.body.appendChild(stats.dom);
79 |
80 | renderer.gammaInput = true;
81 | renderer.gammaOutput = true;
82 |
83 | renderer.shadowMap.enabled = true;
84 | }
85 |
86 | export function glowingParticles() {
87 | var particleTextureLoader = new THREE.TextureLoader(manager);
88 | var particleTexture = particleTextureLoader.load('../src/jsm/spark.png');
89 |
90 | particleGroup = new THREE.Object3D();
91 | particleGroup.position.x = -1;
92 | particleGroup.position.y = 7;
93 | particleGroup.position.z = 45;
94 | particleAttributes = { startSize: [], startPosition: [], randomness: [] };
95 |
96 | var totalParticles = 50;
97 | var radiusRange = 4;
98 | for (var i = 0; i < totalParticles; i++) {
99 | var spriteMaterial = new THREE.SpriteMaterial({
100 | map: particleTexture,
101 | color: 0xffffff,
102 | });
103 |
104 | var sprite = new THREE.Sprite(spriteMaterial);
105 | sprite.scale.set(0.5, 0.5, 1.0); // imageWidth, imageHeight
106 | sprite.position.set(
107 | Math.random() - 0.5,
108 | Math.random() - 0.5,
109 | Math.random() - 0.5
110 | );
111 |
112 | sprite.position.setLength(radiusRange * (Math.random() * 0.1 + 0.9));
113 |
114 | sprite.material.color.setHSL(Math.random(), 0.9, 0.7);
115 |
116 | sprite.material.blending = THREE.AdditiveBlending; // "glowing" particles
117 | sprite.renderOrder = 1;
118 | particleGroup.add(sprite);
119 | // add variable qualities to arrays, if they need to be accessed later
120 | particleAttributes.startPosition.push(sprite.position.clone());
121 | particleAttributes.randomness.push(Math.random());
122 | }
123 |
124 | scene.add(particleGroup);
125 | }
126 |
127 | export function createLensFlare(x, y, z, xScale, zScale, boxTexture) {
128 | const boxScale = { x: xScale, y: 0.1, z: zScale };
129 | let quat = { x: 0, y: 0, z: 0, w: 1 };
130 | let mass = 0; //mass of zero = infinite mass
131 |
132 | var geometry = new THREE.PlaneBufferGeometry(xScale, zScale);
133 |
134 | const loader = new THREE.TextureLoader();
135 | const texture = loader.load(boxTexture);
136 | texture.magFilter = THREE.LinearFilter;
137 | texture.minFilter = THREE.LinearFilter;
138 | texture.encoding = THREE.sRGBEncoding;
139 | const loadedTexture = new THREE.MeshBasicMaterial({
140 | map: texture,
141 | transparent: true,
142 | opacity: 0.9,
143 | });
144 | loadedTexture.depthWrite = true;
145 | loadedTexture.depthTest = true;
146 |
147 | lensFlareObject = new THREE.Mesh(geometry, loadedTexture);
148 | lensFlareObject.position.set(x, y, z);
149 | lensFlareObject.renderOrder = 1;
150 |
151 | lensFlareObject.receiveShadow = true;
152 | scene.add(lensFlareObject);
153 | }
154 |
155 | export function addParticles() {
156 | var geometry = new THREE.Geometry();
157 |
158 | for (let i = 0; i < 3000; i++) {
159 | var vertex = new THREE.Vector3();
160 | vertex.x = getRandomArbitrary(-1100, 1100);
161 | vertex.y = getRandomArbitrary(-1100, 1100);
162 | vertex.z = getRandomArbitrary(-1100, -500);
163 | geometry.vertices.push(vertex);
164 | }
165 |
166 | var material = new THREE.PointsMaterial({ size: 3 });
167 | particleSystemObject = new THREE.Points(geometry, material);
168 |
169 | scene.add(particleSystemObject);
170 | }
171 |
172 | function getRandomArbitrary(min, max) {
173 | return Math.random() * (max - min) + min;
174 | }
175 |
176 | export let galaxyMaterial = null;
177 | export let galaxyPoints = null;
178 |
179 | export const generateGalaxy = () => {
180 | const parameters = {};
181 | parameters.count = 50000;
182 | parameters.size = 0.005;
183 | parameters.radius = 100;
184 | parameters.branches = 3;
185 | parameters.spin = 1;
186 |
187 | parameters.randomnessPower = 3;
188 | parameters.insideColor = '#ff6030';
189 | parameters.outsideColor = '#1b3984';
190 | parameters.randomness = 0.2;
191 |
192 | let geometry = null;
193 | galaxyMaterial = null;
194 | galaxyPoints = null;
195 | if (galaxyPoints !== null) {
196 | geometry.dispose();
197 | galaxyMaterial.dispose();
198 | scene.remove(galaxyPoints);
199 | }
200 |
201 | /**
202 | * Geometry
203 | */
204 | geometry = new THREE.BufferGeometry();
205 |
206 | const positions = new Float32Array(parameters.count * 3);
207 | const randomness = new Float32Array(parameters.count * 3);
208 |
209 | const colors = new Float32Array(parameters.count * 3);
210 | const scales = new Float32Array(parameters.count * 1);
211 |
212 | const insideColor = new THREE.Color(parameters.insideColor);
213 | const outsideColor = new THREE.Color(parameters.outsideColor);
214 |
215 | for (let i = 0; i < parameters.count; i++) {
216 | const i3 = i * 3;
217 |
218 | // Position
219 | const radius = Math.random() * parameters.radius;
220 |
221 | const branchAngle =
222 | ((i % parameters.branches) / parameters.branches) * Math.PI * 2;
223 |
224 | const randomX =
225 | Math.pow(Math.random(), parameters.randomnessPower) *
226 | (Math.random() < 0.5 ? 1 : -1) *
227 | parameters.randomness *
228 | radius;
229 | const randomY =
230 | Math.pow(Math.random(), parameters.randomnessPower) *
231 | (Math.random() < 0.5 ? 1 : -1) *
232 | parameters.randomness *
233 | radius;
234 | const randomZ =
235 | Math.pow(Math.random(), parameters.randomnessPower) *
236 | (Math.random() < 0.5 ? 1 : -1) *
237 | parameters.randomness *
238 | radius -
239 | 50;
240 |
241 | positions[i3] = Math.cos(branchAngle) * radius;
242 | positions[i3 + 1] = 0;
243 | positions[i3 + 2] = Math.sin(branchAngle) * radius;
244 |
245 | randomness[i3] = randomX;
246 | randomness[i3 + 1] = randomY;
247 | randomness[i3 + 2] = randomZ;
248 |
249 | // Color
250 | const mixedColor = insideColor.clone();
251 | mixedColor.lerp(outsideColor, radius / parameters.radius);
252 |
253 | colors[i3] = mixedColor.r;
254 | colors[i3 + 1] = mixedColor.g;
255 | colors[i3 + 2] = mixedColor.b;
256 |
257 | // Scale
258 | scales[i] = Math.random();
259 | }
260 |
261 | geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
262 | geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
263 | geometry.setAttribute('aScale', new THREE.BufferAttribute(scales, 1));
264 | geometry.setAttribute(
265 | 'aRandomness',
266 | new THREE.BufferAttribute(randomness, 3)
267 | );
268 |
269 | /**
270 | * Material
271 | */
272 | galaxyMaterial = new THREE.ShaderMaterial({
273 | size: parameters.size,
274 | sizeAttenuation: true,
275 | depthWrite: false,
276 | blending: THREE.AdditiveBlending,
277 | vertexColors: true,
278 | vertexShader: galaxyVertexShader,
279 | fragmentShader: galaxyFragmentShader,
280 | uniforms: {
281 | uTime: { value: 0 },
282 | uSize: { value: 30 * renderer.getPixelRatio() },
283 | },
284 | });
285 |
286 | /**
287 | * Points
288 | */
289 | galaxyPoints = new THREE.Points(geometry, galaxyMaterial);
290 | galaxyPoints.position.y = -50;
291 | scene.add(galaxyPoints);
292 | };
293 |
294 | export function moveParticles() {
295 | particleSystemObject.rotation.z += 0.0003;
296 | lensFlareObject.rotation.z += 0.0002;
297 | if (lensFlareObject.position.x < 750) {
298 | lensFlareObject.position.x += 0.025;
299 | lensFlareObject.position.y -= 0.001;
300 | } else {
301 | lensFlareObject.position.x = -750;
302 | lensFlareObject.position.y = -50;
303 | }
304 |
305 | //move stemkoski particles
306 | var time = 7 * clock.getElapsedTime();
307 |
308 | for (var c = 0; c < particleGroup.children.length; c++) {
309 | var sprite = particleGroup.children[c];
310 |
311 | // pulse away/towards center
312 | // individual rates of movement
313 | var a = particleAttributes.randomness[c] + 0.75;
314 | var pulseFactor = Math.sin(a * time) * 0.1 + 0.9;
315 | sprite.position.x = particleAttributes.startPosition[c].x * pulseFactor;
316 | sprite.position.y =
317 | particleAttributes.startPosition[c].y * pulseFactor * 1.5;
318 | sprite.position.z = particleAttributes.startPosition[c].z * pulseFactor;
319 | }
320 |
321 | // rotate the entire group
322 | //particleGroup.rotation.x = time * 0.5;
323 | particleGroup.rotation.y = time * 0.75;
324 | // particleGroup.rotation.z = time * 1.0;
325 | }
326 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { WEBGL } from './WebGL';
3 | import * as Ammo from './builds/ammo';
4 | import { billboardTextures, boxTexture, inputText, URL, stoneTexture, woodTexture } from './resources/textures';
5 |
6 | import { setupEventHandlers, moveDirection, isTouchscreenDevice, touchEvent, createJoystick } from './resources/eventHandlers';
7 |
8 | import { preloadDivs, preloadOpacity, postloadDivs, startScreenDivs, startButton, noWebGL, fadeOutDivs } from './resources/preload';
9 |
10 | import {
11 | clock,
12 | scene,
13 | camera,
14 | renderer,
15 | stats,
16 | manager,
17 | createWorld,
18 | lensFlareObject,
19 | createLensFlare,
20 | particleGroup,
21 | particleAttributes,
22 | particleSystemObject,
23 | glowingParticles,
24 | addParticles,
25 | moveParticles,
26 | generateGalaxy,
27 | galaxyMaterial,
28 | galaxyClock,
29 | galaxyPoints,
30 | } from './resources/world';
31 |
32 | import { simpleText, floatingLabel, allSkillsSection, createTextOnPlane } from './resources/surfaces';
33 |
34 | import { pickPosition, launchClickPosition, getCanvasRelativePosition, rotateCamera, launchHover } from './resources/utils';
35 |
36 | export let cursorHoverObjects = [];
37 |
38 | // start Ammo Engine
39 | Ammo().then((Ammo) => {
40 | //Ammo.js variable declaration
41 | let rigidBodies = [],
42 | physicsWorld;
43 |
44 | //Ammo Dynamic bodies for ball
45 | let ballObject = null;
46 | const STATE = { DISABLE_DEACTIVATION: 4 };
47 |
48 | //default transform object
49 | let tmpTrans = new Ammo.btTransform();
50 |
51 | // list of hyperlink objects
52 | var objectsWithLinks = [];
53 |
54 | //function to create physics world with Ammo.js
55 | function createPhysicsWorld() {
56 | //algortihms for full (not broadphase) collision detection
57 | let collisionConfiguration = new Ammo.btDefaultCollisionConfiguration(),
58 | dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration), // dispatch calculations for overlapping pairs/ collisions.
59 | overlappingPairCache = new Ammo.btDbvtBroadphase(), //broadphase collision detection list of all possible colliding pairs
60 | constraintSolver = new Ammo.btSequentialImpulseConstraintSolver(); //causes the objects to interact properly, like gravity, game logic forces, collisions
61 |
62 | // see bullet physics docs for info
63 | physicsWorld = new Ammo.btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, constraintSolver, collisionConfiguration);
64 |
65 | // add gravity
66 | physicsWorld.setGravity(new Ammo.btVector3(0, -50, 0));
67 | }
68 |
69 | //create flat plane
70 | function createGridPlane() {
71 | // block properties
72 | let pos = { x: 0, y: -0.25, z: 0 };
73 | let scale = { x: 175, y: 0.5, z: 175 };
74 | let quat = { x: 0, y: 0, z: 0, w: 1 };
75 | let mass = 0; //mass of zero = infinite mass
76 |
77 | //create grid overlay on plane
78 | var grid = new THREE.GridHelper(175, 20, 0xffffff, 0xffffff);
79 | grid.material.opacity = 0.5;
80 | grid.material.transparent = true;
81 | grid.position.y = 0.005;
82 | scene.add(grid);
83 |
84 | //Create Threejs Plane
85 | let blockPlane = new THREE.Mesh(
86 | new THREE.BoxBufferGeometry(),
87 | new THREE.MeshPhongMaterial({
88 | color: 0xffffff,
89 | transparent: true,
90 | opacity: 0.25,
91 | })
92 | );
93 | blockPlane.position.set(pos.x, pos.y, pos.z);
94 | blockPlane.scale.set(scale.x, scale.y, scale.z);
95 | blockPlane.receiveShadow = true;
96 | scene.add(blockPlane);
97 |
98 | //Ammo.js Physics
99 | let transform = new Ammo.btTransform();
100 | transform.setIdentity(); // sets safe default values
101 | transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
102 | transform.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));
103 | let motionState = new Ammo.btDefaultMotionState(transform);
104 |
105 | //setup collision box
106 | let colShape = new Ammo.btBoxShape(new Ammo.btVector3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
107 | colShape.setMargin(0.05);
108 |
109 | let localInertia = new Ammo.btVector3(0, 0, 0);
110 | colShape.calculateLocalInertia(mass, localInertia);
111 |
112 | // provides information to create a rigid body
113 | let rigidBodyStruct = new Ammo.btRigidBodyConstructionInfo(mass, motionState, colShape, localInertia);
114 | let body = new Ammo.btRigidBody(rigidBodyStruct);
115 | body.setFriction(10);
116 | body.setRollingFriction(10);
117 |
118 | // add to world
119 | physicsWorld.addRigidBody(body);
120 | }
121 |
122 | // create ball
123 | function createBall() {
124 | let pos = { x: 8.75, y: 0, z: 0 };
125 | let radius = 2;
126 | let quat = { x: 0, y: 0, z: 0, w: 1 };
127 | let mass = 3;
128 |
129 | var marble_loader = new THREE.TextureLoader(manager);
130 | var marbleTexture = marble_loader.load('./src/jsm/earth.jpg');
131 | marbleTexture.wrapS = marbleTexture.wrapT = THREE.RepeatWrapping;
132 | marbleTexture.repeat.set(1, 1);
133 | marbleTexture.anisotropy = 1;
134 | marbleTexture.encoding = THREE.sRGBEncoding;
135 |
136 | //threeJS Section
137 | let ball = (ballObject = new THREE.Mesh(new THREE.SphereGeometry(radius, 32, 32), new THREE.MeshLambertMaterial({ map: marbleTexture })));
138 |
139 | ball.geometry.computeBoundingSphere();
140 | ball.geometry.computeBoundingBox();
141 |
142 | ball.position.set(pos.x, pos.y, pos.z);
143 |
144 | ball.castShadow = true;
145 | ball.receiveShadow = true;
146 |
147 | scene.add(ball);
148 |
149 | //Ammojs Section
150 | let transform = new Ammo.btTransform();
151 | transform.setIdentity();
152 | transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
153 | transform.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));
154 | let motionState = new Ammo.btDefaultMotionState(transform);
155 |
156 | let colShape = new Ammo.btSphereShape(radius);
157 | colShape.setMargin(0.05);
158 |
159 | let localInertia = new Ammo.btVector3(0, 0, 0);
160 | colShape.calculateLocalInertia(mass, localInertia);
161 |
162 | let rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, colShape, localInertia);
163 | let body = new Ammo.btRigidBody(rbInfo);
164 | //body.setFriction(4);
165 | body.setRollingFriction(10);
166 |
167 | //set ball friction
168 |
169 | //once state is set to disable, dynamic interaction no longer calculated
170 | body.setActivationState(STATE.DISABLE_DEACTIVATION);
171 |
172 | physicsWorld.addRigidBody(
173 | body //collisionGroupRedBall, collisionGroupGreenBall | collisionGroupPlane
174 | );
175 |
176 | ball.userData.physicsBody = body;
177 | ballObject.userData.physicsBody = body;
178 |
179 | rigidBodies.push(ball);
180 | rigidBodies.push(ballObject);
181 | }
182 |
183 | //create beach ball Mesh
184 | function createBeachBall() {
185 | let pos = { x: 20, y: 30, z: 0 };
186 | let radius = 2;
187 | let quat = { x: 0, y: 0, z: 0, w: 1 };
188 | let mass = 20;
189 |
190 | //import beach ball texture
191 | var texture_loader = new THREE.TextureLoader(manager);
192 | var beachTexture = texture_loader.load('./src/jsm/BeachBallColor.jpg');
193 | beachTexture.wrapS = beachTexture.wrapT = THREE.RepeatWrapping;
194 | beachTexture.repeat.set(1, 1);
195 | beachTexture.anisotropy = 1;
196 | beachTexture.encoding = THREE.sRGBEncoding;
197 |
198 | //threeJS Section
199 | let ball = new THREE.Mesh(new THREE.SphereGeometry(radius, 32, 32), new THREE.MeshLambertMaterial({ map: beachTexture }));
200 |
201 | ball.position.set(pos.x, pos.y, pos.z);
202 | ball.castShadow = true;
203 | ball.receiveShadow = true;
204 | scene.add(ball);
205 |
206 | //Ammojs Section
207 | let transform = new Ammo.btTransform();
208 | transform.setIdentity();
209 | transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
210 | transform.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));
211 | let motionState = new Ammo.btDefaultMotionState(transform);
212 |
213 | let colShape = new Ammo.btSphereShape(radius);
214 | colShape.setMargin(0.05);
215 |
216 | let localInertia = new Ammo.btVector3(0, 0, 0);
217 | colShape.calculateLocalInertia(mass, localInertia);
218 |
219 | let rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, colShape, localInertia);
220 | let body = new Ammo.btRigidBody(rbInfo);
221 |
222 | body.setRollingFriction(1);
223 | physicsWorld.addRigidBody(body);
224 |
225 | ball.userData.physicsBody = body;
226 | rigidBodies.push(ball);
227 | }
228 |
229 | //create link boxes
230 | function createBox(x, y, z, scaleX, scaleY, scaleZ, boxTexture, URLLink, color = 0x000000, transparent = true) {
231 | const boxScale = { x: scaleX, y: scaleY, z: scaleZ };
232 | let quat = { x: 0, y: 0, z: 0, w: 1 };
233 | let mass = 0; //mass of zero = infinite mass
234 |
235 | //load link logo
236 | const loader = new THREE.TextureLoader(manager);
237 | const texture = loader.load(boxTexture);
238 | texture.magFilter = THREE.LinearFilter;
239 | texture.minFilter = THREE.LinearFilter;
240 | texture.encoding = THREE.sRGBEncoding;
241 | const loadedTexture = new THREE.MeshBasicMaterial({
242 | map: texture,
243 | transparent: transparent,
244 | color: 0xffffff,
245 | });
246 |
247 | var borderMaterial = new THREE.MeshBasicMaterial({
248 | color: color,
249 | });
250 | borderMaterial.color.convertSRGBToLinear();
251 |
252 | var materials = [
253 | borderMaterial, // Left side
254 | borderMaterial, // Right side
255 | borderMaterial, // Top side ---> THIS IS THE FRONT
256 | borderMaterial, // Bottom side --> THIS IS THE BACK
257 | loadedTexture, // Front side
258 | borderMaterial, // Back side
259 | ];
260 |
261 | const linkBox = new THREE.Mesh(new THREE.BoxBufferGeometry(boxScale.x, boxScale.y, boxScale.z), materials);
262 | linkBox.position.set(x, y, z);
263 | linkBox.renderOrder = 1;
264 | linkBox.castShadow = true;
265 | linkBox.receiveShadow = true;
266 | linkBox.userData = { URL: URLLink, email: URLLink };
267 | scene.add(linkBox);
268 | objectsWithLinks.push(linkBox.uuid);
269 |
270 | addRigidPhysics(linkBox, boxScale);
271 |
272 | cursorHoverObjects.push(linkBox);
273 | }
274 |
275 | //create Ammo.js body to add solid mass to "Software Engineer"
276 | function floydWords(x, y, z) {
277 | const boxScale = { x: 37, y: 3, z: 2 };
278 | let quat = { x: 0, y: 0, z: 0, w: 1 };
279 | let mass = 0; //mass of zero = infinite mass
280 |
281 | const linkBox = new THREE.Mesh(
282 | new THREE.BoxBufferGeometry(boxScale.x, boxScale.y, boxScale.z),
283 | new THREE.MeshPhongMaterial({
284 | color: 0xff6600,
285 | })
286 | );
287 |
288 | linkBox.position.set(x, y, z);
289 | linkBox.castShadow = true;
290 | linkBox.receiveShadow = true;
291 | objectsWithLinks.push(linkBox.uuid);
292 |
293 | addRigidPhysics(linkBox, boxScale);
294 | }
295 |
296 | //loads text for Floyd Mesh
297 | function loadFloydText() {
298 | var text_loader = new THREE.FontLoader();
299 |
300 | text_loader.load('./src/jsm/Roboto_Regular.json', function (font) {
301 | var xMid, text;
302 |
303 | var color = 0xfffc00;
304 |
305 | var textMaterials = [
306 | new THREE.MeshBasicMaterial({ color: color }), // front
307 | new THREE.MeshPhongMaterial({ color: color }), // side
308 | ];
309 |
310 | var geometry = new THREE.TextGeometry('0xFloyd', {
311 | font: font,
312 | size: 3,
313 | height: 0.5,
314 | curveSegments: 12,
315 | bevelEnabled: true,
316 | bevelThickness: 0.1,
317 | bevelSize: 0.11,
318 | bevelOffset: 0,
319 | bevelSegments: 1,
320 | });
321 |
322 | geometry.computeBoundingBox();
323 | geometry.computeVertexNormals();
324 |
325 | xMid = -0.15 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
326 |
327 | geometry.translate(xMid, 0, 0);
328 |
329 | var textGeo = new THREE.BufferGeometry().fromGeometry(geometry);
330 |
331 | text = new THREE.Mesh(geometry, textMaterials);
332 | text.position.z = -20;
333 | text.position.y = 0.1;
334 | text.receiveShadow = true;
335 | text.castShadow = true;
336 | scene.add(text);
337 | });
338 | }
339 |
340 | //create "software engineer text"
341 | function loadEngineerText() {
342 | var text_loader = new THREE.FontLoader();
343 |
344 | text_loader.load('./src/jsm/Roboto_Regular.json', function (font) {
345 | var xMid, text;
346 |
347 | var color = 0x00ff08;
348 |
349 | var textMaterials = [
350 | new THREE.MeshBasicMaterial({ color: color }), // front
351 | new THREE.MeshPhongMaterial({ color: color }), // side
352 | ];
353 |
354 | var geometry = new THREE.TextGeometry('SOFTWARE ENGINEER', {
355 | font: font,
356 | size: 1.5,
357 | height: 0.5,
358 | curveSegments: 20,
359 | bevelEnabled: true,
360 | bevelThickness: 0.25,
361 | bevelSize: 0.1,
362 | });
363 |
364 | geometry.computeBoundingBox();
365 | geometry.computeVertexNormals();
366 |
367 | xMid = -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
368 |
369 | geometry.translate(xMid, 0, 0);
370 |
371 | var textGeo = new THREE.BufferGeometry().fromGeometry(geometry);
372 |
373 | text = new THREE.Mesh(textGeo, textMaterials);
374 | text.position.z = -20;
375 | text.position.y = 0.1;
376 | text.position.x = 24;
377 | text.receiveShadow = true;
378 | text.castShadow = true;
379 | scene.add(text);
380 | });
381 | }
382 |
383 | //function to create billboard
384 | function createBillboard(x, y, z, textureImage = billboardTextures.grassImage, urlLink, rotation = 0) {
385 | const billboardPoleScale = { x: 1, y: 5, z: 1 };
386 | const billboardSignScale = { x: 30, y: 15, z: 1 };
387 |
388 | /* default texture loading */
389 | const loader = new THREE.TextureLoader(manager);
390 |
391 | const billboardPole = new THREE.Mesh(
392 | new THREE.BoxBufferGeometry(billboardPoleScale.x, billboardPoleScale.y, billboardPoleScale.z),
393 | new THREE.MeshStandardMaterial({
394 | map: loader.load(woodTexture),
395 | })
396 | );
397 |
398 | const texture = loader.load(textureImage);
399 | texture.magFilter = THREE.LinearFilter;
400 | texture.minFilter = THREE.LinearFilter;
401 | texture.encoding = THREE.sRGBEncoding;
402 | var borderMaterial = new THREE.MeshBasicMaterial({
403 | color: 0x000000,
404 | });
405 | const loadedTexture = new THREE.MeshBasicMaterial({
406 | map: texture,
407 | });
408 |
409 | var materials = [
410 | borderMaterial, // Left side
411 | borderMaterial, // Right side
412 | borderMaterial, // Top side ---> THIS IS THE FRONT
413 | borderMaterial, // Bottom side --> THIS IS THE BACK
414 | loadedTexture, // Front side
415 | borderMaterial, // Back side
416 | ];
417 | // order to add materials: x+,x-,y+,y-,z+,z-
418 | const billboardSign = new THREE.Mesh(new THREE.BoxGeometry(billboardSignScale.x, billboardSignScale.y, billboardSignScale.z), materials);
419 |
420 | billboardPole.position.x = x;
421 | billboardPole.position.y = y;
422 | billboardPole.position.z = z;
423 |
424 | billboardSign.position.x = x;
425 | billboardSign.position.y = y + 10;
426 | billboardSign.position.z = z;
427 |
428 | /* Rotate Billboard */
429 | billboardPole.rotation.y = rotation;
430 | billboardSign.rotation.y = rotation;
431 |
432 | billboardPole.castShadow = true;
433 | billboardPole.receiveShadow = true;
434 |
435 | billboardSign.castShadow = true;
436 | billboardSign.receiveShadow = true;
437 |
438 | billboardSign.userData = { URL: urlLink };
439 |
440 | scene.add(billboardPole);
441 | scene.add(billboardSign);
442 | addRigidPhysics(billboardPole, billboardPoleScale);
443 |
444 | cursorHoverObjects.push(billboardSign);
445 | }
446 |
447 | //create vertical billboard
448 | function createBillboardRotated(x, y, z, textureImage = billboardTextures.grassImage, urlLink, rotation = 0) {
449 | const billboardPoleScale = { x: 1, y: 2.5, z: 1 };
450 | const billboardSignScale = { x: 15, y: 20, z: 1 };
451 |
452 | /* default texture loading */
453 | const loader = new THREE.TextureLoader(manager);
454 | const billboardPole = new THREE.Mesh(
455 | new THREE.BoxBufferGeometry(billboardPoleScale.x, billboardPoleScale.y, billboardPoleScale.z),
456 | new THREE.MeshStandardMaterial({
457 | map: loader.load(woodTexture),
458 | })
459 | );
460 | const texture = loader.load(textureImage);
461 | texture.magFilter = THREE.LinearFilter;
462 | texture.minFilter = THREE.LinearFilter;
463 | texture.encoding = THREE.sRGBEncoding;
464 | var borderMaterial = new THREE.MeshBasicMaterial({
465 | color: 0x000000,
466 | });
467 | const loadedTexture = new THREE.MeshBasicMaterial({
468 | map: texture,
469 | });
470 |
471 | var materials = [
472 | borderMaterial, // Left side
473 | borderMaterial, // Right side
474 | borderMaterial, // Top side ---> THIS IS THE FRONT
475 | borderMaterial, // Bottom side --> THIS IS THE BACK
476 | loadedTexture, // Front side
477 | borderMaterial, // Back side
478 | ];
479 | // order to add materials: x+,x-,y+,y-,z+,z-
480 | const billboardSign = new THREE.Mesh(new THREE.BoxGeometry(billboardSignScale.x, billboardSignScale.y, billboardSignScale.z), materials);
481 |
482 | billboardPole.position.x = x;
483 | billboardPole.position.y = y;
484 | billboardPole.position.z = z;
485 |
486 | billboardSign.position.x = x;
487 | billboardSign.position.y = y + 11.25;
488 | billboardSign.position.z = z;
489 |
490 | /* Rotate Billboard */
491 | billboardPole.rotation.y = rotation;
492 | billboardSign.rotation.y = rotation;
493 |
494 | billboardPole.castShadow = true;
495 | billboardPole.receiveShadow = true;
496 |
497 | billboardSign.castShadow = true;
498 | billboardSign.receiveShadow = true;
499 |
500 | billboardSign.userData = { URL: urlLink };
501 |
502 | scene.add(billboardPole);
503 | scene.add(billboardSign);
504 | addRigidPhysics(billboardPole, billboardPoleScale);
505 | addRigidPhysics(billboardSign, billboardSignScale);
506 |
507 | cursorHoverObjects.push(billboardSign);
508 | }
509 |
510 | //create X axis wall around entire plane
511 | function createWallX(x, y, z) {
512 | const wallScale = { x: 0.125, y: 4, z: 175 };
513 |
514 | const wall = new THREE.Mesh(
515 | new THREE.BoxBufferGeometry(wallScale.x, wallScale.y, wallScale.z),
516 | new THREE.MeshStandardMaterial({
517 | color: 0xffffff,
518 | opacity: 0.75,
519 | transparent: true,
520 | })
521 | );
522 |
523 | wall.position.x = x;
524 | wall.position.y = y;
525 | wall.position.z = z;
526 |
527 | wall.receiveShadow = true;
528 |
529 | scene.add(wall);
530 |
531 | addRigidPhysics(wall, wallScale);
532 | }
533 |
534 | //create Z axis wall around entire plane
535 | function createWallZ(x, y, z) {
536 | const wallScale = { x: 175, y: 4, z: 0.125 };
537 |
538 | const wall = new THREE.Mesh(
539 | new THREE.BoxBufferGeometry(wallScale.x, wallScale.y, wallScale.z),
540 | new THREE.MeshStandardMaterial({
541 | color: 0xffffff,
542 | opacity: 0.75,
543 | transparent: true,
544 | })
545 | );
546 |
547 | wall.position.x = x;
548 | wall.position.y = y;
549 | wall.position.z = z;
550 |
551 | wall.receiveShadow = true;
552 |
553 | scene.add(wall);
554 |
555 | addRigidPhysics(wall, wallScale);
556 | }
557 |
558 | //create brick wall
559 | function wallOfBricks() {
560 | const loader = new THREE.TextureLoader(manager);
561 | var pos = new THREE.Vector3();
562 | var quat = new THREE.Quaternion();
563 | var brickMass = 0.1;
564 | var brickLength = 3;
565 | var brickDepth = 3;
566 | var brickHeight = 1.5;
567 | var numberOfBricksAcross = 6;
568 | var numberOfRowsHigh = 6;
569 |
570 | pos.set(70, brickHeight * 0.5, -60);
571 | quat.set(0, 0, 0, 1);
572 |
573 | for (var j = 0; j < numberOfRowsHigh; j++) {
574 | var oddRow = j % 2 == 1;
575 |
576 | pos.x = 60;
577 |
578 | if (oddRow) {
579 | pos.x += 0.25 * brickLength;
580 | }
581 |
582 | var currentRow = oddRow ? numberOfBricksAcross + 1 : numberOfBricksAcross;
583 | for (let i = 0; i < currentRow; i++) {
584 | var brickLengthCurrent = brickLength;
585 | var brickMassCurrent = brickMass;
586 | if (oddRow && (i == 0 || i == currentRow - 1)) {
587 | //first or last brick
588 | brickLengthCurrent *= 0.5;
589 | brickMassCurrent *= 0.5;
590 | }
591 | var brick = createBrick(
592 | brickLengthCurrent,
593 | brickHeight,
594 | brickDepth,
595 | brickMassCurrent,
596 | pos,
597 | quat,
598 | new THREE.MeshStandardMaterial({
599 | map: loader.load(stoneTexture),
600 | })
601 | );
602 | brick.castShadow = true;
603 | brick.receiveShadow = true;
604 |
605 | if (oddRow && (i == 0 || i == currentRow - 2)) {
606 | //first or last brick
607 | pos.x += brickLength * 0.25;
608 | } else {
609 | pos.x += brickLength;
610 | }
611 | pos.z += 0.0001;
612 | }
613 | pos.y += brickHeight;
614 | }
615 | }
616 |
617 | //helper function to create individual brick mesh
618 | function createBrick(sx, sy, sz, mass, pos, quat, material) {
619 | var threeObject = new THREE.Mesh(new THREE.BoxBufferGeometry(sx, sy, sz, 1, 1, 1), material);
620 | var shape = new Ammo.btBoxShape(new Ammo.btVector3(sx * 0.5, sy * 0.5, sz * 0.5));
621 | shape.setMargin(0.05);
622 |
623 | createBrickBody(threeObject, shape, mass, pos, quat);
624 |
625 | return threeObject;
626 | }
627 |
628 | //add physics to brick body
629 | function createBrickBody(threeObject, physicsShape, mass, pos, quat) {
630 | threeObject.position.copy(pos);
631 | threeObject.quaternion.copy(quat);
632 |
633 | var transform = new Ammo.btTransform();
634 | transform.setIdentity();
635 | transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
636 | transform.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));
637 | var motionState = new Ammo.btDefaultMotionState(transform);
638 |
639 | var localInertia = new Ammo.btVector3(0, 0, 0);
640 | physicsShape.calculateLocalInertia(mass, localInertia);
641 |
642 | var rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, physicsShape, localInertia);
643 | var body = new Ammo.btRigidBody(rbInfo);
644 |
645 | threeObject.userData.physicsBody = body;
646 |
647 | scene.add(threeObject);
648 |
649 | if (mass > 0) {
650 | rigidBodies.push(threeObject);
651 |
652 | // Disable deactivation
653 | body.setActivationState(4);
654 | }
655 |
656 | physicsWorld.addRigidBody(body);
657 | }
658 |
659 | function createTriangle(x, z) {
660 | var geom = new THREE.Geometry();
661 | var v1 = new THREE.Vector3(4, 0, 0);
662 | var v2 = new THREE.Vector3(5, 0, 0);
663 | var v3 = new THREE.Vector3(4.5, 1, 0);
664 |
665 | geom.vertices.push(v1);
666 | geom.vertices.push(v2);
667 | geom.vertices.push(v3);
668 |
669 | geom.faces.push(new THREE.Face3(0, 1, 2));
670 | geom.computeFaceNormals();
671 |
672 | var mesh = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({ color: 0xffffff }));
673 | mesh.rotation.x = -Math.PI * 0.5;
674 | //mesh.rotation.z = -90;
675 | mesh.position.y = 0.01;
676 | mesh.position.x = x;
677 | mesh.position.z = z;
678 | scene.add(mesh);
679 | }
680 |
681 | //generic function to add physics to Mesh with scale
682 | function addRigidPhysics(item, itemScale) {
683 | let pos = { x: item.position.x, y: item.position.y, z: item.position.z };
684 | let scale = { x: itemScale.x, y: itemScale.y, z: itemScale.z };
685 | let quat = { x: 0, y: 0, z: 0, w: 1 };
686 | let mass = 0;
687 | var transform = new Ammo.btTransform();
688 | transform.setIdentity();
689 | transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
690 | transform.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));
691 |
692 | var localInertia = new Ammo.btVector3(0, 0, 0);
693 | var motionState = new Ammo.btDefaultMotionState(transform);
694 | let colShape = new Ammo.btBoxShape(new Ammo.btVector3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
695 | colShape.setMargin(0.05);
696 | colShape.calculateLocalInertia(mass, localInertia);
697 | let rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, colShape, localInertia);
698 | let body = new Ammo.btRigidBody(rbInfo);
699 | body.setActivationState(STATE.DISABLE_DEACTIVATION);
700 | body.setCollisionFlags(2);
701 | physicsWorld.addRigidBody(body);
702 | }
703 |
704 | function moveBall() {
705 | let scalingFactor = 20;
706 | let moveX = moveDirection.right - moveDirection.left;
707 | let moveZ = moveDirection.back - moveDirection.forward;
708 | let moveY = 0;
709 |
710 | if (ballObject.position.y < 2.01) {
711 | moveX = moveDirection.right - moveDirection.left;
712 | moveZ = moveDirection.back - moveDirection.forward;
713 | moveY = 0;
714 | } else {
715 | moveX = moveDirection.right - moveDirection.left;
716 | moveZ = moveDirection.back - moveDirection.forward;
717 | moveY = -0.25;
718 | }
719 |
720 | // no movement
721 | if (moveX == 0 && moveY == 0 && moveZ == 0) return;
722 |
723 | let resultantImpulse = new Ammo.btVector3(moveX, moveY, moveZ);
724 | resultantImpulse.op_mul(scalingFactor);
725 | let physicsBody = ballObject.userData.physicsBody;
726 | physicsBody.setLinearVelocity(resultantImpulse);
727 | }
728 |
729 | function renderFrame() {
730 | // FPS stats module
731 | stats.begin();
732 |
733 | const elapsedTime = galaxyClock.getElapsedTime() + 150;
734 |
735 | let deltaTime = clock.getDelta();
736 | if (!isTouchscreenDevice())
737 | if (document.hasFocus()) {
738 | moveBall();
739 | } else {
740 | moveDirection.forward = 0;
741 | moveDirection.back = 0;
742 | moveDirection.left = 0;
743 | moveDirection.right = 0;
744 | }
745 | else {
746 | moveBall();
747 | }
748 |
749 | updatePhysics(deltaTime);
750 |
751 | moveParticles();
752 |
753 | renderer.render(scene, camera);
754 | stats.end();
755 |
756 | galaxyMaterial.uniforms.uTime.value = elapsedTime * 5;
757 | //galaxyPoints.position.set(-50, -50, 0);
758 |
759 | // tells browser theres animation, update before the next repaint
760 | requestAnimationFrame(renderFrame);
761 | }
762 |
763 | //loading page section
764 | function startButtonEventListener() {
765 | for (let i = 0; i < fadeOutDivs.length; i++) {
766 | fadeOutDivs[i].classList.add('fade-out');
767 | }
768 | setTimeout(() => {
769 | document.getElementById('preload-overlay').style.display = 'none';
770 | }, 750);
771 |
772 | startButton.removeEventListener('click', startButtonEventListener);
773 | document.addEventListener('click', launchClickPosition);
774 | createBeachBall();
775 |
776 | setTimeout(() => {
777 | document.addEventListener('mousemove', launchHover);
778 | }, 1000);
779 | }
780 |
781 | function updatePhysics(deltaTime) {
782 | // Step world
783 | physicsWorld.stepSimulation(deltaTime, 10);
784 |
785 | // Update rigid bodies
786 | for (let i = 0; i < rigidBodies.length; i++) {
787 | let objThree = rigidBodies[i];
788 | let objAmmo = objThree.userData.physicsBody;
789 | let ms = objAmmo.getMotionState();
790 | if (ms) {
791 | ms.getWorldTransform(tmpTrans);
792 | let p = tmpTrans.getOrigin();
793 | let q = tmpTrans.getRotation();
794 | objThree.position.set(p.x(), p.y(), p.z());
795 | objThree.quaternion.set(q.x(), q.y(), q.z(), q.w());
796 | }
797 | }
798 |
799 | //check to see if ball escaped the plane
800 | if (ballObject.position.y < -50) {
801 | scene.remove(ballObject);
802 | createBall();
803 | }
804 |
805 | //check to see if ball is on text to rotate camera
806 | rotateCamera(ballObject);
807 | }
808 |
809 | //document loading
810 | manager.onStart = function (item, loaded, total) {
811 | //console.log("Loading started");
812 | };
813 |
814 | manager.onLoad = function () {
815 | var readyStateCheckInterval = setInterval(function () {
816 | if (document.readyState === 'complete') {
817 | clearInterval(readyStateCheckInterval);
818 | for (let i = 0; i < preloadDivs.length; i++) {
819 | preloadDivs[i].style.visibility = 'hidden'; // or
820 | preloadDivs[i].style.display = 'none';
821 | }
822 | for (let i = 0; i < postloadDivs.length; i++) {
823 | postloadDivs[i].style.visibility = 'visible'; // or
824 | postloadDivs[i].style.display = 'block';
825 | }
826 | }
827 | }, 1000);
828 | //console.log("Loading complete");
829 | };
830 |
831 | manager.onError = function (url) {
832 | //console.log("Error loading");
833 | };
834 |
835 | startButton.addEventListener('click', startButtonEventListener);
836 |
837 | if (isTouchscreenDevice()) {
838 | document.getElementById('appDirections').innerHTML =
839 | 'Use the joystick in the bottom left to move the ball. Please use your device in portrait orientation!';
840 | createJoystick(document.getElementById('joystick-wrapper'));
841 | document.getElementById('joystick-wrapper').style.visibility = 'visible';
842 | document.getElementById('joystick').style.visibility = 'visible';
843 | }
844 |
845 | //initialize world and begin
846 | function start() {
847 | createWorld();
848 | createPhysicsWorld();
849 |
850 | createGridPlane();
851 | createBall();
852 |
853 | createWallX(87.5, 1.75, 0);
854 | createWallX(-87.5, 1.75, 0);
855 | createWallZ(0, 1.75, 87.5);
856 | createWallZ(0, 1.75, -87.5);
857 |
858 | createBillboard(-80, 2.5, -70, billboardTextures.terpSolutionsTexture, URL.terpsolutions, Math.PI * 0.22);
859 |
860 | createBillboard(-45, 2.5, -78, billboardTextures.bagHolderBetsTexture, URL.githubBagholder, Math.PI * 0.17);
861 |
862 | createBillboardRotated(-17, 1.25, -75, billboardTextures.homeSweetHomeTexture, URL.githubHomeSweetHome, Math.PI * 0.15);
863 |
864 | floydWords(16.2, 1, -20);
865 | createTextOnPlane(-70, 0.01, -48, inputText.terpSolutionsText, 20, 40);
866 | createTextOnPlane(-42, 0.01, -53, inputText.bagholderBetsText, 20, 40);
867 | createTextOnPlane(-14, 0.01, -49, inputText.homeSweetHomeText, 20, 40);
868 |
869 | createBox(12, 2, -70, 4, 4, 1, boxTexture.Github, URL.gitHub, 0x000000, true);
870 |
871 | // createBox(
872 | // 4,
873 | // 2,
874 | // -70,
875 | // 4,
876 | // 4,
877 | // 1,
878 | // boxTexture.twitter,
879 | // URL.twitter,
880 | // 0xffffff,
881 | // true
882 | // );
883 |
884 | createBox(19, 2, -70, 4, 4, 1, boxTexture.twitter, URL.twitter, 0x0077b5, true);
885 | // createBox(
886 | // 35,
887 | // 2,
888 | // -70,
889 | // 4,
890 | // 4,
891 | // 1,
892 | // boxTexture.globe,
893 | // 0xffffff,
894 | // false
895 | // );
896 |
897 | createBox(27, 2, -70, 4, 4, 1, boxTexture.mail, 'mailto:arfloyd7@gmail.com', 0x000000, false);
898 |
899 | // createBox(
900 | // 44,
901 | // 2,
902 | // -70,
903 | // 4,
904 | // 4,
905 | // 1,
906 | // boxTexture.writing,
907 | // URL.devTo,
908 | // 0x000000,
909 | // false
910 | // );
911 |
912 | createBox(35, 2, -70, 4, 4, 1, boxTexture.writing, URL.devTo, 0x000000, false);
913 |
914 | // floatingLabel(3.875, 4.5, -70, 'Twitter');
915 | floatingLabel(11.875, 4.5, -70, 'Github');
916 | floatingLabel(19.125, 4.5, -70, 'Twitter');
917 | floatingLabel(26.875, 4.5, -70, 'Email');
918 | // floatingLabel(35, 6.5, -70, ' Static \nWebsite');
919 | floatingLabel(35, 6.5, -70, ' How I \nmade this');
920 | // floatingLabel(44, 6.5, -70, ' How I \nmade this');
921 |
922 | // allSkillsSection(-50, 0.025, 20, 40, 40, boxTexture.allSkills);
923 | allSkillsSection(61, 0.025, 13, 30, 60, inputText.activities);
924 |
925 | //lensflare
926 | createLensFlare(50, -50, -800, 200, 200, boxTexture.lensFlareMain);
927 |
928 | loadFloydText();
929 | loadEngineerText();
930 |
931 | let touchText, instructionsText;
932 | if (isTouchscreenDevice()) {
933 | touchText = 'Touch boxes with your \nfinger to open links';
934 | instructionsText = ' Use the joystick in the bottom \nleft of the screen to move the ball.';
935 | } else {
936 | touchText = 'Click on boxes with \nthe mouse to open links';
937 | instructionsText = 'Use the arrow keys on your \n keyboard to move the ball.';
938 | }
939 |
940 | simpleText(9, 0.01, 5, instructionsText, 1.25);
941 |
942 | simpleText(23, 0.01, -60, touchText, 1.5);
943 | // simpleText(-50, 0.01, -5, 'SKILLS', 3);
944 | simpleText(-42, 0.01, -30, 'PROJECTS', 3);
945 | simpleText(61, 0.01, -15, 'TIMELINE', 3);
946 |
947 | wallOfBricks();
948 | createTriangle(63, -55);
949 | createTriangle(63, -51);
950 | createTriangle(63, -47);
951 | createTriangle(63, -43);
952 |
953 | addParticles();
954 | glowingParticles();
955 | generateGalaxy();
956 |
957 | setupEventHandlers();
958 | // window.addEventListener('mousemove', onDocumentMouseMove, false);
959 | renderFrame();
960 | }
961 |
962 | //check if user's browser has WebGL capabilities
963 | if (WEBGL.isWebGLAvailable()) {
964 | start();
965 | } else {
966 | noWebGL();
967 | }
968 | });
969 |
--------------------------------------------------------------------------------