first-comit

This commit is contained in:
NICE CODE BY DEV 2024-01-21 11:21:47 +08:00
commit 026312520f
386 changed files with 28032 additions and 0 deletions

6
.babelrc Normal file
View File

@ -0,0 +1,6 @@
{
"presets":
[
"@babel/preset-env"
]
}

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

79
.eslintrc.json Normal file
View File

@ -0,0 +1,79 @@
{
"env":
{
"browser": true,
"es6": true
},
"extends": ["eslint:recommended"],
"parserOptions":
{
"sourceType": "module",
"ecmaFeatures":{
"experimentalObjectRestSpread": true
}
},
"globals":
{
},
"rules":
{
"no-unused-vars": 1,
"no-console": 0,
"dot-notation": 1,
"eqeqeq": 1,
"no-else-return": 0,
"no-new-func": 1,
"no-param-reassign": [1, { "props": false }],
"no-useless-concat": 1,
"no-useless-escape": 1,
"radix": [1, "as-needed"],
"no-undef": 2,
"array-bracket-spacing": [1, "never"],
"brace-style": [1, "allman"],
"camelcase": [1, { "properties": "never" }],
"comma-dangle": [1, "never"],
"comma-style": [1, "last"],
"func-style": [1, "expression"],
"id-length": 0,
"indent": [1, 4, { "SwitchCase": 1 }],
"keyword-spacing": [1, { "after": false, "before": true, "overrides": { "from": { "after": true }, "return": { "after": true }, "import": { "after": true }, "case": { "after": true } } }],
"max-len": 0,
"new-cap": [1, { "newIsCap": true, "newIsCapExceptions": [], "capIsNew": false, "capIsNewExceptions": ["Immutable.Map", "Immutable.Set", "Immutable.List"] }],
"no-array-constructor": 1,
"no-bitwise": 0,
"no-mixed-operators": 0,
"no-nested-ternary": 0,
"no-new-object": 1,
"no-plusplus": 0,
"no-restricted-syntax": 0,
"no-trailing-spaces": 1,
"no-underscore-dangle": 0,
"no-unneeded-ternary": 1,
"no-whitespace-before-property": 1,
"object-curly-spacing": [1, "always"],
"one-var": [1, "never"],
"padded-blocks": [1, "never"],
"quote-props": [1, "as-needed"],
"quotes": [1, "single"],
"semi": [1, "never"],
"space-before-blocks": [1, "always"],
"space-before-function-paren": [1, { "anonymous": "never", "named": "never", "asyncArrow": "never" }],
"space-in-parens": [1, "never"],
"space-infix-ops": 1,
"spaced-comment": [1, "always"],
"arrow-body-style": 0,
"arrow-parens": [1, "always"],
"arrow-spacing": [1, { "before": true, "after": true }],
"no-confusing-arrow": 0,
"no-dupe-class-members": 1,
"no-duplicate-imports": 0,
"no-useless-constructor": 1,
"no-var": 1,
"object-shorthand": 0,
"prefer-const": 1,
"prefer-rest-params": 1,
"prefer-spread": 1,
"prefer-template": 0,
"template-curly-spacing": [1, "never"]
}
}

146
.gitignore vendored Normal file
View File

@ -0,0 +1,146 @@
# Project build
dist/
# Parcel cache
.cache
# Created by https://www.gitignore.io/api/vim,node,macos,visualstudiocode
# Edit at https://www.gitignore.io/?templates=vim,node,macos,visualstudiocode
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# FuseBox cache
.fusebox/
### Vim ###
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
# End of https://www.gitignore.io/api/vim,node,macos,visualstudiocode

107
bundler/webpack.common.js Normal file
View File

@ -0,0 +1,107 @@
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
module.exports = {
entry: path.resolve(__dirname, '../src/index.js'),
output:
{
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, '../dist')
},
devtool: 'source-map',
plugins:
[
new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../static') } ]),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/index.html'),
minify: true
})
],
module:
{
rules:
[
// HTML
{
test: /\.(html)$/,
use: ['html-loader']
},
// JS
{
test: /\.js$/,
exclude: /node_modules/,
use:
[
'babel-loader'
]
},
// CSS
{
test: /\.css$/,
use:
[
'style-loader',
'css-loader'
]
},
// Images
{
test: /\.(jpg|png|gif|svg)$/,
use:
[
{
loader: 'file-loader',
options:
{
outputPath: 'assets/images/'
}
}
]
},
// Models
{
test: /\.(glb|gltf|fbx|obj)$/,
use:
[
{
loader: 'file-loader',
options:
{
outputPath: 'assets/models/'
}
}
]
},
// MP3
{
test: /\.(mp3)$/,
use:
[
{
loader: 'file-loader',
options:
{
outputPath: 'assets/audios/'
}
}
]
},
// Shaders
{
test: /\.(glsl|vs|fs|vert|frag)$/,
exclude: /node_modules/,
use: [
'raw-loader',
'glslify-loader'
]
}
]
}
}

14
bundler/webpack.dev.js Normal file
View File

@ -0,0 +1,14 @@
const webpackMerge = require('webpack-merge')
const commonConfiguration = require('./webpack.common.js')
module.exports = webpackMerge(
commonConfiguration,
{
mode: 'development',
devServer:
{
contentBase: './dist',
open: true
}
}
)

14
bundler/webpack.prod.js Normal file
View File

@ -0,0 +1,14 @@
const webpackMerge = require('webpack-merge')
const commonConfiguration = require('./webpack.common.js')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = webpackMerge(
commonConfiguration,
{
mode: 'production',
plugins:
[
new CleanWebpackPlugin()
]
}
)

21
license.md Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Bruno SIMON
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

BIN
model.blend Normal file

Binary file not shown.

17251
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

48
package.json Normal file
View File

@ -0,0 +1,48 @@
{
"name": "three.js-template",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --config ./bundler/webpack.prod.js",
"dev": "webpack-dev-server --config ./bundler/webpack.dev.js",
"watch": "webpack --watch --config ./bundler/webpack.config.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/brunosimon/three.js-template.git"
},
"author": "brunosimon",
"license": "MIT",
"bugs": {
"url": "https://github.com/brunosimon/three.js-template/issues"
},
"homepage": "https://github.com/brunosimon/three.js-template#readme",
"devDependencies": {
"@babel/core": "^7.7.7",
"@babel/preset-env": "^7.7.7",
"babel-loader": "^8.0.6",
"cannon": "^0.6.2",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.4.0",
"dat.gui": "^0.7.3",
"file-loader": "^5.0.2",
"glslify-loader": "^2.0.0",
"gsap": "^2.1.3",
"howler": "^2.1.2",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"raw-loader": "^4.0.0",
"style-loader": "^1.1.2",
"three": "^0.131.3",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1",
"webpack-merge": "^4.2.2"
},
"staticFiles": {
"staticPath": "static",
"watcherGlob": "**"
}
}

23
readme.md Normal file
View File

@ -0,0 +1,23 @@
# Folio 2019
## Setup
Download [Node.js](https://nodejs.org/en/download/).
Run this followed commands:
``` bash
# Just be sure that you've got parcel js on you system
npm install -g parcel-bundler
# Install dependencies (only for first time)
npm i
# Serve at localhost:1234
npm run dev
# Build for production in the dist/ directory
npm run build
```
```
🥚 2021eggpvlzscw
```

BIN
render.blend Normal file

Binary file not shown.

BIN
resources/3d/model.blend Normal file

Binary file not shown.

BIN
resources/3d/model.blend1 Normal file

Binary file not shown.

Binary file not shown.

BIN
resources/3d/render.blend Normal file

Binary file not shown.

BIN
resources/3d/render.blend1 Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

BIN
src/favicon/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<svg version="1" xmlns="http://www.w3.org/2000/svg" width="346.667" height="346.667" viewBox="0 0 260.000000 260.000000"><path d="M176.3 32.1c-19.4 3.1-33.5 16.4-37.3 35.4-1.7 8.1-.7 24.3 1.9 31.7 4.4 12.8 8.8 18 34.1 40.1 25.8 22.7 30.8 29.9 31.1 45.4.3 11.1-4.7 17.3-14.5 18.1-4.6.4-6.2 0-10-2.2-2.4-1.4-5.3-4.1-6.5-5.9-2.6-4.2-6-16.8-6.2-23.2-.1-2.8-.4-5.3-.5-5.6-.3-.5-11.9 1.5-26.9 4.5-1.1.3-2.4.5-2.9.5-.5.1-.6 3.3-.3 7.3 3 35.8 21.7 53.4 55.7 52.3 23.5-.7 40.4-14.3 45.4-36.5 2.6-11.8.5-28.3-5.1-39.5-5.7-11.2-13.1-19.6-32.3-36.5-21.7-19-24.7-22.2-27.9-29-4.7-10.4-2.9-23.3 4.2-28.4 4.2-3.1 15-3 19.4.2 4 2.9 7.1 10.8 7.9 20.5.3 3.9.7 7.4 1 7.8.2.4 2.1.3 4.1-.1 2.1-.5 5.6-1.1 7.8-1.4 2.3-.4 5.2-.9 6.5-1.2 1.4-.2 4.7-.7 7.3-1.1l4.9-.6-.7-6.6c-.4-3.6-1.8-10.1-3.1-14.4-5.5-17.2-13.7-26.2-27.8-30.2-6.7-1.9-21.4-2.6-29.3-1.4zM20.6 34c-.1.3-.2 43.9-.2 97l.1 96.5H55c36.1 0 38.6-.2 47.1-3.5 16-6.3 24.6-18.6 28.4-40.5.3-1.7.5-7.5.5-13 0-16.2-4.4-28.9-12.8-37.1-3.9-3.8-10.7-7.6-15.7-8.8l-3-.8 5.2-2.2c15.3-6.5 21.9-20.2 20.7-42.9C124 52.5 110.9 38.5 84 34.4c-5.4-.8-63-1.2-63.4-.4zm55 25.5c5.2.7 11 3.3 13.6 6.2 4.6 5.1 6.6 17.4 4.6 28.5-2.5 13.8-8.2 17.7-26.5 18.6l-11.3.5v-27c0-14.8.2-27.2.5-27.4.6-.7 13.1-.3 19.1.6zm3.8 79.6c8.1 1.9 13.5 7.9 16.1 18 1.8 6.8 1.8 21.4 0 28.4-3.4 12.8-9.5 16.6-27.7 17.3l-11.8.4v-32.5c0-29.4.2-32.6 1.6-32.9 3.4-.6 17 .2 21.8 1.3z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,19 @@
{
"name": "Bruno Simon",
"short_name": "Bruno Simon",
"icons": [
{
"src": "/favicon/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/favicon/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

BIN
src/images/boyHiArm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
src/images/boyHiBody.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
src/images/boyShrugging.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
src/images/boyYay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

4
src/images/bubbleTip.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="12" height="15" viewBox="0 0 12 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 7.5L1 1V14L10.5 7.5Z" fill="#f8c684"/>
<path d="M10.5 7.5L11.0647 8.32531C11.3371 8.13891 11.5 7.83009 11.5 7.5C11.5 7.16991 11.3371 6.86109 11.0647 6.67469L10.5 7.5ZM0.435316 1.82531L9.93532 8.32531L11.0647 6.67469L1.56468 0.174693L0.435316 1.82531ZM9.93532 6.67469L0.435316 13.1747L1.56468 14.8253L11.0647 8.32531L9.93532 6.67469Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 467 B

BIN
src/images/mobile/cross.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

96
src/index.html Normal file
View File

@ -0,0 +1,96 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bruno Simon</title>
<meta name="description" content="Creative developer living in Paris, freelancer, former lead developer at Immersive Garden, former developer at Uzik and teacher." />
<meta itemprop=name content="Bruno Simon - Creative developer">
<meta itemprop="description" content="Creative developer living in Paris, freelancer, former lead developer at Immersive Garden, former developer at Uzik and teacher.">
<meta itemprop="image" content=https://bruno-simon.com/social/share-1200x630.png>
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Bruno Simon - Creative developer">
<meta name="twitter:description" content="Creative developer living in Paris, freelancer, former lead developer at Immersive Garden, former developer at Uzik and teacher.">
<meta name="twitter:image" content="https://bruno-simon.com/social/share-1200x600.png">
<meta property="og:site_name" content="Bruno Simon - Creative developer">
<meta property="og:type" content="website">
<meta property="og:url" content="https://bruno-simon.com">
<meta property="og:title" content="Bruno Simon - Creative developer">
<meta property="og:description" content="Creative developer living in Paris, freelancer, former lead developer at Immersive Garden, former developer at Uzik and teacher.">
<meta property="og:image" content="https://bruno-simon.com/social/share-1200x630.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
<link rel="manifest" href="/favicon/site.webmanifest">
<link rel="mask-icon" href="/favicon/safari-pinned-tab.svg" color="#ff8908">
<meta name="apple-mobile-web-app-title" content="Bruno Simon">
<meta name="application-name" content="Bruno Simon">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&display=block" rel="stylesheet">
</head>
<body>
<canvas class="canvas js-canvas"></canvas>
<div class="threejs-journey is-hover-none js-threejs-journey">
<div class="message js-message">
<div class="boy">
<div class="variant is-hi">
<div class="body"></div>
<div class="arm js-boy-arm"></div>
</div>
<div class="variant is-yay"></div>
<div class="variant is-shrugging"></div>
</div>
<div class="bubble">
<div class="text">Hey! You seem to really enjoy my portfolio.</div>
<div class="tip"></div>
</div>
</div>
<div class="message js-message">
<div class="bubble">
<div class="text">Would you like to learn how to create cool websites like this?</div>
<div class="tip"></div>
</div>
</div>
<div class="message js-message is-answers">
<a href="#" class="answer is-no js-no">
<span class="background"></span>
<span class="hover"></span>
<span class="label">Nah, I'm good</span>
</a>
<a href="https://threejs-journey.com?c=p1" target="_blank" rel="noopener" class="answer is-yes js-yes">
<span class="background"></span>
<span class="hover"></span>
<span class="label">Yes, teach me!</span>
</a>
</div>
<div class="message js-message">
<div class="bubble">
<div class="text">Alright then.<br>Have fun and try not to break my car!</div>
<div class="tip"></div>
</div>
</div>
</div>
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-3966601-5"></script>
<script>
window.dataLayer = window.dataLayer || []
function gtag(){dataLayer.push(arguments)}
gtag('js', new Date())
gtag('config', 'UA-3966601-5')
</script>
</body>
</html>

7
src/index.js Normal file
View File

@ -0,0 +1,7 @@
import './style/main.css'
import Application from './javascript/Application.js'
window.application = new Application({
$canvas: document.querySelector('.js-canvas'),
useComposer: true
})

View File

@ -0,0 +1,294 @@
import * as THREE from 'three'
import * as dat from 'dat.gui'
import Sizes from './Utils/Sizes.js'
import Time from './Utils/Time.js'
import World from './World/index.js'
import Resources from './Resources.js'
import Camera from './Camera.js'
import ThreejsJourney from './ThreejsJourney.js'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
import BlurPass from './Passes/Blur.js'
import GlowsPass from './Passes/Glows.js'
export default class Application
{
/**
* Constructor
*/
constructor(_options)
{
// Options
this.$canvas = _options.$canvas
// Set up
this.time = new Time()
this.sizes = new Sizes()
this.resources = new Resources()
this.setConfig()
this.setDebug()
this.setRenderer()
this.setCamera()
this.setPasses()
this.setWorld()
this.setTitle()
this.setThreejsJourney()
}
/**
* Set config
*/
setConfig()
{
this.config = {}
this.config.debug = window.location.hash === '#debug'
this.config.cyberTruck = window.location.hash === '#cybertruck'
this.config.touch = false
window.addEventListener('touchstart', () =>
{
this.config.touch = true
this.world.controls.setTouch()
this.passes.horizontalBlurPass.strength = 1
this.passes.horizontalBlurPass.material.uniforms.uStrength.value = new THREE.Vector2(this.passes.horizontalBlurPass.strength, 0)
this.passes.verticalBlurPass.strength = 1
this.passes.verticalBlurPass.material.uniforms.uStrength.value = new THREE.Vector2(0, this.passes.verticalBlurPass.strength)
}, { once: true })
}
/**
* Set debug
*/
setDebug()
{
if(this.config.debug)
{
this.debug = new dat.GUI({ width: 420 })
}
}
/**
* Set renderer
*/
setRenderer()
{
// Scene
this.scene = new THREE.Scene()
// Renderer
this.renderer = new THREE.WebGLRenderer({
canvas: this.$canvas,
alpha: true
})
// this.renderer.setClearColor(0x414141, 1)
this.renderer.setClearColor(0x000000, 1)
// this.renderer.setPixelRatio(Math.min(Math.max(window.devicePixelRatio, 1.5), 2))
this.renderer.setPixelRatio(2)
this.renderer.setSize(this.sizes.viewport.width, this.sizes.viewport.height)
this.renderer.physicallyCorrectLights = true
this.renderer.gammaFactor = 2.2
this.renderer.gammaOutPut = true
this.renderer.autoClear = false
// Resize event
this.sizes.on('resize', () =>
{
this.renderer.setSize(this.sizes.viewport.width, this.sizes.viewport.height)
})
}
/**
* Set camera
*/
setCamera()
{
this.camera = new Camera({
time: this.time,
sizes: this.sizes,
renderer: this.renderer,
debug: this.debug,
config: this.config
})
this.scene.add(this.camera.container)
this.time.on('tick', () =>
{
if(this.world && this.world.car)
{
this.camera.target.x = this.world.car.chassis.object.position.x
this.camera.target.y = this.world.car.chassis.object.position.y
}
})
}
setPasses()
{
this.passes = {}
// Debug
if(this.debug)
{
this.passes.debugFolder = this.debug.addFolder('postprocess')
// this.passes.debugFolder.open()
}
this.passes.composer = new EffectComposer(this.renderer)
// Create passes
this.passes.renderPass = new RenderPass(this.scene, this.camera.instance)
this.passes.horizontalBlurPass = new ShaderPass(BlurPass)
this.passes.horizontalBlurPass.strength = this.config.touch ? 0 : 1
this.passes.horizontalBlurPass.material.uniforms.uResolution.value = new THREE.Vector2(this.sizes.viewport.width, this.sizes.viewport.height)
this.passes.horizontalBlurPass.material.uniforms.uStrength.value = new THREE.Vector2(this.passes.horizontalBlurPass.strength, 0)
this.passes.verticalBlurPass = new ShaderPass(BlurPass)
this.passes.verticalBlurPass.strength = this.config.touch ? 0 : 1
this.passes.verticalBlurPass.material.uniforms.uResolution.value = new THREE.Vector2(this.sizes.viewport.width, this.sizes.viewport.height)
this.passes.verticalBlurPass.material.uniforms.uStrength.value = new THREE.Vector2(0, this.passes.verticalBlurPass.strength)
// Debug
if(this.debug)
{
const folder = this.passes.debugFolder.addFolder('blur')
folder.open()
folder.add(this.passes.horizontalBlurPass.material.uniforms.uStrength.value, 'x').step(0.001).min(0).max(10)
folder.add(this.passes.verticalBlurPass.material.uniforms.uStrength.value, 'y').step(0.001).min(0).max(10)
}
this.passes.glowsPass = new ShaderPass(GlowsPass)
this.passes.glowsPass.color = '#ffcfe0'
this.passes.glowsPass.material.uniforms.uPosition.value = new THREE.Vector2(0, 0.25)
this.passes.glowsPass.material.uniforms.uRadius.value = 0.7
this.passes.glowsPass.material.uniforms.uColor.value = new THREE.Color(this.passes.glowsPass.color)
this.passes.glowsPass.material.uniforms.uAlpha.value = 0.55
// Debug
if(this.debug)
{
const folder = this.passes.debugFolder.addFolder('glows')
folder.open()
folder.add(this.passes.glowsPass.material.uniforms.uPosition.value, 'x').step(0.001).min(- 1).max(2).name('positionX')
folder.add(this.passes.glowsPass.material.uniforms.uPosition.value, 'y').step(0.001).min(- 1).max(2).name('positionY')
folder.add(this.passes.glowsPass.material.uniforms.uRadius, 'value').step(0.001).min(0).max(2).name('radius')
folder.addColor(this.passes.glowsPass, 'color').name('color').onChange(() =>
{
this.passes.glowsPass.material.uniforms.uColor.value = new THREE.Color(this.passes.glowsPass.color)
})
folder.add(this.passes.glowsPass.material.uniforms.uAlpha, 'value').step(0.001).min(0).max(1).name('alpha')
}
// Add passes
this.passes.composer.addPass(this.passes.renderPass)
this.passes.composer.addPass(this.passes.horizontalBlurPass)
this.passes.composer.addPass(this.passes.verticalBlurPass)
this.passes.composer.addPass(this.passes.glowsPass)
// Time tick
this.time.on('tick', () =>
{
this.passes.horizontalBlurPass.enabled = this.passes.horizontalBlurPass.material.uniforms.uStrength.value.x > 0
this.passes.verticalBlurPass.enabled = this.passes.verticalBlurPass.material.uniforms.uStrength.value.y > 0
// Renderer
this.passes.composer.render()
// this.renderer.domElement.style.background = 'black'
// this.renderer.render(this.scene, this.camera.instance)
})
// Resize event
this.sizes.on('resize', () =>
{
this.renderer.setSize(this.sizes.viewport.width, this.sizes.viewport.height)
this.passes.composer.setSize(this.sizes.viewport.width, this.sizes.viewport.height)
this.passes.horizontalBlurPass.material.uniforms.uResolution.value.x = this.sizes.viewport.width
this.passes.horizontalBlurPass.material.uniforms.uResolution.value.y = this.sizes.viewport.height
this.passes.verticalBlurPass.material.uniforms.uResolution.value.x = this.sizes.viewport.width
this.passes.verticalBlurPass.material.uniforms.uResolution.value.y = this.sizes.viewport.height
})
}
/**
* Set world
*/
setWorld()
{
this.world = new World({
config: this.config,
debug: this.debug,
resources: this.resources,
time: this.time,
sizes: this.sizes,
camera: this.camera,
renderer: this.renderer,
passes: this.passes
})
this.scene.add(this.world.container)
}
/**
* Set title
*/
setTitle()
{
this.title = {}
this.title.frequency = 300
this.title.width = 20
this.title.position = 0
this.title.$element = document.querySelector('title')
this.title.absolutePosition = Math.round(this.title.width * 0.25)
this.time.on('tick', () =>
{
if(this.world.physics)
{
this.title.absolutePosition += this.world.physics.car.forwardSpeed
if(this.title.absolutePosition < 0)
{
this.title.absolutePosition = 0
}
}
})
window.setInterval(() =>
{
this.title.position = Math.round(this.title.absolutePosition % this.title.width)
document.title = `${'_'.repeat(this.title.width - this.title.position)}🚗${'_'.repeat(this.title.position)}`
}, this.title.frequency)
}
/**
* Set Three.js Journey
*/
setThreejsJourney()
{
this.threejsJourney = new ThreejsJourney({
config: this.config,
time: this.time,
world: this.world
})
}
/**
* Destructor
*/
destructor()
{
this.time.off('tick')
this.sizes.off('resize')
this.camera.orbitControls.dispose()
this.renderer.dispose()
this.debug.destroy()
}
}

348
src/javascript/Camera.js Normal file
View File

@ -0,0 +1,348 @@
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { TweenLite } from 'gsap/TweenLite'
import { Power1 } from 'gsap/EasePack'
export default class Camera
{
constructor(_options)
{
// Options
this.time = _options.time
this.sizes = _options.sizes
this.renderer = _options.renderer
this.debug = _options.debug
this.config = _options.config
// Set up
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.target = new THREE.Vector3(0, 0, 0)
this.targetEased = new THREE.Vector3(0, 0, 0)
this.easing = 0.15
// Debug
if(this.debug)
{
this.debugFolder = this.debug.addFolder('camera')
// this.debugFolder.open()
}
this.setAngle()
this.setInstance()
this.setZoom()
this.setPan()
this.setOrbitControls()
}
setAngle()
{
// Set up
this.angle = {}
// Items
this.angle.items = {
default: new THREE.Vector3(1.135, - 1.45, 1.15),
projects: new THREE.Vector3(0.38, - 1.4, 1.63)
}
// Value
this.angle.value = new THREE.Vector3()
this.angle.value.copy(this.angle.items.default)
// Set method
this.angle.set = (_name) =>
{
const angle = this.angle.items[_name]
if(typeof angle !== 'undefined')
{
TweenLite.to(this.angle.value, 2, { ...angle, ease: Power1.easeInOut })
}
}
// Debug
if(this.debug)
{
this.debugFolder.add(this, 'easing').step(0.0001).min(0).max(1).name('easing')
this.debugFolder.add(this.angle.value, 'x').step(0.001).min(- 2).max(2).name('invertDirectionX').listen()
this.debugFolder.add(this.angle.value, 'y').step(0.001).min(- 2).max(2).name('invertDirectionY').listen()
this.debugFolder.add(this.angle.value, 'z').step(0.001).min(- 2).max(2).name('invertDirectionZ').listen()
}
}
setInstance()
{
// Set up
this.instance = new THREE.PerspectiveCamera(40, this.sizes.viewport.width / this.sizes.viewport.height, 1, 80)
this.instance.up.set(0, 0, 1)
this.instance.position.copy(this.angle.value)
this.instance.lookAt(new THREE.Vector3())
this.container.add(this.instance)
// Resize event
this.sizes.on('resize', () =>
{
this.instance.aspect = this.sizes.viewport.width / this.sizes.viewport.height
this.instance.updateProjectionMatrix()
})
// Time tick
this.time.on('tick', () =>
{
if(!this.orbitControls.enabled)
{
this.targetEased.x += (this.target.x - this.targetEased.x) * this.easing
this.targetEased.y += (this.target.y - this.targetEased.y) * this.easing
this.targetEased.z += (this.target.z - this.targetEased.z) * this.easing
// Apply zoom
this.instance.position.copy(this.targetEased).add(this.angle.value.clone().normalize().multiplyScalar(this.zoom.distance))
// Look at target
this.instance.lookAt(this.targetEased)
// Apply pan
this.instance.position.x += this.pan.value.x
this.instance.position.y += this.pan.value.y
}
})
}
setZoom()
{
// Set up
this.zoom = {}
this.zoom.easing = 0.1
this.zoom.minDistance = 14
this.zoom.amplitude = 15
this.zoom.value = this.config.cyberTruck ? 0.3 : 0.5
this.zoom.targetValue = this.zoom.value
this.zoom.distance = this.zoom.minDistance + this.zoom.amplitude * this.zoom.value
// Listen to mousewheel event
document.addEventListener('mousewheel', (_event) =>
{
this.zoom.targetValue += _event.deltaY * 0.001
this.zoom.targetValue = Math.min(Math.max(this.zoom.targetValue, 0), 1)
}, { passive: true })
// Touch
this.zoom.touch = {}
this.zoom.touch.startDistance = 0
this.zoom.touch.startValue = 0
this.renderer.domElement.addEventListener('touchstart', (_event) =>
{
if(_event.touches.length === 2)
{
this.zoom.touch.startDistance = Math.hypot(_event.touches[0].clientX - _event.touches[1].clientX, _event.touches[0].clientX - _event.touches[1].clientX)
this.zoom.touch.startValue = this.zoom.targetValue
}
})
this.renderer.domElement.addEventListener('touchmove', (_event) =>
{
if(_event.touches.length === 2)
{
_event.preventDefault()
const distance = Math.hypot(_event.touches[0].clientX - _event.touches[1].clientX, _event.touches[0].clientX - _event.touches[1].clientX)
const ratio = distance / this.zoom.touch.startDistance
this.zoom.targetValue = this.zoom.touch.startValue - (ratio - 1)
this.zoom.targetValue = Math.min(Math.max(this.zoom.targetValue, 0), 1)
}
})
// Time tick event
this.time.on('tick', () =>
{
this.zoom.value += (this.zoom.targetValue - this.zoom.value) * this.zoom.easing
this.zoom.distance = this.zoom.minDistance + this.zoom.amplitude * this.zoom.value
})
}
setPan()
{
// Set up
this.pan = {}
this.pan.enabled = false
this.pan.active = false
this.pan.easing = 0.1
this.pan.start = {}
this.pan.start.x = 0
this.pan.start.y = 0
this.pan.value = {}
this.pan.value.x = 0
this.pan.value.y = 0
this.pan.targetValue = {}
this.pan.targetValue.x = this.pan.value.x
this.pan.targetValue.y = this.pan.value.y
this.pan.raycaster = new THREE.Raycaster()
this.pan.mouse = new THREE.Vector2()
this.pan.needsUpdate = false
this.pan.hitMesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(500, 500, 1, 1),
new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true, visible: false })
)
this.container.add(this.pan.hitMesh)
this.pan.reset = () =>
{
this.pan.targetValue.x = 0
this.pan.targetValue.y = 0
}
this.pan.enable = () =>
{
this.pan.enabled = true
// Update cursor
this.renderer.domElement.classList.add('has-cursor-grab')
}
this.pan.disable = () =>
{
this.pan.enabled = false
// Update cursor
this.renderer.domElement.classList.remove('has-cursor-grab')
}
this.pan.down = (_x, _y) =>
{
if(!this.pan.enabled)
{
return
}
// Update cursor
this.renderer.domElement.classList.add('has-cursor-grabbing')
// Activate
this.pan.active = true
// Update mouse position
this.pan.mouse.x = (_x / this.sizes.viewport.width) * 2 - 1
this.pan.mouse.y = - (_y / this.sizes.viewport.height) * 2 + 1
// Get start position
this.pan.raycaster.setFromCamera(this.pan.mouse, this.instance)
const intersects = this.pan.raycaster.intersectObjects([this.pan.hitMesh])
if(intersects.length)
{
this.pan.start.x = intersects[0].point.x
this.pan.start.y = intersects[0].point.y
}
}
this.pan.move = (_x, _y) =>
{
if(!this.pan.enabled)
{
return
}
if(!this.pan.active)
{
return
}
this.pan.mouse.x = (_x / this.sizes.viewport.width) * 2 - 1
this.pan.mouse.y = - (_y / this.sizes.viewport.height) * 2 + 1
this.pan.needsUpdate = true
}
this.pan.up = () =>
{
// Deactivate
this.pan.active = false
// Update cursor
this.renderer.domElement.classList.remove('has-cursor-grabbing')
}
// Mouse
window.addEventListener('mousedown', (_event) =>
{
this.pan.down(_event.clientX, _event.clientY)
})
window.addEventListener('mousemove', (_event) =>
{
this.pan.move(_event.clientX, _event.clientY)
})
window.addEventListener('mouseup', () =>
{
this.pan.up()
})
// Touch
this.renderer.domElement.addEventListener('touchstart', (_event) =>
{
if(_event.touches.length === 1)
{
this.pan.down(_event.touches[0].clientX, _event.touches[0].clientY)
}
})
this.renderer.domElement.addEventListener('touchmove', (_event) =>
{
if(_event.touches.length === 1)
{
this.pan.move(_event.touches[0].clientX, _event.touches[0].clientY)
}
})
this.renderer.domElement.addEventListener('touchend', () =>
{
this.pan.up()
})
// Time tick event
this.time.on('tick', () =>
{
// If active
if(this.pan.active && this.pan.needsUpdate)
{
// Update target value
this.pan.raycaster.setFromCamera(this.pan.mouse, this.instance)
const intersects = this.pan.raycaster.intersectObjects([this.pan.hitMesh])
if(intersects.length)
{
this.pan.targetValue.x = - (intersects[0].point.x - this.pan.start.x)
this.pan.targetValue.y = - (intersects[0].point.y - this.pan.start.y)
}
// Update needsUpdate
this.pan.needsUpdate = false
}
// Update value and apply easing
this.pan.value.x += (this.pan.targetValue.x - this.pan.value.x) * this.pan.easing
this.pan.value.y += (this.pan.targetValue.y - this.pan.value.y) * this.pan.easing
})
}
setOrbitControls()
{
// Set up
this.orbitControls = new OrbitControls(this.instance, this.renderer.domElement)
this.orbitControls.enabled = false
this.orbitControls.enableKeys = false
this.orbitControls.zoomSpeed = 0.5
// Debug
if(this.debug)
{
this.debugFolder.add(this.orbitControls, 'enabled').name('orbitControlsEnabled')
}
}
}

View File

@ -0,0 +1,129 @@
import * as THREE from 'three'
// AreaFenceBufferGeometry
class AreaFenceBufferGeometry
{
constructor(_width, _height, _depth,)
{
// Parameters
this.parameters = {
width: _width,
height: _height,
depth: _depth
}
// Set up
this.type = 'AreaFloorBufferGeometry'
// buffers
const length = 8
const vertices = new Float32Array(length * 3)
const uvs = new Uint32Array(length * 2)
const indices = new Uint32Array(length * 6)
// Vertices
vertices[0 * 3 + 0] = _width * 0.5
vertices[0 * 3 + 1] = _height * 0.5
vertices[0 * 3 + 2] = 0
vertices[1 * 3 + 0] = _width * 0.5
vertices[1 * 3 + 1] = - _height * 0.5
vertices[1 * 3 + 2] = 0
vertices[2 * 3 + 0] = - _width * 0.5
vertices[2 * 3 + 1] = - _height * 0.5
vertices[2 * 3 + 2] = 0
vertices[3 * 3 + 0] = - _width * 0.5
vertices[3 * 3 + 1] = _height * 0.5
vertices[3 * 3 + 2] = 0
vertices[4 * 3 + 0] = _width * 0.5
vertices[4 * 3 + 1] = _height * 0.5
vertices[4 * 3 + 2] = _depth
vertices[5 * 3 + 0] = _width * 0.5
vertices[5 * 3 + 1] = - _height * 0.5
vertices[5 * 3 + 2] = _depth
vertices[6 * 3 + 0] = - _width * 0.5
vertices[6 * 3 + 1] = - _height * 0.5
vertices[6 * 3 + 2] = _depth
vertices[7 * 3 + 0] = - _width * 0.5
vertices[7 * 3 + 1] = _height * 0.5
vertices[7 * 3 + 2] = _depth
// Uvs
uvs[0 * 2 + 0] = 0
uvs[0 * 2 + 1] = 0
uvs[1 * 2 + 0] = 1 / 3
uvs[1 * 2 + 1] = 0
uvs[2 * 2 + 0] = 1 / 3 * 2
uvs[2 * 2 + 1] = 0
uvs[3 * 2 + 0] = 1
uvs[3 * 2 + 1] = 0
uvs[4 * 2 + 0] = 0
uvs[4 * 2 + 1] = 1
uvs[5 * 2 + 0] = 1 / 3
uvs[5 * 2 + 1] = 1
uvs[6 * 2 + 0] = 1 / 3 * 2
uvs[6 * 2 + 1] = 1
uvs[7 * 2 + 0] = 1
uvs[7 * 2 + 1] = 1
// Index
indices[0 * 3 + 0] = 0
indices[0 * 3 + 1] = 4
indices[0 * 3 + 2] = 1
indices[1 * 3 + 0] = 5
indices[1 * 3 + 1] = 1
indices[1 * 3 + 2] = 4
indices[2 * 3 + 0] = 1
indices[2 * 3 + 1] = 5
indices[2 * 3 + 2] = 2
indices[3 * 3 + 0] = 6
indices[3 * 3 + 1] = 2
indices[3 * 3 + 2] = 5
indices[4 * 3 + 0] = 2
indices[4 * 3 + 1] = 6
indices[4 * 3 + 2] = 3
indices[5 * 3 + 0] = 7
indices[5 * 3 + 1] = 3
indices[5 * 3 + 2] = 6
indices[6 * 3 + 0] = 3
indices[6 * 3 + 1] = 7
indices[6 * 3 + 2] = 0
indices[7 * 3 + 0] = 4
indices[7 * 3 + 1] = 0
indices[7 * 3 + 2] = 7
const geometry = new THREE.BufferGeometry()
// Set indices
geometry.setIndex(new THREE.BufferAttribute(indices, 1, false))
// Set attributes
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3))
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2))
return geometry
}
}
export default AreaFenceBufferGeometry

View File

@ -0,0 +1,107 @@
import * as THREE from 'three'
class AreaFloorBorderBufferGeometry
{
constructor(_width, _height, _thickness)
{
// Parameters
this.parameters = {
width: _width,
height: _height,
thickness: _thickness
}
// Set up
this.type = 'AreaFloorBufferGeometry'
// buffers
const length = 8
const vertices = new Float32Array(length * 3)
const indices = new Uint32Array(length * 6)
const outerWidth = _width
const outerHeight = _height
const innerWidth = outerWidth - _thickness
const innerHeight = outerHeight - _thickness
// Vertices
vertices[0 * 3 + 0] = innerWidth * 0.5
vertices[0 * 3 + 1] = innerHeight * 0.5
vertices[0 * 3 + 2] = 0
vertices[1 * 3 + 0] = innerWidth * 0.5
vertices[1 * 3 + 1] = - innerHeight * 0.5
vertices[1 * 3 + 2] = 0
vertices[2 * 3 + 0] = - innerWidth * 0.5
vertices[2 * 3 + 1] = - innerHeight * 0.5
vertices[2 * 3 + 2] = 0
vertices[3 * 3 + 0] = - innerWidth * 0.5
vertices[3 * 3 + 1] = innerHeight * 0.5
vertices[3 * 3 + 2] = 0
vertices[4 * 3 + 0] = outerWidth * 0.5
vertices[4 * 3 + 1] = outerHeight * 0.5
vertices[4 * 3 + 2] = 0
vertices[5 * 3 + 0] = outerWidth * 0.5
vertices[5 * 3 + 1] = - outerHeight * 0.5
vertices[5 * 3 + 2] = 0
vertices[6 * 3 + 0] = - outerWidth * 0.5
vertices[6 * 3 + 1] = - outerHeight * 0.5
vertices[6 * 3 + 2] = 0
vertices[7 * 3 + 0] = - outerWidth * 0.5
vertices[7 * 3 + 1] = outerHeight * 0.5
vertices[7 * 3 + 2] = 0
// Index
indices[0 * 3 + 0] = 4
indices[0 * 3 + 1] = 0
indices[0 * 3 + 2] = 1
indices[1 * 3 + 0] = 1
indices[1 * 3 + 1] = 5
indices[1 * 3 + 2] = 4
indices[2 * 3 + 0] = 5
indices[2 * 3 + 1] = 1
indices[2 * 3 + 2] = 2
indices[3 * 3 + 0] = 2
indices[3 * 3 + 1] = 6
indices[3 * 3 + 2] = 5
indices[4 * 3 + 0] = 6
indices[4 * 3 + 1] = 2
indices[4 * 3 + 2] = 3
indices[5 * 3 + 0] = 3
indices[5 * 3 + 1] = 7
indices[5 * 3 + 2] = 6
indices[6 * 3 + 0] = 7
indices[6 * 3 + 1] = 3
indices[6 * 3 + 2] = 0
indices[7 * 3 + 0] = 0
indices[7 * 3 + 1] = 4
indices[7 * 3 + 2] = 7
const geometry = new THREE.BufferGeometry()
// Set indices
geometry.setIndex(new THREE.BufferAttribute(indices, 1, false))
// Set attributes
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3))
return geometry
}
}
export default AreaFloorBorderBufferGeometry

View File

@ -0,0 +1,26 @@
import * as THREE from 'three'
import shaderFragment from '../../shaders/areaFence/fragment.glsl'
import shaderVertex from '../../shaders/areaFence/vertex.glsl'
export default function()
{
const uniforms = {
uTime: { value: null },
uBorderAlpha: { value: null },
uStrikeAlpha: { value: null }
}
const material = new THREE.ShaderMaterial({
wireframe: false,
transparent: true,
side: THREE.DoubleSide,
depthTest: true,
depthWrite: false,
uniforms,
vertexShader: shaderVertex,
fragmentShader: shaderFragment
})
return material
}

View File

@ -0,0 +1,26 @@
import * as THREE from 'three'
import shaderFragment from '../../shaders/areaFloorBorder/fragment.glsl'
import shaderVertex from '../../shaders/areaFloorBorder/vertex.glsl'
export default function()
{
const uniforms = {
uColor: { value: null },
uAlpha: { value: null },
uLoadProgress: { value: null },
uProgress: { value: null }
}
const material = new THREE.ShaderMaterial({
wireframe: false,
transparent: true,
depthTest: true,
depthWrite: false,
uniforms,
vertexShader: shaderVertex,
fragmentShader: shaderFragment
})
return material
}

View File

@ -0,0 +1,21 @@
import * as THREE from 'three'
import shaderFragment from '../../shaders/floor/fragment.glsl'
import shaderVertex from '../../shaders/floor/vertex.glsl'
export default function()
{
const uniforms = {
tBackground: { value: null }
}
const material = new THREE.ShaderMaterial({
wireframe: false,
transparent: false,
uniforms,
vertexShader: shaderVertex,
fragmentShader: shaderFragment
})
return material
}

View File

@ -0,0 +1,23 @@
import * as THREE from 'three'
import shaderFragment from '../../shaders/floorShadow/fragment.glsl'
import shaderVertex from '../../shaders/floorShadow/vertex.glsl'
export default function()
{
const uniforms = {
tShadow: { value: null },
uShadowColor: { value: null },
uAlpha: { value: null }
}
const material = new THREE.ShaderMaterial({
wireframe: false,
transparent: true,
uniforms,
vertexShader: shaderVertex,
fragmentShader: shaderFragment
})
return material
}

View File

@ -0,0 +1,48 @@
import * as THREE from 'three'
import shaderFragment from '../../shaders/matcap/fragment.glsl'
import shaderVertex from '../../shaders/matcap/vertex.glsl'
export default function()
{
const uniforms = {
...THREE.UniformsLib.common,
...THREE.UniformsLib.bumpmap,
...THREE.UniformsLib.normalmap,
...THREE.UniformsLib.displacementmap,
...THREE.UniformsLib.fog,
matcap: { value: null },
uRevealProgress: { value: null },
uIndirectDistanceAmplitude: { value: null },
uIndirectDistanceStrength: { value: null },
uIndirectDistancePower: { value: null },
uIndirectAngleStrength: { value: null },
uIndirectAngleOffset: { value: null },
uIndirectAnglePower: { value: null },
uIndirectColor: { value: null }
}
const extensions = {
derivatives: false,
fragDepth: false,
drawBuffers: false,
shaderTextureLOD: false
}
const defines = {
MATCAP: ''
}
const material = new THREE.ShaderMaterial({
wireframe: false,
transparent: false,
uniforms,
extensions,
defines,
lights: false,
vertexShader: shaderVertex,
fragmentShader: shaderFragment
})
return material
}

View File

@ -0,0 +1,23 @@
import * as THREE from 'three'
import shaderFragment from '../../shaders/projectBoard/fragment.glsl'
import shaderVertex from '../../shaders/projectBoard/vertex.glsl'
export default function()
{
const uniforms = {
uTexture: { value: null },
uTextureAlpha: { value: null },
uColor: { value: null }
}
const material = new THREE.ShaderMaterial({
wireframe: false,
transparent: false,
uniforms,
vertexShader: shaderVertex,
fragmentShader: shaderFragment
})
return material
}

View File

@ -0,0 +1,23 @@
import * as THREE from 'three'
import shaderFragment from '../../shaders/shadow/fragment.glsl'
import shaderVertex from '../../shaders/shadow/vertex.glsl'
export default function()
{
const uniforms = {
uColor: { value: null },
uAlpha: { value: null },
uFadeRadius: { value: null }
}
const material = new THREE.ShaderMaterial({
wireframe: false,
transparent: true,
uniforms,
vertexShader: shaderVertex,
fragmentShader: shaderFragment
})
return material
}

View File

@ -0,0 +1,13 @@
import shaderFragment from '../../shaders/blur/fragment.glsl'
import shaderVertex from '../../shaders/blur/vertex.glsl'
export default {
uniforms:
{
tDiffuse: { type: 't', value: null },
uResolution: { type: 'v2', value: null },
uStrength: { type: 'v2', value: null }
},
vertexShader: shaderVertex,
fragmentShader: shaderFragment
}

View File

@ -0,0 +1,15 @@
import shaderFragment from '../../shaders/glows/fragment.glsl'
import shaderVertex from '../../shaders/glows/vertex.glsl'
export default {
uniforms:
{
tDiffuse: { type: 't', value: null },
uPosition: { type: 'v2', value: null },
uRadius: { type: 'f', value: null },
uColor: { type: 'v3', value: null },
uAlpha: { type: 'f', value: null }
},
vertexShader: shaderVertex,
fragmentShader: shaderFragment
}

456
src/javascript/Resources.js Normal file
View File

@ -0,0 +1,456 @@
import * as THREE from 'three'
import Loader from './Utils/Loader.js'
import EventEmitter from './Utils/EventEmitter.js'
// Matcaps
import matcapBeigeSource from '../models/matcaps/beige.png'
import matcapBlackSource from '../models/matcaps/black.png'
import matcapOrangeSource from '../models/matcaps/orange.png'
import matcapRedSource from '../models/matcaps/red.png'
import matcapWhiteSource from '../models/matcaps/white.png'
import matcapGreenSource from '../models/matcaps/green.png'
import matcapBrownSource from '../models/matcaps/brown.png'
import matcapGraySource from '../models/matcaps/gray.png'
import matcapEmeraldGreenSource from '../models/matcaps/emeraldGreen.png'
import matcapPurpleSource from '../models/matcaps/purple.png'
import matcapBlueSource from '../models/matcaps/blue.png'
import matcapYellowSource from '../models/matcaps/yellow.png'
import matcapMetalSource from '../models/matcaps/metal.png'
// import matcapGoldSource from '../models/matcaps/gold.png'
// Intro
import introStaticBaseSource from '../models/intro/static/base.glb'
import introStaticCollisionSource from '../models/intro/static/collision.glb'
import introStaticFloorShadowSource from '../models/intro/static/floorShadow.png'
import introInstructionsLabelsSource from '../models/intro/instructions/labels.glb'
import introInstructionsArrowsSource from '../models/intro/instructions/arrows.png'
import introInstructionsControlsSource from '../models/intro/instructions/controls.png'
import introInstructionsOtherSource from '../models/intro/instructions/other.png'
import introArrowKeyBaseSource from '../models/intro/arrowKey/base.glb'
import introArrowKeyCollisionSource from '../models/intro/arrowKey/collision.glb'
import introBBaseSource from '../models/intro/b/base.glb'
import introBCollisionSource from '../models/intro/b/collision.glb'
import introRBaseSource from '../models/intro/r/base.glb'
import introRCollisionSource from '../models/intro/r/collision.glb'
import introUBaseSource from '../models/intro/u/base.glb'
import introUCollisionSource from '../models/intro/u/collision.glb'
import introNBaseSource from '../models/intro/n/base.glb'
import introNCollisionSource from '../models/intro/n/collision.glb'
import introOBaseSource from '../models/intro/o/base.glb'
import introOCollisionSource from '../models/intro/o/collision.glb'
import introSBaseSource from '../models/intro/s/base.glb'
import introSCollisionSource from '../models/intro/s/collision.glb'
import introIBaseSource from '../models/intro/i/base.glb'
import introICollisionSource from '../models/intro/i/collision.glb'
import introMBaseSource from '../models/intro/m/base.glb'
import introMCollisionSource from '../models/intro/m/collision.glb'
import introCreativeBaseSource from '../models/intro/creative/base.glb'
import introCreativeCollisionSource from '../models/intro/creative/collision.glb'
import introDevBaseSource from '../models/intro/dev/base.glb'
import introDevCollisionSource from '../models/intro/dev/collision.glb'
// Crossroads
import crossroadsStaticFloorShadowSource from '../models/crossroads/static/floorShadow.png'
import crossroadsStaticBaseSource from '../models/crossroads/static/base.glb'
import crossroadsStaticCollisionSource from '../models/crossroads/static/collision.glb'
// Car default
import carDefaultChassisSource from '../models/car/default/chassis.glb'
import carDefaultWheelSource from '../models/car/default/wheel.glb'
import carDefaultBackLightsBrakeSource from '../models/car/default/backLightsBrake.glb'
import carDefaultBackLightsReverseSource from '../models/car/default/backLightsReverse.glb'
import carDefaultAntenaSource from '../models/car/default/antena.glb'
// import carDefaultBunnyEarLeftSource from '../models/car/default/bunnyEarLeft.glb'
// import carDefaultBunnyEarRightSource from '../models/car/default/bunnyEarRight.glb'
// Car cyber truck
import carCyberTruckChassisSource from '../models/car/cyberTruck/chassis.glb'
import carCyberTruckWheelSource from '../models/car/cyberTruck/wheel.glb'
import carCyberTruckBackLightsBrakeSource from '../models/car/cyberTruck/backLightsBrake.glb'
import carCyberTruckBackLightsReverseSource from '../models/car/cyberTruck/backLightsReverse.glb'
import carCyberTruckAntenaSource from '../models/car/cyberTruck/antena.glb'
// Projects
import projectsBoardStructureSource from '../models/projects/board/structure.glb'
import projectsBoardCollisionSource from '../models/projects/board/collision.glb'
import projectsBoardStructureFloorShadowSource from '../models/projects/board/floorShadow.png'
import projectsBoardPlaneSource from '../models/projects/board/plane.glb'
import projectsDistinctionsAwwwardsBaseSource from '../models/projects/distinctions/awwwards/base.glb'
import projectsDistinctionsAwwwardsCollisionSource from '../models/projects/distinctions/awwwards/collision.glb'
import projectsDistinctionsFWABaseSource from '../models/projects/distinctions/fwa/base.glb'
import projectsDistinctionsFWACollisionSource from '../models/projects/distinctions/fwa/collision.glb'
import projectsDistinctionsCSSDABaseSource from '../models/projects/distinctions/cssda/base.glb'
import projectsDistinctionsCSSDACollisionSource from '../models/projects/distinctions/cssda/collision.glb'
import projectsThreejsJourneyFloorSource from '../models/projects/threejsJourney/floorTexture.png'
import projectsMadboxFloorSource from '../models/projects/madbox/floorTexture.png'
import projectsScoutFloorSource from '../models/projects/scout/floorTexture.png'
import projectsChartogneFloorSource from '../models/projects/chartogne/floorTexture.png'
import projectsZenlyFloorSource from '../models/projects/zenly/floorTexture.png'
import projectsCitrixRedbullFloorSource from '../models/projects/citrixRedbull/floorTexture.png'
import projectsPriorHoldingsFloorSource from '../models/projects/priorHoldings/floorTexture.png'
import projectsOranoFloorSource from '../models/projects/orano/floorTexture.png'
// import projectsGleecChatFloorSource from '../models/projects/gleecChat/floorTexture.png'
import projectsKepplerFloorSource from '../models/projects/keppler/floorTexture.png'
// Information
import informationStaticBaseSource from '../models/information/static/base.glb'
import informationStaticCollisionSource from '../models/information/static/collision.glb'
import informationStaticFloorShadowSource from '../models/information/static/floorShadow.png'
import informationBaguetteBaseSource from '../models/information/baguette/base.glb'
import informationBaguetteCollisionSource from '../models/information/baguette/collision.glb'
import informationContactTwitterLabelSource from '../models/information/static/contactTwitterLabel.png'
import informationContactGithubLabelSource from '../models/information/static/contactGithubLabel.png'
import informationContactLinkedinLabelSource from '../models/information/static/contactLinkedinLabel.png'
import informationContactMailLabelSource from '../models/information/static/contactMailLabel.png'
import informationActivitiesSource from '../models/information/static/activities.png'
// Playground
import playgroundStaticFloorShadowSource from '../models/playground/static/floorShadow.png'
import playgroundStaticBaseSource from '../models/playground/static/base.glb'
import playgroundStaticCollisionSource from '../models/playground/static/collision.glb'
// Brick
import brickBaseSource from '../models/brick/base.glb'
import brickCollisionSource from '../models/brick/collision.glb'
// Horn
import hornBaseSource from '../models/horn/base.glb'
import hornCollisionSource from '../models/horn/collision.glb'
// // Distinction A
// import distinctionAStaticFloorShadowSource from '../models/distinctionA/static/floorShadow.png'
// import distinctionAStaticBaseSource from '../models/distinctionA/static/base.glb'
// import distinctionAStaticCollisionSource from '../models/distinctionA/static/collision.glb'
// // Distinction B
// import distinctionBStaticFloorShadowSource from '../models/distinctionB/static/floorShadow.png'
// import distinctionBStaticBaseSource from '../models/distinctionB/static/base.glb'
// import distinctionBStaticCollisionSource from '../models/distinctionB/static/collision.glb'
// // Distinction C
// import distinctionCStaticFloorShadowSource from '../models/distinctionC/static/floorShadow.png'
// import distinctionCStaticBaseSource from '../models/distinctionC/static/base.glb'
// import distinctionCStaticCollisionSource from '../models/distinctionC/static/collision.glb'
// // Cone
// import coneBaseSource from '../models/cone/base.glb'
// import coneCollisionSource from '../models/cone/collision.glb'
// // Awwwards trophy
// import awwwardsTrophyBaseSource from '../models/awwwardsTrophy/base.glb'
// import awwwardsTrophyCollisionSource from '../models/awwwardsTrophy/collision.glb'
// Awwwards trophy
import webbyTrophyBaseSource from '../models/webbyTrophy/base.glb'
import webbyTrophyCollisionSource from '../models/webbyTrophy/collision.glb'
// Lemon
import lemonBaseSource from '../models/lemon/base.glb'
import lemonCollisionSource from '../models/lemon/collision.glb'
// Bowling ball
import bowlingBallBaseSource from '../models/bowlingBall/base.glb'
import bowlingBallCollisionSource from '../models/bowlingBall/collision.glb'
// Bowling pin
import bowlingPinBaseSource from '../models/bowlingPin/base.glb'
import bowlingPinCollisionSource from '../models/bowlingPin/collision.glb'
// Area
import areaKeyEnterSource from '../models/area/keyEnter.png'
import areaEnterSource from '../models/area/enter.png'
import areaOpenSource from '../models/area/open.png'
import areaResetSource from '../models/area/reset.png'
import areaQuestionMarkSource from '../models/area/questionMark.png'
// Tiles
import tilesABaseSource from '../models/tiles/a/base.glb'
import tilesACollisionSource from '../models/tiles/a/collision.glb'
import tilesBBaseSource from '../models/tiles/b/base.glb'
import tilesBCollisionSource from '../models/tiles/b/collision.glb'
import tilesCBaseSource from '../models/tiles/c/base.glb'
import tilesCCollisionSource from '../models/tiles/c/collision.glb'
import tilesDBaseSource from '../models/tiles/d/base.glb'
import tilesDCollisionSource from '../models/tiles/d/collision.glb'
import tilesEBaseSource from '../models/tiles/e/base.glb'
import tilesECollisionSource from '../models/tiles/e/collision.glb'
// Konami
import konamiLabelSource from '../models/konami/label.png'
import konamiLabelTouchSource from '../models/konami/label-touch.png'
// Wigs
import wig1Source from '../models/wigs/wig1.glb'
import wig2Source from '../models/wigs/wig2.glb'
import wig3Source from '../models/wigs/wig3.glb'
import wig4Source from '../models/wigs/wig4.glb'
// // Egg
// import eggBaseSource from '../models/egg/base.glb'
// import eggCollisionSource from '../models/egg/collision.glb'
export default class Resources extends EventEmitter
{
constructor()
{
super()
this.loader = new Loader()
this.items = {}
this.loader.load([
// Matcaps
{ name: 'matcapBeige', source: matcapBeigeSource, type: 'texture' },
{ name: 'matcapBlack', source: matcapBlackSource, type: 'texture' },
{ name: 'matcapOrange', source: matcapOrangeSource, type: 'texture' },
{ name: 'matcapRed', source: matcapRedSource, type: 'texture' },
{ name: 'matcapWhite', source: matcapWhiteSource, type: 'texture' },
{ name: 'matcapGreen', source: matcapGreenSource, type: 'texture' },
{ name: 'matcapBrown', source: matcapBrownSource, type: 'texture' },
{ name: 'matcapGray', source: matcapGraySource, type: 'texture' },
{ name: 'matcapEmeraldGreen', source: matcapEmeraldGreenSource, type: 'texture' },
{ name: 'matcapPurple', source: matcapPurpleSource, type: 'texture' },
{ name: 'matcapBlue', source: matcapBlueSource, type: 'texture' },
{ name: 'matcapYellow', source: matcapYellowSource, type: 'texture' },
{ name: 'matcapMetal', source: matcapMetalSource, type: 'texture' },
// { name: 'matcapGold', source: matcapGoldSource, type: 'texture' },
// Intro
{ name: 'introStaticBase', source: introStaticBaseSource },
{ name: 'introStaticCollision', source: introStaticCollisionSource },
{ name: 'introStaticFloorShadow', source: introStaticFloorShadowSource, type: 'texture' },
{ name: 'introInstructionsLabels', source: introInstructionsLabelsSource },
{ name: 'introInstructionsArrows', source: introInstructionsArrowsSource, type: 'texture' },
{ name: 'introInstructionsControls', source: introInstructionsControlsSource, type: 'texture' },
{ name: 'introInstructionsOther', source: introInstructionsOtherSource, type: 'texture' },
{ name: 'introArrowKeyBase', source: introArrowKeyBaseSource },
{ name: 'introArrowKeyCollision', source: introArrowKeyCollisionSource },
{ name: 'introBBase', source: introBBaseSource },
{ name: 'introBCollision', source: introBCollisionSource },
{ name: 'introRBase', source: introRBaseSource },
{ name: 'introRCollision', source: introRCollisionSource },
{ name: 'introUBase', source: introUBaseSource },
{ name: 'introUCollision', source: introUCollisionSource },
{ name: 'introNBase', source: introNBaseSource },
{ name: 'introNCollision', source: introNCollisionSource },
{ name: 'introOBase', source: introOBaseSource },
{ name: 'introOCollision', source: introOCollisionSource },
{ name: 'introSBase', source: introSBaseSource },
{ name: 'introSCollision', source: introSCollisionSource },
{ name: 'introIBase', source: introIBaseSource },
{ name: 'introICollision', source: introICollisionSource },
{ name: 'introMBase', source: introMBaseSource },
{ name: 'introMCollision', source: introMCollisionSource },
{ name: 'introCreativeBase', source: introCreativeBaseSource },
{ name: 'introCreativeCollision', source: introCreativeCollisionSource },
{ name: 'introDevBase', source: introDevBaseSource },
{ name: 'introDevCollision', source: introDevCollisionSource },
// Intro
{ name: 'crossroadsStaticBase', source: crossroadsStaticBaseSource },
{ name: 'crossroadsStaticCollision', source: crossroadsStaticCollisionSource },
{ name: 'crossroadsStaticFloorShadow', source: crossroadsStaticFloorShadowSource, type: 'texture' },
// Car default
{ name: 'carDefaultChassis', source: carDefaultChassisSource },
{ name: 'carDefaultWheel', source: carDefaultWheelSource },
{ name: 'carDefaultBackLightsBrake', source: carDefaultBackLightsBrakeSource },
{ name: 'carDefaultBackLightsReverse', source: carDefaultBackLightsReverseSource },
{ name: 'carDefaultAntena', source: carDefaultAntenaSource },
// { name: 'carDefaultBunnyEarLeft', source: carDefaultBunnyEarLeftSource },
// { name: 'carDefaultBunnyEarRight', source: carDefaultBunnyEarRightSource },
// Car default
{ name: 'carCyberTruckChassis', source: carCyberTruckChassisSource },
{ name: 'carCyberTruckWheel', source: carCyberTruckWheelSource },
{ name: 'carCyberTruckBackLightsBrake', source: carCyberTruckBackLightsBrakeSource },
{ name: 'carCyberTruckBackLightsReverse', source: carCyberTruckBackLightsReverseSource },
{ name: 'carCyberTruckAntena', source: carCyberTruckAntenaSource },
// Project
{ name: 'projectsBoardStructure', source: projectsBoardStructureSource },
{ name: 'projectsBoardCollision', source: projectsBoardCollisionSource },
{ name: 'projectsBoardStructureFloorShadow', source: projectsBoardStructureFloorShadowSource, type: 'texture' },
{ name: 'projectsBoardPlane', source: projectsBoardPlaneSource },
{ name: 'projectsDistinctionsAwwwardsBase', source: projectsDistinctionsAwwwardsBaseSource },
{ name: 'projectsDistinctionsAwwwardsCollision', source: projectsDistinctionsAwwwardsCollisionSource },
{ name: 'projectsDistinctionsFWABase', source: projectsDistinctionsFWABaseSource },
{ name: 'projectsDistinctionsFWACollision', source: projectsDistinctionsFWACollisionSource },
{ name: 'projectsDistinctionsCSSDABase', source: projectsDistinctionsCSSDABaseSource },
{ name: 'projectsDistinctionsCSSDACollision', source: projectsDistinctionsCSSDACollisionSource },
{ name: 'projectsThreejsJourneyFloor', source: projectsThreejsJourneyFloorSource, type: 'texture' },
{ name: 'projectsMadboxFloor', source: projectsMadboxFloorSource, type: 'texture' },
{ name: 'projectsScoutFloor', source: projectsScoutFloorSource, type: 'texture' },
{ name: 'projectsChartogneFloor', source: projectsChartogneFloorSource, type: 'texture' },
{ name: 'projectsZenlyFloor', source: projectsZenlyFloorSource, type: 'texture' },
{ name: 'projectsCitrixRedbullFloor', source: projectsCitrixRedbullFloorSource, type: 'texture' },
{ name: 'projectsPriorHoldingsFloor', source: projectsPriorHoldingsFloorSource, type: 'texture' },
{ name: 'projectsOranoFloor', source: projectsOranoFloorSource, type: 'texture' },
// { name: 'projectsGleecChatFloor', source: projectsGleecChatFloorSource, type: 'texture' },
{ name: 'projectsKepplerFloor', source: projectsKepplerFloorSource, type: 'texture' },
// Information
{ name: 'informationStaticBase', source: informationStaticBaseSource },
{ name: 'informationStaticCollision', source: informationStaticCollisionSource },
{ name: 'informationStaticFloorShadow', source: informationStaticFloorShadowSource, type: 'texture' },
{ name: 'informationBaguetteBase', source: informationBaguetteBaseSource },
{ name: 'informationBaguetteCollision', source: informationBaguetteCollisionSource },
{ name: 'informationContactTwitterLabel', source: informationContactTwitterLabelSource, type: 'texture' },
{ name: 'informationContactGithubLabel', source: informationContactGithubLabelSource, type: 'texture' },
{ name: 'informationContactLinkedinLabel', source: informationContactLinkedinLabelSource, type: 'texture' },
{ name: 'informationContactMailLabel', source: informationContactMailLabelSource, type: 'texture' },
{ name: 'informationActivities', source: informationActivitiesSource, type: 'texture' },
// Playground
{ name: 'playgroundStaticBase', source: playgroundStaticBaseSource },
{ name: 'playgroundStaticCollision', source: playgroundStaticCollisionSource },
{ name: 'playgroundStaticFloorShadow', source: playgroundStaticFloorShadowSource, type: 'texture' },
// Brick
{ name: 'brickBase', source: brickBaseSource },
{ name: 'brickCollision', source: brickCollisionSource },
// Horn
{ name: 'hornBase', source: hornBaseSource },
{ name: 'hornCollision', source: hornCollisionSource },
// // Distinction A
// { name: 'distinctionAStaticBase', source: distinctionAStaticBaseSource },
// { name: 'distinctionAStaticCollision', source: distinctionAStaticCollisionSource },
// { name: 'distinctionAStaticFloorShadow', source: distinctionAStaticFloorShadowSource, type: 'texture' },
// // Distinction B
// { name: 'distinctionBStaticBase', source: distinctionBStaticBaseSource },
// { name: 'distinctionBStaticCollision', source: distinctionBStaticCollisionSource },
// { name: 'distinctionBStaticFloorShadow', source: distinctionBStaticFloorShadowSource, type: 'texture' },
// // Distinction C
// { name: 'distinctionCStaticBase', source: distinctionCStaticBaseSource },
// { name: 'distinctionCStaticCollision', source: distinctionCStaticCollisionSource },
// { name: 'distinctionCStaticFloorShadow', source: distinctionCStaticFloorShadowSource, type: 'texture' },
// // Cone
// { name: 'coneBase', source: coneBaseSource },
// { name: 'coneCollision', source: coneCollisionSource },
// // Awwwards trophy
// { name: 'awwwardsTrophyBase', source: awwwardsTrophyBaseSource },
// { name: 'awwwardsTrophyCollision', source: awwwardsTrophyCollisionSource },
// Webby trophy
{ name: 'webbyTrophyBase', source: webbyTrophyBaseSource },
{ name: 'webbyTrophyCollision', source: webbyTrophyCollisionSource },
// Lemon
{ name: 'lemonBase', source: lemonBaseSource },
{ name: 'lemonCollision', source: lemonCollisionSource },
// Bownling ball
{ name: 'bowlingBallBase', source: bowlingBallBaseSource },
{ name: 'bowlingBallCollision', source: bowlingBallCollisionSource },
// Bownling ball
{ name: 'bowlingPinBase', source: bowlingPinBaseSource },
{ name: 'bowlingPinCollision', source: bowlingPinCollisionSource },
// Areas
{ name: 'areaKeyEnter', source: areaKeyEnterSource, type: 'texture' },
{ name: 'areaEnter', source: areaEnterSource, type: 'texture' },
{ name: 'areaOpen', source: areaOpenSource, type: 'texture' },
{ name: 'areaReset', source: areaResetSource, type: 'texture' },
{ name: 'areaQuestionMark', source: areaQuestionMarkSource, type: 'texture' },
// Tiles
{ name: 'tilesABase', source: tilesABaseSource },
{ name: 'tilesACollision', source: tilesACollisionSource },
{ name: 'tilesBBase', source: tilesBBaseSource },
{ name: 'tilesBCollision', source: tilesBCollisionSource },
{ name: 'tilesCBase', source: tilesCBaseSource },
{ name: 'tilesCCollision', source: tilesCCollisionSource },
{ name: 'tilesDBase', source: tilesDBaseSource },
{ name: 'tilesDCollision', source: tilesDCollisionSource },
{ name: 'tilesEBase', source: tilesEBaseSource },
{ name: 'tilesECollision', source: tilesECollisionSource },
// Konami
{ name: 'konamiLabel', source: konamiLabelSource, type: 'texture' },
{ name: 'konamiLabelTouch', source: konamiLabelTouchSource, type: 'texture' },
// Wigs
{ name: 'wig1', source: wig1Source },
{ name: 'wig2', source: wig2Source },
{ name: 'wig3', source: wig3Source },
{ name: 'wig4', source: wig4Source },
// // Egg
// { name: 'eggBase', source: eggBaseSource },
// { name: 'eggCollision', source: eggCollisionSource },
])
this.loader.on('fileEnd', (_resource, _data) =>
{
this.items[_resource.name] = _data
// Texture
if(_resource.type === 'texture')
{
const texture = new THREE.Texture(_data)
texture.needsUpdate = true
this.items[`${_resource.name}Texture`] = texture
}
// Trigger progress
this.trigger('progress', [this.loader.loaded / this.loader.toLoad])
})
this.loader.on('end', () =>
{
// Trigger ready
this.trigger('ready')
})
}
}

View File

@ -0,0 +1,219 @@
import { TweenLite } from 'gsap/TweenLite'
export default class ThreejsJourney
{
constructor(_options)
{
// Options
this.config = _options.config
this.time = _options.time
this.world = _options.world
// Setup
this.$container = document.querySelector('.js-threejs-journey')
this.$messages = [...this.$container.querySelectorAll('.js-message')]
this.$yes = this.$container.querySelector('.js-yes')
this.$no = this.$container.querySelector('.js-no')
this.step = 0
this.maxStep = this.$messages.length - 1
this.seenCount = window.localStorage.getItem('threejsJourneySeenCount') || 0
this.seenCount = parseInt(this.seenCount)
this.shown = false
this.traveledDistance = 0
this.minTraveledDistance = (this.config.debug ? 5 : 75) * (this.seenCount + 1)
this.prevent = !!window.localStorage.getItem('threejsJourneyPrevent')
if(this.config.debug)
this.start()
if(this.prevent)
return
this.setYesNo()
this.setLog()
this.time.on('tick', () =>
{
if(this.world.physics)
{
this.traveledDistance += this.world.physics.car.forwardSpeed
if(!this.config.touch && !this.shown && this.traveledDistance > this.minTraveledDistance)
{
this.start()
}
}
})
}
setYesNo()
{
// Clicks
this.$yes.addEventListener('click', () =>
{
TweenLite.delayedCall(2, () =>
{
this.hide()
})
window.localStorage.setItem('threejsJourneyPrevent', 1)
})
this.$no.addEventListener('click', () =>
{
this.next()
TweenLite.delayedCall(5, () =>
{
this.hide()
})
})
// Hovers
this.$yes.addEventListener('mouseenter', () =>
{
this.$container.classList.remove('is-hover-none')
this.$container.classList.remove('is-hover-no')
this.$container.classList.add('is-hover-yes')
})
this.$no.addEventListener('mouseenter', () =>
{
this.$container.classList.remove('is-hover-none')
this.$container.classList.add('is-hover-no')
this.$container.classList.remove('is-hover-yes')
})
this.$yes.addEventListener('mouseleave', () =>
{
this.$container.classList.add('is-hover-none')
this.$container.classList.remove('is-hover-no')
this.$container.classList.remove('is-hover-yes')
})
this.$no.addEventListener('mouseleave', () =>
{
this.$container.classList.add('is-hover-none')
this.$container.classList.remove('is-hover-no')
this.$container.classList.remove('is-hover-yes')
})
}
setLog()
{
// console.log(
// `%c
// ▶
// ▶▶▶▶
// ▶▶▶▶▶▶▶
// ▶▶▶▶▶▶▶▶▶▶
// ▶▶▶▶▶▶▶▶ ▶
// ▶▶▶▶ ▶▶▶▶▶▶▶▶
// ▶ ▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶
// ▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶
// ▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶
// ▶▶ ▶▶▶▶▶▶▶▶▶▶ ▶ ▶▶▶
// ▶▶▶▶▶▶ ▶ ▶▶▶▶▶ ▶▶▶▶▶▶
// ▶▶▶▶▶▶▶▶▶▶▶ ▶▶▶▶▶▶▶▶ ▶▶▶▶▶▶▶▶▶
// ▶▶▶▶▶▶▶▶▶▶▶▶▶ ▶▶▶▶▶▶▶▶▶▶ ▶▶▶▶▶▶▶
// ▶▶▶▶▶▶▶▶▶▶▶▶▶ ▶▶▶▶▶▶▶▶▶▶ ▶▶▶▶
// ▶▶▶▶▶▶▶▶▶▶▶▶▶ ▶▶▶▶▶▶▶▶▶▶ ▶
// ▶▶▶▶▶▶▶▶▶▶▶▶ ▶▶▶▶▶▶▶▶▶▶
// ▶▶▶▶▶▶▶▶ ▶▶▶▶▶▶▶
// ▶▶▶▶ ▶▶▶▶ ▶▶▶
// ▶▶▶▶▶▶▶ ▶
// ▶▶▶▶▶▶▶▶▶▶
// ▶▶▶▶▶▶▶
// ▶▶
// `,
// 'color: #705df2;'
// )
console.log('%cWhat are you doing here?! you sneaky developer...', 'color: #32ffce');
console.log('%cDo you want to learn how this portfolio has been made?', 'color: #32ffce');
console.log('%cCheckout Three.js Journey 👉 https://threejs-journey.com?c=p2', 'color: #32ffce');
console.log('%c— Bruno', 'color: #777777');
}
hide()
{
for(const _$message of this.$messages)
{
_$message.classList.remove('is-visible')
}
TweenLite.delayedCall(0.5, () =>
{
this.$container.classList.remove('is-active')
})
}
start()
{
this.$container.classList.add('is-active')
window.requestAnimationFrame(() =>
{
this.next()
TweenLite.delayedCall(4, () =>
{
this.next()
})
TweenLite.delayedCall(7, () =>
{
this.next()
})
})
this.shown = true
window.localStorage.setItem('threejsJourneySeenCount', this.seenCount + 1)
}
updateMessages()
{
let i = 0
// Visibility
for(const _$message of this.$messages)
{
if(i < this.step)
_$message.classList.add('is-visible')
i++
}
// Position
this.$messages.reverse()
let height = 0
i = this.maxStep
for(const _$message of this.$messages)
{
const messageHeight = _$message.offsetHeight
if(i < this.step)
{
_$message.style.transform = `translateY(${- height}px)`
height += messageHeight + 20
}
else
{
_$message.style.transform = `translateY(${messageHeight}px)`
}
i--
}
this.$messages.reverse()
}
next()
{
if(this.step > this.maxStep)
return
this.step++
this.updateMessages()
}
}

View File

@ -0,0 +1,220 @@
export default class
{
/**
* Constructor
*/
constructor()
{
this.callbacks = {}
this.callbacks.base = {}
}
/**
* On
*/
on(_names, callback)
{
const that = this
// Errors
if(typeof _names === 'undefined' || _names === '')
{
console.warn('wrong names')
return false
}
if(typeof callback === 'undefined')
{
console.warn('wrong callback')
return false
}
// Resolve names
const names = this.resolveNames(_names)
// Each name
names.forEach(function(_name)
{
// Resolve name
const name = that.resolveName(_name)
// Create namespace if not exist
if(!(that.callbacks[ name.namespace ] instanceof Object))
that.callbacks[ name.namespace ] = {}
// Create callback if not exist
if(!(that.callbacks[ name.namespace ][ name.value ] instanceof Array))
that.callbacks[ name.namespace ][ name.value ] = []
// Add callback
that.callbacks[ name.namespace ][ name.value ].push(callback)
})
return this
}
/**
* Off
*/
off(_names)
{
const that = this
// Errors
if(typeof _names === 'undefined' || _names === '')
{
console.warn('wrong name')
return false
}
// Resolve names
const names = this.resolveNames(_names)
// Each name
names.forEach(function(_name)
{
// Resolve name
const name = that.resolveName(_name)
// Remove namespace
if(name.namespace !== 'base' && name.value === '')
{
delete that.callbacks[ name.namespace ]
}
// Remove specific callback in namespace
else
{
// Default
if(name.namespace === 'base')
{
// Try to remove from each namespace
for(const namespace in that.callbacks)
{
if(that.callbacks[ namespace ] instanceof Object && that.callbacks[ namespace ][ name.value ] instanceof Array)
{
delete that.callbacks[ namespace ][ name.value ]
// Remove namespace if empty
if(Object.keys(that.callbacks[ namespace ]).length === 0)
delete that.callbacks[ namespace ]
}
}
}
// Specified namespace
else if(that.callbacks[ name.namespace ] instanceof Object && that.callbacks[ name.namespace ][ name.value ] instanceof Array)
{
delete that.callbacks[ name.namespace ][ name.value ]
// Remove namespace if empty
if(Object.keys(that.callbacks[ name.namespace ]).length === 0)
delete that.callbacks[ name.namespace ]
}
}
})
return this
}
/**
* Trigger
*/
trigger(_name, _args)
{
// Errors
if(typeof _name === 'undefined' || _name === '')
{
console.warn('wrong name')
return false
}
const that = this
let finalResult = null
let result = null
// Default args
const args = !(_args instanceof Array) ? [] : _args
// Resolve names (should on have one event)
let name = this.resolveNames(_name)
// Resolve name
name = this.resolveName(name[ 0 ])
// Default namespace
if(name.namespace === 'base')
{
// Try to find callback in each namespace
for(const namespace in that.callbacks)
{
if(that.callbacks[ namespace ] instanceof Object && that.callbacks[ namespace ][ name.value ] instanceof Array)
{
that.callbacks[ namespace ][ name.value ].forEach(function(callback)
{
result = callback.apply(that, args)
if(typeof finalResult === 'undefined')
{
finalResult = result
}
})
}
}
}
// Specified namespace
else if(this.callbacks[ name.namespace ] instanceof Object)
{
if(name.value === '')
{
console.warn('wrong name')
return this
}
that.callbacks[ name.namespace ][ name.value ].forEach(function(callback)
{
result = callback.apply(that, args)
if(typeof finalResult === 'undefined')
finalResult = result
})
}
return finalResult
}
/**
* Resolve names
*/
resolveNames(_names)
{
let names = _names
names = names.replace(/[^a-zA-Z0-9 ,/.]/g, '')
names = names.replace(/[,/]+/g, ' ')
names = names.split(' ')
return names
}
/**
* Resolve name
*/
resolveName(name)
{
const newName = {}
const parts = name.split('.')
newName.original = name
newName.value = parts[ 0 ]
newName.namespace = 'base' // Base namespace
// Specified namespace
if(parts.length > 1 && parts[ 1 ] !== '')
{
newName.namespace = parts[ 1 ]
}
return newName
}
}

View File

@ -0,0 +1,144 @@
import EventEmitter from './EventEmitter.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
export default class Resources extends EventEmitter
{
/**
* Constructor
*/
constructor()
{
super()
this.setLoaders()
this.toLoad = 0
this.loaded = 0
this.items = {}
}
/**
* Set loaders
*/
setLoaders()
{
this.loaders = []
// Images
this.loaders.push({
extensions: ['jpg', 'png'],
action: (_resource) =>
{
const image = new Image()
image.addEventListener('load', () =>
{
this.fileLoadEnd(_resource, image)
})
image.addEventListener('error', () =>
{
this.fileLoadEnd(_resource, image)
})
image.src = _resource.source
}
})
// Draco
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('draco/')
dracoLoader.setDecoderConfig({ type: 'js' })
this.loaders.push({
extensions: ['drc'],
action: (_resource) =>
{
dracoLoader.load(_resource.source, (_data) =>
{
this.fileLoadEnd(_resource, _data)
DRACOLoader.releaseDecoderModule()
})
}
})
// GLTF
const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
this.loaders.push({
extensions: ['glb', 'gltf'],
action: (_resource) =>
{
gltfLoader.load(_resource.source, (_data) =>
{
this.fileLoadEnd(_resource, _data)
})
}
})
// FBX
const fbxLoader = new FBXLoader()
this.loaders.push({
extensions: ['fbx'],
action: (_resource) =>
{
fbxLoader.load(_resource.source, (_data) =>
{
this.fileLoadEnd(_resource, _data)
})
}
})
}
/**
* Load
*/
load(_resources = [])
{
for(const _resource of _resources)
{
this.toLoad++
const extensionMatch = _resource.source.match(/\.([a-z]+)$/)
if(typeof extensionMatch[1] !== 'undefined')
{
const extension = extensionMatch[1]
const loader = this.loaders.find((_loader) => _loader.extensions.find((_extension) => _extension === extension))
if(loader)
{
loader.action(_resource)
}
else
{
console.warn(`Cannot found loader for ${_resource}`)
}
}
else
{
console.warn(`Cannot found extension of ${_resource}`)
}
}
}
/**
* File load end
*/
fileLoadEnd(_resource, _data)
{
this.loaded++
this.items[_resource.name] = _data
this.trigger('fileEnd', [_resource, _data])
if(this.loaded === this.toLoad)
{
this.trigger('end')
}
}
}

View File

@ -0,0 +1,44 @@
import EventEmitter from './EventEmitter.js'
export default class Sizes extends EventEmitter
{
/**
* Constructor
*/
constructor()
{
super()
// Viewport size
this.viewport = {}
this.$sizeViewport = document.createElement('div')
this.$sizeViewport.style.width = '100vw'
this.$sizeViewport.style.height = '100vh'
this.$sizeViewport.style.position = 'absolute'
this.$sizeViewport.style.top = 0
this.$sizeViewport.style.left = 0
this.$sizeViewport.style.pointerEvents = 'none'
// Resize event
this.resize = this.resize.bind(this)
window.addEventListener('resize', this.resize)
this.resize()
}
/**
* Resize
*/
resize()
{
document.body.appendChild(this.$sizeViewport)
this.viewport.width = this.$sizeViewport.offsetWidth
this.viewport.height = this.$sizeViewport.offsetHeight
document.body.removeChild(this.$sizeViewport)
this.width = window.innerWidth
this.height = window.innerHeight
this.trigger('resize')
}
}

View File

@ -0,0 +1,49 @@
import EventEmitter from './EventEmitter.js'
export default class Time extends EventEmitter
{
/**
* Constructor
*/
constructor()
{
super()
this.start = Date.now()
this.current = this.start
this.elapsed = 0
this.delta = 16
this.tick = this.tick.bind(this)
this.tick()
}
/**
* Tick
*/
tick()
{
this.ticker = window.requestAnimationFrame(this.tick)
const current = Date.now()
this.delta = current - this.current
this.elapsed = current - this.start
this.current = current
if(this.delta > 60)
{
this.delta = 60
}
this.trigger('tick')
}
/**
* Stop
*/
stop()
{
window.cancelAnimationFrame(this.ticker)
}
}

View File

@ -0,0 +1,309 @@
import * as THREE from 'three'
import { TweenLite } from 'gsap/TweenLite'
import { Back } from 'gsap/EasePack'
import EventEmitter from '../Utils/EventEmitter.js'
import AreaFloorBorderBufferGeometry from '../Geometries/AreaFloorBorderBufferGeometry.js'
import AreaFenceBufferGeometry from '../Geometries/AreaFenceBufferGeometry.js'
import AreaFenceMaterial from '../Materials/AreaFence.js'
import AreaFloorBordereMaterial from '../Materials/AreaFloorBorder.js'
export default class Area extends EventEmitter
{
constructor(_options)
{
super()
// Options
this.config = _options.config
this.renderer = _options.renderer
this.resources = _options.resources
this.car = _options.car
this.sounds = _options.sounds
this.time = _options.time
this.position = _options.position
this.halfExtents = _options.halfExtents
this.hasKey = _options.hasKey
this.testCar = _options.testCar
this.active = _options.active
// Set up
this.container = new THREE.Object3D()
this.container.position.x = this.position.x
this.container.position.y = this.position.y
this.container.matrixAutoUpdate = false
this.container.updateMatrix()
this.initialTestCar = this.testCar
this.isIn = false
this.setFloorBorder()
this.setFence()
this.setInteractions()
if(this.hasKey)
{
this.setKey()
}
}
activate()
{
this.active = true
if(this.isIn)
{
this.in()
}
}
deactivate()
{
this.active = false
if(this.isIn)
{
this.out()
}
}
setFloorBorder()
{
this.floorBorder = {}
this.floorBorder.geometry = new AreaFloorBorderBufferGeometry(this.halfExtents.x * 2, this.halfExtents.y * 2, 0.25)
this.floorBorder.material = new AreaFloorBordereMaterial()
this.floorBorder.material.uniforms.uColor.value = new THREE.Color(0xffffff)
this.floorBorder.material.uniforms.uAlpha.value = 0.5
this.floorBorder.material.uniforms.uLoadProgress.value = 1
this.floorBorder.material.uniforms.uProgress.value = 1
this.floorBorder.mesh = new THREE.Mesh(this.floorBorder.geometry, this.floorBorder.material)
this.floorBorder.mesh.matrixAutoUpdate = false
this.container.add(this.floorBorder.mesh)
}
setFence()
{
// Set up
this.fence = {}
this.fence.depth = 0.5
this.fence.offset = 0.5
// Geometry
this.fence.geometry = new AreaFenceBufferGeometry(this.halfExtents.x * 2, this.halfExtents.y * 2, this.fence.depth)
// Material
// this.fence.material = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true, transparent: true, opacity: 0.5 })
this.fence.material = new AreaFenceMaterial()
this.fence.material.uniforms.uBorderAlpha.value = 0.5
this.fence.material.uniforms.uStrikeAlpha.value = 0.25
// Mesh
this.fence.mesh = new THREE.Mesh(this.fence.geometry, this.fence.material)
this.fence.mesh.position.z = - this.fence.depth
this.container.add(this.fence.mesh)
// Time tick
this.time.on('tick', () =>
{
this.fence.material.uniforms.uTime.value = this.time.elapsed
})
}
setKey()
{
this.key = {}
this.key.hiddenZ = 1.5
this.key.shownZ = 2.5
// Container
this.key.container = new THREE.Object3D()
this.key.container.position.z = this.key.hiddenZ
this.container.add(this.key.container)
// Enter
this.key.enter = {}
this.key.enter.size = 1.4
this.key.enter.geometry = new THREE.PlaneBufferGeometry(this.key.enter.size, this.key.enter.size / 4, 1, 1)
this.key.enter.texture = this.resources.items.areaEnterTexture
this.key.enter.texture.magFilter = THREE.NearestFilter
this.key.enter.texture.minFilter = THREE.LinearFilter
this.key.enter.material = new THREE.MeshBasicMaterial({ color: 0xffffff, alphaMap: this.key.enter.texture, transparent: true, opacity: 0, depthWrite: false })
this.key.enter.mesh = new THREE.Mesh(this.key.enter.geometry, this.key.enter.material)
this.key.enter.mesh.rotation.x = Math.PI * 0.5
this.key.enter.mesh.position.x = this.key.enter.size * 0.75
this.key.enter.mesh.matrixAutoUpdate = false
this.key.enter.mesh.updateMatrix()
this.key.container.add(this.key.enter.mesh)
// Icon
this.key.icon = {}
this.key.icon.size = 0.75
this.key.icon.geometry = new THREE.PlaneBufferGeometry(this.key.icon.size, this.key.icon.size, 1, 1)
this.key.icon.texture = this.resources.items.areaKeyEnterTexture
this.key.icon.texture.magFilter = THREE.NearestFilter
this.key.icon.texture.minFilter = THREE.LinearFilter
this.key.icon.material = new THREE.MeshBasicMaterial({ color: 0xffffff, alphaMap: this.key.icon.texture, transparent: true, opacity: 0, depthWrite: false })
this.key.icon.mesh = new THREE.Mesh(this.key.icon.geometry, this.key.icon.material)
this.key.icon.mesh.rotation.x = Math.PI * 0.5
this.key.icon.mesh.position.x = - this.key.enter.size * 0.15
this.key.icon.mesh.matrixAutoUpdate = false
this.key.icon.mesh.updateMatrix()
this.key.container.add(this.key.icon.mesh)
}
interact(_showKey = true)
{
// Not active
if(!this.active)
{
return
}
// Kill tweens
TweenLite.killTweensOf(this.fence.mesh.position)
TweenLite.killTweensOf(this.floorBorder.material.uniforms.uAlpha)
TweenLite.killTweensOf(this.fence.material.uniforms.uBorderAlpha)
if(this.hasKey)
{
TweenLite.killTweensOf(this.key.container.position)
TweenLite.killTweensOf(this.key.icon.material)
TweenLite.killTweensOf(this.key.enter.material)
}
// Animate
TweenLite.to(this.fence.mesh.position, 0.05, { z: 0, onComplete: () =>
{
TweenLite.to(this.fence.mesh.position, 0.25, { z: 0.5, ease: Back.easeOut.config(2) })
TweenLite.fromTo(this.floorBorder.material.uniforms.uAlpha, 1.5, { value: 1 }, { value: 0.5 })
TweenLite.fromTo(this.fence.material.uniforms.uBorderAlpha, 1.5, { value: 1 }, { value: 0.5 })
} })
if(this.hasKey && _showKey)
{
this.key.container.position.z = this.key.shownZ
TweenLite.fromTo(this.key.icon.material, 1.5, { opacity: 1 }, { opacity: 0.5 })
TweenLite.fromTo(this.key.enter.material, 1.5, { opacity: 1 }, { opacity: 0.5 })
}
// Play sound
this.sounds.play('uiArea')
this.trigger('interact')
}
in(_showKey = true)
{
this.isIn = true
// Not active
if(!this.active)
{
return
}
// Fence
TweenLite.killTweensOf(this.fence.mesh.position)
TweenLite.to(this.fence.mesh.position, 0.35, { z: this.fence.offset, ease: Back.easeOut.config(3) })
// Key
if(this.hasKey)
{
TweenLite.killTweensOf(this.key.container.position)
TweenLite.killTweensOf(this.key.icon.material)
TweenLite.killTweensOf(this.key.enter.material)
// Animate
if(_showKey)
{
TweenLite.to(this.key.container.position, 0.35, { z: this.key.shownZ, ease: Back.easeOut.config(3), delay: 0.1 })
TweenLite.to(this.key.icon.material, 0.35, { opacity: 0.5, ease: Back.easeOut.config(3), delay: 0.1 })
TweenLite.to(this.key.enter.material, 0.35, { opacity: 0.5, ease: Back.easeOut.config(3), delay: 0.1 })
}
}
// Change cursor
if(!this.config.touch)
{
this.renderer.domElement.classList.add('has-cursor-pointer')
}
this.trigger('in')
}
out()
{
this.isIn = false
// Fence
TweenLite.killTweensOf(this.fence.mesh.position)
TweenLite.to(this.fence.mesh.position, 0.35, { z: - this.fence.depth, ease: Back.easeIn.config(4) })
// Key
if(this.hasKey)
{
TweenLite.killTweensOf(this.key.container.position)
TweenLite.killTweensOf(this.key.icon.material)
TweenLite.killTweensOf(this.key.enter.material)
TweenLite.to(this.key.container.position, 0.35, { z: this.key.hiddenZ, ease: Back.easeIn.config(4), delay: 0.1 })
TweenLite.to(this.key.icon.material, 0.35, { opacity: 0, ease: Back.easeIn.config(4), delay: 0.1 })
TweenLite.to(this.key.enter.material, 0.35, { opacity: 0, ease: Back.easeIn.config(4), delay: 0.1 })
}
// Change cursor
if(!this.config.touch)
{
this.renderer.domElement.classList.remove('has-cursor-pointer')
}
this.trigger('out')
}
setInteractions()
{
this.mouseMesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(this.halfExtents.x * 2, this.halfExtents.y * 2, 1, 1),
new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 })
)
this.mouseMesh.position.z = - 0.01
this.mouseMesh.matrixAutoUpdate = false
this.mouseMesh.updateMatrix()
this.container.add(this.mouseMesh)
this.time.on('tick', () =>
{
if(this.testCar)
{
const isIn = Math.abs(this.car.position.x - this.position.x) < Math.abs(this.halfExtents.x) && Math.abs(this.car.position.y - this.position.y) < Math.abs(this.halfExtents.y)
if(isIn !== this.isIn)
{
if(isIn)
{
this.in(!this.config.touch)
}
else
{
this.out()
}
}
}
})
window.addEventListener('keydown', (_event) =>
{
if((_event.key === 'f' || _event.key === 'e' || _event.key === 'Enter') && this.isIn)
{
this.interact()
}
})
}
}

View File

@ -0,0 +1,132 @@
import * as THREE from 'three'
import Area from './Area.js'
export default class Areas
{
constructor(_options)
{
// Options
this.config = _options.config
this.resources = _options.resources
this.car = _options.car
this.sounds = _options.sounds
this.renderer = _options.renderer
this.camera = _options.camera
this.time = _options.time
this.debug = _options.debug
// Set up
this.items = []
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.setMouse()
}
setMouse()
{
// Set up
this.mouse = {}
this.mouse.raycaster = new THREE.Raycaster()
this.mouse.coordinates = new THREE.Vector2()
this.mouse.currentArea = null
this.mouse.needsUpdate = false
// Mouse move event
window.addEventListener('mousemove', (_event) =>
{
this.mouse.coordinates.x = (_event.clientX / window.innerWidth) * 2 - 1
this.mouse.coordinates.y = - (_event.clientY / window.innerHeight) * 2 + 1
this.mouse.needsUpdate = true
})
// Mouse click event
window.addEventListener('mousedown', () =>
{
if(this.mouse.currentArea)
{
this.mouse.currentArea.interact(false)
}
})
// Touch
this.renderer.domElement.addEventListener('touchstart', (_event) =>
{
this.mouse.coordinates.x = (_event.changedTouches[0].clientX / window.innerWidth) * 2 - 1
this.mouse.coordinates.y = - (_event.changedTouches[0].clientY / window.innerHeight) * 2 + 1
this.mouse.needsUpdate = true
})
// Time tick event
this.time.on('tick', () =>
{
// Only update if needed
if(this.mouse.needsUpdate)
{
this.mouse.needsUpdate = false
// Set up
this.mouse.raycaster.setFromCamera(this.mouse.coordinates, this.camera.instance)
const objects = this.items.map((_area) => _area.mouseMesh)
const intersects = this.mouse.raycaster.intersectObjects(objects)
// Intersections found
if(intersects.length)
{
// Find the area
const area = this.items.find((_area) => _area.mouseMesh === intersects[0].object)
// Area did change
if(area !== this.mouse.currentArea)
{
// Was previously over an area
if(this.mouse.currentArea !== null)
{
// Play out
this.mouse.currentArea.out()
this.mouse.currentArea.testCar = this.mouse.currentArea.initialTestCar
}
// Play in
this.mouse.currentArea = area
this.mouse.currentArea.in(false)
this.mouse.currentArea.testCar = false
}
}
// No intersections found but was previously over an area
else if(this.mouse.currentArea !== null)
{
// Play out
this.mouse.currentArea.out()
this.mouse.currentArea.testCar = this.mouse.currentArea.initialTestCar
this.mouse.currentArea = null
}
}
})
}
add(_options)
{
const area = new Area({
config: this.config,
renderer: this.renderer,
resources: this.resources,
car: this.car,
sounds: this.sounds,
time: this.time,
hasKey: true,
testCar: true,
active: true,
..._options
})
this.container.add(area.container)
this.items.push(area)
return area
}
}

388
src/javascript/World/Car.js Normal file
View File

@ -0,0 +1,388 @@
import * as THREE from 'three'
import CANNON from 'cannon'
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
export default class Car
{
constructor(_options)
{
// Options
this.time = _options.time
this.resources = _options.resources
this.objects = _options.objects
this.physics = _options.physics
this.shadows = _options.shadows
this.materials = _options.materials
this.controls = _options.controls
this.sounds = _options.sounds
this.renderer = _options.renderer
this.camera = _options.camera
this.debug = _options.debug
this.config = _options.config
// Set up
this.container = new THREE.Object3D()
this.position = new THREE.Vector3()
// Debug
if(this.debug)
{
this.debugFolder = this.debug.addFolder('car')
// this.debugFolder.open()
}
this.setModels()
this.setMovement()
this.setChassis()
this.setAntena()
this.setBackLights()
this.setWheels()
this.setTransformControls()
this.setShootingBall()
this.setKlaxon()
}
setModels()
{
this.models = {}
// Cyber truck
if(this.config.cyberTruck)
{
this.models.chassis = this.resources.items.carCyberTruckChassis
this.models.antena = this.resources.items.carCyberTruckAntena
this.models.backLightsBrake = this.resources.items.carCyberTruckBackLightsBrake
this.models.backLightsReverse = this.resources.items.carCyberTruckBackLightsReverse
this.models.wheel = this.resources.items.carCyberTruckWheel
}
// Default
else
{
this.models.chassis = this.resources.items.carDefaultChassis
this.models.antena = this.resources.items.carDefaultAntena
// this.models.bunnyEarLeft = this.resources.items.carDefaultBunnyEarLeft
// this.models.bunnyEarRight = this.resources.items.carDefaultBunnyEarRight
this.models.backLightsBrake = this.resources.items.carDefaultBackLightsBrake
this.models.backLightsReverse = this.resources.items.carDefaultBackLightsReverse
this.models.wheel = this.resources.items.carDefaultWheel
}
}
setMovement()
{
this.movement = {}
this.movement.speed = new THREE.Vector3()
this.movement.localSpeed = new THREE.Vector3()
this.movement.acceleration = new THREE.Vector3()
this.movement.localAcceleration = new THREE.Vector3()
// Time tick
this.time.on('tick', () =>
{
// Movement
const movementSpeed = new THREE.Vector3()
movementSpeed.copy(this.chassis.object.position).sub(this.chassis.oldPosition)
this.movement.acceleration = movementSpeed.clone().sub(this.movement.speed)
this.movement.speed.copy(movementSpeed)
this.movement.localSpeed = this.movement.speed.clone().applyAxisAngle(new THREE.Vector3(0, 0, 1), - this.chassis.object.rotation.z)
this.movement.localAcceleration = this.movement.acceleration.clone().applyAxisAngle(new THREE.Vector3(0, 0, 1), - this.chassis.object.rotation.z)
// Sound
this.sounds.engine.speed = this.movement.localSpeed.x
this.sounds.engine.acceleration = this.controls.actions.up ? (this.controls.actions.boost ? 1 : 0.5) : 0
if(this.movement.localAcceleration.x > 0.01)
{
this.sounds.play('screech')
}
})
}
setChassis()
{
this.chassis = {}
this.chassis.offset = new THREE.Vector3(0, 0, - 0.28)
this.chassis.object = this.objects.getConvertedMesh(this.models.chassis.scene.children)
this.chassis.object.position.copy(this.physics.car.chassis.body.position)
this.chassis.oldPosition = this.chassis.object.position.clone()
this.container.add(this.chassis.object)
this.shadows.add(this.chassis.object, { sizeX: 3, sizeY: 2, offsetZ: 0.2 })
// Time tick
this.time.on('tick', () =>
{
// Save old position for movement calculation
this.chassis.oldPosition = this.chassis.object.position.clone()
// Update if mode physics
if(!this.transformControls.enabled)
{
this.chassis.object.position.copy(this.physics.car.chassis.body.position).add(this.chassis.offset)
this.chassis.object.quaternion.copy(this.physics.car.chassis.body.quaternion)
}
// Update position
this.position.copy(this.chassis.object.position)
})
}
setAntena()
{
this.antena = {}
this.antena.speedStrength = 10
this.antena.damping = 0.035
this.antena.pullBackStrength = 0.02
this.antena.object = this.objects.getConvertedMesh(this.models.antena.scene.children)
this.chassis.object.add(this.antena.object)
// this.antena.bunnyEarLeft = this.objects.getConvertedMesh(this.models.bunnyEarLeft.scene.children)
// this.chassis.object.add(this.antena.bunnyEarLeft)
// this.antena.bunnyEarRight = this.objects.getConvertedMesh(this.models.bunnyEarRight.scene.children)
// this.chassis.object.add(this.antena.bunnyEarRight)
this.antena.speed = new THREE.Vector2()
this.antena.absolutePosition = new THREE.Vector2()
this.antena.localPosition = new THREE.Vector2()
// Time tick
this.time.on('tick', () =>
{
const max = 1
const accelerationX = Math.min(Math.max(this.movement.acceleration.x, - max), max)
const accelerationY = Math.min(Math.max(this.movement.acceleration.y, - max), max)
this.antena.speed.x -= accelerationX * this.antena.speedStrength
this.antena.speed.y -= accelerationY * this.antena.speedStrength
const position = this.antena.absolutePosition.clone()
const pullBack = position.negate().multiplyScalar(position.length() * this.antena.pullBackStrength)
this.antena.speed.add(pullBack)
this.antena.speed.x *= 1 - this.antena.damping
this.antena.speed.y *= 1 - this.antena.damping
this.antena.absolutePosition.add(this.antena.speed)
this.antena.localPosition.copy(this.antena.absolutePosition)
this.antena.localPosition.rotateAround(new THREE.Vector2(), - this.chassis.object.rotation.z)
this.antena.object.rotation.y = this.antena.localPosition.x * 0.1
this.antena.object.rotation.x = this.antena.localPosition.y * 0.1
// this.antena.bunnyEarLeft.rotation.y = this.antena.localPosition.x * 0.1
// this.antena.bunnyEarLeft.rotation.x = this.antena.localPosition.y * 0.1
// this.antena.bunnyEarRight.rotation.y = this.antena.localPosition.x * 0.1
// this.antena.bunnyEarRight.rotation.x = this.antena.localPosition.y * 0.1
})
// Debug
if(this.debug)
{
const folder = this.debugFolder.addFolder('antena')
folder.open()
folder.add(this.antena, 'speedStrength').step(0.001).min(0).max(50)
folder.add(this.antena, 'damping').step(0.0001).min(0).max(0.1)
folder.add(this.antena, 'pullBackStrength').step(0.0001).min(0).max(0.1)
}
}
setBackLights()
{
this.backLightsBrake = {}
this.backLightsBrake.material = this.materials.pures.items.red.clone()
this.backLightsBrake.material.transparent = true
this.backLightsBrake.material.opacity = 0.5
this.backLightsBrake.object = this.objects.getConvertedMesh(this.models.backLightsBrake.scene.children)
for(const _child of this.backLightsBrake.object.children)
{
_child.material = this.backLightsBrake.material
}
this.chassis.object.add(this.backLightsBrake.object)
// Back lights brake
this.backLightsReverse = {}
this.backLightsReverse.material = this.materials.pures.items.yellow.clone()
this.backLightsReverse.material.transparent = true
this.backLightsReverse.material.opacity = 0.5
this.backLightsReverse.object = this.objects.getConvertedMesh(this.models.backLightsReverse.scene.children)
for(const _child of this.backLightsReverse.object.children)
{
_child.material = this.backLightsReverse.material
}
this.chassis.object.add(this.backLightsReverse.object)
// Time tick
this.time.on('tick', () =>
{
this.backLightsBrake.material.opacity = this.physics.controls.actions.brake ? 1 : 0.5
this.backLightsReverse.material.opacity = this.physics.controls.actions.down ? 1 : 0.5
})
}
setWheels()
{
this.wheels = {}
this.wheels.object = this.objects.getConvertedMesh(this.models.wheel.scene.children)
this.wheels.items = []
for(let i = 0; i < 4; i++)
{
const object = this.wheels.object.clone()
this.wheels.items.push(object)
this.container.add(object)
}
// Time tick
this.time.on('tick', () =>
{
if(!this.transformControls.enabled)
{
for(const _wheelKey in this.physics.car.wheels.bodies)
{
const wheelBody = this.physics.car.wheels.bodies[_wheelKey]
const wheelObject = this.wheels.items[_wheelKey]
wheelObject.position.copy(wheelBody.position)
wheelObject.quaternion.copy(wheelBody.quaternion)
}
}
})
}
setTransformControls()
{
this.transformControls = new TransformControls(this.camera.instance, this.renderer.domElement)
this.transformControls.size = 0.5
this.transformControls.attach(this.chassis.object)
this.transformControls.enabled = false
this.transformControls.visible = this.transformControls.enabled
document.addEventListener('keydown', (_event) =>
{
if(this.mode === 'transformControls')
{
if(_event.key === 'r')
{
this.transformControls.setMode('rotate')
}
else if(_event.key === 'g')
{
this.transformControls.setMode('translate')
}
}
})
this.transformControls.addEventListener('dragging-changed', (_event) =>
{
this.camera.orbitControls.enabled = !_event.value
})
this.container.add(this.transformControls)
if(this.debug)
{
const folder = this.debugFolder.addFolder('controls')
folder.open()
folder.add(this.transformControls, 'enabled').onChange(() =>
{
this.transformControls.visible = this.transformControls.enabled
})
}
}
setShootingBall()
{
if(!this.config.cyberTruck)
{
return
}
window.addEventListener('keydown', (_event) =>
{
if(_event.key === 'b')
{
const angle = Math.random() * Math.PI * 2
const distance = 10
const x = this.position.x + Math.cos(angle) * distance
const y = this.position.y + Math.sin(angle) * distance
const z = 2 + 2 * Math.random()
const bowlingBall = this.objects.add({
base: this.resources.items.bowlingBallBase.scene,
collision: this.resources.items.bowlingBallCollision.scene,
offset: new THREE.Vector3(x, y, z),
rotation: new THREE.Euler(Math.PI * 0.5, 0, 0),
duplicated: true,
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.15, alpha: 0.35 },
mass: 5,
soundName: 'bowlingBall',
sleep: false
})
const carPosition = new CANNON.Vec3(this.position.x, this.position.y, this.position.z + 1)
let direction = carPosition.vsub(bowlingBall.collision.body.position)
direction.normalize()
direction = direction.scale(100)
bowlingBall.collision.body.applyImpulse(direction, bowlingBall.collision.body.position)
}
})
}
setKlaxon()
{
this.klaxon = {}
this.klaxon.waitDuration = 150
this.klaxon.can = true
window.addEventListener('keydown', (_event) =>
{
// Play horn sound
if(_event.key === 'h' && this.klaxon.can)
{
this.klaxon.can = false
window.setTimeout(() =>
{
this.klaxon.can = true
}, this.klaxon.waitDuration)
this.physics.car.jump(false, 20)
this.sounds.play(Math.random() < 0.002 ? 'carHorn2' : 'carHorn1')
}
// Rain horns
if(_event.key === 'k')
{
const x = this.position.x + (Math.random() - 0.5) * 3
const y = this.position.y + (Math.random() - 0.5) * 3
const z = 6 + 2 * Math.random()
this.objects.add({
base: this.resources.items.hornBase.scene,
collision: this.resources.items.hornCollision.scene,
offset: new THREE.Vector3(x, y, z),
rotation: new THREE.Euler(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2),
duplicated: true,
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.15, alpha: 0.35 },
mass: 5,
soundName: 'horn',
sleep: false
})
}
})
}
}

View File

@ -0,0 +1,653 @@
import mobileDoubleTriangle from '../../images/mobile/doubleTriangle.png'
import mobileTriangle from '../../images/mobile/triangle.png'
import mobileCross from '../../images/mobile/cross.png'
import EventEmitter from '../Utils/EventEmitter'
import { TweenLite } from 'gsap/TweenLite'
export default class Controls extends EventEmitter
{
constructor(_options)
{
super()
this.config = _options.config
this.sizes = _options.sizes
this.time = _options.time
this.camera = _options.camera
this.sounds = _options.sounds
this.setActions()
this.setKeyboard()
}
setActions()
{
this.actions = {}
this.actions.up = false
this.actions.right = false
this.actions.down = false
this.actions.left = false
this.actions.brake = false
this.actions.boost = false
document.addEventListener('visibilitychange', () =>
{
if(!document.hidden)
{
this.actions.up = false
this.actions.right = false
this.actions.down = false
this.actions.left = false
this.actions.brake = false
this.actions.boost = false
}
})
}
setKeyboard()
{
this.keyboard = {}
this.keyboard.events = {}
this.keyboard.events.keyDown = (_event) =>
{
switch(_event.key)
{
case 'ArrowUp':
case 'z':
case 'w':
this.camera.pan.reset()
this.actions.up = true
break
case 'ArrowRight':
case 'd':
this.actions.right = true
break
case 'ArrowDown':
case 's':
this.camera.pan.reset()
this.actions.down = true
break
case 'ArrowLeft':
case 'q':
case 'a':
this.actions.left = true
break
case 'Control':
case ' ':
this.actions.brake = true
break
case 'Shift':
this.actions.boost = true
break
// case ' ':
// this.jump(true)
// break
}
}
this.keyboard.events.keyUp = (_event) =>
{
switch(_event.key)
{
case 'ArrowUp':
case 'z':
case 'w':
this.actions.up = false
break
case 'ArrowRight':
case 'd':
this.actions.right = false
break
case 'ArrowDown':
case 's':
this.actions.down = false
break
case 'ArrowLeft':
case 'q':
case 'a':
this.actions.left = false
break
case 'Control':
case ' ':
this.actions.brake = false
break
case 'Shift':
this.actions.boost = false
break
case 'r':
this.trigger('action', ['reset'])
break
}
}
document.addEventListener('keydown', this.keyboard.events.keyDown)
document.addEventListener('keyup', this.keyboard.events.keyUp)
}
setTouch()
{
this.touch = {}
/**
* Joystick
*/
this.touch.joystick = {}
this.touch.joystick.active = false
// Element
this.touch.joystick.$element = document.createElement('div')
this.touch.joystick.$element.style.userSelect = 'none'
this.touch.joystick.$element.style.position = 'fixed'
this.touch.joystick.$element.style.bottom = '10px'
this.touch.joystick.$element.style.left = '10px'
this.touch.joystick.$element.style.width = '170px'
this.touch.joystick.$element.style.height = '170px'
this.touch.joystick.$element.style.borderRadius = '50%'
this.touch.joystick.$element.style.transition = 'opacity 0.3s 0.0s'
this.touch.joystick.$element.style.willChange = 'opacity'
this.touch.joystick.$element.style.opacity = '0'
// this.touch.joystick.$element.style.backgroundColor = '#ff0000'
document.body.appendChild(this.touch.joystick.$element)
this.touch.joystick.$cursor = document.createElement('div')
this.touch.joystick.$cursor.style.position = 'absolute'
this.touch.joystick.$cursor.style.top = 'calc(50% - 30px)'
this.touch.joystick.$cursor.style.left = 'calc(50% - 30px)'
this.touch.joystick.$cursor.style.width = '60px'
this.touch.joystick.$cursor.style.height = '60px'
this.touch.joystick.$cursor.style.border = '2px solid #ffffff'
this.touch.joystick.$cursor.style.borderRadius = '50%'
this.touch.joystick.$cursor.style.boxSizing = 'border-box'
this.touch.joystick.$cursor.style.pointerEvents = 'none'
this.touch.joystick.$cursor.style.willChange = 'transform'
this.touch.joystick.$element.appendChild(this.touch.joystick.$cursor)
this.touch.joystick.$limit = document.createElement('div')
this.touch.joystick.$limit.style.position = 'absolute'
this.touch.joystick.$limit.style.top = 'calc(50% - 75px)'
this.touch.joystick.$limit.style.left = 'calc(50% - 75px)'
this.touch.joystick.$limit.style.width = '150px'
this.touch.joystick.$limit.style.height = '150px'
this.touch.joystick.$limit.style.border = '2px solid #ffffff'
this.touch.joystick.$limit.style.borderRadius = '50%'
this.touch.joystick.$limit.style.opacity = '0.25'
this.touch.joystick.$limit.style.pointerEvents = 'none'
this.touch.joystick.$limit.style.boxSizing = 'border-box'
this.touch.joystick.$element.appendChild(this.touch.joystick.$limit)
// Angle
this.touch.joystick.angle = {}
this.touch.joystick.angle.offset = Math.PI * 0.18
this.touch.joystick.angle.center = {}
this.touch.joystick.angle.center.x = 0
this.touch.joystick.angle.center.y = 0
this.touch.joystick.angle.current = {}
this.touch.joystick.angle.current.x = 0
this.touch.joystick.angle.current.y = 0
this.touch.joystick.angle.originalValue = 0
this.touch.joystick.angle.value = - Math.PI * 0.5
// Resize
this.touch.joystick.resize = () =>
{
const boundings = this.touch.joystick.$element.getBoundingClientRect()
this.touch.joystick.angle.center.x = boundings.left + boundings.width * 0.5
this.touch.joystick.angle.center.y = boundings.top + boundings.height * 0.5
}
this.sizes.on('resize', this.touch.joystick.resize)
this.touch.joystick.resize()
// Time tick
this.time.on('tick', () =>
{
// Joystick active
if(this.touch.joystick.active)
{
// Calculate joystick angle
this.touch.joystick.angle.originalValue = - Math.atan2(
this.touch.joystick.angle.current.y - this.touch.joystick.angle.center.y,
this.touch.joystick.angle.current.x - this.touch.joystick.angle.center.x
)
this.touch.joystick.angle.value = this.touch.joystick.angle.originalValue + this.touch.joystick.angle.offset
// Update joystick
const distance = Math.hypot(this.touch.joystick.angle.current.y - this.touch.joystick.angle.center.y, this.touch.joystick.angle.current.x - this.touch.joystick.angle.center.x)
let radius = distance
if(radius > 20)
{
radius = 20 + Math.log(distance - 20) * 5
}
if(radius > 43)
{
radius = 43
}
const cursorX = Math.sin(this.touch.joystick.angle.originalValue + Math.PI * 0.5) * radius
const cursorY = Math.cos(this.touch.joystick.angle.originalValue + Math.PI * 0.5) * radius
this.touch.joystick.$cursor.style.transform = `translateX(${cursorX}px) translateY(${cursorY}px)`
}
})
// Events
this.touch.joystick.events = {}
this.touch.joystick.touchIdentifier = null
this.touch.joystick.events.touchstart = (_event) =>
{
_event.preventDefault()
const touch = _event.changedTouches[0]
if(touch)
{
this.touch.joystick.active = true
this.touch.joystick.touchIdentifier = touch.identifier
this.touch.joystick.angle.current.x = touch.clientX
this.touch.joystick.angle.current.y = touch.clientY
this.touch.joystick.$limit.style.opacity = '0.5'
document.addEventListener('touchend', this.touch.joystick.events.touchend)
document.addEventListener('touchmove', this.touch.joystick.events.touchmove, { passive: false })
this.trigger('joystickStart')
}
}
this.touch.joystick.events.touchmove = (_event) =>
{
_event.preventDefault()
const touches = [..._event.changedTouches]
const touch = touches.find((_touch) => _touch.identifier === this.touch.joystick.touchIdentifier)
if(touch)
{
this.touch.joystick.angle.current.x = touch.clientX
this.touch.joystick.angle.current.y = touch.clientY
this.trigger('joystickMove')
}
}
this.touch.joystick.events.touchend = (_event) =>
{
const touches = [..._event.changedTouches]
const touch = touches.find((_touch) => _touch.identifier === this.touch.joystick.touchIdentifier)
if(touch)
{
this.touch.joystick.active = false
this.touch.joystick.$limit.style.opacity = '0.25'
this.touch.joystick.$cursor.style.transform = 'translateX(0px) translateY(0px)'
document.removeEventListener('touchend', this.touch.joystick.events.touchend)
this.trigger('joystickEnd')
}
}
this.touch.joystick.$element.addEventListener('touchstart', this.touch.joystick.events.touchstart, { passive: false })
/**
* Boost
*/
this.touch.boost = {}
// Element
this.touch.boost.$element = document.createElement('div')
this.touch.boost.$element.style.userSelect = 'none'
this.touch.boost.$element.style.position = 'fixed'
this.touch.boost.$element.style.bottom = 'calc(70px * 3 + 15px)'
this.touch.boost.$element.style.right = '0px'
this.touch.boost.$element.style.width = '95px'
this.touch.boost.$element.style.height = '70px'
this.touch.boost.$element.style.transition = 'opacity 0.3s 0.4s'
this.touch.boost.$element.style.willChange = 'opacity'
this.touch.boost.$element.style.opacity = '0'
// this.touch.boost.$element.style.backgroundColor = '#00ff00'
document.body.appendChild(this.touch.boost.$element)
this.touch.boost.$border = document.createElement('div')
this.touch.boost.$border.style.position = 'absolute'
this.touch.boost.$border.style.top = 'calc(50% - 30px)'
this.touch.boost.$border.style.left = 'calc(50% - 30px)'
this.touch.boost.$border.style.width = '60px'
this.touch.boost.$border.style.height = '60px'
this.touch.boost.$border.style.border = '2px solid #ffffff'
this.touch.boost.$border.style.borderRadius = '10px'
this.touch.boost.$border.style.boxSizing = 'border-box'
this.touch.boost.$border.style.opacity = '0.25'
this.touch.boost.$border.style.willChange = 'opacity'
this.touch.boost.$element.appendChild(this.touch.boost.$border)
this.touch.boost.$icon = document.createElement('div')
this.touch.boost.$icon.style.position = 'absolute'
this.touch.boost.$icon.style.top = 'calc(50% - 13px)'
this.touch.boost.$icon.style.left = 'calc(50% - 11px)'
this.touch.boost.$icon.style.width = '22px'
this.touch.boost.$icon.style.height = '26px'
this.touch.boost.$icon.style.backgroundImage = `url(${mobileDoubleTriangle})`
this.touch.boost.$icon.style.backgroundSize = 'cover'
this.touch.boost.$element.appendChild(this.touch.boost.$icon)
// Events
this.touch.boost.events = {}
this.touch.boost.touchIdentifier = null
this.touch.boost.events.touchstart = (_event) =>
{
_event.preventDefault()
const touch = _event.changedTouches[0]
if(touch)
{
this.camera.pan.reset()
this.touch.boost.touchIdentifier = touch.identifier
this.actions.up = true
this.actions.boost = true
this.touch.boost.$border.style.opacity = '0.5'
document.addEventListener('touchend', this.touch.boost.events.touchend)
}
}
this.touch.boost.events.touchend = (_event) =>
{
const touches = [..._event.changedTouches]
const touch = touches.find((_touch) => _touch.identifier === this.touch.boost.touchIdentifier)
if(touch)
{
this.actions.up = false
this.actions.boost = false
this.touch.boost.$border.style.opacity = '0.25'
document.removeEventListener('touchend', this.touch.boost.events.touchend)
}
}
this.touch.boost.$element.addEventListener('touchstart', this.touch.boost.events.touchstart)
/**
* Forward
*/
this.touch.forward = {}
// Element
this.touch.forward.$element = document.createElement('div')
this.touch.forward.$element.style.userSelect = 'none'
this.touch.forward.$element.style.position = 'fixed'
this.touch.forward.$element.style.bottom = 'calc(70px * 2 + 15px)'
this.touch.forward.$element.style.right = '0px'
this.touch.forward.$element.style.width = '95px'
this.touch.forward.$element.style.height = '70px'
this.touch.forward.$element.style.transition = 'opacity 0.3s 0.3s'
this.touch.forward.$element.style.willChange = 'opacity'
this.touch.forward.$element.style.opacity = '0'
// this.touch.forward.$element.style.backgroundColor = '#00ff00'
document.body.appendChild(this.touch.forward.$element)
this.touch.forward.$border = document.createElement('div')
this.touch.forward.$border.style.position = 'absolute'
this.touch.forward.$border.style.top = 'calc(50% - 30px)'
this.touch.forward.$border.style.left = 'calc(50% - 30px)'
this.touch.forward.$border.style.width = '60px'
this.touch.forward.$border.style.height = '60px'
this.touch.forward.$border.style.border = '2px solid #ffffff'
this.touch.forward.$border.style.borderRadius = '10px'
this.touch.forward.$border.style.boxSizing = 'border-box'
this.touch.forward.$border.style.opacity = '0.25'
this.touch.forward.$border.style.willChange = 'opacity'
this.touch.forward.$element.appendChild(this.touch.forward.$border)
this.touch.forward.$icon = document.createElement('div')
this.touch.forward.$icon.style.position = 'absolute'
this.touch.forward.$icon.style.top = 'calc(50% - 9px)'
this.touch.forward.$icon.style.left = 'calc(50% - 11px)'
this.touch.forward.$icon.style.width = '22px'
this.touch.forward.$icon.style.height = '18px'
this.touch.forward.$icon.style.backgroundImage = `url(${mobileTriangle})`
this.touch.forward.$icon.style.backgroundSize = 'cover'
this.touch.forward.$element.appendChild(this.touch.forward.$icon)
// Events
this.touch.forward.events = {}
this.touch.forward.touchIdentifier = null
this.touch.forward.events.touchstart = (_event) =>
{
_event.preventDefault()
const touch = _event.changedTouches[0]
if(touch)
{
this.camera.pan.reset()
this.touch.forward.touchIdentifier = touch.identifier
this.actions.up = true
this.touch.forward.$border.style.opacity = '0.5'
document.addEventListener('touchend', this.touch.forward.events.touchend)
}
}
this.touch.forward.events.touchend = (_event) =>
{
const touches = [..._event.changedTouches]
const touch = touches.find((_touch) => _touch.identifier === this.touch.forward.touchIdentifier)
if(touch)
{
this.actions.up = false
this.touch.forward.$border.style.opacity = '0.25'
document.removeEventListener('touchend', this.touch.forward.events.touchend)
}
}
this.touch.forward.$element.addEventListener('touchstart', this.touch.forward.events.touchstart)
/**
* Brake
*/
this.touch.brake = {}
// Element
this.touch.brake.$element = document.createElement('div')
this.touch.brake.$element.style.userSelect = 'none'
this.touch.brake.$element.style.position = 'fixed'
this.touch.brake.$element.style.bottom = 'calc(70px + 15px)'
this.touch.brake.$element.style.right = '0px'
this.touch.brake.$element.style.width = '95px'
this.touch.brake.$element.style.height = '70px'
this.touch.brake.$element.style.transition = 'opacity 0.3s 0.2s'
this.touch.brake.$element.style.willChange = 'opacity'
this.touch.brake.$element.style.opacity = '0'
// this.touch.brake.$element.style.backgroundColor = '#ff0000'
document.body.appendChild(this.touch.brake.$element)
this.touch.brake.$border = document.createElement('div')
this.touch.brake.$border.style.position = 'absolute'
this.touch.brake.$border.style.top = 'calc(50% - 30px)'
this.touch.brake.$border.style.left = 'calc(50% - 30px)'
this.touch.brake.$border.style.width = '60px'
this.touch.brake.$border.style.height = '60px'
this.touch.brake.$border.style.border = '2px solid #ffffff'
this.touch.brake.$border.style.borderRadius = '10px'
this.touch.brake.$border.style.boxSizing = 'border-box'
this.touch.brake.$border.style.opacity = '0.25'
this.touch.brake.$border.style.willChange = 'opacity'
this.touch.brake.$element.appendChild(this.touch.brake.$border)
this.touch.brake.$icon = document.createElement('div')
this.touch.brake.$icon.style.position = 'absolute'
this.touch.brake.$icon.style.top = 'calc(50% - 7px)'
this.touch.brake.$icon.style.left = 'calc(50% - 7px)'
this.touch.brake.$icon.style.width = '15px'
this.touch.brake.$icon.style.height = '15px'
this.touch.brake.$icon.style.backgroundImage = `url(${mobileCross})`
this.touch.brake.$icon.style.backgroundSize = 'cover'
this.touch.brake.$icon.style.transform = 'rotate(180deg)'
this.touch.brake.$element.appendChild(this.touch.brake.$icon)
// Events
this.touch.brake.events = {}
this.touch.brake.touchIdentifier = null
this.touch.brake.events.touchstart = (_event) =>
{
_event.preventDefault()
const touch = _event.changedTouches[0]
if(touch)
{
this.touch.brake.touchIdentifier = touch.identifier
this.actions.brake = true
this.touch.brake.$border.style.opacity = '0.5'
document.addEventListener('touchend', this.touch.brake.events.touchend)
}
}
this.touch.brake.events.touchend = (_event) =>
{
const touches = [..._event.changedTouches]
const touch = touches.find((_touch) => _touch.identifier === this.touch.brake.touchIdentifier)
if(touch)
{
this.actions.brake = false
this.touch.brake.$border.style.opacity = '0.25'
document.removeEventListener('touchend', this.touch.brake.events.touchend)
}
}
this.touch.brake.$element.addEventListener('touchstart', this.touch.brake.events.touchstart)
/**
* Backward
*/
this.touch.backward = {}
// Element
this.touch.backward.$element = document.createElement('div')
this.touch.backward.$element.style.userSelect = 'none'
this.touch.backward.$element.style.position = 'fixed'
this.touch.backward.$element.style.bottom = '15px'
this.touch.backward.$element.style.right = '0px'
this.touch.backward.$element.style.width = '95px'
this.touch.backward.$element.style.height = '70px'
this.touch.backward.$element.style.transition = 'opacity 0.3s 0.1s'
this.touch.backward.$element.style.willChange = 'opacity'
this.touch.backward.$element.style.opacity = '0'
// this.touch.backward.$element.style.backgroundColor = '#0000ff'
document.body.appendChild(this.touch.backward.$element)
this.touch.backward.$border = document.createElement('div')
this.touch.backward.$border.style.position = 'absolute'
this.touch.backward.$border.style.top = 'calc(50% - 30px)'
this.touch.backward.$border.style.left = 'calc(50% - 30px)'
this.touch.backward.$border.style.width = '60px'
this.touch.backward.$border.style.height = '60px'
this.touch.backward.$border.style.border = '2px solid #ffffff'
this.touch.backward.$border.style.borderRadius = '10px'
this.touch.backward.$border.style.boxSizing = 'border-box'
this.touch.backward.$border.style.opacity = '0.25'
this.touch.backward.$border.style.willChange = 'opacity'
this.touch.backward.$element.appendChild(this.touch.backward.$border)
this.touch.backward.$icon = document.createElement('div')
this.touch.backward.$icon.style.position = 'absolute'
this.touch.backward.$icon.style.top = 'calc(50% - 9px)'
this.touch.backward.$icon.style.left = 'calc(50% - 11px)'
this.touch.backward.$icon.style.width = '22px'
this.touch.backward.$icon.style.height = '18px'
this.touch.backward.$icon.style.backgroundImage = `url(${mobileTriangle})`
this.touch.backward.$icon.style.backgroundSize = 'cover'
this.touch.backward.$icon.style.transform = 'rotate(180deg)'
this.touch.backward.$element.appendChild(this.touch.backward.$icon)
// Events
this.touch.backward.events = {}
this.touch.backward.touchIdentifier = null
this.touch.backward.events.touchstart = (_event) =>
{
_event.preventDefault()
const touch = _event.changedTouches[0]
if(touch)
{
this.camera.pan.reset()
this.touch.backward.touchIdentifier = touch.identifier
this.actions.down = true
this.touch.backward.$border.style.opacity = '0.5'
document.addEventListener('touchend', this.touch.backward.events.touchend)
}
}
this.touch.backward.events.touchend = (_event) =>
{
const touches = [..._event.changedTouches]
const touch = touches.find((_touch) => _touch.identifier === this.touch.backward.touchIdentifier)
if(touch)
{
this.actions.down = false
this.touch.backward.$border.style.opacity = '0.25'
document.removeEventListener('touchend', this.touch.backward.events.touchend)
}
}
this.touch.backward.$element.addEventListener('touchstart', this.touch.backward.events.touchstart)
// Reveal
this.touch.reveal = () =>
{
this.touch.joystick.$element.style.opacity = 1
this.touch.backward.$element.style.opacity = 1
this.touch.brake.$element.style.opacity = 1
this.touch.forward.$element.style.opacity = 1
this.touch.boost.$element.style.opacity = 1
}
}
}

View File

@ -0,0 +1,385 @@
import * as THREE from 'three'
export default class EasterEggs
{
constructor(_options)
{
// Options
this.resources = _options.resources
this.car = _options.car
this.walls = _options.walls
this.objects = _options.objects
this.materials = _options.materials
this.areas = _options.areas
this.config = _options.config
this.physics = _options.physics
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.setKonamiCode()
this.setWigs()
// this.setEggs()
}
setKonamiCode()
{
this.konamiCode = {}
this.konamiCode.x = - 60
this.konamiCode.y = - 100
this.konamiCode.sequence = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight']
if(!this.config.touch)
{
this.konamiCode.sequence.push('b', 'a')
}
this.konamiCode.keyIndex = 0
this.konamiCode.latestKeys = []
this.konamiCode.count = 0
// Label
if(this.config.touch)
{
this.konamiCode.labelTexture = this.resources.items.konamiLabelTouchTexture
}
else
{
this.konamiCode.labelTexture = this.resources.items.konamiLabelTexture
}
this.konamiCode.labelTexture.magFilter = THREE.NearestFilter
this.konamiCode.labelTexture.minFilter = THREE.LinearFilter
this.konamiCode.label = new THREE.Mesh(new THREE.PlaneBufferGeometry(8, 8 / 16), new THREE.MeshBasicMaterial({ transparent: true, depthWrite: false, color: 0xffffff, alphaMap: this.konamiCode.labelTexture }))
this.konamiCode.label.position.x = this.konamiCode.x + 5
this.konamiCode.label.position.y = this.konamiCode.y
this.konamiCode.label.matrixAutoUpdate = false
this.konamiCode.label.updateMatrix()
this.container.add(this.konamiCode.label)
// Lemon option
this.konamiCode.lemonOption = {
base: this.resources.items.lemonBase.scene,
collision: this.resources.items.lemonCollision.scene,
offset: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(Math.PI * 0.5, - Math.PI * 0.3, 0),
duplicated: true,
shadow: { sizeX: 1.2, sizeY: 1.8, offsetZ: - 0.15, alpha: 0.35 },
mass: 0.5,
sleep: true,
soundName: 'woodHit'
}
// First lemon
this.objects.add({
...this.konamiCode.lemonOption,
offset: new THREE.Vector3(this.konamiCode.x, this.konamiCode.y, 0.4)
})
this.konamiCode.testInput = (_input) =>
{
this.konamiCode.latestKeys.push(_input)
if(this.konamiCode.latestKeys.length > this.konamiCode.sequence.length)
{
this.konamiCode.latestKeys.shift()
}
if(this.konamiCode.sequence.toString() === this.konamiCode.latestKeys.toString())
{
this.konamiCode.count++
for(let i = 0; i < Math.pow(3, this.konamiCode.count); i++)
{
window.setTimeout(() =>
{
const x = this.car.chassis.object.position.x + (Math.random() - 0.5) * 10
const y = this.car.chassis.object.position.y + (Math.random() - 0.5) * 10
this.objects.add({
...this.konamiCode.lemonOption,
offset: new THREE.Vector3(x, y, 10),
rotation: new THREE.Euler(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2),
sleep: false
})
// this.eggs.add({
// offset: new THREE.Vector3(x, y, 10),
// rotation: new THREE.Euler(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2),
// material: this.materials.shades.items.yellow,
// code: 'MjAyMWVnZ2N2b3V6ZXI=',
// sleep: false
// })
}, i * 50)
}
}
}
/**
* Keyboard handling
*/
window.addEventListener('keydown', (_event) =>
{
this.konamiCode.testInput(_event.key)
})
/**
* Touch handling
*/
this.konamiCode.touch = {}
this.konamiCode.touch.x = 0
this.konamiCode.touch.y = 0
this.konamiCode.touch.touchstart = (_event) =>
{
window.addEventListener('touchend', this.konamiCode.touch.touchend)
this.konamiCode.touch.x = _event.changedTouches[0].clientX
this.konamiCode.touch.y = _event.changedTouches[0].clientY
}
this.konamiCode.touch.touchend = (_event) =>
{
window.removeEventListener('touchend', this.konamiCode.touch.touchend)
const endX = _event.changedTouches[0].clientX
const endY = _event.changedTouches[0].clientY
const deltaX = endX - this.konamiCode.touch.x
const deltaY = endY - this.konamiCode.touch.y
const distance = Math.hypot(deltaX, deltaY)
if(distance > 30)
{
const angle = Math.atan2(deltaY, deltaX)
let direction = null
if(angle < - Math.PI * 0.75)
{
direction = 'ArrowLeft'
}
else if(angle < - Math.PI * 0.25)
{
direction = 'ArrowUp'
}
else if(angle < Math.PI * 0.25)
{
direction = 'ArrowRight'
}
else if(angle < Math.PI * 0.75)
{
direction = 'ArrowDown'
}
else
{
direction = 'ArrowLeft'
}
this.konamiCode.testInput(direction)
}
}
window.addEventListener('touchstart', this.konamiCode.touch.touchstart)
}
setWigs()
{
this.wigs = {}
this.wigs.currentWig = null
// Container
this.wigs.container = new THREE.Object3D()
this.wigs.container.position.x = - 0.1
this.wigs.container.position.y = - 30
this.wigs.container.matrixAutoUpdate = false
this.wigs.container.updateMatrix()
this.container.add(this.wigs.container)
// Materials
this.wigs.materials = [
this.materials.shades.items.green,
this.materials.shades.items.red,
this.materials.shades.items.emeraldGreen,
this.materials.shades.items.purple,
this.materials.shades.items.yellow,
this.materials.shades.items.white
]
// List
this.wigs.list = [
this.resources.items.wig1,
this.resources.items.wig2,
this.resources.items.wig3,
this.resources.items.wig4
]
// Items
this.wigs.items = []
for(const _wig of this.wigs.list)
{
const container = new THREE.Object3D()
container.visible = false
container.matrixAutoUpdate = false
this.wigs.container.add(container)
const children = [..._wig.scene.children]
for(const _mesh of children)
{
_mesh.material = this.wigs.materials[0]
container.add(_mesh)
}
this.wigs.items.push(container)
}
// Change
this.wigs.change = () =>
{
// Hide previous wig
if(this.wigs.currentWig)
{
this.wigs.currentWig.visible = false
}
// Set random wig
let randomWig = null
do
{
randomWig = this.wigs.items[Math.floor(Math.random() * this.wigs.items.length)]
} while(this.wigs.currentWig === randomWig)
this.wigs.currentWig = randomWig
this.wigs.currentWig.visible = true
// Set random material
const randomMaterial = this.wigs.materials[Math.floor(Math.random() * this.wigs.materials.length)]
for(const _mesh of this.wigs.currentWig.children)
{
_mesh.material = randomMaterial
}
// this.eggs.add({
// offset: new THREE.Vector3(0, 80, 10),
// material: this.materials.shades.items.metal,
// code: 'MjAyMWVnZ2F6ZW9jYmI=',
// sleep: false
// })
}
// Area
this.wigs.area = this.areas.add({
position: new THREE.Vector2(0, 80),
halfExtents: new THREE.Vector2(2, 2)
})
this.wigs.area.on('interact', this.wigs.change)
// Label
this.resources.items.areaQuestionMarkTexture.magFilter = THREE.NearestFilter
this.resources.items.areaQuestionMarkTexture.minFilter = THREE.LinearFilter
this.wigs.areaLabel = new THREE.Mesh(new THREE.PlaneBufferGeometry(1, 1), new THREE.MeshBasicMaterial({ transparent: true, depthWrite: false, color: 0xffffff, alphaMap: this.resources.items.areaQuestionMarkTexture }))
this.wigs.areaLabel.position.x = 0
this.wigs.areaLabel.position.y = 80
this.wigs.areaLabel.matrixAutoUpdate = false
this.wigs.areaLabel.updateMatrix()
this.container.add(this.wigs.areaLabel)
}
setEggs()
{
this.eggs = {}
this.eggs.items = []
// Console
console.log('🥚 2021eggbvpoabe')
// Base eggs options
const eggOptions = [
{
offset: new THREE.Vector3(- 29.80, - 18.94, 0.5),
material: this.materials.shades.items.emeraldGreen,
code: 'MjAyMWVnZ2Fvem5kZXo='
},
{
offset: new THREE.Vector3(103.91, 128.56, 0.5),
material: this.materials.shades.items.red,
code: 'MjAyMWVnZ3Fxc3ZwcG8='
},
{
offset: new THREE.Vector3(39.68, -23.67, 0.5),
material: this.materials.shades.items.purple,
code: 'MjAyMWVnZ212b2Focnc='
},
{
offset: new THREE.Vector3(107.62, -155.75, 0.5),
material: this.materials.shades.items.blue,
code: 'MjAyMWVnZ2N1ZHBhaW4='
},
]
this.eggs.add = (_options) =>
{
const egg = {}
egg.found = false
egg.code = _options.code
// Create object
egg.object = this.objects.add({
base: this.resources.items.eggBase.scene,
collision: this.resources.items.eggCollision.scene,
duplicated: true,
shadow: { sizeX: 1.2, sizeY: 1.8, offsetZ: - 0.15, alpha: 0.35 },
mass: 0.5,
sleep: typeof _options.sleep !== 'undefined' ? _options.sleep : true,
soundName: 'woodHit',
offset: _options.offset,
rotation: typeof _options.sleep !== 'undefined' ? _options.rotation : new THREE.Euler(0, 0, 0)
})
// Change material
egg.object.container.children[0].material = _options.material
// Collision callback
egg.collisionCallback = (_event) =>
{
// Collision with car
if(_event.body === this.physics.car.chassis.body && !egg.found)
{
egg.found = true
// egg.object.collision.body.removeEventListener('collide', egg.collisionCallback)
const code = atob(egg.code)
window.setTimeout(() =>
{
if(window.confirm(`
You find an egg!
Here is your code for a 30% discount on https://threejs-journey.xyz
${code}
Would you like to go on the subscription page?
`))
{
window.open(`https://threejs-journey.xyz/subscribe/${code}`, '_blank')
}
window.setTimeout(() =>
{
egg.found = false
}, 1000)
}, 600)
}
}
// Listen to collide event
egg.object.collision.body.addEventListener('collide', egg.collisionCallback)
// Save
this.eggs.items.push(egg)
}
// Create base eggs
for(const _eggOption of eggOptions)
{
this.eggs.add(_eggOption)
}
}
}

View File

@ -0,0 +1,70 @@
import * as THREE from 'three'
import FloorMaterial from '../Materials/Floor.js'
export default class Floor
{
constructor(_options)
{
// Options
this.debug = _options.debug
// Container
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
// Geometry
this.geometry = new THREE.PlaneBufferGeometry(2, 2, 10, 10)
// Colors
this.colors = {}
this.colors.topLeft = '#f5883c'
this.colors.topRight = '#ff9043'
this.colors.bottomRight = '#fccf92'
this.colors.bottomLeft = '#f5aa58'
// Material
this.material = new FloorMaterial()
this.updateMaterial = () =>
{
const topLeft = new THREE.Color(this.colors.topLeft)
const topRight = new THREE.Color(this.colors.topRight)
const bottomRight = new THREE.Color(this.colors.bottomRight)
const bottomLeft = new THREE.Color(this.colors.bottomLeft)
const data = new Uint8Array([
Math.round(bottomLeft.r * 255), Math.round(bottomLeft.g * 255), Math.round(bottomLeft.b * 255),
Math.round(bottomRight.r * 255), Math.round(bottomRight.g * 255), Math.round(bottomRight.b * 255),
Math.round(topLeft.r * 255), Math.round(topLeft.g * 255), Math.round(topLeft.b * 255),
Math.round(topRight.r * 255), Math.round(topRight.g * 255), Math.round(topRight.b * 255)
])
this.backgroundTexture = new THREE.DataTexture(data, 2, 2, THREE.RGBFormat)
this.backgroundTexture.magFilter = THREE.LinearFilter
this.backgroundTexture.needsUpdate = true
this.material.uniforms.tBackground.value = this.backgroundTexture
}
this.updateMaterial()
// Mesh
this.mesh = new THREE.Mesh(this.geometry, this.material)
this.mesh.frustumCulled = false
this.mesh.matrixAutoUpdate = false
this.mesh.updateMatrix()
this.container.add(this.mesh)
// Debug
if(this.debug)
{
const folder = this.debug.addFolder('floor')
// folder.open()
folder.addColor(this.colors, 'topLeft').onChange(this.updateMaterial)
folder.addColor(this.colors, 'topRight').onChange(this.updateMaterial)
folder.addColor(this.colors, 'bottomRight').onChange(this.updateMaterial)
folder.addColor(this.colors, 'bottomLeft').onChange(this.updateMaterial)
}
}
}

View File

@ -0,0 +1,216 @@
import * as THREE from 'three'
import FloorShadowMaterial from '../Materials/FloorShadow.js'
import MatcapMaterial from '../Materials/Matcap.js'
export default class Materials
{
constructor(_options)
{
// Options
this.resources = _options.resources
this.debug = _options.debug
// Debug
if(this.debug)
{
this.debugFolder = this.debug.addFolder('materials')
this.debugFolder.open()
}
// Set up
this.items = {}
this.setPures()
this.setShades()
this.setFloorShadow()
}
setPures()
{
// Setup
this.pures = {}
this.pures.items = {}
this.pures.items.red = new THREE.MeshBasicMaterial({ color: 0xff0000 })
this.pures.items.red.name = 'pureRed'
this.pures.items.white = new THREE.MeshBasicMaterial({ color: 0xffffff })
this.pures.items.white.name = 'pureWhite'
this.pures.items.yellow = new THREE.MeshBasicMaterial({ color: 0xffe889 })
this.pures.items.yellow.name = 'pureYellow'
}
setShades()
{
// Setup
this.shades = {}
this.shades.items = {}
this.shades.indirectColor = '#d04500'
this.shades.uniforms = {
uRevealProgress: 0,
uIndirectDistanceAmplitude: 1.75,
uIndirectDistanceStrength: 0.5,
uIndirectDistancePower: 2.0,
uIndirectAngleStrength: 1.5,
uIndirectAngleOffset: 0.6,
uIndirectAnglePower: 1.0,
uIndirectColor: null
}
// White
this.shades.items.white = new MatcapMaterial()
this.shades.items.white.name = 'shadeWhite'
this.shades.items.white.uniforms.matcap.value = this.resources.items.matcapWhiteTexture
this.items.white = this.shades.items.white
// Orange
this.shades.items.orange = new MatcapMaterial()
this.shades.items.orange.name = 'shadeOrange'
this.shades.items.orange.uniforms.matcap.value = this.resources.items.matcapOrangeTexture
this.items.orange = this.shades.items.orange
// Green
this.shades.items.green = new MatcapMaterial()
this.shades.items.green.name = 'shadeGreen'
this.shades.items.green.uniforms.matcap.value = this.resources.items.matcapGreenTexture
this.items.green = this.shades.items.green
// Brown
this.shades.items.brown = new MatcapMaterial()
this.shades.items.brown.name = 'shadeBrown'
this.shades.items.brown.uniforms.matcap.value = this.resources.items.matcapBrownTexture
this.items.brown = this.shades.items.brown
// Gray
this.shades.items.gray = new MatcapMaterial()
this.shades.items.gray.name = 'shadeGray'
this.shades.items.gray.uniforms.matcap.value = this.resources.items.matcapGrayTexture
this.items.gray = this.shades.items.gray
// Beige
this.shades.items.beige = new MatcapMaterial()
this.shades.items.beige.name = 'shadeBeige'
this.shades.items.beige.uniforms.matcap.value = this.resources.items.matcapBeigeTexture
this.items.beige = this.shades.items.beige
// Red
this.shades.items.red = new MatcapMaterial()
this.shades.items.red.name = 'shadeRed'
this.shades.items.red.uniforms.matcap.value = this.resources.items.matcapRedTexture
this.items.red = this.shades.items.red
// Black
this.shades.items.black = new MatcapMaterial()
this.shades.items.black.name = 'shadeBlack'
this.shades.items.black.uniforms.matcap.value = this.resources.items.matcapBlackTexture
this.items.black = this.shades.items.black
// Green emerald
this.shades.items.emeraldGreen = new MatcapMaterial()
this.shades.items.emeraldGreen.name = 'shadeEmeraldGreen'
this.shades.items.emeraldGreen.uniforms.matcap.value = this.resources.items.matcapEmeraldGreenTexture
this.items.emeraldGreen = this.shades.items.emeraldGreen
// Purple
this.shades.items.purple = new MatcapMaterial()
this.shades.items.purple.name = 'shadePurple'
this.shades.items.purple.uniforms.matcap.value = this.resources.items.matcapPurpleTexture
this.items.purple = this.shades.items.purple
// Blue
this.shades.items.blue = new MatcapMaterial()
this.shades.items.blue.name = 'shadeBlue'
this.shades.items.blue.uniforms.matcap.value = this.resources.items.matcapBlueTexture
this.items.blue = this.shades.items.blue
// Yellow
this.shades.items.yellow = new MatcapMaterial()
this.shades.items.yellow.name = 'shadeYellow'
this.shades.items.yellow.uniforms.matcap.value = this.resources.items.matcapYellowTexture
this.items.yellow = this.shades.items.yellow
// Metal
this.shades.items.metal = new MatcapMaterial()
this.shades.items.metal.name = 'shadeMetal'
this.shades.items.metal.uniforms.matcap.value = this.resources.items.matcapMetalTexture
this.items.metal = this.shades.items.metal
// // Gold
// this.shades.items.gold = new MatcapMaterial()
// this.shades.items.gold.name = 'shadeGold'
// this.shades.items.gold.uniforms.matcap.value = this.resources.items.matcapGoldTexture
// this.items.gold = this.shades.items.gold
// Update materials uniforms
this.shades.updateMaterials = () =>
{
this.shades.uniforms.uIndirectColor = new THREE.Color(this.shades.indirectColor)
// Each uniform
for(const _uniformName in this.shades.uniforms)
{
const _uniformValue = this.shades.uniforms[_uniformName]
// Each material
for(const _materialKey in this.shades.items)
{
const material = this.shades.items[_materialKey]
material.uniforms[_uniformName].value = _uniformValue
}
}
}
this.shades.updateMaterials()
// Debug
if(this.debug)
{
const folder = this.debugFolder.addFolder('shades')
folder.open()
folder.add(this.shades.uniforms, 'uIndirectDistanceAmplitude').step(0.001).min(0).max(3).onChange(this.shades.updateMaterials)
folder.add(this.shades.uniforms, 'uIndirectDistanceStrength').step(0.001).min(0).max(2).onChange(this.shades.updateMaterials)
folder.add(this.shades.uniforms, 'uIndirectDistancePower').step(0.001).min(0).max(5).onChange(this.shades.updateMaterials)
folder.add(this.shades.uniforms, 'uIndirectAngleStrength').step(0.001).min(0).max(2).onChange(this.shades.updateMaterials)
folder.add(this.shades.uniforms, 'uIndirectAngleOffset').step(0.001).min(- 2).max(2).onChange(this.shades.updateMaterials)
folder.add(this.shades.uniforms, 'uIndirectAnglePower').step(0.001).min(0).max(5).onChange(this.shades.updateMaterials)
folder.addColor(this.shades, 'indirectColor').onChange(this.shades.updateMaterials)
}
}
setFloorShadow()
{
this.items.floorShadow = new FloorShadowMaterial()
this.items.floorShadow.depthWrite = false
this.items.floorShadow.shadowColor = '#d04500'
this.items.floorShadow.uniforms.uShadowColor.value = new THREE.Color(this.items.floorShadow.shadowColor)
this.items.floorShadow.uniforms.uAlpha.value = 0
this.items.floorShadow.updateMaterials = () =>
{
this.items.floorShadow.uniforms.uShadowColor.value = new THREE.Color(this.items.floorShadow.shadowColor)
for(const _item of this.objects.items)
{
for(const _child of _item.container.children)
{
if(_child.material instanceof THREE.ShaderMaterial)
{
if(_child.material.uniforms.uShadowColor)
{
_child.material.uniforms.uShadowColor.value = new THREE.Color(this.items.floorShadow.shadowColor)
}
}
}
}
}
// Debug
if(this.debug)
{
const folder = this.debugFolder.addFolder('floorShadow')
folder.open()
folder.addColor(this.items.floorShadow, 'shadowColor').onChange(this.items.floorShadow.updateMaterials)
}
}
}

View File

@ -0,0 +1,353 @@
import * as THREE from 'three'
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
export default class Objects
{
constructor(_options)
{
// Options
this.time = _options.time
this.resources = _options.resources
this.materials = _options.materials
this.physics = _options.physics
this.shadows = _options.shadows
this.sounds = _options.sounds
this.debug = _options.debug
// Set up
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.items = []
this.floorShadows = []
this.setParsers()
this.setMerge()
}
setParsers()
{
this.parsers = {}
this.parsers.items = [
// Shade
{
regex: /^shade([a-z]+)_?[0-9]{0,3}?/i,
apply: (_mesh, _options) =>
{
// Find material
const match = _mesh.name.match(/^shade([a-z]+)_?[0-9]{0,3}?/i)
const materialName = `${match[1].substring(0, 1).toLowerCase()}${match[1].substring(1)}` // PastalCase to camelCase
let material = this.materials.shades.items[materialName]
// Default
if(typeof material === 'undefined')
{
material = new THREE.MeshNormalMaterial()
}
// Create clone mesh with new material
const mesh = _options.duplicated ? _mesh.clone() : _mesh
mesh.material = material
if(mesh.children.length)
{
for(const _child of mesh.children)
{
if(_child instanceof THREE.Mesh)
{
_child.material = material
}
}
}
return mesh
}
},
// Shade
{
regex: /^pure([a-z]+)_?[0-9]{0,3}?/i,
apply: (_mesh, _options) =>
{
// Find material
const match = _mesh.name.match(/^pure([a-z]+)_?[0-9]{0,3}?/i)
const materialName = match[1].toLowerCase()
let material = this.materials.pures.items[materialName]
// Default
if(typeof material === 'undefined')
{
material = new THREE.MeshNormalMaterial()
}
// Create clone mesh with new material
const mesh = _options.duplicated ? _mesh.clone() : _mesh
mesh.material = material
return mesh
}
},
// Floor
{
regex: /^floor_?[0-9]{0,3}?/i,
apply: (_mesh, _options) =>
{
// Create floor manually because of missing UV
const geometry = new THREE.PlaneBufferGeometry(_mesh.scale.x, _mesh.scale.y, 10, 10)
const material = this.materials.items.floorShadow.clone()
material.uniforms.tShadow.value = _options.floorShadowTexture
material.uniforms.uShadowColor.value = new THREE.Color(this.materials.items.floorShadow.shadowColor)
material.uniforms.uAlpha.value = 0
const mesh = new THREE.Mesh(geometry, material)
mesh.matrixAutoUpdate = false
mesh.updateMatrix()
this.floorShadows.push(mesh)
return mesh
}
}
]
// Default
this.parsers.default = {}
this.parsers.default.apply = (_mesh) =>
{
// Create clone mesh with normal material
const mesh = _mesh.clone()
mesh.material = this.materials.shades.items.white
return mesh
}
}
setMerge()
{
this.merge = {}
this.merge.items = {}
this.merge.container = new THREE.Object3D()
this.merge.container.matrixAutoUpdate = false
this.container.add(this.merge.container)
this.merge.add = (_name, _mesh) =>
{
let mergeItem = this.merge.items[_name]
// Create merge item if not found
if(!mergeItem)
{
mergeItem = {}
// Geometry
mergeItem.geometry = new THREE.BufferGeometry()
mergeItem.geometriesToMerge = []
// Material
mergeItem.material = _mesh.material
mergeItem.material.side = THREE.DoubleSide
// Mesh
mergeItem.mesh = new THREE.Mesh(mergeItem.geometry, mergeItem.material)
this.merge.container.add(mergeItem.mesh)
// Save
this.merge.items[_name] = mergeItem
}
// Apply the object transform to the geometry and save it for later merge
const geometry = _mesh.geometry
_mesh.updateMatrixWorld() // Maybe not
geometry.applyMatrix(_mesh.matrixWorld)
mergeItem.geometriesToMerge.push(geometry)
}
this.merge.applyMerge = () =>
{
for(const _mergeItemName in this.merge.items)
{
const mergeItem = this.merge.items[_mergeItemName]
mergeItem.geometry = BufferGeometryUtils.mergeBufferGeometries(mergeItem.geometriesToMerge) // Should add original geometry
mergeItem.mesh.geometry = mergeItem.geometry
}
}
this.merge.update = () =>
{
for(const _object of this.items)
{
if(_object.shouldMerge)
{
const children = [..._object.container.children]
for(const _child of children)
{
const materialName = _child.material.name
if(materialName !== '')
{
this.merge.add(materialName, _child)
// Remove from parent
_object.container.remove(_child)
}
}
// If no children, remove
_object.shouldMerge = false
}
}
// Apply merge
this.merge.applyMerge()
}
}
getConvertedMesh(_children, _options = {})
{
const container = new THREE.Object3D()
const center = new THREE.Vector3()
// Go through each base child
const baseChildren = [..._children]
for(const _child of baseChildren)
{
// Find center
if(_child.name.match(/^center_?[0-9]{0,3}?/i))
{
center.set(_child.position.x, _child.position.y, _child.position.z)
}
if(_child instanceof THREE.Mesh)
{
// Find parser and use default if not found
let parser = this.parsers.items.find((_item) => _child.name.match(_item.regex))
if(typeof parser === 'undefined')
{
parser = this.parsers.default
}
// Create mesh by applying parser
const mesh = parser.apply(_child, _options)
// Add to container
container.add(mesh)
}
}
// Recenter
if(center.length() > 0)
{
for(const _child of container.children)
{
_child.position.sub(center)
}
container.position.add(center)
}
if(_options.mass && _options.mass === 0)
{
container.matrixAutoUpdate = false
container.updateMatrix()
}
return container
}
add(_options)
{
const object = {}
object.merged = false
object.shouldMerge = _options.mass === 0
// Offset
const offset = new THREE.Vector3()
if(_options.offset)
{
offset.copy(_options.offset)
}
// Rotation
const rotation = new THREE.Euler()
if(_options.rotation)
{
rotation.copy(_options.rotation)
}
// Sleep
const sleep = typeof _options.sleep === 'undefined' ? true : _options.sleep
// Container
object.container = this.getConvertedMesh(_options.base.children, _options)
object.container.position.copy(offset)
object.container.rotation.copy(rotation)
this.container.add(object.container)
// Deactivate matrix auto update
if(_options.mass === 0)
{
object.container.matrixAutoUpdate = false
object.container.updateMatrix()
for(const _child of object.container.children)
{
_child.matrixAutoUpdate = false
_child.updateMatrix()
}
}
// Create physics object
object.collision = this.physics.addObjectFromThree({
meshes: [..._options.collision.children],
offset,
rotation,
mass: _options.mass,
sleep
})
for(const _child of object.container.children)
{
_child.position.sub(object.collision.center)
}
// Sound
if(_options.soundName)
{
object.collision.body.addEventListener('collide', (_event) =>
{
const relativeVelocity = _event.contact.getImpactVelocityAlongNormal()
this.sounds.play(_options.soundName, relativeVelocity)
})
}
// Shadow
// Add shadow
if(_options.shadow)
{
this.shadows.add(object.container, _options.shadow)
}
// Time tick event
if(_options.mass > 0)
{
this.time.on('tick', () =>
{
object.container.position.copy(object.collision.body.position)
object.container.quaternion.copy(object.collision.body.quaternion)
})
}
// Save
this.items.push(object)
return object
}
}

View File

@ -0,0 +1,825 @@
import CANNON from 'cannon'
import * as THREE from 'three'
export default class Physics
{
constructor(_options)
{
this.config = _options.config
this.debug = _options.debug
this.time = _options.time
this.sizes = _options.sizes
this.controls = _options.controls
this.sounds = _options.sounds
// Set up
if(this.debug)
{
this.debugFolder = this.debug.addFolder('physics')
// this.debugFolder.open()
}
this.setWorld()
this.setModels()
this.setMaterials()
this.setFloor()
this.setCar()
this.time.on('tick', () =>
{
this.world.step(1 / 60, this.time.delta, 3)
})
}
setWorld()
{
this.world = new CANNON.World()
this.world.gravity.set(0, 0, - 3.25)
this.world.allowSleep = true
// this.world.gravity.set(0, 0, 0)
// this.world.broadphase = new CANNON.SAPBroadphase(this.world)
this.world.defaultContactMaterial.friction = 0
this.world.defaultContactMaterial.restitution = 0.2
// Debug
if(this.debug)
{
this.debugFolder.add(this.world.gravity, 'z').step(0.001).min(- 20).max(20).name('gravity')
}
}
setModels()
{
this.models = {}
this.models.container = new THREE.Object3D()
this.models.container.visible = false
this.models.materials = {}
this.models.materials.static = new THREE.MeshBasicMaterial({ color: 0x0000ff, wireframe: true })
this.models.materials.dynamic = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true })
this.models.materials.dynamicSleeping = new THREE.MeshBasicMaterial({ color: 0xffff00, wireframe: true })
// Debug
if(this.debug)
{
this.debugFolder.add(this.models.container, 'visible').name('modelsVisible')
}
}
setMaterials()
{
this.materials = {}
// All materials
this.materials.items = {}
this.materials.items.floor = new CANNON.Material('floorMaterial')
this.materials.items.dummy = new CANNON.Material('dummyMaterial')
this.materials.items.wheel = new CANNON.Material('wheelMaterial')
// Contact between materials
this.materials.contacts = {}
this.materials.contacts.floorDummy = new CANNON.ContactMaterial(this.materials.items.floor, this.materials.items.dummy, { friction: 0.05, restitution: 0.3, contactEquationStiffness: 1000 })
this.world.addContactMaterial(this.materials.contacts.floorDummy)
this.materials.contacts.dummyDummy = new CANNON.ContactMaterial(this.materials.items.dummy, this.materials.items.dummy, { friction: 0.5, restitution: 0.3, contactEquationStiffness: 1000 })
this.world.addContactMaterial(this.materials.contacts.dummyDummy)
this.materials.contacts.floorWheel = new CANNON.ContactMaterial(this.materials.items.floor, this.materials.items.wheel, { friction: 0.3, restitution: 0, contactEquationStiffness: 1000 })
this.world.addContactMaterial(this.materials.contacts.floorWheel)
}
setFloor()
{
this.floor = {}
this.floor.body = new CANNON.Body({
mass: 0,
shape: new CANNON.Plane(),
material: this.materials.items.floor
})
// this.floor.body.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), - Math.PI * 0.5)
this.world.addBody(this.floor.body)
}
setCar()
{
this.car = {}
this.car.steering = 0
this.car.accelerating = 0
this.car.speed = 0
this.car.worldForward = new CANNON.Vec3()
this.car.angle = 0
this.car.forwardSpeed = 0
this.car.oldPosition = new CANNON.Vec3()
this.car.goingForward = true
/**
* Options
*/
this.car.options = {}
this.car.options.chassisWidth = 1.02
this.car.options.chassisHeight = 1.16
this.car.options.chassisDepth = 2.03
this.car.options.chassisOffset = new CANNON.Vec3(0, 0, 0.41)
this.car.options.chassisMass = 20
this.car.options.wheelFrontOffsetDepth = 0.635
this.car.options.wheelBackOffsetDepth = - 0.475
this.car.options.wheelOffsetWidth = 0.39
this.car.options.wheelRadius = 0.25
this.car.options.wheelHeight = 0.24
this.car.options.wheelSuspensionStiffness = 25
this.car.options.wheelSuspensionRestLength = 0.1
this.car.options.wheelFrictionSlip = 5
this.car.options.wheelDampingRelaxation = 1.8
this.car.options.wheelDampingCompression = 1.5
this.car.options.wheelMaxSuspensionForce = 100000
this.car.options.wheelRollInfluence = 0.01
this.car.options.wheelMaxSuspensionTravel = 0.3
this.car.options.wheelCustomSlidingRotationalSpeed = - 30
this.car.options.wheelMass = 5
this.car.options.controlsSteeringSpeed = 0.005
this.car.options.controlsSteeringMax = Math.PI * 0.17
this.car.options.controlsSteeringQuad = false
this.car.options.controlsAcceleratinMaxSpeed = 0.055
this.car.options.controlsAcceleratinMaxSpeedBoost = 0.11
this.car.options.controlsAcceleratingSpeed = 2
this.car.options.controlsAcceleratingSpeedBoost = 3.5
this.car.options.controlsAcceleratingQuad = true
this.car.options.controlsBrakeStrength = 0.45
/**
* Upsize down
*/
this.car.upsideDown = {}
this.car.upsideDown.state = 'watching' // 'wathing' | 'pending' | 'turning'
this.car.upsideDown.pendingTimeout = null
this.car.upsideDown.turningTimeout = null
/**
* Jump
*/
this.car.jump = (_toReturn = true, _strength = 60) =>
{
let worldPosition = this.car.chassis.body.position
worldPosition = worldPosition.vadd(new CANNON.Vec3(_toReturn ? 0.08 : 0, 0, 0))
this.car.chassis.body.applyImpulse(new CANNON.Vec3(0, 0, _strength), worldPosition)
}
/**
* Create method
*/
this.car.create = () =>
{
/**
* Chassis
*/
this.car.chassis = {}
this.car.chassis.shape = new CANNON.Box(new CANNON.Vec3(this.car.options.chassisDepth * 0.5, this.car.options.chassisWidth * 0.5, this.car.options.chassisHeight * 0.5))
this.car.chassis.body = new CANNON.Body({ mass: this.car.options.chassisMass })
this.car.chassis.body.allowSleep = false
this.car.chassis.body.position.set(0, 0, 12)
this.car.chassis.body.sleep()
this.car.chassis.body.addShape(this.car.chassis.shape, this.car.options.chassisOffset)
this.car.chassis.body.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 0, 1), - Math.PI * 0.5)
/**
* Sound
*/
this.car.chassis.body.addEventListener('collide', (_event) =>
{
if(_event.body.mass === 0)
{
const relativeVelocity = _event.contact.getImpactVelocityAlongNormal()
this.sounds.play('carHit', relativeVelocity)
}
})
/**
* Vehicle
*/
this.car.vehicle = new CANNON.RaycastVehicle({
chassisBody: this.car.chassis.body
})
/**
* Wheel
*/
this.car.wheels = {}
this.car.wheels.options = {
radius: this.car.options.wheelRadius,
height: this.car.options.wheelHeight,
suspensionStiffness: this.car.options.wheelSuspensionStiffness,
suspensionRestLength: this.car.options.wheelSuspensionRestLength,
frictionSlip: this.car.options.wheelFrictionSlip,
dampingRelaxation: this.car.options.wheelDampingRelaxation,
dampingCompression: this.car.options.wheelDampingCompression,
maxSuspensionForce: this.car.options.wheelMaxSuspensionForce,
rollInfluence: this.car.options.wheelRollInfluence,
maxSuspensionTravel: this.car.options.wheelMaxSuspensionTravel,
customSlidingRotationalSpeed: this.car.options.wheelCustomSlidingRotationalSpeed,
useCustomSlidingRotationalSpeed: true,
directionLocal: new CANNON.Vec3(0, 0, - 1),
axleLocal: new CANNON.Vec3(0, 1, 0),
chassisConnectionPointLocal: new CANNON.Vec3(1, 1, 0) // Will be changed for each wheel
}
// Front left
this.car.wheels.options.chassisConnectionPointLocal.set(this.car.options.wheelFrontOffsetDepth, this.car.options.wheelOffsetWidth, 0)
this.car.vehicle.addWheel(this.car.wheels.options)
// Front right
this.car.wheels.options.chassisConnectionPointLocal.set(this.car.options.wheelFrontOffsetDepth, - this.car.options.wheelOffsetWidth, 0)
this.car.vehicle.addWheel(this.car.wheels.options)
// Back left
this.car.wheels.options.chassisConnectionPointLocal.set(this.car.options.wheelBackOffsetDepth, this.car.options.wheelOffsetWidth, 0)
this.car.vehicle.addWheel(this.car.wheels.options)
// Back right
this.car.wheels.options.chassisConnectionPointLocal.set(this.car.options.wheelBackOffsetDepth, - this.car.options.wheelOffsetWidth, 0)
this.car.vehicle.addWheel(this.car.wheels.options)
this.car.vehicle.addToWorld(this.world)
this.car.wheels.indexes = {}
this.car.wheels.indexes.frontLeft = 0
this.car.wheels.indexes.frontRight = 1
this.car.wheels.indexes.backLeft = 2
this.car.wheels.indexes.backRight = 3
this.car.wheels.bodies = []
for(const _wheelInfos of this.car.vehicle.wheelInfos)
{
const shape = new CANNON.Cylinder(_wheelInfos.radius, _wheelInfos.radius, this.car.wheels.options.height, 20)
const body = new CANNON.Body({ mass: this.car.options.wheelMass, material: this.materials.items.wheel })
const quaternion = new CANNON.Quaternion()
quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI / 2)
body.type = CANNON.Body.KINEMATIC
body.addShape(shape, new CANNON.Vec3(), quaternion)
this.car.wheels.bodies.push(body)
}
/**
* Model
*/
this.car.model = {}
this.car.model.container = new THREE.Object3D()
this.models.container.add(this.car.model.container)
this.car.model.material = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true })
this.car.model.chassis = new THREE.Mesh(new THREE.BoxBufferGeometry(this.car.options.chassisDepth, this.car.options.chassisWidth, this.car.options.chassisHeight), this.car.model.material)
this.car.model.container.add(this.car.model.chassis)
this.car.model.wheels = []
const wheelGeometry = new THREE.CylinderBufferGeometry(this.car.options.wheelRadius, this.car.options.wheelRadius, this.car.options.wheelHeight, 8, 1)
for(let i = 0; i < 4; i++)
{
const wheel = new THREE.Mesh(wheelGeometry, this.car.model.material)
this.car.model.container.add(wheel)
this.car.model.wheels.push(wheel)
}
}
/**
* Destroy method
*/
this.car.destroy = () =>
{
this.car.vehicle.removeFromWorld(this.world)
this.models.container.remove(this.car.model.container)
}
/**
* Recreate method
*/
this.car.recreate = () =>
{
this.car.destroy()
this.car.create()
this.car.chassis.body.wakeUp()
}
/**
* Brake
*/
this.car.brake = () =>
{
this.car.vehicle.setBrake(1, 0)
this.car.vehicle.setBrake(1, 1)
this.car.vehicle.setBrake(1, 2)
this.car.vehicle.setBrake(1, 3)
}
/**
* Unbrake
*/
this.car.unbrake = () =>
{
this.car.vehicle.setBrake(0, 0)
this.car.vehicle.setBrake(0, 1)
this.car.vehicle.setBrake(0, 2)
this.car.vehicle.setBrake(0, 3)
}
/**
* Actions
*/
this.controls.on('action', (_name) =>
{
switch(_name)
{
case 'reset':
this.car.recreate()
break
}
})
/**
* Cannon tick
*/
this.world.addEventListener('postStep', () =>
{
// Update speed
let positionDelta = new CANNON.Vec3()
positionDelta = positionDelta.copy(this.car.chassis.body.position)
positionDelta = positionDelta.vsub(this.car.oldPosition)
this.car.oldPosition.copy(this.car.chassis.body.position)
this.car.speed = positionDelta.length()
// Update forward
const localForward = new CANNON.Vec3(1, 0, 0)
this.car.chassis.body.vectorToWorldFrame(localForward, this.car.worldForward)
this.car.angle = Math.atan2(this.car.worldForward.y, this.car.worldForward.x)
this.car.forwardSpeed = this.car.worldForward.dot(positionDelta)
this.car.goingForward = this.car.forwardSpeed > 0
// Updise down
const localUp = new CANNON.Vec3(0, 0, 1)
const worldUp = new CANNON.Vec3()
this.car.chassis.body.vectorToWorldFrame(localUp, worldUp)
if(worldUp.dot(localUp) < 0.5)
{
if(this.car.upsideDown.state === 'watching')
{
this.car.upsideDown.state = 'pending'
this.car.upsideDown.pendingTimeout = window.setTimeout(() =>
{
this.car.upsideDown.state = 'turning'
this.car.jump(true)
this.car.upsideDown.turningTimeout = window.setTimeout(() =>
{
this.car.upsideDown.state = 'watching'
}, 1000)
}, 1000)
}
}
else
{
if(this.car.upsideDown.state === 'pending')
{
this.car.upsideDown.state = 'watching'
window.clearTimeout(this.car.upsideDown.pendingTimeout)
}
}
// Update wheel bodies
for(let i = 0; i < this.car.vehicle.wheelInfos.length; i++)
{
this.car.vehicle.updateWheelTransform(i)
const transform = this.car.vehicle.wheelInfos[i].worldTransform
this.car.wheels.bodies[i].position.copy(transform.position)
this.car.wheels.bodies[i].quaternion.copy(transform.quaternion)
// Rotate the wheels on the right
if(i === 1 || i === 3)
{
const rotationQuaternion = new CANNON.Quaternion(0, 0, 0, 1)
rotationQuaternion.setFromAxisAngle(new CANNON.Vec3(0, 0, 1), Math.PI)
this.car.wheels.bodies[i].quaternion = this.car.wheels.bodies[i].quaternion.mult(rotationQuaternion)
}
}
// Slow down back
if(!this.controls.actions.up && !this.controls.actions.down)
{
let slowDownForce = this.car.worldForward.clone()
if(this.car.goingForward)
{
slowDownForce = slowDownForce.negate()
}
slowDownForce = slowDownForce.scale(this.car.chassis.body.velocity.length() * 0.1)
this.car.chassis.body.applyImpulse(slowDownForce, this.car.chassis.body.position)
}
})
/**
* Time tick
*/
this.time.on('tick', () =>
{
/**
* Body
*/
// Update chassis model
this.car.model.chassis.position.copy(this.car.chassis.body.position).add(this.car.options.chassisOffset)
this.car.model.chassis.quaternion.copy(this.car.chassis.body.quaternion)
// Update wheel models
for(const _wheelKey in this.car.wheels.bodies)
{
const wheelBody = this.car.wheels.bodies[_wheelKey]
const wheelMesh = this.car.model.wheels[_wheelKey]
wheelMesh.position.copy(wheelBody.position)
wheelMesh.quaternion.copy(wheelBody.quaternion)
}
/**
* Steering
*/
if(this.controls.touch)
{
let deltaAngle = 0
if(this.controls.touch.joystick.active)
{
// Calculate delta between joystick and car angles
deltaAngle = (this.controls.touch.joystick.angle.value - this.car.angle + Math.PI) % (Math.PI * 2) - Math.PI
deltaAngle = deltaAngle < - Math.PI ? deltaAngle + Math.PI * 2 : deltaAngle
}
// Update steering directly
const goingForward = Math.abs(this.car.forwardSpeed) < 0.01 ? true : this.car.goingForward
this.car.steering = deltaAngle * (goingForward ? - 1 : 1)
// Clamp steer
if(Math.abs(this.car.steering) > this.car.options.controlsSteeringMax)
{
this.car.steering = Math.sign(this.car.steering) * this.car.options.controlsSteeringMax
}
}
if(!this.controls.touch || !this.controls.touch.joystick.active)
{
const steerStrength = this.time.delta * this.car.options.controlsSteeringSpeed
// Steer right
if(this.controls.actions.right)
{
this.car.steering += steerStrength
}
// Steer left
else if(this.controls.actions.left)
{
this.car.steering -= steerStrength
}
// Steer center
else
{
if(Math.abs(this.car.steering) > steerStrength)
{
this.car.steering -= steerStrength * Math.sign(this.car.steering)
}
else
{
this.car.steering = 0
}
}
// Clamp steer
if(Math.abs(this.car.steering) > this.car.options.controlsSteeringMax)
{
this.car.steering = Math.sign(this.car.steering) * this.car.options.controlsSteeringMax
}
}
// Update wheels
this.car.vehicle.setSteeringValue(- this.car.steering, this.car.wheels.indexes.frontLeft)
this.car.vehicle.setSteeringValue(- this.car.steering, this.car.wheels.indexes.frontRight)
if(this.car.options.controlsSteeringQuad)
{
this.car.vehicle.setSteeringValue(this.car.steering, this.car.wheels.indexes.backLeft)
this.car.vehicle.setSteeringValue(this.car.steering, this.car.wheels.indexes.backRight)
}
/**
* Accelerate
*/
const accelerationSpeed = this.controls.actions.boost ? this.car.options.controlsAcceleratingSpeedBoost : this.car.options.controlsAcceleratingSpeed
const accelerateStrength = this.time.delta * accelerationSpeed
const controlsAcceleratinMaxSpeed = this.controls.actions.boost ? this.car.options.controlsAcceleratinMaxSpeedBoost : this.car.options.controlsAcceleratinMaxSpeed
// Accelerate up
if(this.controls.actions.up)
{
if(this.car.speed < controlsAcceleratinMaxSpeed || !this.car.goingForward)
{
this.car.accelerating = accelerateStrength
}
else
{
this.car.accelerating = 0
}
}
// Accelerate Down
else if(this.controls.actions.down)
{
if(this.car.speed < controlsAcceleratinMaxSpeed || this.car.goingForward)
{
this.car.accelerating = - accelerateStrength
}
else
{
this.car.accelerating = 0
}
}
else
{
this.car.accelerating = 0
}
this.car.vehicle.applyEngineForce(- this.car.accelerating, this.car.wheels.indexes.backLeft)
this.car.vehicle.applyEngineForce(- this.car.accelerating, this.car.wheels.indexes.backRight)
if(this.car.options.controlsSteeringQuad)
{
this.car.vehicle.applyEngineForce(- this.car.accelerating, this.car.wheels.indexes.frontLeft)
this.car.vehicle.applyEngineForce(- this.car.accelerating, this.car.wheels.indexes.frontRight)
}
/**
* Brake
*/
if(this.controls.actions.brake)
{
this.car.vehicle.setBrake(this.car.options.controlsBrakeStrength, 0)
this.car.vehicle.setBrake(this.car.options.controlsBrakeStrength, 1)
this.car.vehicle.setBrake(this.car.options.controlsBrakeStrength, 2)
this.car.vehicle.setBrake(this.car.options.controlsBrakeStrength, 3)
}
else
{
this.car.vehicle.setBrake(0, 0)
this.car.vehicle.setBrake(0, 1)
this.car.vehicle.setBrake(0, 2)
this.car.vehicle.setBrake(0, 3)
}
})
// Create the initial car
this.car.create()
// Debug
if(this.debug)
{
this.car.debugFolder = this.debugFolder.addFolder('car')
this.car.debugFolder.open()
this.car.debugFolder.add(this.car.options, 'chassisWidth').step(0.001).min(0).max(5).name('chassisWidth').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'chassisHeight').step(0.001).min(0).max(5).name('chassisHeight').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'chassisDepth').step(0.001).min(0).max(5).name('chassisDepth').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options.chassisOffset, 'z').step(0.001).min(0).max(5).name('chassisOffset').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'chassisMass').step(0.001).min(0).max(1000).name('chassisMass').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelFrontOffsetDepth').step(0.001).min(0).max(5).name('wheelFrontOffsetDepth').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelBackOffsetDepth').step(0.001).min(- 5).max(0).name('wheelBackOffsetDepth').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelOffsetWidth').step(0.001).min(0).max(5).name('wheelOffsetWidth').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelRadius').step(0.001).min(0).max(2).name('wheelRadius').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelHeight').step(0.001).min(0).max(2).name('wheelHeight').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelSuspensionStiffness').step(0.001).min(0).max(300).name('wheelSuspensionStiffness').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelSuspensionRestLength').step(0.001).min(0).max(5).name('wheelSuspensionRestLength').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelFrictionSlip').step(0.001).min(0).max(30).name('wheelFrictionSlip').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelDampingRelaxation').step(0.001).min(0).max(30).name('wheelDampingRelaxation').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelDampingCompression').step(0.001).min(0).max(30).name('wheelDampingCompression').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelMaxSuspensionForce').step(0.001).min(0).max(1000000).name('wheelMaxSuspensionForce').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelRollInfluence').step(0.001).min(0).max(1).name('wheelRollInfluence').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelMaxSuspensionTravel').step(0.001).min(0).max(5).name('wheelMaxSuspensionTravel').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelCustomSlidingRotationalSpeed').step(0.001).min(- 45).max(45).name('wheelCustomSlidingRotationalSpeed').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'wheelMass').step(0.001).min(0).max(1000).name('wheelMass').onFinishChange(this.car.recreate)
this.car.debugFolder.add(this.car.options, 'controlsSteeringSpeed').step(0.001).min(0).max(0.1).name('controlsSteeringSpeed')
this.car.debugFolder.add(this.car.options, 'controlsSteeringMax').step(0.001).min(0).max(Math.PI * 0.5).name('controlsSteeringMax')
this.car.debugFolder.add(this.car.options, 'controlsSteeringQuad').name('controlsSteeringQuad')
this.car.debugFolder.add(this.car.options, 'controlsAcceleratingSpeed').step(0.001).min(0).max(30).name('controlsAcceleratingSpeed')
this.car.debugFolder.add(this.car.options, 'controlsAcceleratingSpeedBoost').step(0.001).min(0).max(30).name('controlsAcceleratingSpeedBoost')
this.car.debugFolder.add(this.car.options, 'controlsAcceleratingQuad').name('controlsAcceleratingQuad')
this.car.debugFolder.add(this.car.options, 'controlsBrakeStrength').step(0.001).min(0).max(5).name('controlsBrakeStrength')
this.car.debugFolder.add(this.car, 'recreate')
this.car.debugFolder.add(this.car, 'jump')
}
}
addObjectFromThree(_options)
{
// Set up
const collision = {}
collision.model = {}
collision.model.meshes = []
collision.model.container = new THREE.Object3D()
this.models.container.add(collision.model.container)
collision.children = []
// Material
const bodyMaterial = this.materials.items.dummy
// Body
collision.body = new CANNON.Body({
position: new CANNON.Vec3(_options.offset.x, _options.offset.y, _options.offset.z),
mass: _options.mass,
material: bodyMaterial
})
collision.body.allowSleep = true
collision.body.sleepSpeedLimit = 0.01
if(_options.sleep)
{
collision.body.sleep()
}
this.world.addBody(collision.body)
// Rotation
if(_options.rotation)
{
const rotationQuaternion = new CANNON.Quaternion()
rotationQuaternion.setFromEuler(_options.rotation.x, _options.rotation.y, _options.rotation.z, _options.rotation.order)
collision.body.quaternion = collision.body.quaternion.mult(rotationQuaternion)
}
// Center
collision.center = new CANNON.Vec3(0, 0, 0)
// Shapes
const shapes = []
// Each mesh
for(let i = 0; i < _options.meshes.length; i++)
{
const mesh = _options.meshes[i]
// Define shape
let shape = null
if(mesh.name.match(/^cube_?[0-9]{0,3}?|box[0-9]{0,3}?$/i))
{
shape = 'box'
}
else if(mesh.name.match(/^cylinder_?[0-9]{0,3}?$/i))
{
shape = 'cylinder'
}
else if(mesh.name.match(/^sphere_?[0-9]{0,3}?$/i))
{
shape = 'sphere'
}
else if(mesh.name.match(/^center_?[0-9]{0,3}?$/i))
{
shape = 'center'
}
// Shape is the center
if(shape === 'center')
{
collision.center.set(mesh.position.x, mesh.position.y, mesh.position.z)
}
// Other shape
else if(shape)
{
// Geometry
let shapeGeometry = null
if(shape === 'cylinder')
{
shapeGeometry = new CANNON.Cylinder(mesh.scale.x, mesh.scale.x, mesh.scale.z, 8)
}
else if(shape === 'box')
{
const halfExtents = new CANNON.Vec3(mesh.scale.x * 0.5, mesh.scale.y * 0.5, mesh.scale.z * 0.5)
shapeGeometry = new CANNON.Box(halfExtents)
}
else if(shape === 'sphere')
{
shapeGeometry = new CANNON.Sphere(mesh.scale.x)
}
// Position
const shapePosition = new CANNON.Vec3(mesh.position.x, mesh.position.y, mesh.position.z)
// Quaternion
const shapeQuaternion = new CANNON.Quaternion(mesh.quaternion.x, mesh.quaternion.y, mesh.quaternion.z, mesh.quaternion.w)
if(shape === 'cylinder')
{
// Rotate cylinder
// shapeQuaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), - Math.PI * 0.5)
}
// Save
shapes.push({ shapeGeometry, shapePosition, shapeQuaternion })
// Create model object
let modelGeometry = null
if(shape === 'cylinder')
{
modelGeometry = new THREE.CylinderBufferGeometry(1, 1, 1, 8, 1)
modelGeometry.rotateX(Math.PI * 0.5)
}
else if(shape === 'box')
{
modelGeometry = new THREE.BoxBufferGeometry(1, 1, 1)
}
else if(shape === 'sphere')
{
modelGeometry = new THREE.SphereBufferGeometry(1, 8, 8)
}
const modelMesh = new THREE.Mesh(modelGeometry, this.models.materials[_options.mass === 0 ? 'static' : 'dynamic'])
modelMesh.position.copy(mesh.position)
modelMesh.scale.copy(mesh.scale)
modelMesh.quaternion.copy(mesh.quaternion)
collision.model.meshes.push(modelMesh)
}
}
// Update meshes to match center
for(const _mesh of collision.model.meshes)
{
_mesh.position.x -= collision.center.x
_mesh.position.y -= collision.center.y
_mesh.position.z -= collision.center.z
collision.model.container.add(_mesh)
}
// Update shapes to match center
for(const _shape of shapes)
{
// Create physic object
_shape.shapePosition.x -= collision.center.x
_shape.shapePosition.y -= collision.center.y
_shape.shapePosition.z -= collision.center.z
collision.body.addShape(_shape.shapeGeometry, _shape.shapePosition, _shape.shapeQuaternion)
}
// Update body to match center
collision.body.position.x += collision.center.x
collision.body.position.y += collision.center.y
collision.body.position.z += collision.center.z
// Save origin
collision.origin = {}
collision.origin.position = collision.body.position.clone()
collision.origin.quaternion = collision.body.quaternion.clone()
collision.origin.sleep = _options.sleep
// Time tick update
this.time.on('tick', () =>
{
collision.model.container.position.set(collision.body.position.x, collision.body.position.y, collision.body.position.z)
collision.model.container.quaternion.set(collision.body.quaternion.x, collision.body.quaternion.y, collision.body.quaternion.z, collision.body.quaternion.w)
if(this.models.container.visible && _options.mass > 0)
{
for(const _mesh of collision.model.container.children)
{
_mesh.material = collision.body.sleepState === 2 ? this.models.materials.dynamicSleeping : this.models.materials.dynamic
}
}
})
// Reset
collision.reset = () =>
{
collision.body.position.copy(collision.origin.position)
collision.body.quaternion.copy(collision.origin.quaternion)
if(collision.origin.sleep)
{
collision.body.sleep()
}
}
return collision
}
}

View File

@ -0,0 +1,56 @@
import * as THREE from 'three'
export default class CrossroadsSection
{
constructor(_options)
{
// Options
this.time = _options.time
this.resources = _options.resources
this.objects = _options.objects
this.areas = _options.areas
this.tiles = _options.tiles
this.debug = _options.debug
this.x = _options.x
this.y = _options.y
// Set up
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.setStatic()
this.setTiles()
}
setStatic()
{
this.objects.add({
base: this.resources.items.crossroadsStaticBase.scene,
collision: this.resources.items.crossroadsStaticCollision.scene,
floorShadowTexture: this.resources.items.crossroadsStaticFloorShadowTexture,
offset: new THREE.Vector3(this.x, this.y, 0),
mass: 0
})
}
setTiles()
{
// To intro
this.tiles.add({
start: new THREE.Vector2(this.x, - 10),
delta: new THREE.Vector2(0, this.y + 14)
})
// To projects
this.tiles.add({
start: new THREE.Vector2(this.x + 12.5, this.y),
delta: new THREE.Vector2(7.5, 0)
})
// To projects
this.tiles.add({
start: new THREE.Vector2(this.x - 13, this.y),
delta: new THREE.Vector2(- 6, 0)
})
}
}

View File

@ -0,0 +1,93 @@
import * as THREE from 'three'
export default class DistinctionASection
{
constructor(_options)
{
// Options
this.time = _options.time
this.resources = _options.resources
this.objects = _options.objects
this.walls = _options.walls
this.debug = _options.debug
this.x = _options.x
this.y = _options.y
// Set up
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.setStatic()
this.setCones()
this.setWall()
}
setStatic()
{
this.objects.add({
base: this.resources.items.distinctionAStaticBase.scene,
collision: this.resources.items.distinctionAStaticCollision.scene,
floorShadowTexture: this.resources.items.distinctionAStaticFloorShadowTexture,
offset: new THREE.Vector3(this.x, this.y, 0),
mass: 0
})
}
setCones()
{
const positions = [
[0, 9],
[0, 3],
[0, - 3],
[0, - 9]
]
for(const _position of positions)
{
this.objects.add({
base: this.resources.items.coneBase.scene,
collision: this.resources.items.coneCollision.scene,
offset: new THREE.Vector3(this.x + _position[0], this.y + _position[1], 0),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 2, sizeY: 2, offsetZ: - 0.5, alpha: 0.5 },
mass: 0.6,
soundName: 'woodHit'
})
}
}
setWall()
{
// Set up
this.wall = {}
this.wall.x = this.x + 0
this.wall.y = this.y - 13
this.wall.items = []
this.walls.add({
object:
{
base: this.resources.items.projectsDistinctionsAwwwardsBase.scene,
collision: this.resources.items.projectsDistinctionsAwwwardsCollision.scene,
offset: new THREE.Vector3(0, 0, 0.1),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 1.2, sizeY: 1.8, offsetZ: - 0.15, alpha: 0.35 },
mass: 0.5,
soundName: 'brick'
},
shape:
{
type: 'brick',
widthCount: 5,
heightCount: 6,
position: new THREE.Vector3(this.wall.x, this.wall.y, 0),
offsetWidth: new THREE.Vector3(1.25, 0, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.6),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.4)
}
})
}
}

View File

@ -0,0 +1,98 @@
import * as THREE from 'three'
export default class DistinctionBSection
{
constructor(_options)
{
// Options
this.time = _options.time
this.resources = _options.resources
this.objects = _options.objects
this.walls = _options.walls
this.debug = _options.debug
this.x = _options.x
this.y = _options.y
// Set up
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.setStatic()
this.setCones()
this.setWall()
}
setStatic()
{
this.objects.add({
base: this.resources.items.distinctionBStaticBase.scene,
collision: this.resources.items.distinctionBStaticCollision.scene,
floorShadowTexture: this.resources.items.distinctionBStaticFloorShadowTexture,
offset: new THREE.Vector3(this.x, this.y, 0),
mass: 0
})
}
setCones()
{
const positions = [
[3, 8],
[3, 4],
[3, 0],
[3, - 4],
[- 3, 8],
[- 3, 4],
[- 3, 0],
[- 3, - 4]
]
for(const _position of positions)
{
this.objects.add({
base: this.resources.items.coneBase.scene,
collision: this.resources.items.coneCollision.scene,
offset: new THREE.Vector3(this.x + _position[0], this.y + _position[1], 0),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 2, sizeY: 2, offsetZ: - 0.5, alpha: 0.5 },
mass: 0.6,
soundName: 'woodHit'
})
}
}
setWall()
{
// Set up
this.wall = {}
this.wall.x = this.x + 0
this.wall.y = this.y - 18
this.wall.items = []
this.walls.add({
object:
{
base: this.resources.items.projectsDistinctionsFWABase.scene,
collision: this.resources.items.projectsDistinctionsFWACollision.scene,
offset: new THREE.Vector3(0, 0, 0.1),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 1.2, sizeY: 1.8, offsetZ: - 0.15, alpha: 0.35 },
mass: 0.5,
soundName: 'brick'
},
shape:
{
type: 'brick',
widthCount: 4,
heightCount: 7,
position: new THREE.Vector3(this.wall.x, this.wall.y, 0),
offsetWidth: new THREE.Vector3(1.7, 0, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.45),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.4)
}
})
}
}

View File

@ -0,0 +1,36 @@
import * as THREE from 'three'
export default class DistinctionCSection
{
constructor(_options)
{
// Options
this.time = _options.time
this.resources = _options.resources
this.objects = _options.objects
this.walls = _options.walls
this.debug = _options.debug
this.x = _options.x
this.y = _options.y
// Set up
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.setTrophy()
}
setTrophy()
{
this.objects.add({
base: this.resources.items.awwwardsTrophyBase.scene,
collision: this.resources.items.awwwardsTrophyCollision.scene,
offset: new THREE.Vector3(0, - 5, 0),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 2, sizeY: 2, offsetZ: - 0.5, alpha: 0.5 },
mass: 50,
soundName: 'woodHit'
})
}
}

View File

@ -0,0 +1,49 @@
import * as THREE from 'three'
export default class DistinctionCSection
{
constructor(_options)
{
// Options
this.time = _options.time
this.resources = _options.resources
this.objects = _options.objects
this.walls = _options.walls
this.debug = _options.debug
this.x = _options.x
this.y = _options.y
// Set up
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.setStatic()
this.setTrophy()
}
setStatic()
{
this.objects.add({
base: this.resources.items.distinctionCStaticBase.scene,
collision: this.resources.items.distinctionCStaticCollision.scene,
floorShadowTexture: this.resources.items.distinctionCStaticFloorShadowTexture,
offset: new THREE.Vector3(this.x, this.y, 0),
mass: 0
})
}
setTrophy()
{
this.objects.add({
base: this.resources.items.webbyTrophyBase.scene,
collision: this.resources.items.webbyTrophyCollision.scene,
offset: new THREE.Vector3(0, - 2.5, 5),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 4, sizeY: 4, offsetZ: - 0.5, alpha: 0.65 },
mass: 15,
soundName: 'woodHit',
sleep: false
})
}
}

View File

@ -0,0 +1,184 @@
import * as THREE from 'three'
export default class InformationSection
{
constructor(_options)
{
// Options
this.time = _options.time
this.resources = _options.resources
this.objects = _options.objects
this.areas = _options.areas
this.tiles = _options.tiles
this.debug = _options.debug
this.x = _options.x
this.y = _options.y
// Set up
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.setStatic()
this.setBaguettes()
this.setLinks()
this.setActivities()
this.setTiles()
}
setStatic()
{
this.objects.add({
base: this.resources.items.informationStaticBase.scene,
collision: this.resources.items.informationStaticCollision.scene,
floorShadowTexture: this.resources.items.informationStaticFloorShadowTexture,
offset: new THREE.Vector3(this.x, this.y, 0),
mass: 0
})
}
setBaguettes()
{
this.baguettes = {}
this.baguettes.x = - 4
this.baguettes.y = 6
this.baguettes.a = this.objects.add({
base: this.resources.items.informationBaguetteBase.scene,
collision: this.resources.items.informationBaguetteCollision.scene,
offset: new THREE.Vector3(this.x + this.baguettes.x - 0.56, this.y + this.baguettes.y - 0.666, 0.2),
rotation: new THREE.Euler(0, 0, - Math.PI * 37 / 180),
duplicated: true,
shadow: { sizeX: 0.6, sizeY: 3.5, offsetZ: - 0.15, alpha: 0.35 },
mass: 1.5,
// soundName: 'woodHit'
})
this.baguettes.b = this.objects.add({
base: this.resources.items.informationBaguetteBase.scene,
collision: this.resources.items.informationBaguetteCollision.scene,
offset: new THREE.Vector3(this.x + this.baguettes.x - 0.8, this.y + this.baguettes.y - 2, 0.5),
rotation: new THREE.Euler(0, - 0.5, Math.PI * 60 / 180),
duplicated: true,
shadow: { sizeX: 0.6, sizeY: 3.5, offsetZ: - 0.15, alpha: 0.35 },
mass: 1.5,
sleep: false,
// soundName: 'woodHit'
})
}
setLinks()
{
// Set up
this.links = {}
this.links.x = 1.95
this.links.y = - 1.5
this.links.halfExtents = {}
this.links.halfExtents.x = 1
this.links.halfExtents.y = 1
this.links.distanceBetween = 2.4
this.links.labelWidth = this.links.halfExtents.x * 2 + 1
this.links.labelGeometry = new THREE.PlaneBufferGeometry(this.links.labelWidth, this.links.labelWidth * 0.25, 1, 1)
this.links.labelOffset = - 1.6
this.links.items = []
this.links.container = new THREE.Object3D()
this.links.container.matrixAutoUpdate = false
this.container.add(this.links.container)
// Options
this.links.options = [
{
href: 'https://twitter.com/bruno_simon/',
labelTexture: this.resources.items.informationContactTwitterLabelTexture
},
{
href: 'https://github.com/brunosimon/',
labelTexture: this.resources.items.informationContactGithubLabelTexture
},
{
href: 'https://www.linkedin.com/in/simonbruno77/',
labelTexture: this.resources.items.informationContactLinkedinLabelTexture
},
{
href: 'mailto:simon.bruno.77@gmail.com',
labelTexture: this.resources.items.informationContactMailLabelTexture
}
]
// Create each link
let i = 0
for(const _option of this.links.options)
{
// Set up
const item = {}
item.x = this.x + this.links.x + this.links.distanceBetween * i
item.y = this.y + this.links.y
item.href = _option.href
// Create area
item.area = this.areas.add({
position: new THREE.Vector2(item.x, item.y),
halfExtents: new THREE.Vector2(this.links.halfExtents.x, this.links.halfExtents.y)
})
item.area.on('interact', () =>
{
window.open(_option.href, '_blank')
})
// Texture
item.texture = _option.labelTexture
item.texture.magFilter = THREE.NearestFilter
item.texture.minFilter = THREE.LinearFilter
// Create label
item.labelMesh = new THREE.Mesh(this.links.labelGeometry, new THREE.MeshBasicMaterial({ wireframe: false, color: 0xffffff, alphaMap: _option.labelTexture, depthTest: true, depthWrite: false, transparent: true }))
item.labelMesh.position.x = item.x + this.links.labelWidth * 0.5 - this.links.halfExtents.x
item.labelMesh.position.y = item.y + this.links.labelOffset
item.labelMesh.matrixAutoUpdate = false
item.labelMesh.updateMatrix()
this.links.container.add(item.labelMesh)
// Save
this.links.items.push(item)
i++
}
}
setActivities()
{
// Set up
this.activities = {}
this.activities.x = this.x + 0
this.activities.y = this.y - 10
this.activities.multiplier = 5.5
// Geometry
this.activities.geometry = new THREE.PlaneBufferGeometry(2 * this.activities.multiplier, 1 * this.activities.multiplier, 1, 1)
// Texture
this.activities.texture = this.resources.items.informationActivitiesTexture
this.activities.texture.magFilter = THREE.NearestFilter
this.activities.texture.minFilter = THREE.LinearFilter
// Material
this.activities.material = new THREE.MeshBasicMaterial({ wireframe: false, color: 0xffffff, alphaMap: this.activities.texture, transparent: true })
// Mesh
this.activities.mesh = new THREE.Mesh(this.activities.geometry, this.activities.material)
this.activities.mesh.position.x = this.activities.x
this.activities.mesh.position.y = this.activities.y
this.activities.mesh.matrixAutoUpdate = false
this.activities.mesh.updateMatrix()
this.container.add(this.activities.mesh)
}
setTiles()
{
this.tiles.add({
start: new THREE.Vector2(this.x - 1.2, this.y + 13),
delta: new THREE.Vector2(0, - 20)
})
}
}

View File

@ -0,0 +1,463 @@
import * as THREE from 'three'
export default class IntroSection
{
constructor(_options)
{
// Options
this.config = _options.config
this.time = _options.time
this.resources = _options.resources
this.objects = _options.objects
this.areas = _options.areas
this.walls = _options.walls
this.tiles = _options.tiles
this.debug = _options.debug
this.x = _options.x
this.y = _options.y
// Set up
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.container.updateMatrix()
this.setStatic()
this.setInstructions()
this.setOtherInstructions()
this.setTitles()
this.setTiles()
this.setDikes()
}
setStatic()
{
this.objects.add({
base: this.resources.items.introStaticBase.scene,
collision: this.resources.items.introStaticCollision.scene,
floorShadowTexture: this.resources.items.introStaticFloorShadowTexture,
offset: new THREE.Vector3(0, 0, 0),
mass: 0
})
}
setInstructions()
{
this.instructions = {}
/**
* Arrows
*/
this.instructions.arrows = {}
// Label
this.instructions.arrows.label = {}
this.instructions.arrows.label.texture = this.config.touch ? this.resources.items.introInstructionsControlsTexture : this.resources.items.introInstructionsArrowsTexture
this.instructions.arrows.label.texture.magFilter = THREE.NearestFilter
this.instructions.arrows.label.texture.minFilter = THREE.LinearFilter
this.instructions.arrows.label.material = new THREE.MeshBasicMaterial({ transparent: true, alphaMap: this.instructions.arrows.label.texture, color: 0xffffff, depthWrite: false, opacity: 0 })
this.instructions.arrows.label.geometry = this.resources.items.introInstructionsLabels.scene.children.find((_mesh) => _mesh.name === 'arrows').geometry
this.instructions.arrows.label.mesh = new THREE.Mesh(this.instructions.arrows.label.geometry, this.instructions.arrows.label.material)
this.container.add(this.instructions.arrows.label.mesh)
if(!this.config.touch)
{
// Keys
this.instructions.arrows.up = this.objects.add({
base: this.resources.items.introArrowKeyBase.scene,
collision: this.resources.items.introArrowKeyCollision.scene,
offset: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 1, sizeY: 1, offsetZ: - 0.2, alpha: 0.5 },
mass: 1.5,
soundName: 'brick'
})
this.instructions.arrows.down = this.objects.add({
base: this.resources.items.introArrowKeyBase.scene,
collision: this.resources.items.introArrowKeyCollision.scene,
offset: new THREE.Vector3(0, - 0.8, 0),
rotation: new THREE.Euler(0, 0, Math.PI),
duplicated: true,
shadow: { sizeX: 1, sizeY: 1, offsetZ: - 0.2, alpha: 0.5 },
mass: 1.5,
soundName: 'brick'
})
this.instructions.arrows.left = this.objects.add({
base: this.resources.items.introArrowKeyBase.scene,
collision: this.resources.items.introArrowKeyCollision.scene,
offset: new THREE.Vector3(- 0.8, - 0.8, 0),
rotation: new THREE.Euler(0, 0, Math.PI * 0.5),
duplicated: true,
shadow: { sizeX: 1, sizeY: 1, offsetZ: - 0.2, alpha: 0.5 },
mass: 1.5,
soundName: 'brick'
})
this.instructions.arrows.right = this.objects.add({
base: this.resources.items.introArrowKeyBase.scene,
collision: this.resources.items.introArrowKeyCollision.scene,
offset: new THREE.Vector3(0.8, - 0.8, 0),
rotation: new THREE.Euler(0, 0, - Math.PI * 0.5),
duplicated: true,
shadow: { sizeX: 1, sizeY: 1, offsetZ: - 0.2, alpha: 0.5 },
mass: 1.5,
soundName: 'brick'
})
}
}
setOtherInstructions()
{
if(this.config.touch)
{
return
}
this.otherInstructions = {}
this.otherInstructions.x = 16
this.otherInstructions.y = - 2
// Container
this.otherInstructions.container = new THREE.Object3D()
this.otherInstructions.container.position.x = this.otherInstructions.x
this.otherInstructions.container.position.y = this.otherInstructions.y
this.otherInstructions.container.matrixAutoUpdate = false
this.otherInstructions.container.updateMatrix()
this.container.add(this.otherInstructions.container)
// Label
this.otherInstructions.label = {}
this.otherInstructions.label.geometry = new THREE.PlaneBufferGeometry(6, 6, 1, 1)
this.otherInstructions.label.texture = this.resources.items.introInstructionsOtherTexture
this.otherInstructions.label.texture.magFilter = THREE.NearestFilter
this.otherInstructions.label.texture.minFilter = THREE.LinearFilter
this.otherInstructions.label.material = new THREE.MeshBasicMaterial({ transparent: true, alphaMap: this.otherInstructions.label.texture, color: 0xffffff, depthWrite: false, opacity: 0 })
this.otherInstructions.label.mesh = new THREE.Mesh(this.otherInstructions.label.geometry, this.otherInstructions.label.material)
this.otherInstructions.label.mesh.matrixAutoUpdate = false
this.otherInstructions.container.add(this.otherInstructions.label.mesh)
// Horn
this.otherInstructions.horn = this.objects.add({
base: this.resources.items.hornBase.scene,
collision: this.resources.items.hornCollision.scene,
offset: new THREE.Vector3(this.otherInstructions.x + 1.25, this.otherInstructions.y - 2.75, 0.2),
rotation: new THREE.Euler(0, 0, 0.5),
duplicated: true,
shadow: { sizeX: 1.65, sizeY: 0.75, offsetZ: - 0.1, alpha: 0.4 },
mass: 1.5,
soundName: 'horn',
sleep: false
})
}
setTitles()
{
// Title
this.objects.add({
base: this.resources.items.introBBase.scene,
collision: this.resources.items.introBCollision.scene,
offset: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.6, alpha: 0.4 },
mass: 1.5,
soundName: 'brick'
})
this.objects.add({
base: this.resources.items.introRBase.scene,
collision: this.resources.items.introRCollision.scene,
offset: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.6, alpha: 0.4 },
mass: 1.5,
soundName: 'brick'
})
this.objects.add({
base: this.resources.items.introUBase.scene,
collision: this.resources.items.introUCollision.scene,
offset: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.6, alpha: 0.4 },
mass: 1.5,
soundName: 'brick'
})
this.objects.add({
base: this.resources.items.introNBase.scene,
collision: this.resources.items.introNCollision.scene,
offset: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.6, alpha: 0.4 },
mass: 1.5,
soundName: 'brick'
})
this.objects.add({
base: this.resources.items.introOBase.scene,
collision: this.resources.items.introOCollision.scene,
offset: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.6, alpha: 0.4 },
mass: 1.5,
soundName: 'brick'
})
this.objects.add({
base: this.resources.items.introSBase.scene,
collision: this.resources.items.introSCollision.scene,
offset: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.6, alpha: 0.4 },
mass: 1.5,
soundName: 'brick'
})
this.objects.add({
base: this.resources.items.introIBase.scene,
collision: this.resources.items.introICollision.scene,
offset: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.6, alpha: 0.4 },
mass: 1.5,
soundName: 'brick'
})
this.objects.add({
base: this.resources.items.introMBase.scene,
collision: this.resources.items.introMCollision.scene,
offset: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.6, alpha: 0.4 },
mass: 1.5,
soundName: 'brick'
})
this.objects.add({
base: this.resources.items.introOBase.scene,
collision: this.resources.items.introOCollision.scene,
offset: new THREE.Vector3(3.95, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.6, alpha: 0.4 },
mass: 1.5,
soundName: 'brick'
})
this.objects.add({
base: this.resources.items.introNBase.scene,
collision: this.resources.items.introNCollision.scene,
offset: new THREE.Vector3(5.85, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.6, alpha: 0.4 },
mass: 1.5,
soundName: 'brick'
})
this.objects.add({
base: this.resources.items.introCreativeBase.scene,
collision: this.resources.items.introCreativeCollision.scene,
offset: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(0, 0, 0.25),
shadow: { sizeX: 5, sizeY: 1.5, offsetZ: - 0.6, alpha: 0.3 },
mass: 1.5,
sleep: false,
soundName: 'brick'
})
this.objects.add({
base: this.resources.items.introDevBase.scene,
collision: this.resources.items.introDevCollision.scene,
offset: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
shadow: { sizeX: 2.5, sizeY: 1.5, offsetZ: - 0.6, alpha: 0.3 },
mass: 1.5,
soundName: 'brick'
})
}
setTiles()
{
this.tiles.add({
start: new THREE.Vector2(0, - 4.5),
delta: new THREE.Vector2(0, - 4.5)
})
}
setDikes()
{
this.dikes = {}
this.dikes.brickOptions = {
base: this.resources.items.brickBase.scene,
collision: this.resources.items.brickCollision.scene,
offset: new THREE.Vector3(0, 0, 0.1),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 1.2, sizeY: 1.8, offsetZ: - 0.15, alpha: 0.35 },
mass: 0.5,
soundName: 'brick'
}
// this.walls.add({
// object:
// {
// ...this.dikes.brickOptions,
// rotation: new THREE.Euler(0, 0, Math.PI * 0.5)
// },
// shape:
// {
// type: 'brick',
// equilibrateLastLine: true,
// widthCount: 3,
// heightCount: 2,
// position: new THREE.Vector3(this.x + 0, this.y - 4, 0),
// offsetWidth: new THREE.Vector3(1.05, 0, 0),
// offsetHeight: new THREE.Vector3(0, 0, 0.45),
// randomOffset: new THREE.Vector3(0, 0, 0),
// randomRotation: new THREE.Vector3(0, 0, 0.2)
// }
// })
this.walls.add({
object: this.dikes.brickOptions,
shape:
{
type: 'brick',
equilibrateLastLine: true,
widthCount: 5,
heightCount: 2,
position: new THREE.Vector3(this.x - 12, this.y - 13, 0),
offsetWidth: new THREE.Vector3(0, 1.05, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.45),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.2)
}
})
this.walls.add({
object:
{
...this.dikes.brickOptions,
rotation: new THREE.Euler(0, 0, Math.PI * 0.5)
},
shape:
{
type: 'brick',
equilibrateLastLine: true,
widthCount: 3,
heightCount: 2,
position: new THREE.Vector3(this.x + 8, this.y + 6, 0),
offsetWidth: new THREE.Vector3(1.05, 0, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.45),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.2)
}
})
this.walls.add({
object: this.dikes.brickOptions,
shape:
{
type: 'brick',
equilibrateLastLine: false,
widthCount: 3,
heightCount: 2,
position: new THREE.Vector3(this.x + 9.9, this.y + 4.7, 0),
offsetWidth: new THREE.Vector3(0, - 1.05, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.45),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.2)
}
})
this.walls.add({
object:
{
...this.dikes.brickOptions,
rotation: new THREE.Euler(0, 0, Math.PI * 0.5)
},
shape:
{
type: 'brick',
equilibrateLastLine: true,
widthCount: 3,
heightCount: 2,
position: new THREE.Vector3(this.x - 14, this.y + 2, 0),
offsetWidth: new THREE.Vector3(1.05, 0, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.45),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.2)
}
})
this.walls.add({
object: this.dikes.brickOptions,
shape:
{
type: 'brick',
equilibrateLastLine: false,
widthCount: 3,
heightCount: 2,
position: new THREE.Vector3(this.x - 14.8, this.y + 0.7, 0),
offsetWidth: new THREE.Vector3(0, - 1.05, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.45),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.2)
}
})
this.walls.add({
object: this.dikes.brickOptions,
shape:
{
type: 'brick',
equilibrateLastLine: true,
widthCount: 3,
heightCount: 2,
position: new THREE.Vector3(this.x - 14.8, this.y - 3.5, 0),
offsetWidth: new THREE.Vector3(0, - 1.05, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.45),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.2)
}
})
if(!this.config.touch)
{
this.walls.add({
object:
{
...this.dikes.brickOptions,
rotation: new THREE.Euler(0, 0, Math.PI * 0.5)
},
shape:
{
type: 'brick',
equilibrateLastLine: true,
widthCount: 2,
heightCount: 2,
position: new THREE.Vector3(this.x + 18.5, this.y + 3, 0),
offsetWidth: new THREE.Vector3(1.05, 0, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.45),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.2)
}
})
this.walls.add({
object: this.dikes.brickOptions,
shape:
{
type: 'brick',
equilibrateLastLine: false,
widthCount: 2,
heightCount: 2,
position: new THREE.Vector3(this.x + 19.9, this.y + 2.2, 0),
offsetWidth: new THREE.Vector3(0, - 1.05, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.45),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.2)
}
})
}
}
}

View File

@ -0,0 +1,229 @@
import * as THREE from 'three'
export default class PlaygroundSection
{
constructor(_options)
{
// Options
this.time = _options.time
this.resources = _options.resources
this.objects = _options.objects
this.areas = _options.areas
this.walls = _options.walls
this.tiles = _options.tiles
this.debug = _options.debug
this.x = _options.x
this.y = _options.y
// Debug
if(this.debug)
{
this.debugFolder = this.debug.addFolder('playgroundSection')
// this.debugFolder.open()
}
// Set up
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.resources.items.areaResetTexture.magFilter = THREE.NearestFilter
this.resources.items.areaResetTexture.minFilter = THREE.LinearFilter
this.setStatic()
this.setBricksWalls()
this.setBowling()
}
setStatic()
{
this.objects.add({
base: this.resources.items.playgroundStaticBase.scene,
collision: this.resources.items.playgroundStaticCollision.scene,
floorShadowTexture: this.resources.items.playgroundStaticFloorShadowTexture,
offset: new THREE.Vector3(this.x, this.y, 0),
mass: 0
})
}
setBricksWalls()
{
// Set up
this.brickWalls = {}
this.brickWalls.x = this.x + 15
this.brickWalls.y = this.y + 14
this.brickWalls.items = []
// Brick options
this.brickWalls.brickOptions = {
base: this.resources.items.brickBase.scene,
collision: this.resources.items.brickCollision.scene,
offset: new THREE.Vector3(0, 0, 0.1),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 1.2, sizeY: 1.8, offsetZ: - 0.15, alpha: 0.35 },
mass: 0.5,
soundName: 'brick'
}
this.brickWalls.items.push(
this.walls.add({
object: this.brickWalls.brickOptions,
shape:
{
type: 'rectangle',
widthCount: 5,
heightCount: 6,
position: new THREE.Vector3(this.brickWalls.x - 6, this.brickWalls.y, 0),
offsetWidth: new THREE.Vector3(0, 1.05, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.45),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.4)
}
}),
this.walls.add({
object: this.brickWalls.brickOptions,
shape:
{
type: 'brick',
widthCount: 5,
heightCount: 6,
position: new THREE.Vector3(this.brickWalls.x - 12, this.brickWalls.y, 0),
offsetWidth: new THREE.Vector3(0, 1.05, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.45),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.4)
}
}),
this.walls.add({
object: this.brickWalls.brickOptions,
shape:
{
type: 'triangle',
widthCount: 6,
position: new THREE.Vector3(this.brickWalls.x - 18, this.brickWalls.y, 0),
offsetWidth: new THREE.Vector3(0, 1.05, 0),
offsetHeight: new THREE.Vector3(0, 0, 0.45),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0.4)
}
})
)
// Reset
this.brickWalls.reset = () =>
{
for(const _wall of this.brickWalls.items)
{
for(const _brick of _wall.items)
{
_brick.collision.reset()
}
}
}
// Reset area
this.brickWalls.resetArea = this.areas.add({
position: new THREE.Vector2(this.brickWalls.x, this.brickWalls.y),
halfExtents: new THREE.Vector2(2, 2)
})
this.brickWalls.resetArea.on('interact', () =>
{
this.brickWalls.reset()
})
// Reset label
this.brickWalls.areaLabelMesh = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 0.5), new THREE.MeshBasicMaterial({ transparent: true, depthWrite: false, color: 0xffffff, alphaMap: this.resources.items.areaResetTexture }))
this.brickWalls.areaLabelMesh.position.x = this.brickWalls.x
this.brickWalls.areaLabelMesh.position.y = this.brickWalls.y
this.brickWalls.areaLabelMesh.matrixAutoUpdate = false
this.brickWalls.areaLabelMesh.updateMatrix()
this.container.add(this.brickWalls.areaLabelMesh)
// Debug
if(this.debugFolder)
{
this.debugFolder.add(this.brickWalls, 'reset').name('brickWalls reset')
}
}
setBowling()
{
this.bowling = {}
this.bowling.x = this.x + 15
this.bowling.y = this.y + 4
this.bowling.pins = this.walls.add({
object:
{
base: this.resources.items.bowlingPinBase.scene,
collision: this.resources.items.bowlingPinCollision.scene,
offset: new THREE.Vector3(0, 0, 0.1),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: 1.4, sizeY: 1.4, offsetZ: - 0.15, alpha: 0.35 },
mass: 0.1,
soundName: 'bowlingPin'
// sleep: false
},
shape:
{
type: 'triangle',
widthCount: 4,
position: new THREE.Vector3(this.bowling.x - 25, this.bowling.y, 0),
offsetWidth: new THREE.Vector3(0, 1, 0),
offsetHeight: new THREE.Vector3(0.65, 0, 0),
randomOffset: new THREE.Vector3(0, 0, 0),
randomRotation: new THREE.Vector3(0, 0, 0)
}
})
this.bowling.ball = this.objects.add({
base: this.resources.items.bowlingBallBase.scene,
collision: this.resources.items.bowlingBallCollision.scene,
offset: new THREE.Vector3(this.bowling.x - 5, this.bowling.y, 0),
rotation: new THREE.Euler(Math.PI * 0.5, 0, 0),
duplicated: true,
shadow: { sizeX: 1.5, sizeY: 1.5, offsetZ: - 0.15, alpha: 0.35 },
mass: 1,
soundName: 'bowlingBall'
// sleep: false
})
// Reset
this.bowling.reset = () =>
{
// Reset pins
for(const _pin of this.bowling.pins.items)
{
_pin.collision.reset()
}
// Reset ball
this.bowling.ball.collision.reset()
}
// Reset area
this.bowling.resetArea = this.areas.add({
position: new THREE.Vector2(this.bowling.x, this.bowling.y),
halfExtents: new THREE.Vector2(2, 2)
})
this.bowling.resetArea.on('interact', () =>
{
this.bowling.reset()
})
// Reset label
this.bowling.areaLabelMesh = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 0.5), new THREE.MeshBasicMaterial({ transparent: true, depthWrite: false, color: 0xffffff, alphaMap: this.resources.items.areaResetTexture }))
this.bowling.areaLabelMesh.position.x = this.bowling.x
this.bowling.areaLabelMesh.position.y = this.bowling.y
this.bowling.areaLabelMesh.matrixAutoUpdate = false
this.bowling.areaLabelMesh.updateMatrix()
this.container.add(this.bowling.areaLabelMesh)
// Debug
if(this.debugFolder)
{
this.debugFolder.add(this.bowling, 'reset').name('bowling reset')
}
}
}

View File

@ -0,0 +1,208 @@
import * as THREE from 'three'
import ProjectBoardMaterial from '../../Materials/ProjectBoard.js'
import TweenLite from 'gsap/TweenLite'
import { Power4 } from 'gsap/EasePack'
export default class Project
{
constructor(_options)
{
// Options
this.time = _options.time
this.resources = _options.resources
this.objects = _options.objects
this.areas = _options.areas
this.name = _options.name
this.geometries = _options.geometries
this.meshes = _options.meshes
this.debug = _options.debug
this.name = _options.name
this.x = _options.x
this.y = _options.y
this.imageSources = _options.imageSources
this.floorTexture = _options.floorTexture
this.link = _options.link
this.distinctions = _options.distinctions
// Set up
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
// this.container.updateMatrix()
this.setBoards()
this.setFloor()
}
setBoards()
{
// Set up
this.boards = {}
this.boards.items = []
this.boards.xStart = - 5
this.boards.xInter = 5
this.boards.y = 5
this.boards.color = '#8e7161'
this.boards.threeColor = new THREE.Color(this.boards.color)
if(this.debug)
{
this.debug.addColor(this.boards, 'color').name('boardColor').onChange(() =>
{
this.boards.threeColor.set(this.boards.color)
})
}
// Create each board
let i = 0
for(const _imageSource of this.imageSources)
{
// Set up
const board = {}
board.x = this.x + this.boards.xStart + i * this.boards.xInter
board.y = this.y + this.boards.y
// Create structure with collision
this.objects.add({
base: this.resources.items.projectsBoardStructure.scene,
collision: this.resources.items.projectsBoardCollision.scene,
floorShadowTexture: this.resources.items.projectsBoardStructureFloorShadowTexture,
offset: new THREE.Vector3(board.x, board.y, 0),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
mass: 0
})
// Image load
const image = new Image()
image.addEventListener('load', () =>
{
board.texture = new THREE.Texture(image)
board.texture.needsUpdate = true
board.texture.magFilter = THREE.NearestFilter
board.texture.minFilter = THREE.LinearFilter
board.planeMesh.material.uniforms.uTexture.value = board.texture
TweenLite.to(board.planeMesh.material.uniforms.uTextureAlpha, 1, { value: 1, ease: Power4.inOut })
})
image.src = _imageSource
// Plane
board.planeMesh = this.meshes.boardPlane.clone()
board.planeMesh.position.x = board.x
board.planeMesh.position.y = board.y
board.planeMesh.matrixAutoUpdate = false
board.planeMesh.updateMatrix()
board.planeMesh.material = new ProjectBoardMaterial()
board.planeMesh.material.uniforms.uColor.value = this.boards.threeColor
board.planeMesh.material.uniforms.uTextureAlpha.value = 0
this.container.add(board.planeMesh)
// Save
this.boards.items.push(board)
i++
}
}
setFloor()
{
this.floor = {}
this.floor.x = 0
this.floor.y = - 2
// Container
this.floor.container = new THREE.Object3D()
this.floor.container.position.x = this.x + this.floor.x
this.floor.container.position.y = this.y + this.floor.y
this.floor.container.matrixAutoUpdate = false
this.floor.container.updateMatrix()
this.container.add(this.floor.container)
// Texture
this.floor.texture = this.floorTexture
this.floor.texture.magFilter = THREE.NearestFilter
this.floor.texture.minFilter = THREE.LinearFilter
// Geometry
this.floor.geometry = this.geometries.floor
// Material
this.floor.material = new THREE.MeshBasicMaterial({ transparent: true, depthWrite: false, alphaMap: this.floor.texture })
// Mesh
this.floor.mesh = new THREE.Mesh(this.floor.geometry, this.floor.material)
this.floor.mesh.matrixAutoUpdate = false
this.floor.container.add(this.floor.mesh)
// Distinctions
if(this.distinctions)
{
for(const _distinction of this.distinctions)
{
let base = null
let collision = null
let shadowSizeX = null
let shadowSizeY = null
switch(_distinction.type)
{
case 'awwwards':
base = this.resources.items.projectsDistinctionsAwwwardsBase.scene
collision = this.resources.items.projectsDistinctionsAwwwardsCollision.scene
shadowSizeX = 1.5
shadowSizeY = 1.5
break
case 'fwa':
base = this.resources.items.projectsDistinctionsFWABase.scene
collision = this.resources.items.projectsDistinctionsFWACollision.scene
shadowSizeX = 2
shadowSizeY = 1
break
case 'cssda':
base = this.resources.items.projectsDistinctionsCSSDABase.scene
collision = this.resources.items.projectsDistinctionsCSSDACollision.scene
shadowSizeX = 1.2
shadowSizeY = 1.2
break
}
this.objects.add({
base: base,
collision: collision,
offset: new THREE.Vector3(this.x + this.floor.x + _distinction.x, this.y + this.floor.y + _distinction.y, 0),
rotation: new THREE.Euler(0, 0, 0),
duplicated: true,
shadow: { sizeX: shadowSizeX, sizeY: shadowSizeY, offsetZ: - 0.1, alpha: 0.5 },
mass: 1.5,
soundName: 'woodHit'
})
}
}
// Area
this.floor.area = this.areas.add({
position: new THREE.Vector2(this.x + this.link.x, this.y + this.floor.y + this.link.y),
halfExtents: new THREE.Vector2(this.link.halfExtents.x, this.link.halfExtents.y)
})
this.floor.area.on('interact', () =>
{
window.open(this.link.href, '_blank')
})
// Area label
this.floor.areaLabel = this.meshes.areaLabel.clone()
this.floor.areaLabel.position.x = this.link.x
this.floor.areaLabel.position.y = this.link.y
this.floor.areaLabel.position.z = 0.001
this.floor.areaLabel.matrixAutoUpdate = false
this.floor.areaLabel.updateMatrix()
this.floor.container.add(this.floor.areaLabel)
}
}

View File

@ -0,0 +1,445 @@
import * as THREE from 'three'
import Project from './Project'
import TweenLite from 'gsap/TweenLite'
import projectsThreejsJourneySlideASources from '../../../models/projects/threejsJourney/slideA.jpg'
import projectsThreejsJourneySlideBSources from '../../../models/projects/threejsJourney/slideB.jpg'
import projectsThreejsJourneySlideCSources from '../../../models/projects/threejsJourney/slideC.jpg'
import projectsThreejsJourneySlideDSources from '../../../models/projects/threejsJourney/slideD.jpg'
import projectsMadboxSlideASources from '../../../models/projects/madbox/slideA.jpg'
import projectsMadboxSlideBSources from '../../../models/projects/madbox/slideB.jpg'
import projectsMadboxSlideCSources from '../../../models/projects/madbox/slideC.jpg'
import projectsScoutSlideASources from '../../../models/projects/scout/slideA.jpg'
import projectsScoutSlideBSources from '../../../models/projects/scout/slideB.jpg'
import projectsScoutSlideCSources from '../../../models/projects/scout/slideC.jpg'
import projectsChartogneSlideASources from '../../../models/projects/chartogne/slideA.jpg'
import projectsChartogneSlideBSources from '../../../models/projects/chartogne/slideB.jpg'
import projectsChartogneSlideCSources from '../../../models/projects/chartogne/slideC.jpg'
import projectsZenlySlideASources from '../../../models/projects/zenly/slideA.jpg'
import projectsZenlySlideBSources from '../../../models/projects/zenly/slideB.jpg'
import projectsZenlySlideCSources from '../../../models/projects/zenly/slideC.jpg'
import projectsCitrixRedbullSlideASources from '../../../models/projects/citrixRedbull/slideA.jpg'
import projectsCitrixRedbullSlideBSources from '../../../models/projects/citrixRedbull/slideB.jpg'
import projectsCitrixRedbullSlideCSources from '../../../models/projects/citrixRedbull/slideC.jpg'
import projectsPriorHoldingsSlideASources from '../../../models/projects/priorHoldings/slideA.jpg'
import projectsPriorHoldingsSlideBSources from '../../../models/projects/priorHoldings/slideB.jpg'
import projectsPriorHoldingsSlideCSources from '../../../models/projects/priorHoldings/slideC.jpg'
import projectsOranoSlideASources from '../../../models/projects/orano/slideA.jpg'
import projectsOranoSlideBSources from '../../../models/projects/orano/slideB.jpg'
import projectsOranoSlideCSources from '../../../models/projects/orano/slideC.jpg'
// import projectsGleecChatSlideASources from '../../../models/projects/gleecChat/slideA.jpg'
// import projectsGleecChatSlideBSources from '../../../models/projects/gleecChat/slideB.jpg'
// import projectsGleecChatSlideCSources from '../../../models/projects/gleecChat/slideC.jpg'
// import projectsGleecChatSlideDSources from '../../../models/projects/gleecChat/slideD.jpg'
import projectsKepplerSlideASources from '../../../models/projects/keppler/slideA.jpg'
import projectsKepplerSlideBSources from '../../../models/projects/keppler/slideB.jpg'
import projectsKepplerSlideCSources from '../../../models/projects/keppler/slideC.jpg'
export default class ProjectsSection
{
constructor(_options)
{
// Options
this.time = _options.time
this.resources = _options.resources
this.camera = _options.camera
this.passes = _options.passes
this.objects = _options.objects
this.areas = _options.areas
this.zones = _options.zones
this.tiles = _options.tiles
this.debug = _options.debug
this.x = _options.x
this.y = _options.y
// Debug
if(this.debug)
{
this.debugFolder = this.debug.addFolder('projects')
this.debugFolder.open()
}
// Set up
this.items = []
this.interDistance = 24
this.positionRandomess = 5
this.projectHalfWidth = 9
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.container.updateMatrix()
this.setGeometries()
this.setMeshes()
this.setList()
this.setZone()
// Add all project from the list
for(const _options of this.list)
{
this.add(_options)
}
}
setGeometries()
{
this.geometries = {}
this.geometries.floor = new THREE.PlaneBufferGeometry(16, 8)
}
setMeshes()
{
this.meshes = {}
// this.meshes.boardStructure = this.objects.getConvertedMesh(this.resources.items.projectsBoardStructure.scene.children, { floorShadowTexture: this.resources.items.projectsBoardStructureFloorShadowTexture })
this.resources.items.areaOpenTexture.magFilter = THREE.NearestFilter
this.resources.items.areaOpenTexture.minFilter = THREE.LinearFilter
this.meshes.boardPlane = this.resources.items.projectsBoardPlane.scene.children[0]
this.meshes.areaLabel = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 0.5), new THREE.MeshBasicMaterial({ transparent: true, depthWrite: false, color: 0xffffff, alphaMap: this.resources.items.areaOpenTexture }))
this.meshes.areaLabel.matrixAutoUpdate = false
}
setList()
{
this.list = [
{
name: 'Three.js Journey',
imageSources:
[
projectsThreejsJourneySlideASources,
projectsThreejsJourneySlideBSources,
projectsThreejsJourneySlideCSources,
projectsThreejsJourneySlideDSources
],
floorTexture: this.resources.items.projectsThreejsJourneyFloorTexture,
link:
{
href: 'https://threejs-journey.com?c=p3',
x: - 4.8,
y: - 3,
halfExtents:
{
x: 3.2,
y: 1.5
}
},
distinctions:
[
{ type: 'fwa', x: 3.95, y: 4.15 }
]
},
{
name: 'Madbox',
imageSources:
[
projectsMadboxSlideASources,
projectsMadboxSlideBSources,
projectsMadboxSlideCSources
],
floorTexture: this.resources.items.projectsMadboxFloorTexture,
link:
{
href: 'https://madbox.io',
x: - 4.8,
y: - 4,
halfExtents:
{
x: 3.2,
y: 1.5
}
},
distinctions:
[
{ type: 'awwwards', x: 3.95, y: 4.15 },
{ type: 'fwa', x: 5.6, y: 4.15 }
]
},
{
name: 'Scout',
imageSources:
[
projectsScoutSlideASources,
projectsScoutSlideBSources,
projectsScoutSlideCSources
],
floorTexture: this.resources.items.projectsScoutFloorTexture,
link:
{
href: 'https://fromscout.com',
x: - 4.8,
y: - 2,
halfExtents:
{
x: 3.2,
y: 1.5
}
},
distinctions:
[
]
},
{
name: 'Chartogne Taillet',
imageSources:
[
projectsChartogneSlideASources,
projectsChartogneSlideBSources,
projectsChartogneSlideCSources
],
floorTexture: this.resources.items.projectsChartogneFloorTexture,
link:
{
href: 'https://chartogne-taillet.com',
x: - 4.8,
y: - 3.3,
halfExtents:
{
x: 3.2,
y: 1.5
}
},
distinctions:
[
{ type: 'awwwards', x: 3.95, y: 4.15 },
{ type: 'fwa', x: 5.6, y: 4.15 },
{ type: 'cssda', x: 7.2, y: 4.15 }
]
},
{
name: 'Zenly',
imageSources:
[
projectsZenlySlideASources,
projectsZenlySlideBSources,
projectsZenlySlideCSources
],
floorTexture: this.resources.items.projectsZenlyFloorTexture,
link:
{
href: 'https://zen.ly',
x: - 4.8,
y: - 4.2,
halfExtents:
{
x: 3.2,
y: 1.5
}
},
distinctions:
[
{ type: 'awwwards', x: 3.95, y: 4.15 },
{ type: 'fwa', x: 5.6, y: 4.15 },
{ type: 'cssda', x: 7.2, y: 4.15 }
]
},
{
name: 'priorHoldings',
imageSources:
[
projectsPriorHoldingsSlideASources,
projectsPriorHoldingsSlideBSources,
projectsPriorHoldingsSlideCSources
],
floorTexture: this.resources.items.projectsPriorHoldingsFloorTexture,
link:
{
href: 'https://prior.co.jp/discover/',
x: - 4.8,
y: - 3,
halfExtents:
{
x: 3.2,
y: 1.5
}
},
distinctions:
[
{ type: 'awwwards', x: 3.95, y: 4.15 },
{ type: 'fwa', x: 5.6, y: 4.15 },
{ type: 'cssda', x: 7.2, y: 4.15 }
]
},
{
name: 'orano',
imageSources:
[
projectsOranoSlideASources,
projectsOranoSlideBSources,
projectsOranoSlideCSources
],
floorTexture: this.resources.items.projectsOranoFloorTexture,
link:
{
href: 'https://orano.imm-g-prod.com/experience/innovation/en',
x: - 4.8,
y: - 3.4,
halfExtents:
{
x: 3.2,
y: 1.5
}
},
distinctions:
[
{ type: 'awwwards', x: 3.95, y: 4.15 },
{ type: 'fwa', x: 5.6, y: 4.15 },
{ type: 'cssda', x: 7.2, y: 4.15 }
]
},
{
name: 'citrixRedbull',
imageSources:
[
projectsCitrixRedbullSlideASources,
projectsCitrixRedbullSlideBSources,
projectsCitrixRedbullSlideCSources
],
floorTexture: this.resources.items.projectsCitrixRedbullFloorTexture,
link:
{
href: 'https://thenewmobileworkforce.imm-g-prod.com/',
x: - 4.8,
y: - 4.4,
halfExtents:
{
x: 3.2,
y: 1.5
}
},
distinctions:
[
{ type: 'awwwards', x: 3.95, y: 4.15 },
{ type: 'fwa', x: 5.6, y: 4.15 },
{ type: 'cssda', x: 7.2, y: 4.15 }
]
},
// {
// name: 'gleecChat',
// imageSources:
// [
// projectsGleecChatSlideASources,
// projectsGleecChatSlideBSources,
// projectsGleecChatSlideCSources,
// projectsGleecChatSlideDSources
// ],
// floorTexture: this.resources.items.projectsGleecChatFloorTexture,
// link:
// {
// href: 'http://gleec.imm-g-prod.com',
// x: - 4.8,
// y: - 3.4,
// halfExtents:
// {
// x: 3.2,
// y: 1.5
// }
// },
// distinctions:
// [
// { type: 'awwwards', x: 3.95, y: 4.15 },
// { type: 'fwa', x: 5.6, y: 4.15 },
// { type: 'cssda', x: 7.2, y: 4.15 }
// ]
// },
{
name: 'keppler',
imageSources:
[
projectsKepplerSlideASources,
projectsKepplerSlideBSources,
projectsKepplerSlideCSources
],
floorTexture: this.resources.items.projectsKepplerFloorTexture,
link:
{
href: 'https://brunosimon.github.io/keppler/',
x: 2.75,
y: - 1.1,
halfExtents:
{
x: 3.2,
y: 1.5
}
},
distinctions: []
}
]
}
setZone()
{
const totalWidth = this.list.length * (this.interDistance / 2)
const zone = this.zones.add({
position: { x: this.x + totalWidth - this.projectHalfWidth - 6, y: this.y },
halfExtents: { x: totalWidth, y: 12 },
data: { cameraAngle: 'projects' }
})
zone.on('in', (_data) =>
{
this.camera.angle.set(_data.cameraAngle)
TweenLite.to(this.passes.horizontalBlurPass.material.uniforms.uStrength.value, 2, { x: 0 })
TweenLite.to(this.passes.verticalBlurPass.material.uniforms.uStrength.value, 2, { y: 0 })
})
zone.on('out', () =>
{
this.camera.angle.set('default')
TweenLite.to(this.passes.horizontalBlurPass.material.uniforms.uStrength.value, 2, { x: this.passes.horizontalBlurPass.strength })
TweenLite.to(this.passes.verticalBlurPass.material.uniforms.uStrength.value, 2, { y: this.passes.verticalBlurPass.strength })
})
}
add(_options)
{
const x = this.x + this.items.length * this.interDistance
let y = this.y
if(this.items.length > 0)
{
y += (Math.random() - 0.5) * this.positionRandomess
}
// Create project
const project = new Project({
time: this.time,
resources: this.resources,
objects: this.objects,
areas: this.areas,
geometries: this.geometries,
meshes: this.meshes,
debug: this.debugFolder,
x: x,
y: y,
..._options
})
this.container.add(project.container)
// Add tiles
if(this.items.length >= 1)
{
const previousProject = this.items[this.items.length - 1]
const start = new THREE.Vector2(previousProject.x + this.projectHalfWidth, previousProject.y)
const end = new THREE.Vector2(project.x - this.projectHalfWidth, project.y)
const delta = end.clone().sub(start)
this.tiles.add({
start: start,
delta: delta
})
}
// Save
this.items.push(project)
}
}

View File

@ -0,0 +1,242 @@
import * as THREE from 'three'
import ShadowMaterial from '../Materials/Shadow.js'
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
export default class Shadows
{
constructor(_options)
{
// Options
this.time = _options.time
this.debug = _options.debug
this.renderer = _options.renderer
this.camera = _options.camera
// Set up
this.alpha = 0
this.maxDistance = 3
this.distancePower = 2
this.zFightingDistance = 0.001
this.color = '#d04500'
this.wireframeVisible = false
this.items = []
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
this.container.updateMatrix()
// Debug
if(this.debug)
{
this.debugFolder = this.debug.addFolder('shadows')
// this.debugFolder.open()
this.debugFolder.add(this, 'alpha').step(0.01).min(0).max(1)
this.debugFolder.add(this, 'maxDistance').step(0.01).min(0).max(10)
this.debugFolder.add(this, 'distancePower').step(0.01).min(1).max(5)
this.debugFolder.add(this, 'wireframeVisible').name('wireframeVisible').onChange(() =>
{
for(const _shadow of this.items)
{
_shadow.mesh.material = this.wireframeVisible ? this.materials.wireframe : _shadow.material
}
})
this.debugFolder.addColor(this, 'color').onChange(() =>
{
this.materials.base.uniforms.uColor.value = new THREE.Color(this.color)
for(const _shadow of this.items)
{
_shadow.material.uniforms.uColor.value = new THREE.Color(this.color)
}
})
}
this.setSun()
this.setMaterials()
this.setGeometry()
this.setHelper()
// Time tick
this.time.on('tick', () =>
{
for(const _shadow of this.items)
{
// Position
const z = Math.max(_shadow.reference.position.z + _shadow.offsetZ, 0)
const sunOffset = this.sun.vector.clone().multiplyScalar(z)
_shadow.mesh.position.x = _shadow.reference.position.x + sunOffset.x
_shadow.mesh.position.y = _shadow.reference.position.y + sunOffset.y
// Angle
// Project the rotation as a vector on a plane and extract the angle
const rotationVector = new THREE.Vector3(1, 0, 0)
rotationVector.applyQuaternion(_shadow.reference.quaternion)
// const planeVector = new THREE.Vector3(0, 0, 1)
// planeVector.normalize()
const projectedRotationVector = rotationVector.clone().projectOnPlane(new THREE.Vector3(0, 0, 1))
let orientationAlpha = Math.abs(rotationVector.angleTo(new THREE.Vector3(0, 0, 1)) - Math.PI * 0.5) / (Math.PI * 0.5)
orientationAlpha /= 0.5
orientationAlpha -= 1 / 0.5
orientationAlpha = Math.abs(orientationAlpha)
orientationAlpha = Math.min(Math.max(orientationAlpha, 0), 1)
const angle = Math.atan2(projectedRotationVector.y, projectedRotationVector.x)
_shadow.mesh.rotation.z = angle
// Alpha
let alpha = (this.maxDistance - z) / this.maxDistance
alpha = Math.min(Math.max(alpha, 0), 1)
alpha = Math.pow(alpha, this.distancePower)
_shadow.material.uniforms.uAlpha.value = this.alpha * _shadow.alpha * orientationAlpha * alpha
}
})
}
setSun()
{
this.sun = {}
this.sun.position = new THREE.Vector3(- 2.5, - 2.65, 3.75)
this.sun.vector = new THREE.Vector3()
this.sun.helper = new THREE.ArrowHelper(new THREE.Vector3(0, 0, 1), new THREE.Vector3(0, 0, 0), 1, 0xffffff, 0.1, 0.4)
this.sun.helper.visible = false
this.container.add(this.sun.helper)
this.sun.update = () =>
{
this.sun.vector.copy(this.sun.position).multiplyScalar(1 / this.sun.position.z).negate()
this.sun.helper.position.copy(this.sun.position)
const direction = this.sun.position.clone().negate().normalize()
this.sun.helper.setDirection(direction)
this.sun.helper.setLength(this.sun.helper.position.length())
}
this.sun.update()
// Debug
if(this.debug)
{
const folder = this.debugFolder.addFolder('sun')
folder.open()
folder.add(this.sun.position, 'x').step(0.01).min(- 10).max(10).name('sunX').onChange(this.sun.update)
folder.add(this.sun.position, 'y').step(0.01).min(- 10).max(10).name('sunY').onChange(this.sun.update)
folder.add(this.sun.position, 'z').step(0.01).min(0).max(10).name('sunZ').onChange(this.sun.update)
folder.add(this.sun.helper, 'visible').name('sunHelperVisible')
}
}
setMaterials()
{
// Wireframe
this.materials = {}
this.materials.wireframe = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true })
// Base
this.materials.base = new ShadowMaterial()
this.materials.base.depthWrite = false
this.materials.base.uniforms.uColor.value = new THREE.Color(this.color)
this.materials.base.uniforms.uAlpha.value = 0
this.materials.base.uniforms.uFadeRadius.value = 0.35
}
setGeometry()
{
this.geometry = new THREE.PlaneBufferGeometry(1, 1, 1, 1)
}
setHelper()
{
if(!this.debug)
{
return
}
this.helper = {}
this.helper.active = false
this.helper.mesh = new THREE.Mesh(new THREE.BoxBufferGeometry(3, 1, 1, 1), new THREE.MeshNormalMaterial())
this.helper.mesh.position.z = 1.5
this.helper.mesh.position.y = - 3
this.helper.mesh.visible = this.helper.active
this.container.add(this.helper.mesh)
this.helper.transformControls = new TransformControls(this.camera.instance, this.renderer.domElement)
this.helper.transformControls.size = 0.5
this.helper.transformControls.attach(this.helper.mesh)
this.helper.transformControls.visible = this.helper.active
this.helper.transformControls.enabled = this.helper.active
this.helper.shadow = this.add(this.helper.mesh, { sizeX: 6, sizeY: 2, offsetZ: - 0.35, alpha: 0.99 })
this.helper.shadow.mesh.visible = this.helper.active
document.addEventListener('keydown', (_event) =>
{
if(_event.key === 'r')
{
this.helper.transformControls.setMode('rotate')
}
else if(_event.key === 'g')
{
this.helper.transformControls.setMode('translate')
}
})
this.helper.transformControls.addEventListener('dragging-changed', (_event) =>
{
this.camera.orbitControls.enabled = !_event.value
})
this.container.add(this.helper.transformControls)
// Debug
if(this.debug)
{
const folder = this.debugFolder.addFolder('helper')
folder.open()
folder.add(this.helper, 'active').name('visible').onChange(() =>
{
this.helper.mesh.visible = this.helper.active
this.helper.transformControls.visible = this.helper.active
this.helper.transformControls.enabled = this.helper.active
this.helper.shadow.mesh.visible = this.helper.active
})
}
}
add(_reference, _options = {})
{
const shadow = {}
// Options
shadow.offsetZ = typeof _options.offsetZ === 'undefined' ? 0 : _options.offsetZ
shadow.alpha = typeof _options.alpha === 'undefined' ? 1 : _options.alpha
// Reference
shadow.reference = _reference
// Material
shadow.material = this.materials.base.clone()
// Mesh
shadow.mesh = new THREE.Mesh(this.geometry, this.wireframeVisible ? this.materials.wireframe : shadow.material)
shadow.mesh.position.z = this.zFightingDistance
shadow.mesh.scale.set(_options.sizeX, _options.sizeY, 2.4)
// Save
this.container.add(shadow.mesh)
this.items.push(shadow)
return shadow
}
}

View File

@ -0,0 +1,368 @@
import { Howl, Howler } from 'howler'
import revealSound from '../../sounds/reveal/reveal-1.mp3'
import engineSound from '../../sounds/engines/1/low_off.mp3'
import brick1Sound from '../../sounds/bricks/brick-1.mp3'
import brick2Sound from '../../sounds/bricks/brick-2.mp3'
// import brick3Sound from '../../sounds/bricks/brick-3.mp3'
import brick4Sound from '../../sounds/bricks/brick-4.mp3'
// import brick5Sound from '../../sounds/bricks/brick-5.mp3'
import brick6Sound from '../../sounds/bricks/brick-6.mp3'
import brick7Sound from '../../sounds/bricks/brick-7.mp3'
import brick8Sound from '../../sounds/bricks/brick-8.mp3'
import bowlingPin1Sound from '../../sounds/bowling/pin-1.mp3'
import carHit1Sound from '../../sounds/car-hits/car-hit-1.mp3'
// import carHit2Sound from '../../sounds/car-hits/car-hit-2.mp3'
import carHit3Sound from '../../sounds/car-hits/car-hit-3.mp3'
import carHit4Sound from '../../sounds/car-hits/car-hit-4.mp3'
import carHit5Sound from '../../sounds/car-hits/car-hit-5.mp3'
import woodHit1Sound from '../../sounds/wood-hits/wood-hit-1.mp3'
import screech1Sound from '../../sounds/screeches/screech-1.mp3'
import uiArea1Sound from '../../sounds/ui/area-1.mp3'
import carHorn1Sound from '../../sounds/car-horns/car-horn-1.mp3'
import carHorn2Sound from '../../sounds/car-horns/car-horn-2.mp3'
import horn1Sound from '../../sounds/horns/horn-1.mp3'
import horn2Sound from '../../sounds/horns/horn-2.mp3'
import horn3Sound from '../../sounds/horns/horn-3.mp3'
export default class Sounds
{
constructor(_options)
{
// Options
this.time = _options.time
this.debug = _options.debug
// Debug
if(this.debug)
{
this.debugFolder = this.debug.addFolder('sounds')
// this.debugFolder.open()
}
// Set up
this.items = []
this.setSettings()
this.setMasterVolume()
this.setMute()
this.setEngine()
}
setSettings()
{
this.settings = [
{
name: 'reveal',
sounds: [revealSound],
minDelta: 100,
velocityMin: 0,
velocityMultiplier: 1,
volumeMin: 1,
volumeMax: 1,
rateMin: 1,
rateMax: 1
},
{
name: 'brick',
sounds: [brick1Sound, brick2Sound, brick4Sound, brick6Sound, brick7Sound, brick8Sound],
minDelta: 100,
velocityMin: 1,
velocityMultiplier: 0.75,
volumeMin: 0.2,
volumeMax: 0.85,
rateMin: 0.5,
rateMax: 0.75
},
{
name: 'bowlingPin',
sounds: [bowlingPin1Sound],
minDelta: 0,
velocityMin: 1,
velocityMultiplier: 0.5,
volumeMin: 0.35,
volumeMax: 1,
rateMin: 0.1,
rateMax: 0.85
},
{
name: 'bowlingBall',
sounds: [bowlingPin1Sound, bowlingPin1Sound, bowlingPin1Sound],
minDelta: 0,
velocityMin: 1,
velocityMultiplier: 0.5,
volumeMin: 0.35,
volumeMax: 1,
rateMin: 0.1,
rateMax: 0.2
},
{
name: 'carHit',
sounds: [carHit1Sound, carHit3Sound, carHit4Sound, carHit5Sound],
minDelta: 100,
velocityMin: 2,
velocityMultiplier: 1,
volumeMin: 0.2,
volumeMax: 0.6,
rateMin: 0.35,
rateMax: 0.55
},
{
name: 'woodHit',
sounds: [woodHit1Sound],
minDelta: 30,
velocityMin: 1,
velocityMultiplier: 1,
volumeMin: 0.5,
volumeMax: 1,
rateMin: 0.75,
rateMax: 1.5
},
{
name: 'screech',
sounds: [screech1Sound],
minDelta: 1000,
velocityMin: 0,
velocityMultiplier: 1,
volumeMin: 0.75,
volumeMax: 1,
rateMin: 0.9,
rateMax: 1.1
},
{
name: 'uiArea',
sounds: [uiArea1Sound],
minDelta: 100,
velocityMin: 0,
velocityMultiplier: 1,
volumeMin: 0.75,
volumeMax: 1,
rateMin: 0.95,
rateMax: 1.05
},
{
name: 'carHorn1',
sounds: [carHorn1Sound],
minDelta: 0,
velocityMin: 0,
velocityMultiplier: 1,
volumeMin: 0.95,
volumeMax: 1,
rateMin: 1,
rateMax: 1
},
{
name: 'carHorn2',
sounds: [carHorn2Sound],
minDelta: 0,
velocityMin: 0,
velocityMultiplier: 1,
volumeMin: 0.95,
volumeMax: 1,
rateMin: 1,
rateMax: 1
},
{
name: 'horn',
sounds: [horn1Sound, horn2Sound, horn3Sound],
minDelta: 100,
velocityMin: 1,
velocityMultiplier: 0.75,
volumeMin: 0.5,
volumeMax: 1,
rateMin: 0.75,
rateMax: 1
}
]
for(const _settings of this.settings)
{
this.add(_settings)
}
}
setMasterVolume()
{
// Set up
this.masterVolume = 0.5
Howler.volume(this.masterVolume)
window.requestAnimationFrame(() =>
{
Howler.volume(this.masterVolume)
})
// Debug
if(this.debug)
{
this.debugFolder.add(this, 'masterVolume').step(0.001).min(0).max(1).onChange(() =>
{
Howler.volume(this.masterVolume)
})
}
}
setMute()
{
// Set up
this.muted = typeof this.debug !== 'undefined'
Howler.mute(this.muted)
// M Key
window.addEventListener('keydown', (_event) =>
{
if(_event.key === 'm')
{
this.muted = !this.muted
Howler.mute(this.muted)
}
})
// Tab focus / blur
document.addEventListener('visibilitychange', () =>
{
if(document.hidden)
{
Howler.mute(true)
}
else
{
Howler.mute(this.muted)
}
})
// Debug
if(this.debug)
{
this.debugFolder.add(this, 'muted').listen().onChange(() =>
{
Howler.mute(this.muted)
})
}
}
setEngine()
{
// Set up
this.engine = {}
this.engine.progress = 0
this.engine.progressEasingUp = 0.3
this.engine.progressEasingDown = 0.15
this.engine.speed = 0
this.engine.speedMultiplier = 2.5
this.engine.acceleration = 0
this.engine.accelerationMultiplier = 0.4
this.engine.rate = {}
this.engine.rate.min = 0.4
this.engine.rate.max = 1.4
this.engine.volume = {}
this.engine.volume.min = 0.4
this.engine.volume.max = 1
this.engine.volume.master = 0
this.engine.sound = new Howl({
src: [engineSound],
loop: true
})
this.engine.sound.play()
// Time tick
this.time.on('tick', () =>
{
let progress = Math.abs(this.engine.speed) * this.engine.speedMultiplier + Math.max(this.engine.acceleration, 0) * this.engine.accelerationMultiplier
progress = Math.min(Math.max(progress, 0), 1)
this.engine.progress += (progress - this.engine.progress) * this.engine[progress > this.engine.progress ? 'progressEasingUp' : 'progressEasingDown']
// Rate
const rateAmplitude = this.engine.rate.max - this.engine.rate.min
this.engine.sound.rate(this.engine.rate.min + rateAmplitude * this.engine.progress)
// Volume
const volumeAmplitude = this.engine.volume.max - this.engine.volume.min
this.engine.sound.volume((this.engine.volume.min + volumeAmplitude * this.engine.progress) * this.engine.volume.master)
})
// Debug
if(this.debug)
{
const folder = this.debugFolder.addFolder('engine')
folder.open()
folder.add(this.engine, 'progressEasingUp').step(0.001).min(0).max(1).name('progressEasingUp')
folder.add(this.engine, 'progressEasingDown').step(0.001).min(0).max(1).name('progressEasingDown')
folder.add(this.engine.rate, 'min').step(0.001).min(0).max(4).name('rateMin')
folder.add(this.engine.rate, 'max').step(0.001).min(0).max(4).name('rateMax')
folder.add(this.engine, 'speedMultiplier').step(0.01).min(0).max(5).name('speedMultiplier')
folder.add(this.engine, 'accelerationMultiplier').step(0.01).min(0).max(100).name('accelerationMultiplier')
folder.add(this.engine, 'progress').step(0.01).min(0).max(1).name('progress').listen()
}
}
add(_options)
{
const item = {
name: _options.name,
minDelta: _options.minDelta,
velocityMin: _options.velocityMin,
velocityMultiplier: _options.velocityMultiplier,
volumeMin: _options.volumeMin,
volumeMax: _options.volumeMax,
rateMin: _options.rateMin,
rateMax: _options.rateMax,
lastTime: 0,
sounds: []
}
for(const _sound of _options.sounds)
{
const sound = new Howl({ src: [_sound] })
item.sounds.push(sound)
}
this.items.push(item)
}
play(_name, _velocity)
{
const item = this.items.find((_item) => _item.name === _name)
const time = Date.now()
const velocity = typeof _velocity === 'undefined' ? 0 : _velocity
if(item && time > item.lastTime + item.minDelta && (item.velocityMin === 0 || velocity > item.velocityMin))
{
// Find random sound
const sound = item.sounds[Math.floor(Math.random() * item.sounds.length)]
// Update volume
let volume = Math.min(Math.max((velocity - item.velocityMin) * item.velocityMultiplier, item.volumeMin), item.volumeMax)
volume = Math.pow(volume, 2)
sound.volume(volume)
// Update rate
const rateAmplitude = item.rateMax - item.rateMin
sound.rate(item.rateMin + Math.random() * rateAmplitude)
// Play
sound.play()
// Save last play time
item.lastTime = time
}
}
}

View File

@ -0,0 +1,135 @@
import * as THREE from 'three'
export default class Tiles
{
constructor(_options)
{
// Options
this.resources = _options.resources
this.objects = _options.objects
this.debug = _options.debug
// Set up
this.items = []
this.interDistance = 1.5
this.tangentDistance = 0.3
this.positionRandomess = 0.3
this.rotationRandomess = 0.1
this.setModels()
}
setModels()
{
this.models = {}
this.models.items = [
{
base: this.resources.items.tilesABase.scene,
collision: this.resources.items.tilesACollision.scene,
chances: 8
},
{
base: this.resources.items.tilesBBase.scene,
collision: this.resources.items.tilesBCollision.scene,
chances: 1
},
{
base: this.resources.items.tilesCBase.scene,
collision: this.resources.items.tilesCCollision.scene,
chances: 2
},
{
base: this.resources.items.tilesDBase.scene,
collision: this.resources.items.tilesDCollision.scene,
chances: 4
},
{
base: this.resources.items.tilesEBase.scene,
collision: this.resources.items.tilesECollision.scene,
chances: 2
}
]
const totalChances = this.models.items.reduce((_totalChances, _item) => _totalChances + _item.chances, 0)
let chances = 0
this.models.items = this.models.items.map((_item) =>
{
// Update chances
_item.minChances = chances
chances += _item.chances / totalChances
_item.maxChances = chances
// Update rotation
_item.rotationIndex = 0
return _item
})
this.models.pick = () =>
{
const random = Math.random()
const model = this.models.items.find((_item) => random >= _item.minChances && random <= _item.maxChances)
model.rotationIndex++
if(model.rotationIndex > 3)
{
model.rotationIndex = 0
}
return model
}
}
add(_options)
{
const tilePath = {}
tilePath.start = _options.start
tilePath.delta = _options.delta
tilePath.distance = tilePath.delta.length()
tilePath.count = Math.floor(tilePath.distance / this.interDistance)
tilePath.directionVector = tilePath.delta.clone().normalize()
tilePath.interVector = tilePath.directionVector.clone().multiplyScalar(this.interDistance)
tilePath.centeringVector = tilePath.delta.clone().sub(tilePath.interVector.clone().multiplyScalar(tilePath.count))
tilePath.tangentVector = tilePath.directionVector.clone().rotateAround(new THREE.Vector2(0, 0), Math.PI * 0.5).multiplyScalar(this.tangentDistance)
tilePath.angle = tilePath.directionVector.angle()
// Create tiles
for(let i = 0; i < tilePath.count; i++)
{
// Model
const model = this.models.pick()
// Position
const position = tilePath.start.clone().add(tilePath.interVector.clone().multiplyScalar(i)).add(tilePath.centeringVector)
position.x += (Math.random() - 0.5) * this.positionRandomess
position.y += (Math.random() - 0.5) * this.positionRandomess
const tangent = tilePath.tangentVector
if(i % 1 === 0)
{
tangent.negate()
}
position.add(tangent)
// Rotation
let rotation = tilePath.angle
rotation += (Math.random() - 0.5) * this.rotationRandomess
rotation += model.rotationIndex / 4 * Math.PI * 2
// Tile
this.objects.add({
base: model.base,
collision: model.collision,
offset: new THREE.Vector3(position.x, position.y, 0),
rotation: new THREE.Euler(0, 0, rotation),
duplicated: true,
mass: 0
})
}
}
}

View File

@ -0,0 +1,122 @@
import * as THREE from 'three'
export default class Walls
{
constructor(_options)
{
// Options
this.resources = _options.resources
this.objects = _options.objects
}
add(_options)
{
const wall = {}
wall.coordinates = []
wall.items = []
const shape = _options.shape
let widthCount = shape.widthCount
let heightCount = shape.heightCount
switch(_options.shape.type)
{
case 'rectangle':
case 'brick':
for(let i = 0; i < heightCount; i++)
{
const lastLine = i === heightCount - 1
let j = 0
let widthCountTemp = widthCount
if(_options.shape.type === 'brick' && lastLine && _options.shape.equilibrateLastLine)
{
if(i % 2 === 0)
{
widthCountTemp--
}
else
{
j++
}
}
for(; j < widthCountTemp; j++)
{
const offset = new THREE.Vector3()
offset.add(shape.offsetWidth.clone().multiplyScalar(j - (shape.widthCount - 1) * 0.5))
offset.add(shape.offsetHeight.clone().multiplyScalar(i))
offset.x += (Math.random() - 0.5) * shape.randomOffset.x
offset.y += (Math.random() - 0.5) * shape.randomOffset.y
offset.z += (Math.random() - 0.5) * shape.randomOffset.z
if(_options.shape.type === 'brick' && i % 2 === 0)
{
offset.add(shape.offsetWidth.clone().multiplyScalar(0.5))
}
const rotation = new THREE.Euler()
rotation.x += (Math.random() - 0.5) * shape.randomRotation.x
rotation.y += (Math.random() - 0.5) * shape.randomRotation.y
rotation.z += (Math.random() - 0.5) * shape.randomRotation.z
wall.coordinates.push({
offset,
rotation
})
}
}
break
case 'triangle':
heightCount = shape.widthCount
for(let i = 0; i < heightCount; i++)
{
for(let j = 0; j < widthCount; j++)
{
const offset = new THREE.Vector3()
offset.add(shape.offsetWidth.clone().multiplyScalar(j - (shape.widthCount - 1) * 0.5))
offset.add(shape.offsetWidth.clone().multiplyScalar(i * 0.5))
offset.add(shape.offsetHeight.clone().multiplyScalar(i))
offset.x += (Math.random() - 0.5) * shape.randomOffset.x
offset.y += (Math.random() - 0.5) * shape.randomOffset.y
offset.z += (Math.random() - 0.5) * shape.randomOffset.z
if(_options.shape.type === 'brick' && i % 2 === 0)
{
offset.add(shape.offsetWidth.clone().multiplyScalar(0.5))
}
const rotation = new THREE.Euler()
rotation.x += (Math.random() - 0.5) * shape.randomRotation.x
rotation.y += (Math.random() - 0.5) * shape.randomRotation.y
rotation.z += (Math.random() - 0.5) * shape.randomRotation.z
wall.coordinates.push({
offset,
rotation
})
}
widthCount--
}
break
}
for(const _coordinates of wall.coordinates)
{
const objectOptions = { ..._options.object }
objectOptions.offset = _options.object.offset.clone().add(_coordinates.offset).add(shape.position)
objectOptions.rotation = _options.object.rotation.clone()
objectOptions.rotation.x += _coordinates.rotation.x
objectOptions.rotation.y += _coordinates.rotation.y
objectOptions.rotation.z += _coordinates.rotation.z
wall.items.push(this.objects.add(objectOptions))
}
return wall
}
}

View File

@ -0,0 +1,28 @@
import * as THREE from 'three'
import EventEmitter from '../Utils/EventEmitter.js'
export default class Zone extends EventEmitter
{
constructor(_options)
{
super()
// Options
this.position = _options.position
this.halfExtents = _options.halfExtents
this.data = _options.data
// Set up
this.isIn = false
// Mesh
this.mesh = new THREE.Mesh(
new THREE.BoxBufferGeometry(_options.halfExtents.x * 2, _options.halfExtents.y * 2, 3, 1, 1, 1),
new THREE.MeshBasicMaterial({ color: 0xff00ff, wireframe: true })
)
this.mesh.position.x = _options.position.x
this.mesh.position.y = _options.position.y
this.mesh.position.z = 1.5
}
}

View File

@ -0,0 +1,80 @@
import * as THREE from 'three'
import Zone from './Zone.js'
export default class Zones
{
constructor(_options)
{
// Options
this.time = _options.time
this.sizes = _options.sizes
this.physics = _options.physics
this.debug = _options.debug
// Set up
this.container = new THREE.Object3D()
this.container.visible = false
this.container.matrixAutoUpdate = false
// Debug
if(this.debug)
{
this.debugFolder = this.debug.addFolder('zones')
this.debugFolder.open()
this.debugFolder.add(this.container, 'visible').name('visible')
}
this.setTester()
this.setItems()
}
setTester()
{
this.tester = {}
this.tester.x = 0
this.tester.y = 0
this.time.on('tick', () =>
{
this.tester.x = this.physics.car.chassis.body.position.x
this.tester.y = this.physics.car.chassis.body.position.y
})
}
setItems()
{
this.items = []
this.time.on('tick', () =>
{
for(const _zone of this.items)
{
const isIn = this.tester.x < _zone.position.x + _zone.halfExtents.x && this.tester.x > _zone.position.x - _zone.halfExtents.x && this.tester.y < _zone.position.y + _zone.halfExtents.y && this.tester.y > _zone.position.y - _zone.halfExtents.y
if(isIn && !_zone.isIn)
{
_zone.trigger('in', [_zone.data])
}
else if(!isIn && _zone.isIn)
{
_zone.trigger('out', [_zone.data])
}
_zone.isIn = isIn
}
})
}
add(_settings)
{
// Set up
const zone = new Zone(_settings)
this.container.add(zone.mesh)
// Save
this.items.push(zone)
return zone
}
}

View File

@ -0,0 +1,511 @@
import * as THREE from 'three'
import Materials from './Materials.js'
import Floor from './Floor.js'
import Shadows from './Shadows.js'
import Physics from './Physics.js'
import Zones from './Zones.js'
import Objects from './Objects.js'
import Car from './Car.js'
import Areas from './Areas.js'
import Tiles from './Tiles.js'
import Walls from './Walls.js'
import IntroSection from './Sections/IntroSection.js'
import ProjectsSection from './Sections/ProjectsSection.js'
import CrossroadsSection from './Sections/CrossroadsSection.js'
import InformationSection from './Sections/InformationSection.js'
import PlaygroundSection from './Sections/PlaygroundSection.js'
// import DistinctionASection from './Sections/DistinctionASection.js'
// import DistinctionBSection from './Sections/DistinctionBSection.js'
// import DistinctionCSection from './Sections/DistinctionCSection.js'
// import DistinctionDSection from './Sections/DistinctionDSection.js'
import Controls from './Controls.js'
import Sounds from './Sounds.js'
import { TweenLite } from 'gsap/TweenLite'
import { Power2 } from 'gsap/EasePack'
import EasterEggs from './EasterEggs.js'
export default class
{
constructor(_options)
{
// Options
this.config = _options.config
this.debug = _options.debug
this.resources = _options.resources
this.time = _options.time
this.sizes = _options.sizes
this.camera = _options.camera
this.renderer = _options.renderer
this.passes = _options.passes
// Debug
if(this.debug)
{
this.debugFolder = this.debug.addFolder('world')
this.debugFolder.open()
}
// Set up
this.container = new THREE.Object3D()
this.container.matrixAutoUpdate = false
// this.setAxes()
this.setSounds()
this.setControls()
this.setFloor()
this.setAreas()
this.setStartingScreen()
}
start()
{
window.setTimeout(() =>
{
this.camera.pan.enable()
}, 2000)
this.setReveal()
this.setMaterials()
this.setShadows()
this.setPhysics()
this.setZones()
this.setObjects()
this.setCar()
this.areas.car = this.car
this.setTiles()
this.setWalls()
this.setSections()
this.setEasterEggs()
}
setReveal()
{
this.reveal = {}
this.reveal.matcapsProgress = 0
this.reveal.floorShadowsProgress = 0
this.reveal.previousMatcapsProgress = null
this.reveal.previousFloorShadowsProgress = null
// Go method
this.reveal.go = () =>
{
TweenLite.fromTo(this.reveal, 3, { matcapsProgress: 0 }, { matcapsProgress: 1 })
TweenLite.fromTo(this.reveal, 3, { floorShadowsProgress: 0 }, { floorShadowsProgress: 1, delay: 0.5 })
TweenLite.fromTo(this.shadows, 3, { alpha: 0 }, { alpha: 0.5, delay: 0.5 })
if(this.sections.intro)
{
TweenLite.fromTo(this.sections.intro.instructions.arrows.label.material, 0.3, { opacity: 0 }, { opacity: 1, delay: 0.5 })
if(this.sections.intro.otherInstructions)
{
TweenLite.fromTo(this.sections.intro.otherInstructions.label.material, 0.3, { opacity: 0 }, { opacity: 1, delay: 0.75 })
}
}
// Car
this.physics.car.chassis.body.sleep()
this.physics.car.chassis.body.position.set(0, 0, 12)
window.setTimeout(() =>
{
this.physics.car.chassis.body.wakeUp()
}, 300)
// Sound
TweenLite.fromTo(this.sounds.engine.volume, 0.5, { master: 0 }, { master: 0.7, delay: 0.3, ease: Power2.easeIn })
window.setTimeout(() =>
{
this.sounds.play('reveal')
}, 400)
// Controls
if(this.controls.touch)
{
window.setTimeout(() =>
{
this.controls.touch.reveal()
}, 400)
}
}
// Time tick
this.time.on('tick',() =>
{
// Matcap progress changed
if(this.reveal.matcapsProgress !== this.reveal.previousMatcapsProgress)
{
// Update each material
for(const _materialKey in this.materials.shades.items)
{
const material = this.materials.shades.items[_materialKey]
material.uniforms.uRevealProgress.value = this.reveal.matcapsProgress
}
// Save
this.reveal.previousMatcapsProgress = this.reveal.matcapsProgress
}
// Matcap progress changed
if(this.reveal.floorShadowsProgress !== this.reveal.previousFloorShadowsProgress)
{
// Update each floor shadow
for(const _mesh of this.objects.floorShadows)
{
_mesh.material.uniforms.uAlpha.value = this.reveal.floorShadowsProgress
}
// Save
this.reveal.previousFloorShadowsProgress = this.reveal.floorShadowsProgress
}
})
// Debug
if(this.debug)
{
this.debugFolder.add(this.reveal, 'matcapsProgress').step(0.0001).min(0).max(1).name('matcapsProgress')
this.debugFolder.add(this.reveal, 'floorShadowsProgress').step(0.0001).min(0).max(1).name('floorShadowsProgress')
this.debugFolder.add(this.reveal, 'go').name('reveal')
}
}
setStartingScreen()
{
this.startingScreen = {}
// Area
this.startingScreen.area = this.areas.add({
position: new THREE.Vector2(0, 0),
halfExtents: new THREE.Vector2(2.35, 1.5),
hasKey: false,
testCar: false,
active: false
})
// Loading label
this.startingScreen.loadingLabel = {}
this.startingScreen.loadingLabel.geometry = new THREE.PlaneBufferGeometry(2.5, 2.5 / 4)
this.startingScreen.loadingLabel.image = new Image()
this.startingScreen.loadingLabel.image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAABABAMAAAAHc7SNAAAAMFBMVEUAAAD///9ra2ucnJzR0dH09PQmJiaNjY24uLjp6end3d1CQkLFxcVYWFiqqqp9fX3nQ5qrAAAEVUlEQVRo3u3YT08TQRQA8JEtW6CATGnDdvljaTwYE2IBI/HGRrwSetGTsZh4MPFQYiQe229gE++WePFY9Oqh1cRzieEDYIgXLxjPJu5M33vbZQszW+fgoS+B7ewO836znRl2lg1jGMP4P2Okw0yFvaKsklr3I99Tvl3iPPelGbQhKqxB4eN6N/7gVcsvbEAz1F4RLn67zzl/v6/oLvejGBQ9LsNphio4UFjmEAsVJuOK/zkDtc6w+gyTcZ3LyP6IAzjBDA+pj6LkEgAjW4kANsMAC6vmOvqAMU5RgVOTskQACicCmCcA9AXjkT5gj1MswqlxWcoTgKJ6HuAQAD5guNoAu8QpMnBul1ONMGD2PCBbRgDAKYq6AEtmXvtdj3S6GhRyW1t1DvkAgM0ggG7mu1t3xWFHFzAqv3wYCi0mY1UCGgiQPU+1oWIY8LoXcAA3qeYfr+kClvHW14PJ5OfCAgHYNAoDAORBQIrDvHjqH5c0ANTbORzBacbAQgUC2IAKAzI9gCSHlWEMLmgBPJxMvyARpIICALDm4nkAbwIA71EZx5UOgO48JnLoOhQIAN9sOgKoBoAE5r0aB8ARcNhtFzrg0VQmwCp8CAMeAADGc44S5GMBsF1aCEU2LcAcAPDCvwFytBDehCaUgJxRAKeF8BNUUQJ43iiAUlqwFKoBrTCAHjiagwEgU0YM5IYWYD4KoIgPwIXQwUbVgCXzgLpIBJNeDciWTQNskVsq1ADX/6kYBdCTjse5owbMiX+IpgGWOCPSuWpA2vN/TAMm5QTYg5IC4FdbMA0YF5Nb5s2rAaLyhzBgektGZWDArrgqi0U1QHxf38OABDwUDgTAjGfyPlTVgJT/67FBACbqyGYaaoBctQwD2vI4DecVAPkgZRhQlxPQks2rAePGAbZsRlaa1QBYEQBUHRCAmaXD0QDYxgFWdye05R9cDQCrmQYkeBA6gGXTgNEeQF4DMG4S4MLjOUZRA5A0CcjADgmjqgGwSwSg9wK1GIBS74KTgTxv/EHoiaVQsTOS5RoCJuiZyosB8EIrHpyowFiYofO0i4wCjhCQwL0hq2sCaFNM22S4JXloLk0AuLDTBzCBAAt3xykeA7CHe/mDbgdTvQ9GswSAwdbqA0giYASHjQUJnhQKhQ6z/d8rDA4hAG2Dsk042ejubHMM2nV6AMf93pCkaRjhh0WsWuz+6aasl2FwiAImReEts1/CSaFfwFouAJxC4RW+I4oCThBQE1X2WbKkBFDkqYDtJ0SHaYKq3pJJwCECjjiFPoC1w+2P0gumurgeBjT6AhIIGKOelGIAngWlFnRnMZjMIYBb7gtIIsAuYU+8GICpEhYyZVgIZ2g9rYYAX1lfAKvjnxzjnWrHALDn9K1h2k2aoI1ewGd2AWAVAVMHcKdW4wDYje739pNufJXhkJohgLu9zy4CHCKAJYUge4ddCojGyPrp9kaHmYjUi9N7+2wYwxjGZfEXMKxGE0GkkfIAAAAASUVORK5CYII='
this.startingScreen.loadingLabel.texture = new THREE.Texture(this.startingScreen.loadingLabel.image)
this.startingScreen.loadingLabel.texture.magFilter = THREE.NearestFilter
this.startingScreen.loadingLabel.texture.minFilter = THREE.LinearFilter
this.startingScreen.loadingLabel.texture.needsUpdate = true
this.startingScreen.loadingLabel.material = new THREE.MeshBasicMaterial({ transparent: true, depthWrite: false, color: 0xffffff, alphaMap: this.startingScreen.loadingLabel.texture })
this.startingScreen.loadingLabel.mesh = new THREE.Mesh(this.startingScreen.loadingLabel.geometry, this.startingScreen.loadingLabel.material)
this.startingScreen.loadingLabel.mesh.matrixAutoUpdate = false
this.container.add(this.startingScreen.loadingLabel.mesh)
// Start label
this.startingScreen.startLabel = {}
this.startingScreen.startLabel.geometry = new THREE.PlaneBufferGeometry(2.5, 2.5 / 4)
this.startingScreen.startLabel.image = new Image()
this.startingScreen.startLabel.image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAABABAMAAAAHc7SNAAAAMFBMVEUAAAD///+cnJxra2vR0dHd3d0mJib09PRYWFjp6em4uLhCQkKqqqqNjY19fX3FxcV3XeRgAAADsklEQVRo3u3YsU9TQRwH8KNgLSDQg9ZCAak1IdE4PKPu1NTEsSzOMDl3I3GpcXAxBhLjXFxNjJgQJ2ON0Rnj4uAAEyv8B/L7tV++5/VN+CM69Ldwfa+534d7d793VzeIQQzi/49c4v5lPF/1vvhFm++rjIpcyErrmrSCuz+cxng1iL/If8drPJD2Lc/Iy4VhaZWlFd4tLPfuMc6e/5LvRilJA2SkVSQA8c0OsI0uNtIAU9rsB8y1rAAZjyimAUa1mQDAeGwF+MA+9lIA69qs9AMKVoDP8vhf35A+NiMAc7YJKFSrX7tcI8BW9+k/O/kz6zSunjSnncMHiQYBcmdXrh3xCVbc2WO8N/YZZI0AxxwMArKivmwAwFKSPmV0UwBbCpj5E+C+yzUbQAaJVwUSA9SFjwFgHQ0jAMrBWgzAPCtHgFFbQAlpEwKC2zWUQgJGbAH+naSdu/fTxQAthPL5/ADD6OCpQwCAsb6LsbEGcBluOAYBmG2fkMIawHVWXEsDIGUGpZCAIRsAS93DPgDbhUmUQgKe2NUB90hfhK0YwEJYHkYpJGDbqBKiB86CGLAlzd6/S8CEvh8sACiBvrSXCshKblWEgNy2vkAMAHwGfjECcJHOu5qUQgDm6vXulshZAXJNL9GJAeg+LxeKPQBj1gzgdlnuCWAhbOi7LwaU9u0A2VWPpUgAC+GR5k0iwBtnB3Bj3qMaRYB17X0IOQhYcjYA7guxxyIAGfd1HNqchPfly7aACQUshAA2W1r5G1yG415YpgB3qIIkAHBH2D075QnQ10fHDsCl+CoGSKpiN8kMAVqIN00BsitnVgKyPIBMB4ADKU92AA5BKQIgszjKBGBLagpwB5xZBGS6pbcuizQAXMA6NAK86OCQ3okAI55BQPe7VoDxXzU/iwPASgS4GAASAiYxWgYAzvAa1loA2AkAFQIU2zEELCJtDDgIAG0CFLvp7LblC2kAtF6eTEJJ2CBAr88bAXKY4WkASbzXmwt5AvTvohHA4WSUBmj2Jt+IThQChrAOLQC13vPFMAOAQwuyTAeAKVQto3OBDOdESh2YxNZPbpYBQNbEAoBfod7e1i1BiwB0voSZWgwAOWgtAGPhD18E8ASIiRIAXNPwXJBtcqMbAFAIr5weIJMAcIx1aAAIqk0lAuycompyFwBMHAsAZlj/lgw0rsy2AkhbsgK4Q+70CUBjxeFXsUb0G1HJDJC9rketZRcCWCJwHM8DgJm7b7ch+XizXm25QQxiEOcXvwGCWOhbCZC0qAAAAABJRU5ErkJggg=='
this.startingScreen.startLabel.texture = new THREE.Texture(this.startingScreen.startLabel.image)
this.startingScreen.startLabel.texture.magFilter = THREE.NearestFilter
this.startingScreen.startLabel.texture.minFilter = THREE.LinearFilter
this.startingScreen.startLabel.texture.needsUpdate = true
this.startingScreen.startLabel.material = new THREE.MeshBasicMaterial({ transparent: true, depthWrite: false, color: 0xffffff, alphaMap: this.startingScreen.startLabel.texture })
this.startingScreen.startLabel.material.opacity = 0
this.startingScreen.startLabel.mesh = new THREE.Mesh(this.startingScreen.startLabel.geometry, this.startingScreen.startLabel.material)
this.startingScreen.startLabel.mesh.matrixAutoUpdate = false
this.container.add(this.startingScreen.startLabel.mesh)
// Progress
this.resources.on('progress', (_progress) =>
{
// Update area
this.startingScreen.area.floorBorder.material.uniforms.uAlpha.value = 1
this.startingScreen.area.floorBorder.material.uniforms.uLoadProgress.value = _progress
})
// Ready
this.resources.on('ready', () =>
{
window.requestAnimationFrame(() =>
{
this.startingScreen.area.activate()
TweenLite.to(this.startingScreen.area.floorBorder.material.uniforms.uAlpha, 0.3, { value: 0.3 })
TweenLite.to(this.startingScreen.loadingLabel.material, 0.3, { opacity: 0 })
TweenLite.to(this.startingScreen.startLabel.material, 0.3, { opacity: 1, delay: 0.3 })
})
})
// On interact, reveal
this.startingScreen.area.on('interact', () =>
{
this.startingScreen.area.deactivate()
TweenLite.to(this.startingScreen.area.floorBorder.material.uniforms.uProgress, 0.3, { value: 0, delay: 0.4 })
TweenLite.to(this.startingScreen.startLabel.material, 0.3, { opacity: 0, delay: 0.4 })
this.start()
window.setTimeout(() =>
{
this.reveal.go()
}, 600)
})
}
setSounds()
{
this.sounds = new Sounds({
debug: this.debugFolder,
time: this.time
})
}
setAxes()
{
this.axis = new THREE.AxesHelper()
this.container.add(this.axis)
}
setControls()
{
this.controls = new Controls({
config: this.config,
sizes: this.sizes,
time: this.time,
camera: this.camera,
sounds: this.sounds
})
}
setMaterials()
{
this.materials = new Materials({
resources: this.resources,
debug: this.debugFolder
})
}
setFloor()
{
this.floor = new Floor({
debug: this.debugFolder
})
this.container.add(this.floor.container)
}
setShadows()
{
this.shadows = new Shadows({
time: this.time,
debug: this.debugFolder,
renderer: this.renderer,
camera: this.camera
})
this.container.add(this.shadows.container)
}
setPhysics()
{
this.physics = new Physics({
config: this.config,
debug: this.debug,
time: this.time,
sizes: this.sizes,
controls: this.controls,
sounds: this.sounds
})
this.container.add(this.physics.models.container)
}
setZones()
{
this.zones = new Zones({
time: this.time,
physics: this.physics,
debug: this.debugFolder
})
this.container.add(this.zones.container)
}
setAreas()
{
this.areas = new Areas({
config: this.config,
resources: this.resources,
debug: this.debug,
renderer: this.renderer,
camera: this.camera,
car: this.car,
sounds: this.sounds,
time: this.time
})
this.container.add(this.areas.container)
}
setTiles()
{
this.tiles = new Tiles({
resources: this.resources,
objects: this.objects,
debug: this.debug
})
}
setWalls()
{
this.walls = new Walls({
resources: this.resources,
objects: this.objects
})
}
setObjects()
{
this.objects = new Objects({
time: this.time,
resources: this.resources,
materials: this.materials,
physics: this.physics,
shadows: this.shadows,
sounds: this.sounds,
debug: this.debugFolder
})
this.container.add(this.objects.container)
// window.requestAnimationFrame(() =>
// {
// this.objects.merge.update()
// })
}
setCar()
{
this.car = new Car({
time: this.time,
resources: this.resources,
objects: this.objects,
physics: this.physics,
shadows: this.shadows,
materials: this.materials,
controls: this.controls,
sounds: this.sounds,
renderer: this.renderer,
camera: this.camera,
debug: this.debugFolder,
config: this.config
})
this.container.add(this.car.container)
}
setSections()
{
this.sections = {}
// Generic options
const options = {
config: this.config,
time: this.time,
resources: this.resources,
camera: this.camera,
passes: this.passes,
objects: this.objects,
areas: this.areas,
zones: this.zones,
walls: this.walls,
tiles: this.tiles,
debug: this.debugFolder
}
// // Distinction A
// this.sections.distinctionA = new DistinctionASection({
// ...options,
// x: 0,
// y: - 15
// })
// this.container.add(this.sections.distinctionA.container)
// // Distinction B
// this.sections.distinctionB = new DistinctionBSection({
// ...options,
// x: 0,
// y: - 15
// })
// this.container.add(this.sections.distinctionB.container)
// // Distinction C
// this.sections.distinctionC = new DistinctionCSection({
// ...options,
// x: 0,
// y: 0
// })
// this.container.add(this.sections.distinctionC.container)
// // Distinction D
// this.sections.distinctionD = new DistinctionDSection({
// ...options,
// x: 0,
// y: 0
// })
// this.container.add(this.sections.distinctionD.container)
// Intro
this.sections.intro = new IntroSection({
...options,
x: 0,
y: 0
})
this.container.add(this.sections.intro.container)
// Crossroads
this.sections.crossroads = new CrossroadsSection({
...options,
x: 0,
y: - 30
})
this.container.add(this.sections.crossroads.container)
// Projects
this.sections.projects = new ProjectsSection({
...options,
x: 30,
y: - 30
// x: 0,
// y: 0
})
this.container.add(this.sections.projects.container)
// Information
this.sections.information = new InformationSection({
...options,
x: 1.2,
y: - 55
// x: 0,
// y: - 10
})
this.container.add(this.sections.information.container)
// Playground
this.sections.playground = new PlaygroundSection({
...options,
x: - 38,
y: - 34
// x: - 15,
// y: - 4
})
this.container.add(this.sections.playground.container)
}
setEasterEggs()
{
this.easterEggs = new EasterEggs({
resources: this.resources,
car: this.car,
walls: this.walls,
objects: this.objects,
materials: this.materials,
areas: this.areas,
config: this.config,
physics: this.physics
})
this.container.add(this.easterEggs.container)
}
}

BIN
src/models/area/enter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

BIN
src/models/area/open.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

BIN
src/models/area/reset.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/models/brick/base.glb Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More