├── .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 | ![alt text](/portfolio_2020.gif) 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 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | Portfolio 2020 35 | 36 | 37 | 38 | 40 | 41 |
42 |
43 |
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 |
50 |
51 |
52 |
53 |
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 | --------------------------------------------------------------------------------