347 lines
9.4 KiB
JavaScript
347 lines
9.4 KiB
JavaScript
import * as THREE from 'three'
|
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
|
|
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'
|
|
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
|
|
|
|
let animations,
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
allPrizes,
|
|
scene,
|
|
camera,
|
|
cardCanvas,
|
|
cardImgUrl,
|
|
renderer,
|
|
orbitControls,
|
|
mixer,
|
|
particles,
|
|
soundAnimation,
|
|
clock = new THREE.Clock()
|
|
|
|
const render = () => {
|
|
orbitControls.update()
|
|
|
|
if (mixer) mixer.update(clock.getDelta())
|
|
|
|
renderer.render(scene, camera)
|
|
|
|
requestAnimationFrame(render)
|
|
}
|
|
|
|
export const unmute = () => {
|
|
document.getElementById('animation').muted = false
|
|
document.getElementById('particles-coins').muted = false
|
|
document.getElementById('particles-fireworks').muted = false
|
|
}
|
|
|
|
export const mute = () => {
|
|
document.getElementById('animation').muted = true
|
|
document.getElementById('particles-coins').muted = true
|
|
document.getElementById('particles-fireworks').muted = true
|
|
}
|
|
|
|
export const onClick = () => {
|
|
console.log('render:onClick', particles)
|
|
if (particles) {
|
|
particles.muted = false
|
|
particles.currentTime = 0
|
|
particles.play()
|
|
}
|
|
}
|
|
|
|
const onResize = () => {
|
|
console.log('render:onResize')
|
|
|
|
const width = window.innerWidth
|
|
const height = window.innerHeight
|
|
renderer.setPixelRatio(window.devicePixelRatio)
|
|
renderer.setSize(width, height)
|
|
renderer.toneMapping = THREE.ACESFilmicToneMapping
|
|
renderer.toneMappingExposure = 1
|
|
camera.aspect = width / height
|
|
camera.updateProjectionMatrix()
|
|
}
|
|
|
|
const lumaKeyVertexShader = `
|
|
varying vec2 vUv;
|
|
void main() {
|
|
vUv = uv;
|
|
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
|
}
|
|
`
|
|
|
|
const lumaKeyFragmentShader = `
|
|
uniform sampler2D tex;
|
|
uniform float threshold;
|
|
varying vec2 vUv;
|
|
void main(void) {
|
|
vec2 texCoord = vUv;
|
|
vec4 rgba = texture2D(tex, texCoord);
|
|
float luma = rgba.r * 0.299 + rgba.g * 0.587 + rgba.b * 0.114;
|
|
rgba.a = step(threshold, luma);
|
|
gl_FragColor = rgba;
|
|
}
|
|
`
|
|
|
|
class LumaKeyMaterial extends THREE.ShaderMaterial {
|
|
constructor(tex, threshold) {
|
|
super()
|
|
this.setValues({
|
|
uniforms: {
|
|
tex: {
|
|
value: tex,
|
|
},
|
|
threshold: {
|
|
value: threshold,
|
|
},
|
|
},
|
|
vertexShader: lumaKeyVertexShader,
|
|
fragmentShader: lumaKeyFragmentShader,
|
|
transparent: true,
|
|
})
|
|
this.needsUpdate = true
|
|
}
|
|
}
|
|
|
|
const prepareCardFace = async (document, window, prize) => {
|
|
// Create a canvas element
|
|
if (!cardCanvas) {
|
|
cardCanvas = document.createElement('canvas')
|
|
cardCanvas.width = 600
|
|
cardCanvas.height = 900
|
|
}
|
|
|
|
// load assets
|
|
const stencilImage = new Image(600, 900)
|
|
stencilImage.src = prize.stencilUrl
|
|
|
|
const brandImage = new Image(...prize.itemResolution)
|
|
brandImage.crossOrigin = 'anonymous'
|
|
brandImage.src = prize.itemUrl
|
|
|
|
await Promise.all([stencilImage.decode(), brandImage.decode()])
|
|
console.log('prepareCardFace:load', stencilImage, brandImage)
|
|
|
|
// draw
|
|
const ctx = cardCanvas.getContext('2d')
|
|
ctx.fillStyle = '#2F318D'
|
|
ctx.fillRect(0, 0, 600, 900)
|
|
ctx.drawImage(brandImage, 80, 230, 440, 440)
|
|
ctx.drawImage(stencilImage, 0, 0)
|
|
|
|
cardImgUrl = cardCanvas.toDataURL('image/png')
|
|
console.log('prepareCardFace:save', cardImgUrl)
|
|
}
|
|
|
|
const prepareScene = (prize) => {
|
|
// lookup materials
|
|
var frontMat, backMat
|
|
scene.traverse((n) => {
|
|
if (n.name === prize.frontMaterialId) {
|
|
frontMat = n.material
|
|
}
|
|
if (n.name === prize.backMaterialId) {
|
|
backMat = n.material
|
|
}
|
|
})
|
|
|
|
// update materials
|
|
scene.traverse((n) => {
|
|
if (n.name === 'particle_video_screen') {
|
|
particles = document.getElementById(prize.particleId)
|
|
// NOTE: display last frame to hide first frame particles
|
|
if (isFinite(particles.duration))
|
|
particles.currentTime = particles.duration
|
|
|
|
const tex = new THREE.VideoTexture(particles)
|
|
tex.flipY = false
|
|
|
|
const mat = new LumaKeyMaterial(tex, 0.15)
|
|
console.log('render:prepareScene:particles', tex, mat, n.material)
|
|
|
|
n.material.dispose()
|
|
n.material = mat
|
|
}
|
|
if (n.name === 'card_front_side') {
|
|
const tex = new THREE.CanvasTexture(cardCanvas)
|
|
tex.colorSpace = THREE.SRGBColorSpace
|
|
tex.flipY = false
|
|
tex.generateMipmaps = true
|
|
tex.needsUpdate = true
|
|
|
|
console.log('render:prepareScene:frontSide', tex)
|
|
|
|
frontMat.map.dispose()
|
|
frontMat.map = tex
|
|
n.material = frontMat
|
|
}
|
|
if (n.name === 'card_back_side') {
|
|
n.material = backMat
|
|
}
|
|
})
|
|
}
|
|
|
|
const playAnimation = (prize, prizeCallback, isLast) => {
|
|
// reset mixer
|
|
const loader = document.getElementById('animation-component-loader')
|
|
if (loader) {
|
|
loader.remove()
|
|
}
|
|
mixer = new THREE.AnimationMixer(scene)
|
|
|
|
soundAnimation.currentTime = 0
|
|
soundAnimation.play()
|
|
|
|
animations.forEach((a) => {
|
|
console.log('render:playAnimation:animations:play', a)
|
|
|
|
mixer.clipAction(a).setLoop(THREE.LoopOnce).play()
|
|
})
|
|
|
|
setTimeout(() => {
|
|
if (particles) {
|
|
console.log('render:playAnimation:particles:play', particles)
|
|
|
|
particles.currentTime = 0
|
|
particles.play()
|
|
let lastSoundtrackTime = soundAnimation.currentTime
|
|
setTimeout(() => {
|
|
console.log('render:playAnimation:sound:resume', soundAnimation.paused)
|
|
|
|
soundAnimation.currentTime = lastSoundtrackTime + 4.4
|
|
soundAnimation.play()
|
|
|
|
// force reload on particles
|
|
particles.load()
|
|
}, 4_400)
|
|
}
|
|
}, 10_000)
|
|
|
|
// notify UI to display prize
|
|
setTimeout(() => {
|
|
prize.cardImgUrl = cardImgUrl
|
|
prizeCallback(prize, isLast)
|
|
}, 17_000)
|
|
}
|
|
|
|
export const init = (document, window, prizes, prizeCallback) => {
|
|
console.log('render:init', !!document, !!window)
|
|
if (!document) return
|
|
if (!window) return
|
|
|
|
soundAnimation = document.getElementById('animation')
|
|
|
|
allPrizes = prizes
|
|
|
|
/* scene
|
|
-------------------------------------------------------------*/
|
|
scene = new THREE.Scene()
|
|
//scene.fog = new THREE.Fog(0x000d20, 0, 1000 * 3);
|
|
|
|
/* camera
|
|
-------------------------------------------------------------*/
|
|
camera = new THREE.PerspectiveCamera(
|
|
45,
|
|
window.innerWidth / window.innerHeight,
|
|
0.1,
|
|
2000,
|
|
)
|
|
camera.position.set(0, -40, 170)
|
|
camera.lookAt(scene.position)
|
|
|
|
/* renderer
|
|
-------------------------------------------------------------*/
|
|
renderer = new THREE.WebGLRenderer({
|
|
antialias: true,
|
|
alpha: true,
|
|
})
|
|
renderer.setPixelRatio(window.devicePixelRatio)
|
|
renderer.setClearColor(new THREE.Color(0x000000), 0)
|
|
renderer.setSize(window.innerWidth, window.innerHeight)
|
|
renderer.shadowMap.enabled = true
|
|
renderer.setClearAlpha(0)
|
|
|
|
/* OrbitControls
|
|
-------------------------------------------------------------*/
|
|
orbitControls = new OrbitControls(camera, renderer.domElement)
|
|
orbitControls.autoRotate = false
|
|
orbitControls.enableDamping = true
|
|
orbitControls.dampingFactor = 0.2
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const rgbeLoader = new RGBELoader().load(
|
|
'environments/royal_esplanade_1k_var.hdr',
|
|
(texture) => {
|
|
console.log('render:init:load:environment', texture)
|
|
texture.mapping = THREE.EquirectangularReflectionMapping
|
|
// scene.background = texture;
|
|
scene.environment = texture
|
|
|
|
const gltfLoader = new GLTFLoader()
|
|
|
|
// // Optional: Provide a DRACOLoader instance to decode compressed mesh data
|
|
const dracoLoader = new DRACOLoader()
|
|
dracoLoader.setDecoderPath('/libs/draco/')
|
|
gltfLoader.setDRACOLoader(dracoLoader)
|
|
|
|
// Load a glTF resource
|
|
gltfLoader.load(
|
|
// resource URL
|
|
'models/chest_shading_prev.gltf',
|
|
// called when the resource is loaded
|
|
(gltf) => {
|
|
console.log('render:init:load:gltf', gltf)
|
|
gltf.scene.traverse((n) => {
|
|
if (n.isLight) {
|
|
console.log('render:init:load:light', n)
|
|
// n.intensity = n.intensity / 3;
|
|
// n.decay = 0;
|
|
}
|
|
if (n.isCamera) {
|
|
console.log('render:init:load:camera', n)
|
|
camera = n
|
|
onResize()
|
|
}
|
|
})
|
|
|
|
scene.add(gltf.scene)
|
|
|
|
gltf.animations // Array<THREE.AnimationClip>
|
|
gltf.scene // THREE.Group
|
|
gltf.scenes // Array<THREE.Group>
|
|
gltf.cameras // Array<THREE.Camera>
|
|
gltf.asset // Object
|
|
|
|
console.log('render:init:load:animations', gltf.animations)
|
|
|
|
mixer = new THREE.AnimationMixer(scene)
|
|
animations = gltf.animations
|
|
prizes.forEach((p, i) => {
|
|
setTimeout(() => {
|
|
prepareCardFace(document, window, p).then(() => prepareScene(p))
|
|
playAnimation(p, prizeCallback, i == prizes.length - 1)
|
|
}, i * 27_000)
|
|
})
|
|
},
|
|
// // called while loading is progressing
|
|
// function (xhr) {
|
|
// console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
|
|
// },
|
|
// // called when loading has errors
|
|
// function (error) {
|
|
// console.log("An error happened");
|
|
// }
|
|
)
|
|
},
|
|
)
|
|
|
|
/* resize
|
|
-------------------------------------------------------------*/
|
|
window.addEventListener('resize', onResize)
|
|
|
|
/* rendering start
|
|
-------------------------------------------------------------*/
|
|
document.getElementById('render-output').appendChild(renderer.domElement)
|
|
requestAnimationFrame(render)
|
|
}
|