Compare commits
4 commits
next
...
three-js-w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
336f8d5bc7 | ||
|
|
7bf904bb78 | ||
|
|
43ab10274a | ||
|
|
3b3efdcbfb |
7 changed files with 245 additions and 14 deletions
64
buildWorkers.mjs
Normal file
64
buildWorkers.mjs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// main worker file intended for computing world geometry is built using prismarine-viewer/buildWorker.mjs
|
||||
import { build, context } from 'esbuild'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const watch = process.argv.includes('-w')
|
||||
|
||||
const workers = ['./prismarine-viewer/viewer/lib/threeJsWorker.ts']
|
||||
|
||||
const result = await (watch ? context : build)({
|
||||
bundle: true,
|
||||
platform: 'browser',
|
||||
entryPoints: workers,
|
||||
outdir: 'prismarine-viewer/public/',
|
||||
write: false,
|
||||
sourcemap: watch ? 'inline' : 'external',
|
||||
minify: !watch,
|
||||
treeShaking: true,
|
||||
logLevel: 'info',
|
||||
alias: {
|
||||
'three': './node_modules/three/src/Three.js',
|
||||
events: 'events', // make explicit
|
||||
buffer: 'buffer',
|
||||
'fs': 'browserfs/dist/shims/fs.js',
|
||||
http: 'http-browserify',
|
||||
perf_hooks: './src/perf_hooks_replacement.js',
|
||||
crypto: './src/crypto.js',
|
||||
stream: 'stream-browserify',
|
||||
net: 'net-browserify',
|
||||
assert: 'assert',
|
||||
dns: './src/dns.js'
|
||||
},
|
||||
inject: [
|
||||
'./src/shims.js'
|
||||
],
|
||||
plugins: [
|
||||
{
|
||||
name: 'writeOutput',
|
||||
setup(build) {
|
||||
build.onEnd(({ outputFiles }) => {
|
||||
for (const file of outputFiles) {
|
||||
for (const dir of ['prismarine-viewer/public', 'dist']) {
|
||||
const baseName = path.basename(file.path)
|
||||
fs.writeFileSync(path.join(dir, baseName), file.contents)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
],
|
||||
loader: {
|
||||
'.vert': 'text',
|
||||
'.frag': 'text',
|
||||
'.wgsl': 'text',
|
||||
},
|
||||
mainFields: [
|
||||
'browser', 'module', 'main'
|
||||
],
|
||||
keepNames: true,
|
||||
})
|
||||
|
||||
if (watch) {
|
||||
await result.watch()
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
"scripts": {
|
||||
"start": "node scripts/build.js copyFilesDev && node scripts/prepareData.mjs && node esbuild.mjs --watch",
|
||||
"start-watch-script": "nodemon -w esbuild.mjs --watch",
|
||||
"build": "node scripts/build.js copyFiles && node scripts/prepareData.mjs -f && node esbuild.mjs --minify --prod",
|
||||
"build": "node scripts/build.js copyFiles && node buildWorkers.mjs && node scripts/prepareData.mjs -f && node esbuild.mjs --minify --prod",
|
||||
"check-build": "tsc && pnpm build",
|
||||
"test:cypress": "cypress run",
|
||||
"test-unit": "vitest",
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build && node scripts/build.js moveStorybookFiles",
|
||||
"start-experiments": "vite --config experiments/vite.config.ts --host",
|
||||
"watch-other-workers": "echo NOT IMPLEMENTED",
|
||||
"watch-other-workers": "node buildWorkers.mjs -w",
|
||||
"watch-mesher": "node prismarine-viewer/buildMesherWorker.mjs -w",
|
||||
"run-playground": "run-p watch-mesher watch-other-workers playground-server watch-playground",
|
||||
"run-all": "run-p start run-playground",
|
||||
|
|
@ -117,6 +117,7 @@
|
|||
"@types/wait-on": "^5.3.4",
|
||||
"@xmcl/installer": "^5.1.0",
|
||||
"assert": "^2.0.0",
|
||||
"three-latest": "npm:three@latest",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
"buffer": "^6.0.3",
|
||||
"constants-browserify": "^1.0.0",
|
||||
|
|
|
|||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
|
|
@ -345,6 +345,9 @@ importers:
|
|||
three:
|
||||
specifier: 0.154.0
|
||||
version: 0.154.0(patch_hash=sj7ocb4p23jym6bkfgueanti2e)
|
||||
three-latest:
|
||||
specifier: npm:three@latest
|
||||
version: three@0.165.0
|
||||
timers-browserify:
|
||||
specifier: ^2.0.12
|
||||
version: 2.0.12
|
||||
|
|
@ -413,7 +416,7 @@ importers:
|
|||
version: 4.7.2
|
||||
three-stdlib:
|
||||
specifier: ^2.26.11
|
||||
version: 2.28.5(three@0.154.0(patch_hash=sj7ocb4p23jym6bkfgueanti2e))
|
||||
version: 2.28.5(three@0.165.0)
|
||||
three.meshline:
|
||||
specifier: ^1.3.0
|
||||
version: 1.4.0
|
||||
|
|
@ -7864,6 +7867,9 @@ packages:
|
|||
three@0.154.0:
|
||||
resolution: {integrity: sha512-Uzz8C/5GesJzv8i+Y2prEMYUwodwZySPcNhuJUdsVMH2Yn4Nm8qlbQe6qRN5fOhg55XB0WiLfTPBxVHxpE60ug==}
|
||||
|
||||
three@0.165.0:
|
||||
resolution: {integrity: sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==}
|
||||
|
||||
throttle-debounce@3.0.1:
|
||||
resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -18021,7 +18027,7 @@ snapshots:
|
|||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
||||
three-stdlib@2.28.5(three@0.154.0(patch_hash=sj7ocb4p23jym6bkfgueanti2e)):
|
||||
three-stdlib@2.28.5(three@0.165.0):
|
||||
dependencies:
|
||||
'@types/draco3d': 1.4.7
|
||||
'@types/offscreencanvas': 2019.7.2
|
||||
|
|
@ -18029,12 +18035,14 @@ snapshots:
|
|||
draco3d: 1.5.6
|
||||
fflate: 0.6.10
|
||||
potpack: 1.0.2
|
||||
three: 0.154.0(patch_hash=sj7ocb4p23jym6bkfgueanti2e)
|
||||
three: 0.165.0
|
||||
|
||||
three.meshline@1.4.0: {}
|
||||
|
||||
three@0.154.0(patch_hash=sj7ocb4p23jym6bkfgueanti2e): {}
|
||||
|
||||
three@0.165.0: {}
|
||||
|
||||
throttle-debounce@3.0.1: {}
|
||||
|
||||
throttleit@1.0.0: {}
|
||||
|
|
|
|||
|
|
@ -111,11 +111,21 @@ async function main () {
|
|||
const chunk1 = new Chunk()
|
||||
//@ts-ignore
|
||||
const chunk2 = new Chunk()
|
||||
chunk1.setBlockStateId(targetPos, 34)
|
||||
chunk2.setBlockStateId(targetPos.offset(1, 0, 0), 34)
|
||||
const addNeighbor = (x, z, light = 15) => {
|
||||
x += 2
|
||||
chunk1.setBlockStateId(targetPos.offset(x, 0, z), 1)
|
||||
chunk1.setBlockLight(targetPos.offset(x, 1, z), light)
|
||||
chunk1.setSkyLight(targetPos.offset(x, 1, z), light)
|
||||
}
|
||||
addNeighbor(0, 1)
|
||||
addNeighbor(0, -1)
|
||||
addNeighbor(1, 0)
|
||||
addNeighbor(-1, 0)
|
||||
chunk1.setBlockStateId(targetPos.offset(1, 1, 0), mcData.blocksByName['grass'].minStateId)
|
||||
addNeighbor(0, 0, 0)
|
||||
const world = new World((chunkX, chunkZ) => {
|
||||
// if (chunkX === 0 && chunkZ === 0) return chunk1
|
||||
// if (chunkX === 1 && chunkZ === 0) return chunk2
|
||||
if (chunkX === 0 && chunkZ === 0) return chunk1
|
||||
if (chunkX === 1 && chunkZ === 0) return chunk2
|
||||
//@ts-ignore
|
||||
const chunk = new Chunk()
|
||||
return chunk
|
||||
|
|
@ -138,7 +148,7 @@ async function main () {
|
|||
viewer.entities.onSkinUpdate = () => {
|
||||
viewer.render()
|
||||
}
|
||||
viewer.world.mesherConfig.enableLighting = false
|
||||
// viewer.world.mesherConfig.enableLighting = false
|
||||
|
||||
viewer.listen(worldView)
|
||||
// Load chunks
|
||||
|
|
@ -260,7 +270,7 @@ async function main () {
|
|||
const controls = new OrbitControls(viewer.camera, renderer.domElement)
|
||||
controls.target.set(targetPos.x + 0.5, targetPos.y + 0.5, targetPos.z + 0.5)
|
||||
|
||||
const cameraPos = targetPos.offset(2, 2, 2)
|
||||
const cameraPos = targetPos.offset(3, 3, 3)
|
||||
const pitch = THREE.MathUtils.degToRad(-45)
|
||||
const yaw = THREE.MathUtils.degToRad(45)
|
||||
viewer.camera.rotation.set(pitch, yaw, 0, 'ZYX')
|
||||
|
|
@ -409,7 +419,7 @@ async function main () {
|
|||
for (const update of Object.values(onUpdate)) {
|
||||
update()
|
||||
}
|
||||
applyChanges(true)
|
||||
// applyChanges(true)
|
||||
gui.openAnimated()
|
||||
})
|
||||
|
||||
|
|
|
|||
104
prismarine-viewer/viewer/lib/threeJsWorker.ts
Normal file
104
prismarine-viewer/viewer/lib/threeJsWorker.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import * as THREE from 'three'
|
||||
import { createWorkerProxy } from './workerProxy'
|
||||
import * as tweenJs from '@tweenjs/tween.js'
|
||||
import testGeometryJson from '../../examples/test-geometry.json'
|
||||
|
||||
let material: THREE.Material
|
||||
let scene = new THREE.Scene()
|
||||
let camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000)
|
||||
let renderer: THREE.WebGLRenderer
|
||||
// scene.add(new THREE.AmbientLight(0xcc_cc_cc))
|
||||
// scene.add(new THREE.DirectionalLight(0xff_ff_ff, 0.5))
|
||||
scene.add(camera)
|
||||
scene.background = new THREE.Color('lightblue')
|
||||
scene.matrixAutoUpdate = false
|
||||
scene.add(new THREE.AmbientLight(0xcc_cc_cc))
|
||||
scene.add(new THREE.DirectionalLight(0xff_ff_ff, 0.5))
|
||||
|
||||
THREE.ColorManagement.enabled = false
|
||||
|
||||
let sections = new Map<string, THREE.Mesh>()
|
||||
globalThis.sections = sections
|
||||
globalThis.camera = camera
|
||||
globalThis.scene = scene
|
||||
globalThis.marks = {}
|
||||
|
||||
let fps = 0
|
||||
let processedSinceLastRender = 0
|
||||
setInterval(() => {
|
||||
// console.log('FPS', fps)
|
||||
globalThis.fps = fps
|
||||
globalThis.worstFps = Math.min(globalThis.worstFps ?? Infinity, fps)
|
||||
fps = 0
|
||||
}, 1000)
|
||||
setInterval(() => {
|
||||
globalThis.worstFps = Infinity
|
||||
}, 10000)
|
||||
const meshesQueue = [] as any[]
|
||||
const render = () => {
|
||||
tweenJs.update()
|
||||
const max = 5
|
||||
for (let i = 0; i < max; i++) {
|
||||
const add = meshesQueue.pop()
|
||||
if (add) {
|
||||
scene.add(add)
|
||||
}
|
||||
}
|
||||
renderer.render(scene, camera)
|
||||
globalThis.maxProcessed = Math.max(globalThis.maxProcessed ?? 0, processedSinceLastRender)
|
||||
processedSinceLastRender = 0
|
||||
fps++
|
||||
}
|
||||
|
||||
export const threeJsWorkerProxyType = createWorkerProxy({
|
||||
async canvas (canvas: OffscreenCanvas, textureBlob: Blob) {
|
||||
const textureBitmap = await createImageBitmap(textureBlob)
|
||||
const texture = new THREE.CanvasTexture(textureBitmap)
|
||||
texture.magFilter = THREE.NearestFilter
|
||||
texture.minFilter = THREE.NearestFilter
|
||||
texture.flipY = false
|
||||
texture.needsUpdate = true
|
||||
material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1, map: texture })
|
||||
|
||||
renderer = new THREE.WebGLRenderer({ canvas })
|
||||
renderer.outputColorSpace = THREE.LinearSRGBColorSpace
|
||||
camera.aspect = canvas.width / canvas.height
|
||||
camera.updateProjectionMatrix()
|
||||
|
||||
renderer.setAnimationLoop(render)
|
||||
},
|
||||
addGeometry (position: { x, y, z }, geometry?: { positions, normals, uvs, colors, indices }) {
|
||||
const key = `${position.x},${position.y},${position.z}`
|
||||
if (sections.has(key)) {
|
||||
const section = sections.get(key)!
|
||||
section.geometry.dispose()
|
||||
scene.remove(section)
|
||||
sections.delete(key)
|
||||
}
|
||||
if (!geometry) return
|
||||
const bufferGeometry = new THREE.BufferGeometry()
|
||||
bufferGeometry.setAttribute('position', new THREE.BufferAttribute(geometry.positions, 3))
|
||||
bufferGeometry.setAttribute('normal', new THREE.BufferAttribute(geometry.normals, 3))
|
||||
bufferGeometry.setAttribute('uv', new THREE.BufferAttribute(geometry.uvs, 2))
|
||||
bufferGeometry.setAttribute('color', new THREE.BufferAttribute(geometry.colors, 3))
|
||||
bufferGeometry.setIndex(geometry.indices)
|
||||
const mesh = new THREE.Mesh(bufferGeometry, material)
|
||||
// mesh.frustumCulled = false
|
||||
const old = mesh.geometry.computeBoundingSphere
|
||||
mesh.geometry.computeBoundingSphere = function () {
|
||||
let start = performance.now()
|
||||
// old.call(mesh.geometry)
|
||||
globalThis.marks.computeBoundingSphere ??= 0
|
||||
globalThis.marks.computeBoundingSphere += performance.now() - start
|
||||
mesh.geometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 16)
|
||||
}
|
||||
mesh.position.set(position.x, position.y, position.z)
|
||||
meshesQueue.push(mesh)
|
||||
processedSinceLastRender++
|
||||
},
|
||||
updateCamera (position: { x, y, z }, rotation: { x, y, z }) {
|
||||
// camera.position.set(position.x, position.y, position.z)
|
||||
new tweenJs.Tween(camera.position).to({ x: position.x, y: position.y, z: position.z }, 50).start()
|
||||
camera.rotation.set(rotation.x, rotation.y, rotation.z, 'ZYX')
|
||||
}
|
||||
})
|
||||
|
|
@ -8,6 +8,14 @@ import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon'
|
|||
import * as tweenJs from '@tweenjs/tween.js'
|
||||
import { BloomPass, RenderPass, UnrealBloomPass, EffectComposer, WaterPass, GlitchPass } from 'three-stdlib'
|
||||
import { disposeObject } from './threeJsUtils'
|
||||
import { type threeJsWorkerProxyType } from 'prismarine-viewer/viewer/lib/threeJsWorker'
|
||||
import { useWorkerProxy } from 'prismarine-viewer/viewer/lib/workerProxy'
|
||||
|
||||
async function imageToBlob (url: string): Promise<Blob> {
|
||||
const response = await fetch(url)
|
||||
const blob = await response.blob()
|
||||
return blob
|
||||
}
|
||||
|
||||
export class WorldRendererThree extends WorldRendererCommon {
|
||||
outputFormat = 'threeJs' as const
|
||||
|
|
@ -17,6 +25,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
signsCache = new Map<string, any>()
|
||||
starField: StarField
|
||||
cameraSectionPos: Vec3 = new Vec3(0, 0, 0)
|
||||
workerProxy?: ReturnType<typeof useWorkerProxy<typeof threeJsWorkerProxyType>>
|
||||
|
||||
get tilesRendered () {
|
||||
return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0)
|
||||
|
|
@ -25,6 +34,19 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
constructor (public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public config: WorldRendererConfig) {
|
||||
super(config)
|
||||
this.starField = new StarField(scene)
|
||||
|
||||
this.renderUpdateEmitter.addListener('textureDownloaded', async () => {
|
||||
const rendererWorker = new Worker('./threeJsWorker.js')
|
||||
this.workerProxy = useWorkerProxy<typeof threeJsWorkerProxyType>(rendererWorker)
|
||||
const img: HTMLImageElement = this.downloadedTextureImage
|
||||
const blob = await imageToBlob(img.src)
|
||||
const newCanvas = document.createElement('canvas')
|
||||
newCanvas.width = outerWidth * window.devicePixelRatio
|
||||
newCanvas.height = outerHeight * window.devicePixelRatio
|
||||
newCanvas.id = 'viewer-canvas'
|
||||
document.body.appendChild(newCanvas)
|
||||
this.workerProxy.canvas(newCanvas.transferControlToOffscreen(), blob)
|
||||
})
|
||||
}
|
||||
|
||||
timeUpdated (newTime: number): void {
|
||||
|
|
@ -60,6 +82,9 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
}
|
||||
}
|
||||
|
||||
lastUpdate = 0
|
||||
updates = [] as number[]
|
||||
|
||||
handleWorkerMessage (data: any): void {
|
||||
if (data.type !== 'geometry') return
|
||||
let object: THREE.Object3D = this.sectionObjects[data.key]
|
||||
|
|
@ -72,6 +97,9 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
const chunkCoords = data.key.split(',')
|
||||
if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || !data.geometry.positions.length || !this.active) return
|
||||
|
||||
this.updates.push(Math.floor(performance.now() - this.lastUpdate))
|
||||
this.lastUpdate = performance.now()
|
||||
|
||||
// if (!this.initialChunksLoad && this.enableChunksLoadDelay) {
|
||||
// const newPromise = new Promise(resolve => {
|
||||
// if (this.droppedFpsPercentage > 0.5) {
|
||||
|
|
@ -86,6 +114,19 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
// }
|
||||
// }
|
||||
|
||||
this.workerProxy?.transfer(data.geometry.positions.buffer, data.geometry.normals.buffer, data.geometry.uvs.buffer, data.geometry.colors.buffer).addGeometry({
|
||||
x: data.geometry.sx,
|
||||
y: data.geometry.sy,
|
||||
z: data.geometry.sz
|
||||
}, {
|
||||
positions: data.geometry.positions,
|
||||
normals: data.geometry.normals,
|
||||
uvs: data.geometry.uvs,
|
||||
colors: data.geometry.colors,
|
||||
indices: data.geometry.indices,
|
||||
})
|
||||
if (!false) return
|
||||
|
||||
const geometry = new THREE.BufferGeometry()
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(data.geometry.positions, 3))
|
||||
geometry.setAttribute('normal', new THREE.BufferAttribute(data.geometry.normals, 3))
|
||||
|
|
@ -158,12 +199,14 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
new tweenJs.Tween(this.camera.position).to({ x: pos.x, y: pos.y, z: pos.z }, 50).start()
|
||||
}
|
||||
this.camera.rotation.set(pitch, yaw, 0, 'ZYX')
|
||||
const posToObj = p => ({ x: p.x, y: p.y, z: p.z })
|
||||
this.workerProxy?.updateCamera(pos ?? posToObj(this.camera.position), posToObj(this.camera.rotation))
|
||||
}
|
||||
|
||||
render () {
|
||||
tweenJs.update()
|
||||
const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
|
||||
this.renderer.render(this.scene, cam)
|
||||
// this.renderer.render(this.scene, cam)
|
||||
}
|
||||
|
||||
renderSign (position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) {
|
||||
|
|
@ -292,6 +335,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
|
||||
this.cleanChunkTextures(x, z)
|
||||
for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) {
|
||||
this.workerProxy?.addGeometry({ x, y, z })
|
||||
this.setSectionDirty(new Vec3(x, y, z), false)
|
||||
const key = `${x},${y},${z}`
|
||||
const mesh = this.sectionObjects[key]
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ try {
|
|||
// renderer.localClippingEnabled = true
|
||||
initWithRenderer(renderer.domElement)
|
||||
const renderWrapper = new ViewerWrapper(renderer.domElement, renderer)
|
||||
renderWrapper.addToPage()
|
||||
// renderWrapper.addToPage()
|
||||
watchValue(options, (o) => {
|
||||
renderWrapper.renderInterval = o.frameLimit ? 1000 / o.frameLimit : 0
|
||||
renderWrapper.renderIntervalUnfocused = o.backgroundRendering === '5fps' ? 1000 / 5 : o.backgroundRendering === '20fps' ? 1000 / 20 : undefined
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue