stable intro
This commit is contained in:
+191
-178
@@ -3,211 +3,224 @@ import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js'
|
||||
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js'
|
||||
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
|
||||
|
||||
// === Scene & Camera ===
|
||||
const scene = new THREE.Scene()
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 100)
|
||||
camera.position.set(0.1, 0.02, 4)
|
||||
//camera.position.z = 4
|
||||
class Intro{
|
||||
|
||||
// === Renderer (transparent) ===
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
alpha: true,
|
||||
canvas: document.querySelector('.intro3d')
|
||||
})
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
renderer.setClearColor(0x000000, 0)
|
||||
renderer.outputColorSpace = THREE.SRGBColorSpace
|
||||
#lettersZ=-1
|
||||
#onResizeCallbacks = []
|
||||
|
||||
constructor(){
|
||||
this.canvas = document.querySelector('.intro3d')
|
||||
const loadHDR = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
new RGBELoader()
|
||||
.setPath('/app/assets/images/')
|
||||
.load('kloofendal_48d_partly_cloudy_puresky_2k.hdr', resolve, this.hdrProgress, reject)
|
||||
})
|
||||
|
||||
const loadTexture = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
new THREE.TextureLoader()
|
||||
.setPath('/app/assets/images/')
|
||||
.load('logop42-intro_three.png', resolve, undefined, reject)
|
||||
})
|
||||
|
||||
Promise.allSettled([loadHDR(), loadTexture()]).then(this.initScene.bind(this))
|
||||
this.Xcorrection = -0.02
|
||||
}
|
||||
|
||||
const rgbeLoader = new RGBELoader()
|
||||
rgbeLoader.load('/app/assets/images/satara_night_no_lamps_2k.hdr', hdrTexture => {
|
||||
hdrTexture.mapping = THREE.EquirectangularReflectionMapping
|
||||
scene.environment = hdrTexture
|
||||
scene.background = hdrTexture
|
||||
})
|
||||
hdrProgress(xhr){
|
||||
const pb = document.querySelector('div.progress')
|
||||
if (xhr.lengthComputable) {
|
||||
const percent = (xhr.loaded / xhr.total) * 100
|
||||
pb.style.width = percent.toFixed(0)
|
||||
if(percent.toFixed(0)>=99) pb.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
initScene(LoadersResults){
|
||||
let hdrTexture
|
||||
const [hdrResult, texResult] = LoadersResults
|
||||
if (hdrResult.status === 'fulfilled' && texResult.status === 'fulfilled') {
|
||||
hdrTexture = hdrResult.value
|
||||
this.backgroundTexture = texResult.value
|
||||
|
||||
// === Load background as a plane that fits the viewport ===
|
||||
const loader = new THREE.TextureLoader()
|
||||
let plane
|
||||
let planeDistance = 4.4
|
||||
hdrTexture.mapping = THREE.EquirectangularReflectionMapping
|
||||
this.backgroundTexture.colorSpace = THREE.SRGBColorSpace
|
||||
} else {
|
||||
console.error('Loading error:', hdrResult, texResult)
|
||||
return
|
||||
}
|
||||
|
||||
loader.load('/app/assets/images/logop42-intro_three.png', texture => {
|
||||
this.scene = new THREE.Scene()
|
||||
|
||||
texture.colorSpace = THREE.SRGBColorSpace
|
||||
texture.wrapS = THREE.ClampToEdgeWrapping
|
||||
texture.wrapT = THREE.ClampToEdgeWrapping
|
||||
texture.minFilter = THREE.LinearFilter
|
||||
texture.magFilter = THREE.LinearFilter
|
||||
const imageAspect = texture.image.width / texture.image.height
|
||||
|
||||
// Create plane sized to exactly fill the camera frustum at Z = -planeDistance
|
||||
function makeOrResizePlane() {
|
||||
const fovRad = THREE.MathUtils.degToRad(camera.fov)
|
||||
this.camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 100)
|
||||
this.camera.position.set(0.1, 0.02, 4)
|
||||
|
||||
this.renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
alpha: true,
|
||||
canvas: this.canvas
|
||||
})
|
||||
|
||||
hdrTexture.mapping = THREE.EquirectangularReflectionMapping
|
||||
this.scene.environment = hdrTexture
|
||||
|
||||
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
|
||||
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
this.renderer.setClearColor(0x000000, 0)
|
||||
this.renderer.outputColorSpace = THREE.SRGBColorSpace
|
||||
|
||||
this.backgroundTexture.colorSpace = THREE.SRGBColorSpace
|
||||
this.backgroundTexture.wrapS = THREE.ClampToEdgeWrapping
|
||||
this.backgroundTexture.wrapT = THREE.ClampToEdgeWrapping
|
||||
this.backgroundTexture.generateMipmaps = false
|
||||
this.backgroundTexture.minFilter = THREE.LinearFilter
|
||||
this.backgroundTexture.magFilter = THREE.LinearFilter
|
||||
this.imageAspect = this.backgroundTexture.image.width / this.backgroundTexture.image.height
|
||||
|
||||
this.makeOrResizePlane()
|
||||
this.#onResizeCallbacks.push(this.makeOrResizePlane.bind(this))
|
||||
|
||||
// === Lights ===
|
||||
const l1 = new THREE.DirectionalLight(0xffbb33, 1)
|
||||
l1.position.set(-1, 1.5, 6)
|
||||
this.scene.add(l1)
|
||||
|
||||
const l2 = new THREE.DirectionalLight(0xffbb33, 1)
|
||||
l2.position.set(2, -1.5, 6)
|
||||
this.scene.add(l2)
|
||||
|
||||
this.lettersPosScale =[
|
||||
{ position: { x: -0.98, y: 0.02, z: -1 }, scale: { x: 1, y: 1.12, z: 1 } },
|
||||
{ position: { x: -0.63, y: -0.04, z: -1.05 }, scale: { x: 1, y: 1.05, z: 1 } },
|
||||
{ position: { x: -0.28, y: -0.05, z: -1.1 }, scale: { x: 0.9, y: 1.15, z: 1, X: 0.8 } },
|
||||
{ position: { x: -0.1, y: -0.04, z: -1.15 }, scale: { x: 1, y: 1, z: 1 } },
|
||||
{ position: { x: 0.12, y: -0.06, z: -1.2 }, scale: { x: 0.65, y: 1, z: 1 } },
|
||||
{ position: { x: 0.355, y: -0.055, z: -1.25 }, scale: { x: 0.5, y: 0.9, z: 1, Y: 0.9 } },
|
||||
{ position: { x: 0.53, y: -0.01, z: -1.3 }, scale: { x: 1, y: 0.94, z: 1 } },
|
||||
{ position: { x: 0.84, y: -0.05, z: -1.35 }, scale: { x: 0.95, y: 1.04, z: 1 } },
|
||||
{ position: { x: 1.17, y: -0.05, z: -1.4 }, scale: { x: 0.7, y: 1.02, z: 1 } },
|
||||
]
|
||||
|
||||
this.glassLetters = []
|
||||
const text = 'Project42'
|
||||
|
||||
const fontLoader = new FontLoader()
|
||||
fontLoader.load('/app/thirdparty/Three/fonts/helvetiker_regular.typeface.json', font => {
|
||||
const mat = new THREE.MeshPhysicalMaterial({
|
||||
metalness: 0.05,
|
||||
roughness: 0.02,
|
||||
transmission: 1,
|
||||
thickness: 0.8,
|
||||
ior: 1.45,
|
||||
envMapIntensity: 1.5,
|
||||
attenuationColor: new THREE.Color(0x88ffcc),
|
||||
attenuationDistance: 2,
|
||||
color: 0x88ffcc,
|
||||
})
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text[i]
|
||||
const geo = new TextGeometry(char, {
|
||||
font,
|
||||
size: .5,
|
||||
height: 0.15,
|
||||
curveSegments: 32,
|
||||
bevelEnabled: true,
|
||||
bevelThickness: 0.03,
|
||||
bevelSize: 0.03,
|
||||
bevelSegments: 32
|
||||
})
|
||||
|
||||
geo.center()
|
||||
const mesh = new THREE.Mesh(geo, mat)
|
||||
mesh.position.set(this.lettersPosScale[i].position.x + this.Xcorrection, this.lettersPosScale[i].position.y, this.lettersPosScale[i].position.z)
|
||||
mesh.scale.set(this.lettersPosScale[i].scale.x, this.lettersPosScale[i].scale.y, this.lettersPosScale[i].scale.z)
|
||||
this.scene.add(mesh)
|
||||
this.glassLetters.push(mesh)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// === Resize handling ===
|
||||
window.addEventListener('resize', () => {
|
||||
this.camera.aspect = window.innerWidth / window.innerHeight
|
||||
this.camera.updateProjectionMatrix()
|
||||
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
this.#onResizeCallbacks.forEach(fn => fn())
|
||||
})
|
||||
|
||||
this.canvas.classList.add('ready')
|
||||
this.nextAnim = Date.now()+2000
|
||||
this.animate()
|
||||
}
|
||||
|
||||
makeOrResizePlane() {
|
||||
const planeDistance = 4.4
|
||||
const fovRad = THREE.MathUtils.degToRad(this.camera.fov)
|
||||
const viewHeight = 2 * Math.tan(fovRad / 2) * planeDistance
|
||||
const viewWidth = viewHeight * camera.aspect
|
||||
const viewWidth = viewHeight * this.camera.aspect
|
||||
|
||||
let planeWidth, planeHeight
|
||||
if (viewWidth / viewHeight > imageAspect) {
|
||||
if (viewWidth / viewHeight > this.imageAspect) {
|
||||
planeHeight = viewHeight
|
||||
planeWidth = planeHeight * imageAspect
|
||||
planeWidth = planeHeight * this.imageAspect
|
||||
} else {
|
||||
planeWidth = viewWidth
|
||||
planeHeight = planeWidth / imageAspect
|
||||
planeHeight = planeWidth / this.imageAspect
|
||||
}
|
||||
|
||||
if (!plane) {
|
||||
if (!this.plane) {
|
||||
const geo = new THREE.PlaneGeometry(planeWidth, planeHeight)
|
||||
const mat = new THREE.MeshBasicMaterial({ map: texture })
|
||||
|
||||
plane = new THREE.Mesh(geo, mat)
|
||||
plane.scale.set(1.44, 1.5, 1)
|
||||
plane.position.z = 0
|
||||
plane.renderOrder = 0
|
||||
scene.add(plane)
|
||||
const mat = new THREE.MeshBasicMaterial({ map: this.backgroundTexture })
|
||||
this.plane = new THREE.Mesh(geo, mat)
|
||||
this.plane.scale.set(1.44, 1.5, 1)
|
||||
this.plane.position.x = this.Xcorrection
|
||||
this.plane.position.z = 0
|
||||
this.plane.renderOrder = 0
|
||||
this.scene.add(this.plane)
|
||||
} else {
|
||||
plane.geometry.dispose()
|
||||
plane.geometry = new THREE.PlaneGeometry(planeWidth, planeHeight)
|
||||
this.plane.geometry.dispose()
|
||||
this.plane.geometry = new THREE.PlaneGeometry(planeWidth, planeHeight)
|
||||
}
|
||||
}
|
||||
makeOrResizePlane()
|
||||
onResizeCallbacks.push(makeOrResizePlane)
|
||||
})
|
||||
|
||||
const glassMat = new THREE.MeshPhysicalMaterial({
|
||||
color: 0xffffff,
|
||||
metalness: .05,
|
||||
roughness: 0.05, //0.02,
|
||||
transmission: 1, //1, // true glass
|
||||
thickness: 1,//0.6,
|
||||
ior: 1.45, //1.5,
|
||||
envMapIntensity: 1.2,
|
||||
attenuationColor: new THREE.Color(0x88ffcc),
|
||||
attenuationDistance: 2,
|
||||
envMapIntensity: 2.5
|
||||
})
|
||||
animate() {
|
||||
requestAnimationFrame(this.animate.bind(this))
|
||||
|
||||
if(Date.now()>this.nextAnim){
|
||||
this.#lettersZ += .005
|
||||
for(const letter of this.glassLetters){
|
||||
letter.position.z += 0.005
|
||||
}
|
||||
|
||||
// === Lights ===
|
||||
const l1 = new THREE.DirectionalLight(0xffbb33, 1)
|
||||
l1.position.set(-1, 1.5, 6)
|
||||
scene.add(l1)
|
||||
|
||||
const l2 = new THREE.DirectionalLight(0xffbb33, 1)
|
||||
l2.position.set(2, -1.5, 6)
|
||||
scene.add(l2)
|
||||
|
||||
if(this.#lettersZ>1.5) {
|
||||
for(const [i, letter] of this.glassLetters.entries()){
|
||||
letter.rotation.x += Math.abs(i-4)/-1000
|
||||
letter.rotation.y += (i-4)/2000
|
||||
letter.position.x += (i-4)/4000
|
||||
}
|
||||
}
|
||||
|
||||
const lettersPosScale =[
|
||||
{ position: { x: -0.98, y: 0.02, z: -1 }, scale: { x: 1, y: 1.12, z: 1 } },
|
||||
{ position: { x: -0.63, y: -0.04, z: -1.05 }, scale: { x: 1, y: 1.05, z: 1 } },
|
||||
{ position: { x: -0.28, y: -0.05, z: -1.1 }, scale: { x: 0.9, y: 1.15, z: 1, X: 0.8 } },
|
||||
{ position: { x: -0.1, y: -0.04, z: -1.15 }, scale: { x: 1, y: 1, z: 1 } },
|
||||
{ position: { x: 0.12, y: -0.06, z: -1.2 }, scale: { x: 0.65, y: 1, z: 1 } },
|
||||
{ position: { x: 0.355, y: -0.055, z: -1.25 }, scale: { x: 0.5, y: 0.9, z: 1, Y: 0.9 } },
|
||||
{ position: { x: 0.53, y: -0.01, z: -1.3 }, scale: { x: 1, y: 0.94, z: 1 } },
|
||||
{ position: { x: 0.84, y: -0.05, z: -1.35 }, scale: { x: 0.95, y: 1.04, z: 1 } },
|
||||
{ position: { x: 1.17, y: -0.05, z: -1.4 }, scale: { x: 0.7, y: 1.02, z: 1 } },
|
||||
]
|
||||
|
||||
|
||||
const glassLetters = []
|
||||
const text = 'Project42'
|
||||
|
||||
const fontLoader = new FontLoader()
|
||||
fontLoader.load('/app/thirdparty/Three/fonts/helvetiker_regular.typeface.json', font => {
|
||||
const mat = new THREE.MeshPhysicalMaterial({
|
||||
metalness: 0.2,
|
||||
roughness: 0.04,
|
||||
transmission: 1,
|
||||
thickness: 0.8,
|
||||
ior: 1.45,
|
||||
envMapIntensity: 1.2,
|
||||
attenuationColor: new THREE.Color(0x88ffcc),
|
||||
attenuationDistance: 2,
|
||||
color: 0x88ffcc,
|
||||
emissive: 0x00CCFF,
|
||||
emissiveIntensity: 0.05,
|
||||
})
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text[i]
|
||||
const geo = new TextGeometry(char, {
|
||||
font,
|
||||
size: .5,
|
||||
height: 0.15,
|
||||
curveSegments: 32,
|
||||
bevelEnabled: true,
|
||||
bevelThickness: 0.03,
|
||||
bevelSize: 0.03,
|
||||
bevelSegments: 32
|
||||
})
|
||||
|
||||
geo.center()
|
||||
const mesh = new THREE.Mesh(geo, mat)
|
||||
mesh.position.set(lettersPosScale[i].position.x, lettersPosScale[i].position.y, lettersPosScale[i].position.z)
|
||||
mesh.scale.set(lettersPosScale[i].scale.x, lettersPosScale[i].scale.y, lettersPosScale[i].scale.z)
|
||||
scene.add(mesh)
|
||||
glassLetters.push(mesh)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
// Helper: update envMap (hide cube → capture → show cube)
|
||||
function updateEnvMap() {
|
||||
const wasVisible = cube.visible
|
||||
cube.visible = false
|
||||
cubeCam.update(renderer, scene)
|
||||
cube.visible = wasVisible
|
||||
glassMat.envMap = cubeRT.texture
|
||||
glassMat.needsUpdate = true
|
||||
}
|
||||
|
||||
|
||||
|
||||
let t = 0
|
||||
let lettersZ=-1
|
||||
function animate() {
|
||||
requestAnimationFrame(animate)
|
||||
lettersZ += .005
|
||||
for(const letter of glassLetters){
|
||||
letter.position.z += 0.005 //= lettersZ
|
||||
}
|
||||
if(lettersZ>1.5) {
|
||||
for(const [i, letter] of glassLetters.entries()){
|
||||
letter.rotation.x += Math.abs(i-4)/-1000
|
||||
letter.rotation.y += (i-4)/2000
|
||||
letter.position.x += (i-4)/4000
|
||||
if(this.#lettersZ>4.5) {
|
||||
this.#lettersZ = -1
|
||||
for(const [i, letter] of this.glassLetters.entries()){
|
||||
letter.rotation.y = 0
|
||||
letter.rotation.x = 0
|
||||
letter.position.x = this.lettersPosScale[i].position.x + this.Xcorrection
|
||||
letter.position.z = this.lettersPosScale[i].position.z
|
||||
}
|
||||
this.nextAnim = Date.now()+10000
|
||||
}
|
||||
}
|
||||
}
|
||||
if(lettersZ>4.5) {
|
||||
lettersZ = -1
|
||||
for(const [i, letter] of glassLetters.entries()){
|
||||
letter.rotation.y = 0
|
||||
letter.rotation.x = 0
|
||||
letter.position.x = lettersPosScale[i].position.x
|
||||
letter.position.z = lettersPosScale[i].position.z
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// refresh envmap if anything moves around glass to update reflections
|
||||
//if ((t++ % 2) === 0) updateEnvMap()
|
||||
|
||||
renderer.render(scene, camera)
|
||||
this.renderer.render(this.scene, this.camera)
|
||||
}
|
||||
|
||||
}
|
||||
animate()
|
||||
|
||||
// === Resize handling ===
|
||||
const onResizeCallbacks = []
|
||||
window.addEventListener('resize', () => {
|
||||
camera.aspect = window.innerWidth / window.innerHeight
|
||||
camera.updateProjectionMatrix()
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
onResizeCallbacks.forEach(fn => fn())
|
||||
})
|
||||
new Intro()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user