From b6349093fa21213ca6f52840b6d8a10b333f47f1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 27 Aug 2024 03:34:49 +0300 Subject: [PATCH 01/10] wip --- prismarine-viewer/viewer/lib/worldrendererThree.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index 6c14e2439..98c0a5f83 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -28,10 +28,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.initCameraGroup() + this.initCameraGroup() // this.initHandObject() } + initCameraGroup () { + this.cameraGroup = new THREE.Group() + this.cameraGroup.onBeforeRender = (renderer, scene, camera) => { + this.cameraGroup.position.copy(camera.position.clone().add(new THREE.Vector3(0, 0, 1))) + this.cameraGroup.rotation.copy(camera.rotation) + } + this.scene.add(this.cameraGroup) + } + timeUpdated (newTime: number): void { const nightTime = 13_500 const morningStart = 23_000 From 8132dea5bbe8a48ce3809f7592bdef828ddda516 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 31 Aug 2024 18:02:10 +0300 Subject: [PATCH 02/10] display is almost done --- .../viewer/lib/worldrendererThree.ts | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index 98c0a5f83..bce5c4480 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -19,7 +19,7 @@ export class WorldRendererThree extends WorldRendererCommon { signsCache = new Map() starField: StarField cameraSectionPos: Vec3 = new Vec3(0, 0, 0) - cameraGroup = new THREE.Group() + cameraGroup = new THREE.Mesh() get tilesRendered () { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0) @@ -29,18 +29,54 @@ export class WorldRendererThree extends WorldRendererCommon { super(config) this.starField = new StarField(scene) this.initCameraGroup() - // this.initHandObject() + this.renderUpdateEmitter.on('textureDownloaded', () => { + this.initHandObject() + }) } initCameraGroup () { - this.cameraGroup = new THREE.Group() - this.cameraGroup.onBeforeRender = (renderer, scene, camera) => { - this.cameraGroup.position.copy(camera.position.clone().add(new THREE.Vector3(0, 0, 1))) - this.cameraGroup.rotation.copy(camera.rotation) - } + this.cameraGroup = new THREE.Mesh() this.scene.add(this.cameraGroup) } + updateCameraGroup () { + const { camera } = this + this.cameraGroup.position.copy(camera.position) + this.cameraGroup.rotation.copy(camera.rotation) + // this.cameraGroup.children[0]?.position.set(window.x ?? 0.25, window.y ?? -0.41, window.z ?? -0.5) + this.cameraGroup.children[0]?.position.set(0.25, window.y ?? -0.41, window.z ?? -0.45) + const scale = window.scale ?? 0.2 + this.cameraGroup.children[0]?.scale.set(scale, scale, scale) + // holding block rotation + // this.cameraGroup.children[0]?.rotation.set(-THREE.MathUtils.degToRad(window.X), -THREE.MathUtils.degToRad(rotation), THREE.MathUtils.degToRad(window.Z), 'ZYX') + // if (window.rotated) {} + } + + startAnim () { + const blockRot = this.cameraGroup.children[0]?.rotation + new tweenJs.Tween(blockRot).to({ + z: THREE.MathUtils.degToRad(90), + x: THREE.MathUtils.degToRad(-45) + }, 400) + } + + initHandObject () { + const blockProvider = worldBlockProvider(this.blockstatesModels, this.blocksAtlases, 'latest') + const models = blockProvider.getAllResolvedModels0_1({ + name: 'stone', + properties: { + } + }, true) + const geometry = renderBlockThree(models, undefined, 'plains', loadedData) + const { material } = this + // block material + const mesh = new THREE.Mesh(geometry, material) + mesh.name = 'hand' + const rotation = 45 + this.cameraGroup.add(mesh) + this.cameraGroup.children[0]?.rotation.set(0, -THREE.MathUtils.degToRad(rotation), 0, 'ZYX') + } + timeUpdated (newTime: number): void { const nightTime = 13_500 const morningStart = 23_000 @@ -184,6 +220,7 @@ export class WorldRendererThree extends WorldRendererCommon { tweenJs.update() // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera + this.updateCameraGroup() this.renderer.render(this.scene, cam) } From e2e20d5075352d2d743de9a9601a427a943178aa Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 31 Aug 2024 23:07:01 +0300 Subject: [PATCH 03/10] it just works for now --- prismarine-viewer/viewer/lib/holdingBlock.ts | 86 +++++++++++++++++++ .../viewer/lib/worldrendererThree.ts | 54 ++---------- 2 files changed, 92 insertions(+), 48 deletions(-) create mode 100644 prismarine-viewer/viewer/lib/holdingBlock.ts diff --git a/prismarine-viewer/viewer/lib/holdingBlock.ts b/prismarine-viewer/viewer/lib/holdingBlock.ts new file mode 100644 index 000000000..4b75082e4 --- /dev/null +++ b/prismarine-viewer/viewer/lib/holdingBlock.ts @@ -0,0 +1,86 @@ +import * as THREE from 'three' +import * as tweenJs from '@tweenjs/tween.js' +import worldBlockProvider from 'mc-assets/dist/worldBlockProvider' +import { renderBlockThree } from './mesher/standaloneRenderer' + +export default class HoldingBlock { + holdingBlock: THREE.Object3D | null = null + swingAnimation: tweenJs.Group = new tweenJs.Group() + cameraGroup = new THREE.Mesh() + objectOuterGroup = new THREE.Group() + objectInnerGroup = new THREE.Group() + camera: THREE.Group | THREE.PerspectiveCamera + + constructor (public scene: THREE.Scene) { + this.initCameraGroup() + } + + initCameraGroup () { + this.cameraGroup = new THREE.Mesh() + this.scene.add(this.cameraGroup) + } + + updateCameraGroup () { + const { camera } = this + this.cameraGroup.position.copy(camera.position) + this.cameraGroup.rotation.copy(camera.rotation) + } + + startSwing () { + this.stopSwing() + const cube = this.cameraGroup.children[0] + if (cube) { + // const DURATION = 1000 * 0.35 / 2 + const DURATION = 1000 + // new tweenJs.Tween(this.holdingBlock!.position, this.swingAnimation).to({ y: this.holdingBlock!.position.y - this.holdingBlock!.scale.y * 2 }, DURATION).yoyo(true).repeat(Infinity).start() + new tweenJs.Tween(this.objectInnerGroup.rotation, this.swingAnimation).to({ z: THREE.MathUtils.degToRad(90) }, DURATION).yoyo(true).repeat(Infinity).start() + } + } + + stopSwing () { + this.swingAnimation.removeAll() + } + + update (camera: typeof this.camera) { + this.camera = camera + this.swingAnimation.update() + this.updateCameraGroup() + } + + initHandObject (material: THREE.Material, blockstatesModels: any, blocksAtlases: any) { + const blockProvider = worldBlockProvider(blockstatesModels, blocksAtlases, 'latest') + const models = blockProvider.getAllResolvedModels0_1({ + name: 'furnace', + properties: { + } + }, true) + // const geometry = new THREE.BoxGeometry(1, 1, 1) + const geometry = renderBlockThree(models, undefined, 'plains', loadedData) + // block material + const block = new THREE.Mesh(geometry, material) + block.name = 'holdingBlock' + this.holdingBlock = block + this.objectInnerGroup = new THREE.Group() + this.objectInnerGroup.add(block) + this.objectInnerGroup.position.set(-0.5, -1, -0.5) + block.position.set(0.5, 1, 0.5) + + this.objectOuterGroup = new THREE.Group() + this.objectOuterGroup.add(this.objectInnerGroup) + + this.cameraGroup.add(this.objectOuterGroup) + // const rotation = 45 + // this.holdingBlock.rotation.set(0, -THREE.MathUtils.degToRad(rotation), 0, 'ZYX') + + const viewerSize = viewer.renderer.getSize(new THREE.Vector2()) + // const x = window.x ?? 0.25 * viewerSize.width / viewerSize.height + // const x = 0.15 * viewerSize.width / viewerSize.height + const x = 0 * viewerSize.width / viewerSize.height + // const scale = window.scale ?? 0.2 + const scale = 0.2 + this.objectOuterGroup.scale.set(scale, scale, scale) + // this.objectOuterGroup.position.set(x, window.y ?? -0.41, window.z ?? -0.45) + this.objectOuterGroup.position.set(x, 0, -0.45) + // this.objectOuterGroup.position.set(x, -0.41, -0.45) + } +} diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index bce5c4480..a797ea5c1 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -9,7 +9,7 @@ import { renderSign } from '../sign-renderer' import { chunkPos, sectionPos } from './simpleUtils' import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon' import { disposeObject } from './threeJsUtils' -import { renderBlockThree } from './mesher/standaloneRenderer' +import HoldingBlock from './holdingBlock' export class WorldRendererThree extends WorldRendererCommon { outputFormat = 'threeJs' as const @@ -19,7 +19,7 @@ export class WorldRendererThree extends WorldRendererCommon { signsCache = new Map() starField: StarField cameraSectionPos: Vec3 = new Vec3(0, 0, 0) - cameraGroup = new THREE.Mesh() + holdingBlock: HoldingBlock get tilesRendered () { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0) @@ -28,55 +28,13 @@ 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.initCameraGroup() + this.holdingBlock = new HoldingBlock(this.scene) + this.renderUpdateEmitter.on('textureDownloaded', () => { - this.initHandObject() + this.holdingBlock.initHandObject(this.material, this.blockstatesModels, this.blocksAtlases) }) } - initCameraGroup () { - this.cameraGroup = new THREE.Mesh() - this.scene.add(this.cameraGroup) - } - - updateCameraGroup () { - const { camera } = this - this.cameraGroup.position.copy(camera.position) - this.cameraGroup.rotation.copy(camera.rotation) - // this.cameraGroup.children[0]?.position.set(window.x ?? 0.25, window.y ?? -0.41, window.z ?? -0.5) - this.cameraGroup.children[0]?.position.set(0.25, window.y ?? -0.41, window.z ?? -0.45) - const scale = window.scale ?? 0.2 - this.cameraGroup.children[0]?.scale.set(scale, scale, scale) - // holding block rotation - // this.cameraGroup.children[0]?.rotation.set(-THREE.MathUtils.degToRad(window.X), -THREE.MathUtils.degToRad(rotation), THREE.MathUtils.degToRad(window.Z), 'ZYX') - // if (window.rotated) {} - } - - startAnim () { - const blockRot = this.cameraGroup.children[0]?.rotation - new tweenJs.Tween(blockRot).to({ - z: THREE.MathUtils.degToRad(90), - x: THREE.MathUtils.degToRad(-45) - }, 400) - } - - initHandObject () { - const blockProvider = worldBlockProvider(this.blockstatesModels, this.blocksAtlases, 'latest') - const models = blockProvider.getAllResolvedModels0_1({ - name: 'stone', - properties: { - } - }, true) - const geometry = renderBlockThree(models, undefined, 'plains', loadedData) - const { material } = this - // block material - const mesh = new THREE.Mesh(geometry, material) - mesh.name = 'hand' - const rotation = 45 - this.cameraGroup.add(mesh) - this.cameraGroup.children[0]?.rotation.set(0, -THREE.MathUtils.degToRad(rotation), 0, 'ZYX') - } - timeUpdated (newTime: number): void { const nightTime = 13_500 const morningStart = 23_000 @@ -218,9 +176,9 @@ export class WorldRendererThree extends WorldRendererCommon { render () { tweenJs.update() + this.holdingBlock.update(this.camera) // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera - this.updateCameraGroup() this.renderer.render(this.scene, cam) } From fd6b2e9a088e2f981fd1af0254559d9fd6a0b3c4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 00:51:50 +0300 Subject: [PATCH 04/10] everything works for blocks! --- experiments/three.html | 1 + experiments/three.ts | 99 +++++++++ prismarine-viewer/viewer/lib/holdingBlock.ts | 188 ++++++++++++++---- .../viewer/lib/mesher/standaloneRenderer.ts | 16 ++ prismarine-viewer/viewer/lib/viewer.ts | 17 +- .../viewer/lib/worldDataEmitter.ts | 6 + .../viewer/lib/worldrendererCommon.ts | 5 + .../viewer/lib/worldrendererThree.ts | 19 +- 8 files changed, 303 insertions(+), 48 deletions(-) create mode 100644 experiments/three.html create mode 100644 experiments/three.ts diff --git a/experiments/three.html b/experiments/three.html new file mode 100644 index 000000000..8765081b5 --- /dev/null +++ b/experiments/three.html @@ -0,0 +1 @@ + diff --git a/experiments/three.ts b/experiments/three.ts new file mode 100644 index 000000000..8168ac541 --- /dev/null +++ b/experiments/three.ts @@ -0,0 +1,99 @@ +import * as THREE from 'three' +import * as tweenJs from '@tweenjs/tween.js' +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' +import * as THREE from 'three'; +import Jimp from 'jimp'; +const scene = new THREE.Scene() +const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) +camera.position.set(0, 0, 5) +const renderer = new THREE.WebGLRenderer() +renderer.setSize(window.innerWidth, window.innerHeight) +document.body.appendChild(renderer.domElement) + +const controls = new OrbitControls(camera, renderer.domElement) + +const geometry = new THREE.BoxGeometry(1, 1, 1) +const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }) +const cube = new THREE.Mesh(geometry, material) +cube.position.set(0.5, 0.5, 0.5); +const group = new THREE.Group() +group.add(cube) +group.position.set(-0.5, -0.5, -0.5); +const outerGroup = new THREE.Group() +outerGroup.add(group) +outerGroup.scale.set(0.2, 0.2, 0.2) +outerGroup.position.set(1, 1, 0) +scene.add(outerGroup) + +// const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 })) +// mesh.position.set(0.5, 1, 0.5) +// const group = new THREE.Group() +// group.add(mesh) +// group.position.set(-0.5, -1, -0.5) +// const outerGroup = new THREE.Group() +// outerGroup.add(group) +// // outerGroup.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z) +// scene.add(outerGroup) + + new tweenJs.Tween(group.rotation).to({ z: THREE.MathUtils.degToRad(90) }, 1000).yoyo(true).repeat(Infinity).start() + +const tweenGroup = new tweenJs.Group() +function animate () { + tweenGroup.update() + requestAnimationFrame(animate) +// cube.rotation.x += 0.01 +// cube.rotation.y += 0.01 + renderer.render(scene, camera) +} +animate() + +// let animation + +window.animate = () => { + // new Tween.Tween(group.position).to({ y: group.position.y - 1}, 1000 * 0.35/2).yoyo(true).repeat(1).start() + new tweenJs.Tween(group.rotation, tweenGroup).to({ z: THREE.MathUtils.degToRad(90) }, 1000 * 0.35 / 2).yoyo(true).repeat(Infinity).start().onRepeat(() => { + console.log('done') + }) +} + +window.stop = () => { + tweenGroup.removeAll() +} + + +function createGeometryFromImage(imagePath) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.src = imagePath; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const context = canvas.getContext('2d'); + context.drawImage(img, 0, 0, img.width, img.height); + const imgData = context.getImageData(0, 0, img.width, img.height); + + const shape = new THREE.Shape(); + for (let y = 0; y < img.height; y++) { + for (let x = 0; x < img.width; x++) { + const index = (y * img.width + x) * 4; + const alpha = imgData.data[index + 3]; + if (alpha !== 0) { + shape.lineTo(x, y); + } + } + } + + const geometry = new THREE.ShapeGeometry(shape); + resolve(geometry); + }; + img.onerror = reject; + }); +} + +// Usage: +const shapeGeomtry = createGeometryFromImage('path/to/image.png').then(geometry => { + const material = new THREE.MeshBasicMaterial({ color: 0xffffff }); + const mesh = new THREE.Mesh(geometry, material); + scene.add(mesh); +}) diff --git a/prismarine-viewer/viewer/lib/holdingBlock.ts b/prismarine-viewer/viewer/lib/holdingBlock.ts index 4b75082e4..739a5ceaf 100644 --- a/prismarine-viewer/viewer/lib/holdingBlock.ts +++ b/prismarine-viewer/viewer/lib/holdingBlock.ts @@ -1,15 +1,29 @@ import * as THREE from 'three' import * as tweenJs from '@tweenjs/tween.js' import worldBlockProvider from 'mc-assets/dist/worldBlockProvider' -import { renderBlockThree } from './mesher/standaloneRenderer' +import { getThreeBlockModelGroup, renderBlockThree, setBlockPosition } from './mesher/standaloneRenderer' + +export type HandItemBlock = { + name + properties +} export default class HoldingBlock { - holdingBlock: THREE.Object3D | null = null - swingAnimation: tweenJs.Group = new tweenJs.Group() + holdingBlock: THREE.Object3D | undefined = undefined + swingAnimation: tweenJs.Group | undefined = undefined + blockSwapAnimation: { + tween: tweenJs.Group + hidden: boolean + } | undefined = undefined cameraGroup = new THREE.Mesh() objectOuterGroup = new THREE.Group() objectInnerGroup = new THREE.Group() camera: THREE.Group | THREE.PerspectiveCamera + stopUpdate = false + lastHeldItem: HandItemBlock | undefined + toBeRenderedItem: HandItemBlock | undefined + isSwinging = false + nextIterStopCallbacks: Array<() => void> | undefined constructor (public scene: THREE.Scene) { this.initCameraGroup() @@ -20,67 +34,165 @@ export default class HoldingBlock { this.scene.add(this.cameraGroup) } - updateCameraGroup () { - const { camera } = this - this.cameraGroup.position.copy(camera.position) - this.cameraGroup.rotation.copy(camera.rotation) - } - startSwing () { - this.stopSwing() + if (this.isSwinging) return + this.swingAnimation = new tweenJs.Group() + this.isSwinging = true const cube = this.cameraGroup.children[0] if (cube) { // const DURATION = 1000 * 0.35 / 2 - const DURATION = 1000 - // new tweenJs.Tween(this.holdingBlock!.position, this.swingAnimation).to({ y: this.holdingBlock!.position.y - this.holdingBlock!.scale.y * 2 }, DURATION).yoyo(true).repeat(Infinity).start() + const DURATION = 1000 * 0.35 / 3 + // const DURATION = 1000 + const initialPos = { + x: this.objectInnerGroup.position.x, + y: this.objectInnerGroup.position.y, + z: this.objectInnerGroup.position.z + } + const initialRot = { + x: this.objectInnerGroup.rotation.x, + y: this.objectInnerGroup.rotation.y, + z: this.objectInnerGroup.rotation.z + } + const mainAnim = new tweenJs.Tween(this.objectInnerGroup.position, this.swingAnimation).to({ y: this.objectInnerGroup.position.y - this.objectInnerGroup.scale.y / 2 }, DURATION).yoyo(true).repeat(Infinity).start() + let i = 0 + mainAnim.onRepeat(() => { + i++ + if (this.nextIterStopCallbacks && i % 2 === 0) { + for (const callback of this.nextIterStopCallbacks) { + callback() + } + this.nextIterStopCallbacks = undefined + this.isSwinging = false + this.swingAnimation!.removeAll() + this.swingAnimation = undefined + // todo refactor to be more generic for animations + this.objectInnerGroup.position.set(initialPos.x, initialPos.y, initialPos.z) + // this.objectInnerGroup.rotation.set(initialRot.x, initialRot.y, initialRot.z) + Object.assign(this.objectInnerGroup.rotation, initialRot) + } + }) + new tweenJs.Tween(this.objectInnerGroup.rotation, this.swingAnimation).to({ z: THREE.MathUtils.degToRad(90) }, DURATION).yoyo(true).repeat(Infinity).start() + new tweenJs.Tween(this.objectInnerGroup.rotation, this.swingAnimation).to({ x: -THREE.MathUtils.degToRad(90) }, DURATION).yoyo(true).repeat(Infinity).start() } } - stopSwing () { - this.swingAnimation.removeAll() + async stopSwing () { + if (!this.isSwinging) return + return new Promise((resolve) => { + this.nextIterStopCallbacks ??= [] + this.nextIterStopCallbacks.push(() => { + resolve() + }) + }) } update (camera: typeof this.camera) { this.camera = camera - this.swingAnimation.update() + this.swingAnimation?.update() + this.blockSwapAnimation?.tween.update() this.updateCameraGroup() } - initHandObject (material: THREE.Material, blockstatesModels: any, blocksAtlases: any) { + // worldTest () { + // const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 })) + // mesh.position.set(0.5, 0.5, 0.5) + // const group = new THREE.Group() + // group.add(mesh) + // group.position.set(-0.5, -0.5, -0.5) + // const outerGroup = new THREE.Group() + // outerGroup.add(group) + // outerGroup.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z) + // this.scene.add(outerGroup) + + // new tweenJs.Tween(group.rotation).to({ z: THREE.MathUtils.degToRad(90) }, 1000).yoyo(true).repeat(Infinity).start() + // } + + async playBlockSwapAnimation () { + // if (this.blockSwapAnimation) return + this.blockSwapAnimation ??= { + tween: new tweenJs.Group(), + hidden: false + } + const DURATION = 1000 * 0.35 / 2 + const tween = new tweenJs.Tween(this.objectInnerGroup.position, this.blockSwapAnimation.tween).to({ + y: this.objectInnerGroup.position.y + (this.objectInnerGroup.scale.y * 1.5 * (this.blockSwapAnimation.hidden ? 1 : -1)) + }, DURATION).start() + return new Promise((resolve) => { + tween.onComplete(() => { + if (this.blockSwapAnimation!.hidden) { + this.blockSwapAnimation = undefined + } else { + this.blockSwapAnimation!.hidden = !this.blockSwapAnimation!.hidden + } + resolve() + }) + }) + } + + isDifferentItem (block: HandItemBlock | undefined) { + return this.lastHeldItem && (this.lastHeldItem.name !== block?.name || JSON.stringify(this.lastHeldItem.properties) !== JSON.stringify(block?.properties ?? '{}')) + } + + updateCameraGroup () { + if (this.stopUpdate) return + const { camera } = this + this.cameraGroup.position.copy(camera.position) + this.cameraGroup.rotation.copy(camera.rotation) + + const viewerSize = viewer.renderer.getSize(new THREE.Vector2()) + // const x = window.x ?? 0.25 * viewerSize.width / viewerSize.height + // const x = 0 * viewerSize.width / viewerSize.height + const x = 0.2 * viewerSize.width / viewerSize.height + this.objectOuterGroup.position.set(x, -0.3, -0.45) + } + + async initHandObject (material: THREE.Material, blockstatesModels: any, blocksAtlases: any, block?: HandItemBlock) { + let animatingCurrent = false + if (!this.swingAnimation && !this.blockSwapAnimation && this.isDifferentItem(block)) { + console.log('play swap') + animatingCurrent = true + await this.playBlockSwapAnimation() + } + this.lastHeldItem = block + if (!block) { + this.holdingBlock?.removeFromParent() + this.holdingBlock = undefined + this.swingAnimation = undefined + this.blockSwapAnimation = undefined + return + } const blockProvider = worldBlockProvider(blockstatesModels, blocksAtlases, 'latest') - const models = blockProvider.getAllResolvedModels0_1({ - name: 'furnace', - properties: { - } - }, true) - // const geometry = new THREE.BoxGeometry(1, 1, 1) - const geometry = renderBlockThree(models, undefined, 'plains', loadedData) - // block material - const block = new THREE.Mesh(geometry, material) - block.name = 'holdingBlock' - this.holdingBlock = block + const models = blockProvider.getAllResolvedModels0_1(block, true) + const blockInner = getThreeBlockModelGroup(material, models, undefined, 'plains', loadedData) + blockInner.name = 'holdingBlock' + const blockOuterGroup = new THREE.Group() + blockOuterGroup.add(blockInner) + this.holdingBlock = blockInner this.objectInnerGroup = new THREE.Group() - this.objectInnerGroup.add(block) - this.objectInnerGroup.position.set(-0.5, -1, -0.5) - block.position.set(0.5, 1, 0.5) + this.objectInnerGroup.add(blockOuterGroup) + this.objectInnerGroup.position.set(-0.5, -0.5, -0.5) + // todo cleanup + if (animatingCurrent) { + this.objectInnerGroup.position.y -= this.objectInnerGroup.scale.y * 1.5 + } + Object.assign(blockOuterGroup.position, { x: 0.5, y: 0.5, z: 0.5 }) this.objectOuterGroup = new THREE.Group() this.objectOuterGroup.add(this.objectInnerGroup) this.cameraGroup.add(this.objectOuterGroup) - // const rotation = 45 - // this.holdingBlock.rotation.set(0, -THREE.MathUtils.degToRad(rotation), 0, 'ZYX') + const rotation = -45 + -90 + this.holdingBlock.rotation.set(0, THREE.MathUtils.degToRad(rotation), 0, 'ZYX') - const viewerSize = viewer.renderer.getSize(new THREE.Vector2()) - // const x = window.x ?? 0.25 * viewerSize.width / viewerSize.height - // const x = 0.15 * viewerSize.width / viewerSize.height - const x = 0 * viewerSize.width / viewerSize.height // const scale = window.scale ?? 0.2 const scale = 0.2 this.objectOuterGroup.scale.set(scale, scale, scale) // this.objectOuterGroup.position.set(x, window.y ?? -0.41, window.z ?? -0.45) - this.objectOuterGroup.position.set(x, 0, -0.45) - // this.objectOuterGroup.position.set(x, -0.41, -0.45) + // this.objectOuterGroup.position.set(x, 0, -0.45) + + if (animatingCurrent) { + await this.playBlockSwapAnimation() + } } } diff --git a/prismarine-viewer/viewer/lib/mesher/standaloneRenderer.ts b/prismarine-viewer/viewer/lib/mesher/standaloneRenderer.ts index 2dc2f5994..43369cc21 100644 --- a/prismarine-viewer/viewer/lib/mesher/standaloneRenderer.ts +++ b/prismarine-viewer/viewer/lib/mesher/standaloneRenderer.ts @@ -272,3 +272,19 @@ export const renderBlockThree = (...args: Parameters) => { + const geometry = renderBlockThree(...args) + const mesh = new THREE.Mesh(geometry, material) + mesh.position.set(-0.5, -0.5, -0.5) + const group = new THREE.Group() + group.add(mesh) + group.rotation.set(0, -THREE.MathUtils.degToRad(90), 0, 'ZYX') + globalThis.mesh = group + return group + // return new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 })) +} + +export const setBlockPosition = (object: THREE.Object3D, position: { x: number, y: number, z: number }) => { + object.position.set(position.x + 0.5, position.y + 0.5, position.z + 0.5) +} diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 7893bccce..5f6590f71 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -7,7 +7,7 @@ import { Entities } from './entities' import { Primitives } from './primitives' import { WorldRendererThree } from './worldrendererThree' import { WorldRendererCommon, WorldRendererConfig, defaultWorldRendererConfig } from './worldrendererCommon' -import { renderBlockThree } from './mesher/standaloneRenderer' +import { getThreeBlockModelGroup, renderBlockThree, setBlockPosition } from './mesher/standaloneRenderer' export class Viewer { scene: THREE.Scene @@ -101,18 +101,19 @@ export class Viewer { } demoModel () { + //@ts-expect-error + const pos = cursorBlockRel(0, 1, 0).position const blockProvider = worldBlockProvider(this.world.blockstatesModels, this.world.blocksAtlases, 'latest') const models = blockProvider.getAllResolvedModels0_1({ - name: 'item_frame', + name: 'furnace', properties: { - map: false + // map: false } - }) - const geometry = renderBlockThree(models, undefined, 'plains', loadedData) + }, true) const { material } = this.world - // block material - const mesh = new THREE.Mesh(geometry, material) - mesh.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z) + const mesh = getThreeBlockModelGroup(material, models, undefined, 'plains', loadedData) + // mesh.rotation.y = THREE.MathUtils.degToRad(90) + setBlockPosition(mesh, pos) const helper = new THREE.BoxHelper(mesh, 0xff_ff_00) mesh.add(helper) this.scene.add(mesh) diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index ea956b814..d42d57bdc 100644 --- a/prismarine-viewer/viewer/lib/worldDataEmitter.ts +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -79,7 +79,13 @@ export class WorldDataEmitter extends EventEmitter { time: () => { this.emitter.emit('time', bot.time.timeOfDay) }, + heldItemChanged (newItem) { + // todo + viewer.world.onHandItemSwitch(newItem ? { name: newItem.name, properties: {} } : undefined) + }, } satisfies Partial + this.eventListeners[bot.username].heldItemChanged(bot.heldItem) + bot._client.on('update_light', ({ chunkX, chunkZ }) => { const chunkPos = new Vec3(chunkX * 16, 0, chunkZ * 16) diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index 204ad65fe..901b7f17e 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -16,6 +16,7 @@ import { toMajorVersion } from '../../../src/utils' import { buildCleanupDecorator } from './cleanupDecorator' import { defaultMesherConfig } from './mesher/shared' import { chunkPos } from './simpleUtils' +import { HandItemBlock } from './holdingBlock' function mod (x, n) { return ((x % n) + n) % n @@ -37,6 +38,7 @@ type CustomTexturesData = { export abstract class WorldRendererCommon { worldConfig = { minY: 0, worldHeight: 256 } + // todo need to cleanup material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 }) @worldCleanup() @@ -59,6 +61,7 @@ export abstract class WorldRendererCommon textureDownloaded (): void }> customTexturesDataUrl = undefined as string | undefined + @worldCleanup() currentTextureImage = undefined as any workers: any[] = [] viewerPosition?: Vec3 @@ -157,6 +160,8 @@ export abstract class WorldRendererCommon } } + onHandItemSwitch (item: HandItemBlock | undefined): void { } + abstract handleWorkerMessage (data: WorkerReceive): void abstract updateCamera (pos: Vec3 | null, yaw: number, pitch: number): void diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index a797ea5c1..e1b7aa7d9 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -9,7 +9,7 @@ import { renderSign } from '../sign-renderer' import { chunkPos, sectionPos } from './simpleUtils' import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon' import { disposeObject } from './threeJsUtils' -import HoldingBlock from './holdingBlock' +import HoldingBlock, { HandItemBlock } from './holdingBlock' export class WorldRendererThree extends WorldRendererCommon { outputFormat = 'threeJs' as const @@ -29,12 +29,27 @@ export class WorldRendererThree extends WorldRendererCommon { super(config) this.starField = new StarField(scene) this.holdingBlock = new HoldingBlock(this.scene) + this.onHandItemSwitch({ + name: 'furnace', + properties: {} + }) this.renderUpdateEmitter.on('textureDownloaded', () => { - this.holdingBlock.initHandObject(this.material, this.blockstatesModels, this.blocksAtlases) + if (this.holdingBlock.toBeRenderedItem) { + this.onHandItemSwitch(this.holdingBlock.toBeRenderedItem) + this.holdingBlock.toBeRenderedItem = undefined + } }) } + onHandItemSwitch (item: HandItemBlock | undefined) { + if (!this.currentTextureImage) { + this.holdingBlock.toBeRenderedItem = item + return + } + void this.holdingBlock.initHandObject(this.material, this.blockstatesModels, this.blocksAtlases, item) + } + timeUpdated (newTime: number): void { const nightTime = 13_500 const morningStart = 23_000 From d4cd8c37dec40229214d926986f0fc6dfbbcf0f4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 02:13:16 +0300 Subject: [PATCH 05/10] finish integration! +setting --- experiments/three.ts | 8 ++++-- prismarine-viewer/viewer/lib/holdingBlock.ts | 4 ++- .../viewer/lib/worldDataEmitter.ts | 28 +++++++++++++------ .../viewer/lib/worldrendererCommon.ts | 1 + .../viewer/lib/worldrendererThree.ts | 8 ++++++ src/optionsGuiScheme.tsx | 1 + src/optionsStorage.ts | 1 + src/watchOptions.ts | 2 +- src/worldInteractions.ts | 6 ++++ 9 files changed, 46 insertions(+), 13 deletions(-) diff --git a/experiments/three.ts b/experiments/three.ts index 8168ac541..7a629a132 100644 --- a/experiments/three.ts +++ b/experiments/three.ts @@ -3,6 +3,7 @@ import * as tweenJs from '@tweenjs/tween.js' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import * as THREE from 'three'; import Jimp from 'jimp'; + const scene = new THREE.Scene() const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) camera.position.set(0, 0, 5) @@ -61,10 +62,11 @@ window.stop = () => { } -function createGeometryFromImage(imagePath) { +function createGeometryFromImage() { return new Promise((resolve, reject) => { const img = new Image(); - img.src = imagePath; + img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABEElEQVQ4jWNkIAPw2Zv9J0cfXPOSvx/+L/n74T+HqsJ/JlI1T9u3i6H91B7ybdY+vgZuO1majV+fppFmPnuz/+ihy2dv9t/49Wm8mlECkV1FHh5FfPZm/1XXTGX4cechA4eKPMNVq1CGH7cfMBJ0rlxX+X8OVYX/xq9P/5frKifoZ0Z0AwS8HRkYGBgYvt+8xyDXUUbQZgwJPnuz/+wq8gw/7zxk+PXsFUFno0h6mon+l5fgZFhwnYmBTUqMgYGBgaAhLMiaHQyFGOZvf8Lw49FXRgYGhv8MDAwwg/7jMoQFFury/C8Y5m9/wnADohnZVryJhoWBARJ9Cw69gtmMAgiFAcuvZ68Yfj17hU8NXgAATdKfkzbQhBEAAAAASUVORK5CYII=' + console.log('img.complete', img.complete) img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width; @@ -92,7 +94,7 @@ function createGeometryFromImage(imagePath) { } // Usage: -const shapeGeomtry = createGeometryFromImage('path/to/image.png').then(geometry => { +const shapeGeomtry = createGeometryFromImage().then(geometry => { const material = new THREE.MeshBasicMaterial({ color: 0xffffff }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); diff --git a/prismarine-viewer/viewer/lib/holdingBlock.ts b/prismarine-viewer/viewer/lib/holdingBlock.ts index 739a5ceaf..25dd879f0 100644 --- a/prismarine-viewer/viewer/lib/holdingBlock.ts +++ b/prismarine-viewer/viewer/lib/holdingBlock.ts @@ -35,6 +35,7 @@ export default class HoldingBlock { } startSwing () { + this.nextIterStopCallbacks = undefined // forget about cancelling if (this.isSwinging) return this.swingAnimation = new tweenJs.Group() this.isSwinging = true @@ -79,7 +80,8 @@ export default class HoldingBlock { async stopSwing () { if (!this.isSwinging) return - return new Promise((resolve) => { + // might never resolve! + /* return */void new Promise((resolve) => { this.nextIterStopCallbacks ??= [] this.nextIterStopCallbacks.push(() => { resolve() diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index d42d57bdc..a4a45fefd 100644 --- a/prismarine-viewer/viewer/lib/worldDataEmitter.ts +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -20,6 +20,14 @@ export class WorldDataEmitter extends EventEmitter { private eventListeners: Record = {} private readonly emitter: WorldDataEmitter keepChunksDistance = 0 + _handDisplay = false + get handDisplay () { + return this._handDisplay + } + set handDisplay (newVal) { + this._handDisplay = newVal + this.eventListeners.heldItemChanged?.() + } constructor (public world: typeof __type_bot['world'], public viewDistance: number, position: Vec3 = new Vec3(0, 0, 0)) { super() @@ -55,7 +63,7 @@ export class WorldDataEmitter extends EventEmitter { }) } - this.eventListeners[bot.username] = { + this.eventListeners = { // 'move': botPosition, entitySpawn (e: any) { emitEntity(e) @@ -70,7 +78,7 @@ export class WorldDataEmitter extends EventEmitter { this.emitter.emit('entity', { id: e.id, delete: true }) }, chunkColumnLoad: (pos: Vec3) => { - this.loadChunk(pos) + void this.loadChunk(pos) }, blockUpdate: (oldBlock: any, newBlock: any) => { const stateId = newBlock.stateId ?? ((newBlock.type << 4) | newBlock.metadata) @@ -79,12 +87,17 @@ export class WorldDataEmitter extends EventEmitter { time: () => { this.emitter.emit('time', bot.time.timeOfDay) }, - heldItemChanged (newItem) { - // todo + heldItemChanged: () => { + if (!this.handDisplay) { + viewer.world.onHandItemSwitch(undefined) + return + } + // todo properties + const newItem = bot.heldItem viewer.world.onHandItemSwitch(newItem ? { name: newItem.name, properties: {} } : undefined) }, } satisfies Partial - this.eventListeners[bot.username].heldItemChanged(bot.heldItem) + this.eventListeners.heldItemChanged() bot._client.on('update_light', ({ chunkX, chunkZ }) => { @@ -108,7 +121,7 @@ export class WorldDataEmitter extends EventEmitter { this.emitter.emit('listening') } - for (const [evt, listener] of Object.entries(this.eventListeners[bot.username])) { + for (const [evt, listener] of Object.entries(this.eventListeners)) { bot.on(evt as any, listener) } @@ -119,10 +132,9 @@ export class WorldDataEmitter extends EventEmitter { } removeListenersFromBot (bot: import('mineflayer').Bot) { - for (const [evt, listener] of Object.entries(this.eventListeners[bot.username])) { + for (const [evt, listener] of Object.entries(this.eventListeners)) { bot.removeListener(evt as any, listener) } - delete this.eventListeners[bot.username] } async init (pos: Vec3) { diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index 901b7f17e..836a55927 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -161,6 +161,7 @@ export abstract class WorldRendererCommon } onHandItemSwitch (item: HandItemBlock | undefined): void { } + changeHandSwingingState (isAnimationPlaying: boolean): void { } abstract handleWorkerMessage (data: WorkerReceive): void diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index e1b7aa7d9..b1644ebd2 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -50,6 +50,14 @@ export class WorldRendererThree extends WorldRendererCommon { void this.holdingBlock.initHandObject(this.material, this.blockstatesModels, this.blocksAtlases, item) } + changeHandSwingingState (isAnimationPlaying: boolean) { + if (isAnimationPlaying) { + this.holdingBlock.startSwing() + } else { + void this.holdingBlock.stopSwing() + } + } + timeUpdated (newTime: number): void { const nightTime = 13_500 const morningStart = 23_000 diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 644ec66cb..2082acdb0 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -88,6 +88,7 @@ export const guiOptionsScheme: { unit: '', tooltip: 'Additional distance to keep the chunks loading before unloading them by marking them as too far', }, + handDisplay: {}, }, ], main: [ diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index cac8c9f8c..b7bf4abed 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -47,6 +47,7 @@ const defaultOptions = { enabledResourcepack: null as string | null, useVersionsTextures: 'latest', serverResourcePacks: 'prompt' as 'prompt' | 'always' | 'never', + handDisplay: false, // antiAliasing: false, diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 9deb46a57..d26d2b90d 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -66,11 +66,11 @@ export const watchOptionsAfterViewerInit = () => { let viewWatched = false export const watchOptionsAfterWorldViewInit = () => { - worldView!.keepChunksDistance = options.keepChunksDistance if (viewWatched) return viewWatched = true watchValue(options, o => { if (!worldView) return worldView.keepChunksDistance = o.keepChunksDistance + worldView.handDisplay = o.handDisplay }) } diff --git a/src/worldInteractions.ts b/src/worldInteractions.ts index 7a9aa5086..f178a785b 100644 --- a/src/worldInteractions.ts +++ b/src/worldInteractions.ts @@ -293,6 +293,8 @@ class WorldInteraction { bot.lookAt = oldLookAt }).catch(console.warn) } + viewer.world.changeHandSwingingState(true) + viewer.world.changeHandSwingingState(false) } else if (!stop) { const offhand = activate ? false : activatableItems(bot.inventory.slots[45]?.name ?? '') bot.activateItem(offhand) // todo offhand @@ -350,10 +352,14 @@ class WorldInteraction { }) customEvents.emit('digStart') this.lastDigged = Date.now() + viewer.world.changeHandSwingingState(true) } else { bot.swingArm('right') } } + if (!this.buttons[0] && this.lastButtons[0]) { + viewer.world.changeHandSwingingState(false) + } this.prevOnGround = onGround // Show cursor From 33ab8745b30295aa388cbecd39bd4cea3bc262bb Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 02:23:33 +0300 Subject: [PATCH 06/10] add debug stats --- src/topRightStats.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/topRightStats.ts b/src/topRightStats.ts index 71303e81a..f0462ae61 100644 --- a/src/topRightStats.ts +++ b/src/topRightStats.ts @@ -75,3 +75,14 @@ export const statsEnd = () => { stats2.end() statsGl.end() } + +window.statsPerSec = {} +let statsPerSec = {} +window.addStatPerSec = (name) => { + statsPerSec[name] ??= 0 + statsPerSec[name]++ +} +setInterval(() => { + window.statsPerSec = statsPerSec + statsPerSec = {} +}, 1000) From 183c1edfa3d86a15eed589858e4932129b3a42e4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 02:37:22 +0300 Subject: [PATCH 07/10] fix: when left click was pressed down the swing arm packet sending was not limited --- src/worldInteractions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/worldInteractions.ts b/src/worldInteractions.ts index f178a785b..cef65b96a 100644 --- a/src/worldInteractions.ts +++ b/src/worldInteractions.ts @@ -38,6 +38,7 @@ class WorldInteraction { currentDigTime prevOnGround lastBlockPlaced: number + lastSwing = 0 buttons = [false, false, false] lastButtons = [false, false, false] breakStartTime: number | undefined = 0 @@ -353,8 +354,9 @@ class WorldInteraction { customEvents.emit('digStart') this.lastDigged = Date.now() viewer.world.changeHandSwingingState(true) - } else { + } else if (performance.now() - this.lastSwing > 200) { bot.swingArm('right') + this.lastSwing = performance.now() } } if (!this.buttons[0] && this.lastButtons[0]) { From 0d224f50fdea267ef72deab3e7d84f2905e3ad13 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 03:08:00 +0300 Subject: [PATCH 08/10] demo item and wip holding item display! --- prismarine-viewer/viewer/lib/entities.js | 85 +++++++++++--------- prismarine-viewer/viewer/lib/holdingBlock.ts | 9 ++- prismarine-viewer/viewer/lib/viewer.ts | 13 +++ 3 files changed, 69 insertions(+), 38 deletions(-) diff --git a/prismarine-viewer/viewer/lib/entities.js b/prismarine-viewer/viewer/lib/entities.js index 57a45a444..01ce7d6d3 100644 --- a/prismarine-viewer/viewer/lib/entities.js +++ b/prismarine-viewer/viewer/lib/entities.js @@ -282,6 +282,46 @@ export class Entities extends EventEmitter { } } + getItemMesh(item) { + const textureUv = this.getItemUv?.(item.itemId ?? item.blockId) + if (textureUv) { + // todo use geometry buffer uv instead! + const { u, v, size, su, sv, texture } = textureUv + const itemsTexture = texture.clone() + itemsTexture.flipY = true + itemsTexture.offset.set(u, 1 - v - (sv ?? size)) + itemsTexture.repeat.set(su ?? size, sv ?? size) + itemsTexture.needsUpdate = true + itemsTexture.magFilter = THREE.NearestFilter + itemsTexture.minFilter = THREE.NearestFilter + const itemsTextureFlipped = itemsTexture.clone() + itemsTextureFlipped.repeat.x *= -1 + itemsTextureFlipped.needsUpdate = true + itemsTextureFlipped.offset.set(u + (su ?? size), 1 - v - (sv ?? size)) + const material = new THREE.MeshStandardMaterial({ + map: itemsTexture, + transparent: true, + alphaTest: 0.1, + }) + const materialFlipped = new THREE.MeshStandardMaterial({ + map: itemsTextureFlipped, + transparent: true, + alphaTest: 0.1, + }) + const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0), [ + // top left and right bottom are black box materials others are transparent + new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), + new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), + material, materialFlipped, + ]) + return { + mesh, + itemsTexture, + itemsTextureFlipped, + } + } + } + update(/** @type {import('prismarine-entity').Entity & {delete?, pos}} */entity, overrides) { let isPlayerModel = entity.name === 'player' if (entity.name === 'zombie' || entity.name === 'zombie_villager' || entity.name === 'husk') { @@ -296,52 +336,23 @@ export class Entities extends EventEmitter { //@ts-expect-error const item = entity.metadata?.find(m => typeof m === 'object' && m?.itemCount) if (item) { - const textureUv = this.getItemUv?.(item.itemId ?? item.blockId) - if (textureUv) { - // todo use geometry buffer uv instead! - const { u, v, size, su, sv, texture } = textureUv - const itemsTexture = texture.clone() - itemsTexture.flipY = true - itemsTexture.offset.set(u, 1 - v - (sv ?? size)) - itemsTexture.repeat.set(su ?? size, sv ?? size) - itemsTexture.needsUpdate = true - itemsTexture.magFilter = THREE.NearestFilter - itemsTexture.minFilter = THREE.NearestFilter - const itemsTextureFlipped = itemsTexture.clone() - itemsTextureFlipped.repeat.x *= -1 - itemsTextureFlipped.needsUpdate = true - itemsTextureFlipped.offset.set(u + (su ?? size), 1 - v - (sv ?? size)) - const material = new THREE.MeshStandardMaterial({ - map: itemsTexture, - transparent: true, - alphaTest: 0.1, - }) - const materialFlipped = new THREE.MeshStandardMaterial({ - map: itemsTextureFlipped, - transparent: true, - alphaTest: 0.1, - }) - mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0), [ - // top left and right bottom are black box materials others are transparent - new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), - new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), - material, materialFlipped, - ]) - mesh.scale.set(0.5, 0.5, 0.5) - mesh.position.set(0, 0.2, 0) + const object = this.getItemMesh(item) + if (object) { + object.scale.set(0.5, 0.5, 0.5) + object.position.set(0, 0.2, 0) // set faces // mesh.position.set(targetPos.x + 0.5 + 2, targetPos.y + 0.5, targetPos.z + 0.5) // viewer.scene.add(mesh) const clock = new THREE.Clock() - mesh.onBeforeRender = () => { + object.onBeforeRender = () => { const delta = clock.getDelta() - mesh.rotation.y += delta + object.rotation.y += delta } //@ts-expect-error group.additionalCleanup = () => { // important: avoid texture memory leak and gpu slowdown - itemsTexture.dispose() - itemsTextureFlipped.dispose() + object.itemsTexture.dispose() + object.itemsTextureFlipped.dispose() } } } diff --git a/prismarine-viewer/viewer/lib/holdingBlock.ts b/prismarine-viewer/viewer/lib/holdingBlock.ts index 25dd879f0..f5a0ca799 100644 --- a/prismarine-viewer/viewer/lib/holdingBlock.ts +++ b/prismarine-viewer/viewer/lib/holdingBlock.ts @@ -152,9 +152,10 @@ export default class HoldingBlock { async initHandObject (material: THREE.Material, blockstatesModels: any, blocksAtlases: any, block?: HandItemBlock) { let animatingCurrent = false if (!this.swingAnimation && !this.blockSwapAnimation && this.isDifferentItem(block)) { - console.log('play swap') animatingCurrent = true await this.playBlockSwapAnimation() + this.holdingBlock?.removeFromParent() + this.holdingBlock = undefined } this.lastHeldItem = block if (!block) { @@ -167,6 +168,11 @@ export default class HoldingBlock { const blockProvider = worldBlockProvider(blockstatesModels, blocksAtlases, 'latest') const models = blockProvider.getAllResolvedModels0_1(block, true) const blockInner = getThreeBlockModelGroup(material, models, undefined, 'plains', loadedData) + // const { mesh: itemMesh } = viewer.entities.getItemMesh({ + // itemId: 541, + // })! + // itemMesh.position.set(0.5, 0.5, 0.5) + // const blockInner = itemMesh blockInner.name = 'holdingBlock' const blockOuterGroup = new THREE.Group() blockOuterGroup.add(blockInner) @@ -185,6 +191,7 @@ export default class HoldingBlock { this.cameraGroup.add(this.objectOuterGroup) const rotation = -45 + -90 + // const rotation = -45 // should be for item this.holdingBlock.rotation.set(0, THREE.MathUtils.degToRad(rotation), 0, 'ZYX') // const scale = window.scale ?? 0.2 diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 5f6590f71..7cd759e40 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -119,6 +119,19 @@ export class Viewer { this.scene.add(mesh) } + demoItem () { + //@ts-expect-error + const pos = cursorBlockRel(0, 1, 0).position + const { mesh } = this.entities.getItemMesh({ + itemId: 541, + })! + mesh.position.set(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5) + // mesh.scale.set(0.5, 0.5, 0.5) + const helper = new THREE.BoxHelper(mesh, 0xff_ff_00) + mesh.add(helper) + this.scene.add(mesh) + } + updateEntity (e) { this.entities.update(e, this.processEntityOverrides(e, { rotation: { From 45fb8fa5925d501989edda06298497e7a62da548 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 03:08:17 +0300 Subject: [PATCH 09/10] workaround for recent regreession with mc data change --- src/inventoryWindows.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 64e589f99..e5964646e 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -87,8 +87,13 @@ export const onGameLoad = (onLoad) => { return } const craftingSlots = bot.inventory.slots.slice(1, 5) - const resultingItem = getResultingRecipe(craftingSlots, 2) - void bot.creative.setInventorySlot(craftingResultSlot, resultingItem ?? null) + try { + const resultingItem = getResultingRecipe(craftingSlots, 2) + void bot.creative.setInventorySlot(craftingResultSlot, resultingItem ?? null) + } catch (err) { + console.error(err) + // todo resolve the error! and why would we ever get here on every update? + } }) as any) bot.on('windowClose', () => { From 362c0106bcc1adc2a3fda1586ac2299d8a9916d1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 03:17:17 +0300 Subject: [PATCH 10/10] use block properties --- prismarine-viewer/viewer/lib/worldDataEmitter.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index 9a54e2a19..d832d3db2 100644 --- a/prismarine-viewer/viewer/lib/worldDataEmitter.ts +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -5,6 +5,7 @@ import { EventEmitter } from 'events' import { generateSpiralMatrix, ViewRect } from 'flying-squid/dist/utils' import { Vec3 } from 'vec3' import { BotEvents } from 'mineflayer' +import { getItemFromBlock } from '../../../src/botUtils' import { chunkPos } from './simpleUtils' export type ChunkPosKey = string @@ -95,9 +96,15 @@ export class WorldDataEmitter extends EventEmitter { viewer.world.onHandItemSwitch(undefined) return } - // todo properties const newItem = bot.heldItem - viewer.world.onHandItemSwitch(newItem ? { name: newItem.name, properties: {} } : undefined) + if (!newItem) { + viewer.world.onHandItemSwitch(undefined) + return + } + const block = loadedData.blocksByName[newItem.name] + // todo clean types + const blockProperties = block ? new window.PrismarineBlock(block.id, 'void', newItem.metadata).getProperties() : {} + viewer.world.onHandItemSwitch({ name: newItem.name, properties: blockProperties }) }, } satisfies Partial this.eventListeners.heldItemChanged()