feat: Display holding block (experimental setting) (#190)

This commit is contained in:
Vitaly 2024-09-01 03:32:53 +03:00 committed by GitHub
commit ee966395c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 489 additions and 57 deletions

1
experiments/three.html Normal file
View file

@ -0,0 +1 @@
<script type="module" src="three.ts"></script>

101
experiments/three.ts Normal file
View file

@ -0,0 +1,101 @@
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() {
return new Promise<THREE.ShapeGeometry>((resolve, reject) => {
const img = new Image();
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;
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().then(geometry => {
const material = new THREE.MeshBasicMaterial({ color: 0xffffff });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
})

View file

@ -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()
}
}
}

View file

@ -0,0 +1,207 @@
import * as THREE from 'three'
import * as tweenJs from '@tweenjs/tween.js'
import worldBlockProvider from 'mc-assets/dist/worldBlockProvider'
import { getThreeBlockModelGroup, renderBlockThree, setBlockPosition } from './mesher/standaloneRenderer'
export type HandItemBlock = {
name
properties
}
export default class HoldingBlock {
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()
}
initCameraGroup () {
this.cameraGroup = new THREE.Mesh()
this.scene.add(this.cameraGroup)
}
startSwing () {
this.nextIterStopCallbacks = undefined // forget about cancelling
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 * 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()
}
}
async stopSwing () {
if (!this.isSwinging) return
// might never resolve!
/* return */void new Promise<void>((resolve) => {
this.nextIterStopCallbacks ??= []
this.nextIterStopCallbacks.push(() => {
resolve()
})
})
}
update (camera: typeof this.camera) {
this.camera = camera
this.swingAnimation?.update()
this.blockSwapAnimation?.tween.update()
this.updateCameraGroup()
}
// 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<void>((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)) {
animatingCurrent = true
await this.playBlockSwapAnimation()
this.holdingBlock?.removeFromParent()
this.holdingBlock = undefined
}
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(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)
this.holdingBlock = blockInner
this.objectInnerGroup = new THREE.Group()
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 + -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
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)
if (animatingCurrent) {
await this.playBlockSwapAnimation()
}
}
}

View file

@ -272,3 +272,19 @@ export const renderBlockThree = (...args: Parameters<typeof renderBlockThreeAttr
return geometry
}
export const getThreeBlockModelGroup = (material: THREE.Material, ...args: Parameters<typeof renderBlockThree>) => {
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)
}

View file

@ -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,32 @@ 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)
}
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)

View file

@ -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
@ -20,6 +21,14 @@ export class WorldDataEmitter extends EventEmitter {
private eventListeners: Record<string, any> = {}
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 +64,7 @@ export class WorldDataEmitter extends EventEmitter {
})
}
this.eventListeners[bot.username] = {
this.eventListeners = {
// 'move': botPosition,
entitySpawn (e: any) {
emitEntity(e)
@ -70,7 +79,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)
},
chunkColumnUnload: (pos: Vec3) => {
this.unloadChunk(pos)
@ -82,7 +91,24 @@ export class WorldDataEmitter extends EventEmitter {
time: () => {
this.emitter.emit('time', bot.time.timeOfDay)
},
heldItemChanged: () => {
if (!this.handDisplay) {
viewer.world.onHandItemSwitch(undefined)
return
}
const newItem = bot.heldItem
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<BotEvents>
this.eventListeners.heldItemChanged()
bot._client.on('update_light', ({ chunkX, chunkZ }) => {
const chunkPos = new Vec3(chunkX * 16, 0, chunkZ * 16)
@ -105,7 +131,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)
}
@ -116,10 +142,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) {

View file

@ -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<WorkerSend = any, WorkerReceive = any> {
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<WorkerSend = any, WorkerReceive = any>
textureDownloaded (): void
}>
customTexturesDataUrl = undefined as string | undefined
@worldCleanup()
currentTextureImage = undefined as any
workers: any[] = []
viewerPosition?: Vec3
@ -157,6 +160,9 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
}
}
onHandItemSwitch (item: HandItemBlock | undefined): void { }
changeHandSwingingState (isAnimationPlaying: boolean): void { }
abstract handleWorkerMessage (data: WorkerReceive): void
abstract updateCamera (pos: Vec3 | null, yaw: number, pitch: number): void

View file

@ -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, { HandItemBlock } from './holdingBlock'
export class WorldRendererThree extends WorldRendererCommon {
outputFormat = 'threeJs' as const
@ -19,7 +19,7 @@ export class WorldRendererThree extends WorldRendererCommon {
signsCache = new Map<string, any>()
starField: StarField
cameraSectionPos: Vec3 = new Vec3(0, 0, 0)
cameraGroup = new THREE.Group()
holdingBlock: HoldingBlock
get tilesRendered () {
return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0)
@ -28,8 +28,34 @@ 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.initHandObject()
this.holdingBlock = new HoldingBlock(this.scene)
this.onHandItemSwitch({
name: 'furnace',
properties: {}
})
this.renderUpdateEmitter.on('textureDownloaded', () => {
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)
}
changeHandSwingingState (isAnimationPlaying: boolean) {
if (isAnimationPlaying) {
this.holdingBlock.startSwing()
} else {
void this.holdingBlock.stopSwing()
}
}
timeUpdated (newTime: number): void {
@ -173,6 +199,7 @@ 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.renderer.render(this.scene, cam)

View file

@ -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', () => {

View file

@ -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: [

View file

@ -47,6 +47,7 @@ const defaultOptions = {
enabledResourcepack: null as string | null,
useVersionsTextures: 'latest',
serverResourcePacks: 'prompt' as 'prompt' | 'always' | 'never',
handDisplay: false,
// antiAliasing: false,

View file

@ -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)

View file

@ -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
})
}

View file

@ -294,6 +294,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
@ -351,11 +353,15 @@ class WorldInteraction {
})
customEvents.emit('digStart')
this.lastDigged = Date.now()
viewer.world.changeHandSwingingState(true)
} else if (performance.now() - this.lastSwing > 200) {
bot.swingArm('right')
this.lastSwing = performance.now()
}
}
if (!this.buttons[0] && this.lastButtons[0]) {
viewer.world.changeHandSwingingState(false)
}
this.prevOnGround = onGround
// Show cursor