301 lines
11 KiB
TypeScript
301 lines
11 KiB
TypeScript
import { join } from 'path'
|
|
import * as THREE from 'three'
|
|
import { getSyncWorld } from 'renderer/playground/shared'
|
|
import { Vec3 } from 'vec3'
|
|
import * as tweenJs from '@tweenjs/tween.js'
|
|
import type { GraphicsInitOptions } from '../../../src/appViewer'
|
|
import { WorldDataEmitter } from '../lib/worldDataEmitter'
|
|
import { defaultWorldRendererConfig, WorldRendererCommon } from '../lib/worldrendererCommon'
|
|
import { getDefaultRendererState } from '../baseGraphicsBackend'
|
|
import { loadThreeJsTextureFromUrl, loadThreeJsTextureFromUrlSync } from '../lib/utils/skins'
|
|
import { ResourcesManager } from '../../../src/resourcesManager'
|
|
import { getInitialPlayerStateRenderer } from '../lib/basePlayerState'
|
|
import { WorldRendererThree } from './worldrendererThree'
|
|
import { EntityMesh } from './entity/EntityMesh'
|
|
import { DocumentRenderer } from './documentRenderer'
|
|
|
|
const panoramaFiles = [
|
|
'panorama_3.png', // right (+x)
|
|
'panorama_1.png', // left (-x)
|
|
'panorama_4.png', // top (+y)
|
|
'panorama_5.png', // bottom (-y)
|
|
'panorama_0.png', // front (+z)
|
|
'panorama_2.png', // back (-z)
|
|
]
|
|
|
|
export class PanoramaRenderer {
|
|
private readonly camera: THREE.PerspectiveCamera
|
|
private scene: THREE.Scene
|
|
private readonly ambientLight: THREE.AmbientLight
|
|
private readonly directionalLight: THREE.DirectionalLight
|
|
private panoramaGroup: THREE.Object3D | null = null
|
|
private time = 0
|
|
private readonly abortController = new AbortController()
|
|
private worldRenderer: WorldRendererCommon | WorldRendererThree | undefined
|
|
public WorldRendererClass = WorldRendererThree
|
|
public startTimes = new Map<THREE.MeshBasicMaterial, number>()
|
|
|
|
constructor (private readonly documentRenderer: DocumentRenderer, private readonly options: GraphicsInitOptions, private readonly doWorldBlocksPanorama = false) {
|
|
this.scene = new THREE.Scene()
|
|
// #324568
|
|
this.scene.background = new THREE.Color(0x32_45_68)
|
|
|
|
// Add ambient light
|
|
this.ambientLight = new THREE.AmbientLight(0xcc_cc_cc)
|
|
this.scene.add(this.ambientLight)
|
|
|
|
// Add directional light
|
|
this.directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5)
|
|
this.directionalLight.position.set(1, 1, 0.5).normalize()
|
|
this.directionalLight.castShadow = true
|
|
this.scene.add(this.directionalLight)
|
|
|
|
this.camera = new THREE.PerspectiveCamera(85, this.documentRenderer.canvas.width / this.documentRenderer.canvas.height, 0.05, 1000)
|
|
this.camera.position.set(0, 0, 0)
|
|
this.camera.rotation.set(0, 0, 0)
|
|
}
|
|
|
|
async start () {
|
|
if (this.doWorldBlocksPanorama) {
|
|
await this.worldBlocksPanorama()
|
|
} else {
|
|
this.addClassicPanorama()
|
|
}
|
|
|
|
|
|
this.documentRenderer.render = (sizeChanged = false) => {
|
|
if (sizeChanged) {
|
|
this.camera.aspect = this.documentRenderer.canvas.width / this.documentRenderer.canvas.height
|
|
this.camera.updateProjectionMatrix()
|
|
}
|
|
this.documentRenderer.renderer.render(this.scene, this.camera)
|
|
}
|
|
}
|
|
|
|
async debugImageInFrontOfCamera () {
|
|
const image = await loadThreeJsTextureFromUrl(join('background', 'panorama_0.png'))
|
|
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1000, 1000), new THREE.MeshBasicMaterial({ map: image }))
|
|
mesh.position.set(0, 0, -500)
|
|
mesh.rotation.set(0, 0, 0)
|
|
this.scene.add(mesh)
|
|
}
|
|
|
|
addClassicPanorama () {
|
|
const panorGeo = new THREE.BoxGeometry(1000, 1000, 1000)
|
|
const panorMaterials = [] as THREE.MeshBasicMaterial[]
|
|
const fadeInDuration = 200
|
|
|
|
// void this.debugImageInFrontOfCamera()
|
|
|
|
for (const file of panoramaFiles) {
|
|
const load = async () => {
|
|
const { texture } = loadThreeJsTextureFromUrlSync(join('background', file))
|
|
|
|
// Instead of using repeat/offset to flip, we'll use the texture matrix
|
|
texture.matrixAutoUpdate = false
|
|
texture.matrix.set(
|
|
-1, 0, 1, 0, 1, 0, 0, 0, 1
|
|
)
|
|
|
|
texture.wrapS = THREE.ClampToEdgeWrapping
|
|
texture.wrapT = THREE.ClampToEdgeWrapping
|
|
texture.minFilter = THREE.LinearFilter
|
|
texture.magFilter = THREE.LinearFilter
|
|
|
|
const material = new THREE.MeshBasicMaterial({
|
|
map: texture,
|
|
transparent: true,
|
|
side: THREE.DoubleSide,
|
|
depthWrite: false,
|
|
opacity: 0 // Start with 0 opacity
|
|
})
|
|
|
|
// Start fade-in when texture is loaded
|
|
this.startTimes.set(material, Date.now())
|
|
panorMaterials.push(material)
|
|
}
|
|
|
|
void load()
|
|
}
|
|
|
|
const panoramaBox = new THREE.Mesh(panorGeo, panorMaterials)
|
|
panoramaBox.onBeforeRender = () => {
|
|
this.time += 0.01
|
|
panoramaBox.rotation.y = Math.PI + this.time * 0.01
|
|
panoramaBox.rotation.z = Math.sin(-this.time * 0.001) * 0.001
|
|
|
|
// Time-based fade in animation for each material
|
|
for (const material of panorMaterials) {
|
|
const startTime = this.startTimes.get(material)
|
|
if (startTime) {
|
|
const elapsed = Date.now() - startTime
|
|
const progress = Math.min(1, elapsed / fadeInDuration)
|
|
material.opacity = progress
|
|
}
|
|
}
|
|
}
|
|
|
|
const group = new THREE.Object3D()
|
|
group.add(panoramaBox)
|
|
|
|
// Add squids
|
|
for (let i = 0; i < 20; i++) {
|
|
const m = new EntityMesh('1.16.4', 'squid').mesh
|
|
m.position.set(Math.random() * 30 - 15, Math.random() * 20 - 10, Math.random() * 10 - 17)
|
|
m.rotation.set(0, Math.PI + Math.random(), -Math.PI / 4, 'ZYX')
|
|
const v = Math.random() * 0.01
|
|
m.children[0].onBeforeRender = () => {
|
|
m.rotation.y += v
|
|
m.rotation.z = Math.cos(panoramaBox.rotation.y * 3) * Math.PI / 4 - Math.PI / 2
|
|
}
|
|
group.add(m)
|
|
}
|
|
|
|
this.scene.add(group)
|
|
this.panoramaGroup = group
|
|
}
|
|
|
|
async worldBlocksPanorama () {
|
|
const version = '1.21.4'
|
|
const fullResourceManager = this.options.resourcesManager as ResourcesManager
|
|
fullResourceManager.currentConfig = { version, noInventoryGui: true, }
|
|
await fullResourceManager.updateAssetsData({ })
|
|
if (this.abortController.signal.aborted) return
|
|
console.time('load panorama scene')
|
|
const world = getSyncWorld(version)
|
|
const PrismarineBlock = require('prismarine-block')
|
|
const Block = PrismarineBlock(version)
|
|
const fullBlocks = loadedData.blocksArray.filter(block => {
|
|
// if (block.name.includes('leaves')) return false
|
|
if (/* !block.name.includes('wool') && */!block.name.includes('stained_glass')/* && !block.name.includes('terracotta') */) return false
|
|
const b = Block.fromStateId(block.defaultState, 0)
|
|
if (b.shapes?.length !== 1) return false
|
|
const shape = b.shapes[0]
|
|
return shape[0] === 0 && shape[1] === 0 && shape[2] === 0 && shape[3] === 1 && shape[4] === 1 && shape[5] === 1
|
|
})
|
|
const Z = -15
|
|
const sizeX = 100
|
|
const sizeY = 100
|
|
for (let x = -sizeX; x < sizeX; x++) {
|
|
for (let y = -sizeY; y < sizeY; y++) {
|
|
const block = fullBlocks[Math.floor(Math.random() * fullBlocks.length)]
|
|
world.setBlockStateId(new Vec3(x, y, Z), block.defaultState)
|
|
}
|
|
}
|
|
this.camera.updateProjectionMatrix()
|
|
this.camera.position.set(0.5, sizeY / 2 + 0.5, 0.5)
|
|
this.camera.rotation.set(0, 0, 0)
|
|
const initPos = new Vec3(...this.camera.position.toArray())
|
|
const worldView = new WorldDataEmitter(world, 2, initPos)
|
|
// worldView.addWaitTime = 0
|
|
if (this.abortController.signal.aborted) return
|
|
|
|
this.worldRenderer = new this.WorldRendererClass(
|
|
this.documentRenderer.renderer,
|
|
this.options,
|
|
{
|
|
version,
|
|
worldView,
|
|
inWorldRenderingConfig: defaultWorldRendererConfig,
|
|
playerStateReactive: getInitialPlayerStateRenderer().reactive,
|
|
rendererState: getDefaultRendererState().reactive,
|
|
nonReactiveState: getDefaultRendererState().nonReactive
|
|
}
|
|
)
|
|
if (this.worldRenderer instanceof WorldRendererThree) {
|
|
this.scene = this.worldRenderer.scene
|
|
}
|
|
void worldView.init(initPos)
|
|
|
|
await this.worldRenderer.waitForChunksToRender()
|
|
if (this.abortController.signal.aborted) return
|
|
// add small camera rotation to side on mouse move depending on absolute position of the cursor
|
|
const { camera } = this
|
|
const initX = camera.position.x
|
|
const initY = camera.position.y
|
|
let prevTwin: tweenJs.Tween<THREE.Vector3> | undefined
|
|
document.body.addEventListener('pointermove', (e) => {
|
|
if (e.pointerType !== 'mouse') return
|
|
const pos = new THREE.Vector2(e.clientX, e.clientY)
|
|
const SCALE = 0.2
|
|
/* -0.5 - 0.5 */
|
|
const xRel = pos.x / window.innerWidth - 0.5
|
|
const yRel = -(pos.y / window.innerHeight - 0.5)
|
|
prevTwin?.stop()
|
|
const to = {
|
|
x: initX + (xRel * SCALE),
|
|
y: initY + (yRel * SCALE)
|
|
}
|
|
prevTwin = new tweenJs.Tween(camera.position).to(to, 0) // todo use the number depending on diff // todo use the number depending on diff
|
|
// prevTwin.easing(tweenJs.Easing.Exponential.InOut)
|
|
prevTwin.start()
|
|
camera.updateProjectionMatrix()
|
|
}, {
|
|
signal: this.abortController.signal
|
|
})
|
|
|
|
console.timeEnd('load panorama scene')
|
|
}
|
|
|
|
dispose () {
|
|
this.scene.clear()
|
|
this.worldRenderer?.destroy()
|
|
this.abortController.abort()
|
|
}
|
|
}
|
|
|
|
// export class ClassicPanoramaRenderer {
|
|
// panoramaGroup: THREE.Object3D
|
|
|
|
// constructor (private readonly backgroundFiles: string[], onRender: Array<(sizeChanged: boolean) => void>, addSquids = true) {
|
|
// const panorGeo = new THREE.BoxGeometry(1000, 1000, 1000)
|
|
// const loader = new THREE.TextureLoader()
|
|
// const panorMaterials = [] as THREE.MeshBasicMaterial[]
|
|
|
|
// for (const file of this.backgroundFiles) {
|
|
// const texture = loader.load(file)
|
|
|
|
// // Instead of using repeat/offset to flip, we'll use the texture matrix
|
|
// texture.matrixAutoUpdate = false
|
|
// texture.matrix.set(
|
|
// -1, 0, 1, 0, 1, 0, 0, 0, 1
|
|
// )
|
|
|
|
// texture.wrapS = THREE.ClampToEdgeWrapping // Changed from RepeatWrapping
|
|
// texture.wrapT = THREE.ClampToEdgeWrapping // Changed from RepeatWrapping
|
|
// texture.minFilter = THREE.LinearFilter
|
|
// texture.magFilter = THREE.LinearFilter
|
|
|
|
// panorMaterials.push(new THREE.MeshBasicMaterial({
|
|
// map: texture,
|
|
// transparent: true,
|
|
// side: THREE.DoubleSide,
|
|
// depthWrite: false,
|
|
// }))
|
|
// }
|
|
|
|
// const panoramaBox = new THREE.Mesh(panorGeo, panorMaterials)
|
|
// panoramaBox.onBeforeRender = () => {
|
|
// }
|
|
|
|
// const group = new THREE.Object3D()
|
|
// group.add(panoramaBox)
|
|
|
|
// if (addSquids) {
|
|
// // Add squids
|
|
// for (let i = 0; i < 20; i++) {
|
|
// const m = new EntityMesh('1.16.4', 'squid').mesh
|
|
// m.position.set(Math.random() * 30 - 15, Math.random() * 20 - 10, Math.random() * 10 - 17)
|
|
// m.rotation.set(0, Math.PI + Math.random(), -Math.PI / 4, 'ZYX')
|
|
// const v = Math.random() * 0.01
|
|
// onRender.push(() => {
|
|
// m.rotation.y += v
|
|
// m.rotation.z = Math.cos(panoramaBox.rotation.y * 3) * Math.PI / 4 - Math.PI / 2
|
|
// })
|
|
// group.add(m)
|
|
// }
|
|
// }
|
|
|
|
// this.panoramaGroup = group
|
|
// }
|
|
// }
|