I wanted to see if I could do a minigame about pac-man with a little twist but somehow ended by doing this cube maze with sparking lights and colors. Then I realize It could be used as the game menu...
A Pen by Ivan Juarez N. on CodePen.
<script type="x-shader/x-vertex" id="shader-passthrough-vertex"> | |
varying vec2 vUv; | |
void main() { | |
vUv = uv; | |
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); | |
} | |
</script> | |
<script type="x-shader/x-fragment" id="shader-passthrough-fragment"> | |
uniform sampler2D tDiffuse; | |
varying vec2 vUv; | |
void main() { | |
gl_FragColor = texture2D( tDiffuse, vec2( vUv.x, vUv.y ) ); | |
} | |
</script> | |
<script type="x-shader/x-fragment" id="shader-volumetric-light-fragment"> | |
varying vec2 vUv; | |
uniform sampler2D tDiffuse; | |
uniform vec2 lightPosition; | |
uniform float exposure; | |
uniform float decay; | |
uniform float density; | |
uniform float weight; | |
uniform int samples; | |
const int MAX_SAMPLES = 100; | |
void main() | |
{ | |
vec2 texCoord = vUv; | |
vec2 deltaTextCoord = texCoord - lightPosition; | |
deltaTextCoord *= 1.0 / float(samples) * density; | |
vec4 color = texture2D(tDiffuse, texCoord); | |
float illuminationDecay = 1.0; | |
for(int i=0; i < MAX_SAMPLES; i++) { | |
if(i == samples) { | |
break; | |
} | |
texCoord -= deltaTextCoord; | |
vec4 sample = texture2D(tDiffuse, texCoord); | |
sample *= illuminationDecay * weight; | |
color += sample; | |
illuminationDecay *= decay; | |
} | |
gl_FragColor = color * exposure; | |
} | |
</script> | |
<script type="x-shader/x-fragment" id="shader-additive-fragment"> | |
uniform sampler2D tDiffuse; | |
uniform sampler2D tAdd; | |
varying vec2 vUv; | |
void main() { | |
vec4 color = texture2D( tDiffuse, vUv ); | |
vec4 add = texture2D( tAdd, vUv ); | |
gl_FragColor = color + add; | |
} | |
</script> | |
<img id="map-test" src="data:image/gif;base64,R0lGODdhHAAfAPIAAAAAAAD/AAAA//8AACZFySZFySZFySZFySH5BAEAAAQALAAAAAAcAB8AAAOwOLrc/iMIAISkNahL7cyKwIhj2JDmADArq7QLrKZ0itYk5+0SL3gdzeBGupWGR2IteeQAhD3oTxp8GIsnB4YyY7q24FUrOp2+VGFMF8VNibcObBhJZ4GjaHh0nkXOsQ1gaxAkb2liGx9bFmeHXC1GC4V5bUcyLnUMOk6MEYpAIJlXS0qifaOSiVAZZT5Pql6RNkt0gHGzCxJ4nq+enWRCsqmZt8SBtE2KYK5pnRDP0AkAOw==" /> |
I wanted to see if I could do a minigame about pac-man with a little twist but somehow ended by doing this cube maze with sparking lights and colors. Then I realize It could be used as the game menu...
A Pen by Ivan Juarez N. on CodePen.
/* | |
TODO: | |
- Cleanup code | |
- Generate different mazes for each face | |
- Try different color schemes | |
- Create UI Menus | |
- Optimize for mobile | |
*/ | |
/* | |
Resources: | |
- Volumetric Light Scattering in three.js | |
https://medium.com/@andrew_b_berg/volumetric-light-scattering-in-three-js-6e1850680a41 | |
- Postprocessing Unreal Bloom | |
https://threejs.org/examples/#webgl_postprocessing_unreal_bloom | |
- THREE Mesh Line | |
https://github.com/spite/THREE.MeshLine | |
- Javascript 2D Perlin & Simplex noise functions | |
https://github.com/josephg/noisejs | |
*/ | |
const CUBE_SIZE = 10; | |
const MAP_WIDTH = 28; | |
const MAP_HEIGHT = 31; | |
// DIRECTIONS | |
const NONE = 0; | |
const UP = 1; | |
const RIGHT = 2; | |
const DOWN = 3; | |
const LEFT = 4; | |
// MAP ITEMS | |
const MAP_EMPTY = 1; | |
const MAP_WALL = 2; | |
const MAP_JUNCTION = 3; | |
const MAP_DIRECTION = 4; | |
const MAP_PARSE_COLORS = { | |
'0,0,0': MAP_EMPTY, | |
'255,0,0': MAP_WALL, | |
'0,255,0': MAP_JUNCTION, | |
'0,0,255': MAP_DIRECTION | |
}; | |
const Utils = { | |
AddDot(scene, position, size = 5) { | |
const geo = new THREE.Geometry(); | |
geo.vertices.push(position.clone()); | |
const mat = new THREE.PointsMaterial( { | |
size, | |
sizeAttenuation: false, | |
color: 0xffffff | |
} ); | |
const dot = new THREE.Points(geo, mat); | |
scene.add( dot ); | |
} | |
} | |
class BoardMap { | |
constructor(mapId) { | |
this.width = 0; | |
this.height = 0; | |
this.tiles = []; | |
this.mapImageData = this.getImageData(mapId); | |
this.parseMap(); | |
} | |
getImageData(id) { | |
const canvas = document.createElement('canvas'); | |
const ctx = canvas.getContext('2d'); | |
const img = document.querySelector(id); | |
const { width: w, height: h } = img; | |
canvas.width = w; | |
canvas.height = h; | |
ctx.drawImage(img, 0, 0); | |
this.width = w; | |
this.height = h; | |
return ctx.getImageData(0, 0, w, h).data; | |
} | |
getMapPixelRGB(x, y) { | |
const { mapImageData: data, width } = this; | |
const idx = (x + width * y) * 4; | |
return [ | |
Math.round(data[idx + 0] / 255) * 255, | |
Math.round(data[idx + 1] / 255) * 255, | |
Math.round(data[idx + 2] / 255) * 255, | |
]; | |
} | |
stepToDirection(x, y, direction) { | |
let xTo = x; | |
let yTo = y; | |
switch(direction) { | |
case UP: yTo -= 1; break; | |
case RIGHT: xTo += 1; break; | |
case DOWN: yTo += 1; break; | |
case LEFT: xTo -= 1; break; | |
} | |
return [xTo, yTo]; | |
} | |
getTileAt(x, y, direction = NONE) { | |
let [xTo, yTo] = this.stepToDirection(x, y, direction); | |
if (x <= this.width && y <= this.height) { | |
let idx = yTo * this.width + xTo; | |
return this.tiles[idx]; | |
} | |
} | |
getNearestNeighborFrom(x, y, direction, type) { | |
let next = this.getTileAt(x, y, direction); | |
let [xTo, yTo] = this.stepToDirection(x, y, direction); | |
if (next === type) { | |
return [xTo, yTo]; | |
} else if (next) { | |
return this.getNearestNeighborFrom(xTo, yTo, direction, type); | |
} | |
} | |
getIndexFromCoords(x, y) { | |
return y * this.width + x; | |
} | |
getCoordsFromIndex(idx) { | |
const x = idx % this.width; | |
const y = Math.floor( idx / this.width ); | |
return [x, y]; | |
} | |
parseMap() { | |
for( let y = 0; y < this.height; y++ ) { | |
for( let x = 0; x < this.width; x++) { | |
const [r, g, b] = this.getMapPixelRGB(x, y); | |
this.tiles.push(MAP_PARSE_COLORS[`${r},${g},${b}`]); | |
} | |
} | |
} | |
} | |
class MapBoundariesMesh { | |
constructor(boardMap) { | |
this.boardMap = boardMap; | |
this.geometry = new THREE.Geometry(); | |
this.generateGeometry(); | |
} | |
generateGeometry() { | |
for( let x = 0; x < MAP_WIDTH; x++ ) { | |
for( let y = 0; y < MAP_HEIGHT; y++ ) { | |
let idx = y * MAP_WIDTH + x; | |
if (this.boardMap.tiles[idx] === MAP_WALL) { | |
this.putBlock(x, y); | |
} | |
} | |
} | |
} | |
putBlock(x, y) { | |
const boxWidth = CUBE_SIZE / MAP_WIDTH; | |
const boxHeight = CUBE_SIZE / MAP_HEIGHT; | |
const halfSize = CUBE_SIZE / 2; | |
const boxElevation = 0.25; | |
const geo = new THREE.BoxGeometry(boxWidth, boxElevation, boxHeight); | |
const tX = -halfSize + x * boxWidth + boxWidth / 2; | |
const tY = -halfSize + y * boxHeight + boxHeight / 2; | |
geo.translate(tX, boxElevation / 2, tY); | |
const mesh = new THREE.Mesh(geo); | |
this.geometry.merge(mesh.geometry, mesh.matrix); | |
} | |
} | |
class Particles { | |
constructor() { | |
this.clusters = []; | |
this.scales = []; | |
this.initParticles(); | |
} | |
initParticles() { | |
this.texture = new THREE.TextureLoader().load('https://s3-us-west-2.amazonaws.com/s.cdpn.io/204379/particle.png'); | |
for( let i = 0; i < 5; i++ ) { | |
const cluster = { | |
scale: i + 2, | |
speed: THREE.Math.randFloat(0.5, 1.8), | |
points: this.getCluster(100), | |
} | |
this.clusters.push(cluster); | |
} | |
} | |
getCluster(count) { | |
const geo = new THREE.Geometry(); | |
const mat = new THREE.PointsMaterial({ | |
color: 0xffff00, | |
size: THREE.Math.randFloat(0.1, 0.25), | |
map: this.texture, | |
sizeAttenuation: true, | |
transparent: true, | |
opacity: 0.9, | |
}); | |
for( let i = 0; i < count; i++) { | |
let p = new THREE.Vector3(); | |
p.x = THREE.Math.randFloatSpread( 2 ); | |
p.y = THREE.Math.randFloatSpread( 2 ); | |
p.z = THREE.Math.randFloatSpread( 2 ); | |
geo.vertices.push(p); | |
} | |
return new THREE.Points(geo, mat); | |
} | |
update(delta) { | |
for( let i = 0; i < this.clusters.length; i++ ) { | |
const cluster = this.clusters[i]; | |
if (cluster.scale > 12) { | |
cluster.scale = 2; | |
cluster.points.material.opacity = 1; | |
} | |
cluster.scale += 0.45 * delta * cluster.speed; | |
cluster.points.scale.set(cluster.scale, cluster.scale, cluster.scale); | |
//const color = this.startColor.lerp(this.endColor, cluster.scale / 12); | |
if (cluster.scale > 8) { | |
const opacity = THREE.Math.lerp(1, 0, 1 - ((12 - cluster.scale) / 4)); | |
cluster.points.material.opacity = opacity; | |
} | |
} | |
} | |
} | |
class Trails { | |
constructor(map) { | |
this.group = new THREE.Object3D(); | |
this.position = new THREE.Vector3(); | |
this.map = map; | |
this.maxPositions = 25; | |
this.history = []; | |
this.junctionTiles = []; | |
this.init(); | |
this.spawn(); | |
} | |
init() { | |
this.geometry = new THREE.Geometry(); | |
const mat = new MeshLineMaterial({color: new THREE.Color(0xffffff), side: THREE.DoubleSide }); | |
this.line = new MeshLine(); | |
this.mesh = new THREE.Mesh(this.line.geometry, mat); | |
this.group.add(this.mesh); | |
for( let i = 0; i < this.maxPositions; i++) { | |
this.geometry.vertices.push(new THREE.Vector3()); | |
} | |
this.map.tiles.forEach( (t, idx) => { | |
if (t === MAP_JUNCTION) { | |
this.junctionTiles.push(idx); | |
} | |
}); | |
this.light = new THREE.PointLight(0x00ff00, 1, 5); | |
this.group.add(this.light); | |
//this.debug(); | |
} | |
spawn() { | |
const { position, line } = this; | |
const { vertices } = this.geometry; | |
const wayPoints = this.getWayPoints(); | |
vertices.forEach( v => v.copy(wayPoints[0]) ); | |
this.position.copy(wayPoints[0]); | |
this.light.position.copy(wayPoints[0]); | |
line.setGeometry(this.geometry, (p) => p * 0.5); | |
this.startPath(wayPoints); | |
} | |
getTileDirections(x, y) { | |
const { map } = this; | |
return [ UP, RIGHT, DOWN, LEFT ].filter( d => { | |
const tile = map.getTileAt(x, y, d); | |
return tile !== undefined && tile === MAP_DIRECTION; | |
} ); | |
} | |
debug() { | |
this.junctionTiles.forEach( t => { | |
const [x, y] = this.map.getCoordsFromIndex(t); | |
Utils.AddDot(this.group, this.scaleTilePosition(new THREE.Vector3(x, y, 0))) | |
}); | |
} | |
scaleTilePosition(position) { | |
const { map } = this; | |
const tileScaleX = CUBE_SIZE / (map.width); | |
const tileScaleY = CUBE_SIZE / (map.height); | |
position.set( | |
(position.x + tileScaleX * 1.5) * tileScaleX, | |
(position.y + tileScaleX * 1.5) * tileScaleY, 0 ) | |
return position; | |
} | |
getWayPoints() { | |
const { map, junctionTiles: jTiles } = this; | |
const startTile = jTiles[ ~~(Math.random() * jTiles.length) ]; | |
let [xCurrent, yCurrent] = map.getCoordsFromIndex(startTile); | |
const visitedTiles = []; | |
let insert = true; | |
while(insert) { | |
visitedTiles.push(map.getIndexFromCoords(xCurrent, yCurrent)); | |
// find available directions from current coords | |
const directions = this.getTileDirections(xCurrent, yCurrent); | |
// get all neighbours for all posibile directions | |
const neighbours = directions | |
.map( d => map.getNearestNeighborFrom(xCurrent, yCurrent, d, MAP_JUNCTION) ) | |
// exclude already visited ones | |
.filter( n => visitedTiles.indexOf( map.getIndexFromCoords(n[0], n[1]) ) === -1); | |
// pick one from available neighbours | |
const nPick = neighbours[~~(Math.random() * neighbours.length)]; | |
if (nPick) { | |
[xCurrent, yCurrent] = nPick; | |
insert = true; | |
} else { | |
insert = false; | |
} | |
} | |
const tileScaleX = map.width / CUBE_SIZE * 0.1; | |
const tileScaleY = map.height / CUBE_SIZE * 0.1; | |
return visitedTiles.map( idx => { | |
let [x, y] = map.getCoordsFromIndex(idx); | |
return this.scaleTilePosition(new THREE.Vector3(x, y, 0)); | |
}); | |
} | |
updatePosition() { | |
const { position } = this; | |
this.light.position.copy(position); | |
this.line.advance(position); | |
} | |
startPath(waypoints) { | |
const { position } = this; | |
this.timeline = new TimelineMax({ onComplete: () => { | |
TweenMax.to(this.position, 1, { | |
onUpdate: this.updatePosition.bind(this), | |
onComplete: () => { | |
setTimeout(this.spawn.bind(this), Math.random() * 2500 + 500); | |
} | |
}) | |
}}); | |
waypoints.forEach( (pos, idx) => { | |
this.timeline.to(this.position, 0.25, { | |
x: pos.x, | |
y: pos.y, | |
onUpdate: this.updatePosition.bind(this), | |
ease: Linear.easeNone | |
}); | |
if (idx === 0) { | |
this.timeline.to(this.light, 0.2, { power: 1.5 * 4 * Math.PI }) | |
} | |
if (idx === waypoints.length - 1) { | |
this.timeline.to(this.light, 0.4, { power: 0.1 * 4 * Math.PI }, '-=0.45') | |
} | |
}); | |
} | |
} | |
class App { | |
constructor() { | |
this.width = 0; | |
this.height = 0; | |
this.mouse = new THREE.Vector2(0, 0); | |
this.init(); | |
this.initLights(); | |
this.initMazeMesh(); | |
this.initTrails(); | |
this.initParticles(); | |
this.initGodRays(); | |
this.setupComposer(); | |
this.setupProstprocessing(); | |
this.addRenderTargetImage(); | |
this.attachEvents(); | |
this.updateSize(); | |
this.onFrame(0); | |
//this.initHelpers(); | |
//this.addGUI(); | |
} | |
addGUI() { | |
this.gui = new dat.GUI(); | |
const setCubeColor = (c) => { this.mazeMesh.material.color.setHex(c.replace('#', '0x')) }; | |
const setOutterLightColor = (c) => { this.outterLight.color.setHex(c.replace('#', '0x')) }; | |
const setLightSphereColor = (c) => { this.lightSphere.material.color.setHex(c.replace('#', '0x')) }; | |
this.cubeColor = "#4583dc"; | |
this.outterLightColor = "#c743ff"; | |
this.lightSphereColor = '#ffd242'; | |
this.gui.addColor(this, 'cubeColor').onChange(setCubeColor); | |
this.gui.addColor(this, 'outterLightColor').onChange(setOutterLightColor); | |
this.gui.addColor(this, 'lightSphereColor').onChange(setLightSphereColor); | |
} | |
init() { | |
const { innerWidth: w, innerHeight: h } = window; | |
const aspect = w / h; | |
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false }); | |
this.renderer.setPixelRatio( window.devicePixelRatio ); | |
this.renderer.setSize(w, h); | |
this.renderer.gammaInput = true; | |
this.scene = new THREE.Scene(); | |
this.camera = new THREE.PerspectiveCamera( 45, w / h, 0.1, 1000 ); | |
this.camera.position.set(14, 14, 14); | |
this.clock = new THREE.Clock(); | |
document.body.appendChild(this.renderer.domElement); | |
} | |
initTrails() { | |
const { scene } = this; | |
const map = new BoardMap('#map-test'); | |
const trails = []; | |
const t1 = new Trails(map); | |
t1.group.position.set(-CUBE_SIZE / 2, CUBE_SIZE / 2 + 0.2, -CUBE_SIZE / 2); | |
t1.group.rotation.x = Math.PI / 2; | |
scene.add(t1.group); | |
const t2 = new Trails(map); | |
t2.group.position.set(CUBE_SIZE / 2, -CUBE_SIZE / 2, -CUBE_SIZE / 2) | |
t2.group.rotation.x = Math.PI / 2; | |
t2.group.rotation.y = Math.PI / 2; | |
scene.add(t2.group); | |
const t3 = new Trails(map); | |
t3.group.position.set(-CUBE_SIZE / 2, -CUBE_SIZE / 2 - 0.1, CUBE_SIZE / 2) | |
scene.add(t3.group); | |
this.trails = trails; | |
} | |
initParticles() { | |
const { scene } = this; | |
this.particles = new Particles(); | |
this.particles.clusters.forEach( ( cluster ) => { | |
scene.add(cluster.points); | |
}); | |
} | |
initLights() { | |
const { scene } = this; | |
const ambientLight = new THREE.AmbientLight( 0xffffff, 0.15 ); | |
const innerLight = new THREE.PointLight(0xfffff); | |
const outterLight = new THREE.PointLight(0xc743ff, 3 , 25); | |
outterLight.position.set(14, 14, 14); | |
this.outterLight = outterLight; | |
//Utils.AddDot(scene, pointLight.position); | |
scene.add( innerLight ); | |
scene.add( outterLight ); | |
scene.add( ambientLight ); | |
} | |
setupProstprocessing() { | |
const { composer } = this; | |
const { innerWidth: w, innerHeight: h } = window; | |
this.effectFXAA = new THREE.ShaderPass( THREE.FXAAShader ); | |
this.effectFXAA.uniforms[ 'resolution' ].value.set(1/w, 1/h); | |
composer.addPass( this.effectFXAA ); | |
this.bloomPass = new THREE.UnrealBloomPass( new THREE.Vector2(w, h), 1.5, 0.4, 0.85 ); | |
this.bloomPass.renderToScreen = true; | |
composer.addPass( this.bloomPass); | |
} | |
initGodRays() { | |
const { scene, mazeMesh } = this; | |
const geoSphere = new THREE.BoxGeometry(CUBE_SIZE * 0.8, CUBE_SIZE * 0.8, CUBE_SIZE * 0.8); | |
const matSphere = new THREE.MeshBasicMaterial({ color: 0xffa602, transparent: true }); | |
this.lightSphere = new THREE.Mesh(geoSphere, matSphere); | |
this.lightSphere.layers.set(1); | |
this.lightSphere.material.opacity = 1; | |
scene.add(this.lightSphere); | |
const matOcclusion = new THREE.MeshBasicMaterial({ color: 0x0 }); | |
this.occlusionMesh = new THREE.Mesh(mazeMesh.geometry, matOcclusion); | |
this.occlusionMesh.position.z = 0; | |
this.occlusionMesh.layers.set(1); | |
scene.add(this.occlusionMesh); | |
} | |
getScriptContent(id) { | |
return document.querySelector(id).textContent; | |
} | |
setupComposer() { | |
const { renderer, camera, scene } = this; | |
const { innerWidth: w, innerHeight: h } = window; | |
const scale = 0.5; | |
this.occlusionRenderTarget = new THREE.WebGLRenderTarget( w * scale, h * scale); | |
this.occlusionComposer = new THREE.EffectComposer(renderer, this.occlusionRenderTarget); | |
this.occlusionComposer.addPass( new THREE.RenderPass(scene, camera)); | |
let occPass = new THREE.ShaderPass({ | |
uniforms: { | |
tDiffuse: { value:null }, | |
lightPosition: { value: new THREE.Vector2(0.5, 0.5) }, | |
exposure: { value: 0.21 }, | |
decay: { value: 0.95 }, | |
density: { value: 0.1 }, | |
weight: { value: 0.7 }, | |
samples: { value: 50 } | |
}, | |
vertexShader: this.getScriptContent('#shader-passthrough-vertex'), | |
fragmentShader: this.getScriptContent('#shader-volumetric-light-fragment') | |
}); | |
occPass.needsSwap = false; | |
this.occlusionPass = occPass; | |
this.occlusionComposer.addPass(occPass); | |
this.composer = new THREE.EffectComposer( renderer ); | |
this.composer.addPass( new THREE.RenderPass( scene, camera )); | |
let addPass = new THREE.ShaderPass( { | |
uniforms: { | |
tDiffuse: { value:null }, | |
tAdd: { value: null } | |
}, | |
vertexShader: this.getScriptContent('#shader-passthrough-vertex'), | |
fragmentShader: this.getScriptContent('#shader-additive-fragment') | |
} ); | |
addPass.uniforms.tAdd.value = this.occlusionRenderTarget.texture; | |
this.composer.addPass(addPass); | |
//addPass.renderToScreen = true; | |
} | |
addRenderTargetImage() { | |
const mat = new THREE.ShaderMaterial( { | |
uniforms: { | |
tDiffuse: { value: null }, | |
vertexShader: this.getScriptContent('#shader-passthrough-vertex'), | |
fragmentShader: this.getScriptContent('#shader-passthrough-fragment') | |
}, | |
} ); | |
mat.uniforms.tDiffuse.value = this.occlusionRenderTarget.texture; | |
const mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry(2, 2), mat); | |
mesh.visible = false; | |
this.composer.passes[1].scene.add(mesh); | |
} | |
initMazeMesh() { | |
const { scene } = this; | |
const geo = new THREE.Geometry(); | |
const up = this.getGamePlaneGeometry('#map-test'); | |
up.position.y = CUBE_SIZE / 2; | |
up.updateMatrix(); | |
geo.merge(up.geometry, up.matrix); | |
const right = this.getGamePlaneGeometry('#map-test'); | |
right.position.x = CUBE_SIZE / 2; | |
right.rotation.z = Math.PI / 2; | |
right.updateMatrix(); | |
geo.merge(right.geometry, right.matrix); | |
const front = this.getGamePlaneGeometry('#map-test'); | |
front.rotation.x = Math.PI / 2; | |
front.position.z = CUBE_SIZE / 2 - 0.25; | |
front.updateMatrix(); | |
geo.merge(front.geometry, front.matrix); | |
const left = this.getGamePlaneGeometry('#map-test'); | |
left.rotation.z = Math.PI / 2; | |
left.position.x = -CUBE_SIZE / 2 + 0.25; | |
left.updateMatrix(); | |
geo.merge(left.geometry, left.matrix); | |
const down = this.getGamePlaneGeometry('#map-test'); | |
down.position.y = -CUBE_SIZE / 2; | |
down.updateMatrix(); | |
geo.merge(down.geometry, down.matrix); | |
const back = this.getGamePlaneGeometry('#map-test'); | |
back.rotation.x = Math.PI / 2; | |
back.position.z = -CUBE_SIZE / 2; | |
back.updateMatrix(); | |
geo.merge(back.geometry, back.matrix); | |
const mat = new THREE.MeshPhongMaterial( { | |
color: 0x4583dc, | |
} ); | |
this.mazeMesh = new THREE.Mesh(geo, mat); | |
this.mazeMesh.updateMatrix(); | |
scene.add(this.mazeMesh); | |
} | |
getGamePlaneGeometry(mapId) { | |
const boardMap = new BoardMap(mapId); | |
const mapBoundariesMesh = new MapBoundariesMesh(boardMap); | |
return new THREE.Mesh(mapBoundariesMesh.geometry); | |
} | |
attachEvents() { | |
window.addEventListener("resize", this.updateSize.bind(this)); | |
window.addEventListener("mousemove", this.onMouseMove.bind(this)); | |
} | |
onMouseMove(event) { | |
this.mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1; | |
this.mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1; | |
} | |
initHelpers() { | |
const { scene, camera, renderer } = this; | |
const c = new THREE.OrbitControls(camera, renderer.domElement); | |
c.enableDamping = true; | |
c.dampingFactor = 0.25; | |
c.minDistance = 1; | |
c.maxDistance = 100; | |
this.orbitControls = c; | |
this.axesHelper = new THREE.AxesHelper(500); | |
//scene.add(this.axesHelper); | |
} | |
updateSize() { | |
const { renderer, camera, composer, occlusionComposer } = this; | |
const { innerWidth: w, innerHeight: h } = window; | |
renderer.setSize(w, h); | |
camera.aspect = w / h; | |
camera.updateProjectionMatrix(); | |
composer.setSize(w, h); | |
occlusionComposer.setSize(w, h); | |
this.width = w; | |
this.height = h; | |
} | |
updateOcclusionIntensity(time) { | |
const { uniforms: u } = this.occlusionPass; | |
const n0 = (noise.perlin2(time * 0.0005, 0) + 1) * 0.5; | |
const n1 = (noise.perlin2(0, time * 0.0005) + 1) * 0.5; | |
u.exposure.value = THREE.Math.lerp(0.05, 0.21, n0); | |
u.decay.value = THREE.Math.lerp(0.95, 0.98, n1); | |
u.density.value = THREE.Math.lerp(0.2, 0.4, n0); | |
u.weight.value = THREE.Math.lerp(0.1, 0.7, n1); | |
} | |
updateLightPosition(time) { | |
const { lightSphere } = this; | |
const n0 = (noise.perlin2(time * 0.0005, 0) + 1) * 0.5; | |
lightSphere.position.y = THREE.Math.lerp(-1, 1, n0); | |
} | |
updateCameraTilt() { | |
const { camera, mouse } = this; | |
TweenMax.to(this.camera.position, 1.5, { | |
x: 14 + mouse.x * 2.5, | |
y: 14 + mouse.y * 2.5, | |
z: 14 + mouse.x * mouse.y | |
}); | |
} | |
onFrame(time) { | |
const { renderer, scene, camera, clock } = this; | |
requestAnimationFrame(this.onFrame.bind(this)); | |
//this.orbitControls.update(); | |
camera.layers.set(1); | |
this.updateCameraTilt(); | |
this.particles.update(clock.getDelta()); | |
this.updateOcclusionIntensity(time); | |
this.updateLightPosition(time); | |
renderer.setClearColor(0x0, 0); | |
this.occlusionComposer.render(); | |
camera.layers.set(0); | |
camera.lookAt( scene.position ); | |
this.composer.render(); | |
//renderer.render(scene, camera); | |
} | |
} | |
window.app = new App(); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.js"></script> | |
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/204379/EffectComposer.js"></script> | |
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/204379/UnrealBloomPass.js"></script> | |
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/204379/perlin.js"></script> | |
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/204379/THREE.MeshLine.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script> |
body | |
overflow: hidden | |
//background-color: white | |
background: radial-gradient(ellipse at center, #000000 0%, #100B0D 99%); | |
img | |
display: none |