lint prismarine-viewer & better ci checks (#171)

This commit is contained in:
Vitaly 2024-08-06 03:20:38 +03:00 committed by GitHub
commit d4f06aa1a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
204 changed files with 2096 additions and 6242 deletions

View file

@ -1,4 +1,6 @@
node_modules
rsbuild.config.ts
*.module.css.d.ts
*.generated.ts
generated
public

View file

@ -1,33 +1,75 @@
{
"extends": "zardoy",
"extends": [
"zardoy",
"plugin:@stylistic/disable-legacy"
],
"ignorePatterns": [
"!*.js",
"prismarine-viewer/"
"!*.js"
],
"plugins": [
"@stylistic"
],
"rules": {
"space-infix-ops": "error",
"no-multi-spaces": "error",
"no-trailing-spaces": "error",
"space-before-function-paren": "error",
"space-in-parens": [
// style
"@stylistic/space-infix-ops": "error",
"@stylistic/no-multi-spaces": "error",
"@stylistic/no-trailing-spaces": "error",
"@stylistic/space-before-function-paren": "error",
"@stylistic/array-bracket-spacing": "error",
// would be great to have but breaks TS code like (url?) => ...
// "@stylistic/arrow-parens": [
// "error",
// "as-needed"
// ],
"@stylistic/arrow-spacing": "error",
"@stylistic/block-spacing": "error",
"@stylistic/brace-style": [
"error",
"1tbs",
{
"allowSingleLine": true
}
],
// too annoying to be forced to multi-line, probably should be enforced to never
// "@stylistic/comma-dangle": [
// "error",
// "always-multiline"
// ],
"@stylistic/computed-property-spacing": "error",
"@stylistic/dot-location": [
"error",
"property"
],
"@stylistic/eol-last": "error",
"@stylistic/function-call-spacing": "error",
"@stylistic/function-paren-newline": [
"error",
"consistent"
],
"@stylistic/generator-star-spacing": "error",
"@stylistic/implicit-arrow-linebreak": "error",
"@stylistic/indent-binary-ops": [
"error",
2
],
"@stylistic/function-call-argument-newline": [
"error",
"consistent"
],
"@stylistic/space-in-parens": [
"error",
"never"
],
"object-curly-spacing": [
"@stylistic/object-curly-spacing": [
"error",
"always"
],
"comma-spacing": "error",
"semi": [
"@stylistic/comma-spacing": "error",
"@stylistic/semi": [
"error",
"never"
],
"comma-dangle": [
"error",
// todo maybe "always-multiline"?
"only-multiline"
],
"indent": [
"@stylistic/indent": [
"error",
2,
{
@ -37,13 +79,30 @@
]
}
],
"quotes": [
"@stylistic/quotes": [
"error",
"single",
{
"allowTemplateLiterals": true
}
],
"@stylistic/key-spacing": "error",
"@stylistic/keyword-spacing": "error",
// "@stylistic/line-comment-position": "error", // not needed
// "@stylistic/lines-around-comment": "error", // also not sure if needed
// "@stylistic/max-len": "error", // also not sure if needed
// "@stylistic/linebreak-style": "error", // let git decide
"@stylistic/max-statements-per-line": [
"error",
{
"max": 5
}
],
// "@stylistic/member-delimiter-style": "error",
// "@stylistic/multiline-ternary": "error", // not needed
// "@stylistic/newline-per-chained-call": "error", // not sure if needed
"@stylistic/new-parens": "error",
"@stylistic/no-confusing-arrow": "error",
// perf
"import/no-deprecated": "off",
// ---
@ -53,6 +112,7 @@
// intentional: improve readability in some cases
"no-else-return": "off",
"@typescript-eslint/padding-line-between-statements": "off",
"@typescript-eslint/no-dynamic-delete": "off",
"arrow-body-style": "off",
"unicorn/prefer-ternary": "off",
"unicorn/switch-case-braces": "off",
@ -89,6 +149,7 @@
"@typescript-eslint/no-confusing-void-expression": "off",
"unicorn/no-empty-file": "off",
"unicorn/prefer-event-target": "off",
"@typescript-eslint/member-ordering": "off",
// needs to be fixed actually
"complexity": "off",
"@typescript-eslint/no-floating-promises": "warn",
@ -103,7 +164,7 @@
"*.js"
],
"rules": {
"space-before-function-paren": [
"@stylistic/space-before-function-paren": [
"error",
{
"anonymous": "always",

View file

@ -21,6 +21,8 @@ jobs:
# cache: "pnpm"
- run: pnpm install
- run: pnpm check-build
- run: pnpm build-playground
- run: pnpm build-storybook
- run: pnpm test-unit
- run: pnpm lint
- run: pnpm tsx scripts/buildNpmReact.ts

View file

@ -23,7 +23,7 @@ jobs:
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- run: pnpm build-storybook
- name: Copy playground files
run: node prismarine-viewer/esbuild.mjs && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js
run: pnpm build-playground && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js
- name: Download Generated Sounds map
run: node scripts/downloadSoundsMap.mjs
- name: Deploy Project Artifacts to Vercel

View file

@ -35,7 +35,7 @@ jobs:
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- run: pnpm build-storybook
- name: Copy playground files
run: node prismarine-viewer/esbuild.mjs && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js
run: pnpm build-playground && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js
- name: Download Generated Sounds map
run: node scripts/downloadSoundsMap.mjs
- name: Deploy Project Artifacts to Vercel

View file

@ -22,7 +22,7 @@ jobs:
- run: vercel build --token=${{ secrets.VERCEL_TOKEN }} --prod
- run: pnpm build-storybook
- name: Copy playground files
run: node prismarine-viewer/esbuild.mjs && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js
run: pnpm build-playground && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js
- name: Download Generated Sounds map
run: node scripts/downloadSoundsMap.mjs
- name: Deploy Project to Vercel

1
.vscode/launch.json vendored
View file

@ -1,6 +1,5 @@
{
"configurations": [
// UPDATED: all configs below are misconfigured and will crash vscode, open dist/index.html and use live preview debug instead
// recommended as much faster
{
// to launch "C:\Program Files\Google\Chrome Beta\Application\chrome.exe" --remote-debugging-port=9222

View file

@ -15,7 +15,7 @@
"test:e2e": "start-test http-get://localhost:8080 test:cypress",
"prod-start": "node server.js --prod",
"test-mc-server": "tsx cypress/minecraft-server.mjs",
"lint": "eslint \"{src,cypress}/**/*.{ts,js,jsx,tsx}\"",
"lint": "eslint \"{src,cypress,prismarine-viewer}/**/*.{ts,js,jsx,tsx}\"",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build && node scripts/build.js moveStorybookFiles",
"start-experiments": "vite --config experiments/vite.config.ts --host",
@ -25,6 +25,7 @@
"run-playground": "run-p watch-mesher watch-other-workers playground-server watch-playground",
"run-all": "run-p start run-playground",
"playground-server": "live-server --port=9090 prismarine-viewer/public",
"build-playground": "node prismarine-viewer/esbuild.mjs",
"watch-playground": "node prismarine-viewer/esbuild.mjs -w"
},
"keywords": [
@ -46,6 +47,7 @@
"@nxg-org/mineflayer-auto-jump": "^0.7.7",
"@nxg-org/mineflayer-tracker": "^1.2.1",
"@react-oauth/google": "^0.12.1",
"@stylistic/eslint-plugin": "^2.6.1",
"@types/gapi": "^0.0.47",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
@ -106,11 +108,11 @@
"workbox-build": "^7.0.0"
},
"devDependencies": {
"@rsbuild/core": "1.0.1-beta.4",
"@rsbuild/core": "^1.0.1-beta.9",
"@rsbuild/plugin-node-polyfill": "^1.0.3",
"@rsbuild/plugin-type-check": "1.0.1-beta.4",
"@rsbuild/plugin-react": "^1.0.1-beta.9",
"@rsbuild/plugin-type-check": "^1.0.1-beta.9",
"@rsbuild/plugin-typed-css-modules": "^1.0.1",
"@rsbuild/plugin-react": "^1.0.1-beta.4",
"@storybook/addon-essentials": "^7.4.6",
"@storybook/addon-links": "^7.4.6",
"@storybook/blocks": "^7.4.6",
@ -138,7 +140,7 @@
"http-browserify": "^1.7.0",
"http-server": "^14.1.1",
"https-browserify": "^1.0.0",
"mc-assets": "^0.2.6",
"mc-assets": "^0.2.7",
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
"mineflayer": "github:zardoy/mineflayer",
"mineflayer-pathfinder": "^2.4.4",

459
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -2,8 +2,8 @@ import { Vec3 } from 'vec3'
import { ExampleSetupFunction } from './type'
const setup: ExampleSetupFunction = (world, mcData, mesherConfig, setupParam) => {
mesherConfig.debugModelVariant = [3]
world.setBlockStateId(new Vec3(0, 0, 0), mcData.blocksByName.sand.defaultState)
mesherConfig.debugModelVariant = [3]
void world.setBlockStateId(new Vec3(0, 0, 0), mcData.blocksByName.sand.defaultState!)
}
export default setup

View file

@ -1,6 +1,6 @@
import { CustomWorld } from 'flying-squid/dist/lib/modules/world'
import { MesherConfig } from '../../viewer/lib/mesher/shared'
import { IndexedData } from 'minecraft-data'
import { MesherConfig } from '../../viewer/lib/mesher/shared'
type SetupParams = {}
export type ExampleSetupFunction = (world: CustomWorld, mcData: IndexedData, mesherConfig: MesherConfig, setupParam: SetupParams) => void

View file

@ -1,22 +1,24 @@
import _ from 'lodash'
import { WorldDataEmitter, Viewer } from '../viewer'
import { Vec3 } from 'vec3'
import BlockLoader from 'prismarine-block'
import ChunkLoader from 'prismarine-chunk'
import WorldLoader from 'prismarine-world'
import * as THREE from 'three'
import { GUI } from 'lil-gui'
import { loadScript } from '../viewer/lib/utils'
import JSZip from 'jszip'
import { TWEEN_DURATION } from '../viewer/lib/entities'
import { EntityMesh } from '../viewer/lib/entity/EntityMesh'
import blockstatesModels from 'mc-assets/dist/blockStatesModels.json'
globalThis.THREE = THREE
//@ts-ignore
//@ts-expect-error
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { IndexedData } from 'minecraft-data'
import { loadScript } from '../viewer/lib/utils'
import { TWEEN_DURATION } from '../viewer/lib/entities'
import { EntityMesh } from '../viewer/lib/entity/EntityMesh'
import { WorldDataEmitter, Viewer } from '../viewer'
import { toMajorVersion } from '../../src/utils'
window.THREE = THREE
const gui = new GUI()
// initial values
@ -44,18 +46,17 @@ const params = {
}
const qs = new URLSearchParams(window.location.search)
qs.forEach((value, key) => {
const parsed = value.match(/^-?\d+$/) ? parseInt(value) : value === 'true' ? true : value === 'false' ? false : value
for (const [key, value] of qs.entries()) {
const parsed = /^-?\d+$/.test(value) ? Number(value) : value === 'true' ? true : value === 'false' ? false : value
params[key] = parsed
})
}
const setQs = () => {
const newQs = new URLSearchParams()
for (const [key, value] of Object.entries(params)) {
if (!value || typeof value === 'function' || params.skipQs.includes(key)) continue
//@ts-ignore
newQs.set(key, value)
}
window.history.replaceState({}, '', `${window.location.pathname}?${newQs}`)
window.history.replaceState({}, '', `${window.location.pathname}?${newQs.toString()}`)
}
let ignoreResize = false
@ -80,7 +81,7 @@ async function main () {
}
}
const mcData = require('minecraft-data')(version)
const mcData: IndexedData = require('minecraft-data')(version)
window['loadedData'] = mcData
gui.add(params, 'version', globalThis.includedVersions)
@ -110,17 +111,17 @@ async function main () {
// const diamondSquare = require('diamond-square')({ version, seed: Math.floor(Math.random() * Math.pow(2, 31)) })
//@ts-ignore
//@ts-expect-error
const chunk1 = new Chunk()
//@ts-ignore
//@ts-expect-error
const chunk2 = new Chunk()
chunk1.setBlockStateId(targetPos, 34)
chunk2.setBlockStateId(targetPos.offset(1, 0, 0), 34)
//@ts-ignore
//@ts-expect-error
const world = new World((chunkX, chunkZ) => {
// if (chunkX === 0 && chunkZ === 0) return chunk1
// if (chunkX === 1 && chunkZ === 0) return chunk2
//@ts-ignore
//@ts-expect-error
const chunk = new Chunk()
return chunk
})
@ -156,7 +157,7 @@ async function main () {
const onlyCurrent = !confirm('Ok - render all blocks, Cancel - render only current one')
const sizeRaw = prompt('Size', '512')
if (!sizeRaw) return
const size = parseInt(sizeRaw)
const size = parseInt(sizeRaw, 10)
// const size = 512
ignoreResize = true
@ -164,7 +165,7 @@ async function main () {
canvas.height = size
renderer.setSize(size, size)
//@ts-ignore
//@ts-expect-error
viewer.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 10)
viewer.scene.background = null
@ -190,15 +191,13 @@ async function main () {
let blockName = allBlocks[0]
const updateBlock = () => {
//@ts-ignore
// viewer.setBlockStateId(targetPos, mcData.blocksByName[blockName].minStateId)
params.block = blockName
// todo cleanup (introduce getDefaultState)
onUpdate.block()
applyChanges(false, true)
}
viewer.waitForChunksToRender().then(async () => {
void viewer.waitForChunksToRender().then(async () => {
// wait for next macro task
await new Promise(resolve => {
setTimeout(resolve, 0)
@ -261,7 +260,6 @@ async function main () {
}
}
//@ts-ignore
const controls = new OrbitControls(viewer.camera, renderer.domElement)
controls.target.set(targetPos.x + 0.5, targetPos.y + 0.5, targetPos.z + 0.5)
@ -274,7 +272,7 @@ async function main () {
controls.update()
let blockProps = {}
let entityOverrides = {}
const entityOverrides = {}
const getBlock = () => {
return mcData.blocksByName[params.block || 'air']
}
@ -304,7 +302,7 @@ async function main () {
const onUpdate = {
version (initialUpdate) {
if (initialUpdate) return
// if (initialUpdate) return
// viewer.world.texturesVersion = params.version
// viewer.world.updateTexturesData()
// todo warning
@ -316,7 +314,7 @@ async function main () {
if (!block) return
console.log('block', block.name)
const props = new Block(block.id, 0, 0).getProperties()
//@ts-ignore
//@ts-expect-error
const { states } = mcData.blocksByStateId[getBlock()?.minStateId] ?? {}
metadataFolder = gui.addFolder('metadata')
if (states) {
@ -397,7 +395,6 @@ async function main () {
}
} else {
try {
//@ts-ignore
block = Block.fromProperties(blockId ?? -1, blockProps, 0)
} catch (err) {
console.error(err)
@ -405,7 +402,7 @@ async function main () {
}
}
//@ts-ignore
//@ts-expect-error
viewer.setBlockStateId(targetPos, block.stateId)
console.log('up stateId', block.stateId)
params.metadata = block.metadata
@ -423,7 +420,7 @@ async function main () {
applyChanges()
}
})
viewer.waitForChunksToRender().then(async () => {
void viewer.waitForChunksToRender().then(async () => {
// TODO!
await new Promise(resolve => {
setTimeout(resolve, 50)

View file

@ -1,6 +0,0 @@
module.exports = {
mineflayer: require('./lib/mineflayer'),
standalone: require('./lib/standalone'),
headless: require('./lib/headless'),
viewer: require('./viewer'),
}

View file

@ -1,12 +0,0 @@
const path = require('path')
const compression = require('compression')
const express = require('express')
function setupRoutes (app, prefix = '') {
app.use(compression())
app.use(prefix + '/', express.static(path.join(__dirname, '../public')))
}
module.exports = {
setupRoutes
}

View file

@ -1,135 +0,0 @@
/* global THREE */
function safeRequire (path) {
try {
return require(path)
} catch (e) {
return {}
}
}
const { spawn } = require('child_process')
const net = require('net')
global.THREE = require('three')
global.Worker = require('worker_threads').Worker
const { createCanvas } = safeRequire('node-canvas-webgl/lib')
const { WorldDataEmitter, Viewer, getBufferFromStream } = require('../viewer')
module.exports = (bot, { viewDistance = 6, output = 'output.mp4', frames = -1, width = 512, height = 512, logFFMPEG = false, jpegOptions }) => {
const canvas = createCanvas(width, height)
const renderer = new THREE.WebGLRenderer({ canvas })
const viewer = new Viewer(renderer)
viewer.setVersion(bot.version)
viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch)
// Load world
const worldView = new WorldDataEmitter(bot.world, viewDistance, bot.entity.position)
viewer.listen(worldView)
worldView.init(bot.entity.position)
function botPosition () {
viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch)
worldView.updatePosition(bot.entity.position)
}
// Render loop streaming
const rtmpOutput = output.startsWith('rtmp://')
const ffmpegOutput = output.endsWith('mp4')
let client = null
if (rtmpOutput) {
const fps = 20
const gop = fps * 2
const gopMin = fps
const probesize = '42M'
const cbr = '1000k'
const threads = 4
const args = `-y -r ${fps} -probesize ${probesize} -i pipe:0 -f flv -ac 2 -ar 44100 -vcodec libx264 -g ${gop} -keyint_min ${gopMin} -b:v ${cbr} -minrate ${cbr} -maxrate ${cbr} -pix_fmt yuv420p -s 1280x720 -preset ultrafast -tune film -threads ${threads} -strict normal -bufsize ${cbr} ${output}`.split(' ')
client = spawn('ffmpeg', args)
if (logFFMPEG) {
client.stdout.on('data', (data) => {
console.log(`stdout: ${data}`)
})
client.stderr.on('data', (data) => {
console.error(`stderr: ${data}`)
})
}
update()
} else if (ffmpegOutput) {
client = spawn('ffmpeg', ['-y', '-i', 'pipe:0', output])
if (logFFMPEG) {
client.stdout.on('data', (data) => {
console.log(`stdout: ${data}`)
})
client.stderr.on('data', (data) => {
console.error(`stderr: ${data}`)
})
}
update()
} else {
const [host, port] = output.split(':')
client = new net.Socket()
client.connect(parseInt(port, 10), host, () => {
update()
})
}
// Force end of stream
bot.on('end', () => { frames = 0 })
let idx = 0
function update () {
viewer.update()
renderer.render(viewer.scene, viewer.camera)
const imageStream = canvas.createJPEGStream({
bufsize: 4096,
quality: 1,
progressive: false,
...jpegOptions
})
if (rtmpOutput || ffmpegOutput) {
imageStream.on('data', (chunk) => {
if (client.stdin.writable) {
client.stdin.write(chunk)
} else {
console.log('Error: ffmpeg stdin closed!')
}
})
imageStream.on('end', () => {
idx++
if (idx < frames || frames < 0) {
setTimeout(update, 16)
} else {
console.log('done streaming')
client.stdin.end()
}
})
imageStream.on('error', () => { })
} else {
getBufferFromStream(imageStream).then((buffer) => {
const sizebuff = new Uint8Array(4)
const view = new DataView(sizebuff.buffer, 0)
view.setUint32(0, buffer.length, true)
client.write(sizebuff)
client.write(buffer)
idx++
if (idx < frames || frames < 0) {
setTimeout(update, 16)
} else {
client.end()
}
}).catch(() => {})
}
}
// Register events
bot.on('move', botPosition)
worldView.listenToBot(bot)
return client
}

View file

@ -1,71 +0,0 @@
/* global THREE */
global.THREE = require('three')
const TWEEN = require('@tweenjs/tween.js')
require('three/examples/js/controls/OrbitControls')
const { Viewer, Entity } = require('../viewer')
const io = require('socket.io-client')
const socket = io()
let firstPositionUpdate = true
const renderer = new THREE.WebGLRenderer()
renderer.setPixelRatio(window.devicePixelRatio || 1)
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const viewer = new Viewer(renderer)
let controls = new THREE.OrbitControls(viewer.camera, renderer.domElement)
function animate () {
window.requestAnimationFrame(animate)
if (controls) controls.update()
viewer.update()
renderer.render(viewer.scene, viewer.camera)
}
animate()
window.addEventListener('resize', () => {
viewer.camera.aspect = window.innerWidth / window.innerHeight
viewer.camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
socket.on('version', (version) => {
viewer.setVersion(version)
firstPositionUpdate = true
viewer.listen(socket)
let botMesh
socket.on('position', ({ pos, addMesh, yaw, pitch }) => {
if (yaw !== undefined && pitch !== undefined) {
if (controls) {
controls.dispose()
controls = null
}
viewer.setFirstPersonCamera(pos, yaw, pitch)
return
}
if (pos.y > 0 && firstPositionUpdate) {
controls.target.set(pos.x, pos.y, pos.z)
viewer.camera.position.set(pos.x, pos.y + 20, pos.z + 20)
controls.update()
firstPositionUpdate = false
}
if (addMesh) {
if (!botMesh) {
botMesh = new Entity('1.16.4', 'player', viewer.scene).mesh
viewer.scene.add(botMesh)
}
new TWEEN.Tween(botMesh.position).to({ x: pos.x, y: pos.y, z: pos.z }, 50).start()
const da = (yaw - botMesh.rotation.y) % (Math.PI * 2)
const dy = 2 * da % (Math.PI * 2) - da
new TWEEN.Tween(botMesh.rotation).to({ y: botMesh.rotation.y + dy }, 50).start()
}
})
})

View file

@ -1,91 +0,0 @@
const EventEmitter = require('events')
const { WorldDataEmitter } = require('../viewer')
module.exports = (bot, { viewDistance = 6, firstPerson = false, port = 3000, prefix = '' }) => {
const express = require('express')
const app = express()
const http = require('http').createServer(app)
const io = require('socket.io')(http, { path: prefix + '/socket.io' })
const { setupRoutes } = require('./common')
setupRoutes(app, prefix)
const sockets = []
const primitives = {}
bot.viewer = new EventEmitter()
bot.viewer.erase = (id) => {
delete primitives[id]
for (const socket of sockets) {
socket.emit('primitive', { id })
}
}
bot.viewer.drawBoxGrid = (id, start, end, color = 'aqua') => {
primitives[id] = { type: 'boxgrid', id, start, end, color }
for (const socket of sockets) {
socket.emit('primitive', primitives[id])
}
}
bot.viewer.drawLine = (id, points, color = 0xff0000) => {
primitives[id] = { type: 'line', id, points, color }
for (const socket of sockets) {
socket.emit('primitive', primitives[id])
}
}
bot.viewer.drawPoints = (id, points, color = 0xff0000, size = 5) => {
primitives[id] = { type: 'points', id, points, color, size }
for (const socket of sockets) {
socket.emit('primitive', primitives[id])
}
}
io.on('connection', (socket) => {
socket.emit('version', bot.version)
sockets.push(socket)
const worldView = new WorldDataEmitter(bot.world, viewDistance, bot.entity.position, socket)
worldView.init(bot.entity.position)
worldView.on('blockClicked', (block, face, button) => {
bot.viewer.emit('blockClicked', block, face, button)
})
for (const id in primitives) {
socket.emit('primitive', primitives[id])
}
function botPosition () {
const packet = { pos: bot.entity.position, yaw: bot.entity.yaw, addMesh: true }
if (firstPerson) {
packet.pitch = bot.entity.pitch
}
socket.emit('position', packet)
worldView.updatePosition(bot.entity.position)
}
bot.on('move', botPosition)
worldView.listenToBot(bot)
socket.on('disconnect', () => {
bot.removeListener('move', botPosition)
worldView.removeListenersFromBot(bot)
sockets.splice(sockets.indexOf(socket), 1)
})
})
http.listen(port, () => {
console.log(`Prismarine viewer web server running on *:${port}`)
})
bot.viewer.close = () => {
http.close()
for (const socket of sockets) {
socket.disconnect()
}
}
}

View file

@ -1,52 +0,0 @@
const { Vec3 } = require('vec3')
module.exports = ({ version, world, center = new Vec3(0, 0, 0), viewDistance = 4, port = 3000, prefix = '' }) => {
const express = require('express')
const app = express()
const http = require('http').createServer(app)
const io = require('socket.io')(http)
const { setupRoutes } = require('./common')
setupRoutes(app, prefix)
const sockets = []
const viewer = { world }
async function sendChunks (sockets) {
const cx = Math.floor(center.x / 16)
const cz = Math.floor(center.z / 16)
for (let x = cx - viewDistance; x <= cx + viewDistance; x++) {
for (let z = cz - viewDistance; z <= cz + viewDistance; z++) {
const chunk = (await viewer.world.getColumn(x, z)).toJson()
for (const socket of sockets) {
socket.emit('loadChunk', { x: x * 16, z: z * 16, chunk })
}
}
}
}
viewer.update = () => {
sendChunks(sockets)
}
io.on('connection', (socket) => {
socket.emit('version', version)
sockets.push(socket)
sendChunks([socket])
socket.emit('position', { pos: center, addMesh: false })
socket.on('disconnect', () => {
sockets.splice(sockets.indexOf(socket), 1)
})
})
http.listen(port, () => {
console.log(`Prismarine viewer web server running on *:${port}`)
})
return viewer
}

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Prismarine Viewer Playground</title>
<title>Renderer Playground</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover'>
<style type="text/css">
html {

View file

@ -1,7 +1,6 @@
module.exports = {
Viewer: require('./lib/viewer').Viewer,
WorldDataEmitter: require('./lib/worldDataEmitter').WorldDataEmitter,
MapControls: require('./lib/controls').MapControls,
Entity: require('./lib/entity/EntityMesh'),
getBufferFromStream: require('./lib/simpleUtils').getBufferFromStream
}

View file

@ -1,6 +1,6 @@
export function buildCleanupDecorator (cleanupMethod: string) {
return function () {
return function (_target: {snapshotInitialValues}, propertyKey: string) {
return function (_target: { snapshotInitialValues }, propertyKey: string) {
const target = _target as any
// Store the initial value of the property
if (!target._snapshotMethodPatched) {
@ -19,7 +19,8 @@ export function buildCleanupDecorator (cleanupMethod: string) {
for (const key of target._toCleanup) {
this[key] = this._initialValues[key]
}
originalMethod.apply(this, arguments)
// eslint-disable-next-line prefer-rest-params
Reflect.apply(originalMethod, this, arguments)
}
}
target._cleanupPatched = true

View file

@ -1,923 +0,0 @@
/* eslint-disable */
// Similar to THREE MapControls with more Minecraft-like
// controls.
// Defaults:
// Shift = Move Down, Space = Move Up
// W/Z - north, S - south, A/Q - west, D - east
const STATE = {
NONE: -1,
ROTATE: 0,
DOLLY: 1,
PAN: 2,
TOUCH_ROTATE: 3,
TOUCH_PAN: 4,
TOUCH_DOLLY_PAN: 5,
TOUCH_DOLLY_ROTATE: 6
}
class MapControls {
constructor(camera, domElement) {
this.enabled = true
this.object = camera
this.element = domElement
// Mouse buttons
this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }
// Touch fingers
this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN }
this.controlMap = {
MOVE_FORWARD: ['KeyW', 'KeyZ'],
MOVE_BACKWARD: 'KeyS',
MOVE_LEFT: ['KeyA', 'KeyQ'],
MOVE_RIGHT: 'KeyD',
MOVE_DOWN: 'ShiftLeft',
MOVE_UP: 'Space'
}
this.target = new THREE.Vector3()
// How far you can dolly in and out ( PerspectiveCamera only )
this.minDistance = 0
this.maxDistance = Infinity
// How far you can zoom in and out ( OrthographicCamera only )
this.minZoom = 0
this.maxZoom = Infinity
// How far you can orbit vertically, upper and lower limits.
// Range is 0 to Math.PI radians.
this.minPolarAngle = 0 // radians
this.maxPolarAngle = Math.PI // radians
// How far you can orbit horizontally, upper and lower limits.
// If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
this.minAzimuthAngle = -Infinity // radians
this.maxAzimuthAngle = Infinity // radians
// Set to true to enable damping (inertia)
// If damping is enabled, you must call controls.update() in your animation loop
this.enableDamping = false
this.dampingFactor = 0.01
// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
// Set to false to disable zooming
this.enableZoom = true
this.enableTouchZoom = true
this.zoomSpeed = 1.0
// Set to false to disable rotating
this.enableRotate = true
this.enableTouchRotate = true
this.rotateSpeed = 1.0
// Set to false to disable panning
this.enablePan = true
this.enableTouchPan = true
this.panSpeed = 1.0
this.screenSpacePanning = false // if false, pan orthogonal to world-space direction camera.up
this.keyPanDistance = 32 // how far to pan
this.keyPanSpeed = 10 // pixels moved per arrow key push
this.verticalTranslationSpeed = 0.5 // how much Y increments moving up/down
this.keyDowns = []
// State-related stuff
this.changeEvent = { type: 'change' }
this.startEvent = { type: 'start' }
this.endEvent = { type: 'end' }
this.state = STATE.NONE
this.EPS = 0.000001
this.spherical = new THREE.Spherical()
this.sphericalDelta = new THREE.Spherical()
this.scale = 1
this.panOffset = new THREE.Vector3()
this.zoomChanged = false
this.rotateStart = new THREE.Vector2()
this.rotateEnd = new THREE.Vector2()
this.rotateDelta = new THREE.Vector2()
this.panStart = new THREE.Vector2()
this.panEnd = new THREE.Vector2()
this.panDelta = new THREE.Vector2()
this.dollyStart = new THREE.Vector2()
this.dollyEnd = new THREE.Vector2()
this.dollyDelta = new THREE.Vector2()
// for reset
this.target0 = this.target.clone()
this.position0 = this.object.position.clone()
this.zoom0 = this.object.zoom
this.ticks = 0
// register event handlers
this.onPointerMove = this.onPointerMove.bind(this)
this.onPointerUp = this.onPointerUp.bind(this)
this.onPointerDown = this.onPointerDown.bind(this)
this.onMouseWheel = this.onMouseWheel.bind(this)
this.onTouchStart = this.onTouchStart.bind(this)
this.onTouchEnd = this.onTouchEnd.bind(this)
this.onTouchMove = this.onTouchMove.bind(this)
this.onContextMenu = this.onContextMenu.bind(this)
this.onKeyDown = this.onKeyDown.bind(this)
this.onKeyUp = this.onKeyUp.bind(this)
this.registerHandlers()
}
//#region Public Methods
setRotationOrigin(position) {
this.target = position.clone()
}
unsetRotationOrigin() {
this.target = new THREE.Vector3()
}
getPolarAngle() {
return this.spherical.phi
}
getAzimuthalAngle() {
return this.spherical.theta
}
saveState() {
this.target0.copy(this.target)
this.position0.copy(this.object.position)
this.zoom0 = this.object.zoom
}
reset() {
this.target.copy(this.target0)
this.object.position.copy(this.position0)
this.object.zoom = this.zoom0
this.object.updateProjectionMatrix()
this.dispatchEvent(this.changeEvent)
this.update(true)
this.state = STATE.NONE
}
// this method is exposed, but perhaps it would be better if we can make it private...
update(force) {
// tick controls if called from render loop
if (!force) {
this.tickControls()
}
var offset = new THREE.Vector3()
// so camera.up is the orbit axis
var quat = new THREE.Quaternion().setFromUnitVectors(this.object.up, new THREE.Vector3(0, 1, 0))
var quatInverse = quat.clone().invert()
var lastPosition = new THREE.Vector3()
var lastQuaternion = new THREE.Quaternion()
var twoPI = 2 * Math.PI
var position = this.object.position
offset.copy(position).sub(this.target)
// rotate offset to "y-axis-is-up" space
offset.applyQuaternion(quat)
// angle from z-axis around y-axis
this.spherical.setFromVector3(offset)
if (this.autoRotate && this.state === STATE.NONE) {
this.rotateLeft(this.getAutoRotationAngle())
}
if (this.enableDamping) {
this.spherical.theta += this.sphericalDelta.theta * this.dampingFactor
this.spherical.phi += this.sphericalDelta.phi * this.dampingFactor
} else {
this.spherical.theta += this.sphericalDelta.theta
this.spherical.phi += this.sphericalDelta.phi
}
// restrict theta to be between desired limits
var min = this.minAzimuthAngle
var max = this.maxAzimuthAngle
if (isFinite(min) && isFinite(max)) {
if (min < - Math.PI) min += twoPI; else if (min > Math.PI) min -= twoPI
if (max < - Math.PI) max += twoPI; else if (max > Math.PI) max -= twoPI
if (min < max) {
this.spherical.theta = Math.max(min, Math.min(max, this.spherical.theta))
} else {
this.spherical.theta = (this.spherical.theta > (min + max) / 2) ?
Math.max(min, this.spherical.theta) :
Math.min(max, this.spherical.theta)
}
}
// restrict phi to be between desired limits
this.spherical.phi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this.spherical.phi))
this.spherical.makeSafe()
this.spherical.radius *= this.scale
// restrict radius to be between desired limits
this.spherical.radius = Math.max(this.minDistance, Math.min(this.maxDistance, this.spherical.radius))
// move target to panned location
if (this.enableDamping === true) {
this.target.addScaledVector(this.panOffset, this.dampingFactor)
} else {
this.target.add(this.panOffset)
}
offset.setFromSpherical(this.spherical)
// rotate offset back to "camera-up-vector-is-up" space
offset.applyQuaternion(quatInverse)
position.copy(this.target).add(offset)
this.object.lookAt(this.target)
if (this.enableDamping === true) {
this.sphericalDelta.theta *= (1 - this.dampingFactor)
this.sphericalDelta.phi *= (1 - this.dampingFactor)
this.panOffset.multiplyScalar(1 - this.dampingFactor)
} else {
this.sphericalDelta.set(0, 0, 0)
this.panOffset.set(0, 0, 0)
}
this.scale = 1
// update condition is:
// min(camera displacement, camera rotation in radians)^2 > EPS
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
if (this.zoomChanged ||
lastPosition.distanceToSquared(this.object.position) > this.EPS ||
8 * (1 - lastQuaternion.dot(this.object.quaternion)) > this.EPS) {
this.dispatchEvent(this.changeEvent)
lastPosition.copy(this.object.position)
lastQuaternion.copy(this.object.quaternion)
this.zoomChanged = false
return true
}
return false
}
//#endregion
//#region Orbit Controls
getAutoRotationAngle() {
return 2 * Math.PI / 60 / 60 * this.autoRotateSpeed
}
getZoomScale() {
return Math.pow(0.95, this.zoomSpeed)
}
rotateLeft(angle) {
this.sphericalDelta.theta -= angle
}
rotateUp(angle) {
this.sphericalDelta.phi -= angle
}
panLeft(distance, objectMatrix) {
let v = new THREE.Vector3()
v.setFromMatrixColumn(objectMatrix, 0) // get X column of objectMatrix
v.multiplyScalar(- distance)
this.panOffset.add(v)
}
panUp(distance, objectMatrix) {
let v = new THREE.Vector3()
if (this.screenSpacePanning === true) {
v.setFromMatrixColumn(objectMatrix, 1)
} else {
v.setFromMatrixColumn(objectMatrix, 0)
v.crossVectors(this.object.up, v)
}
v.multiplyScalar(distance)
this.panOffset.add(v)
}
// Patch - translate Y
translateY(delta) {
this.panOffset.y += delta
}
// deltaX and deltaY are in pixels; right and down are positive
pan(deltaX, deltaY, distance) {
let offset = new THREE.Vector3()
if (this.object.isPerspectiveCamera) {
// perspective
var position = this.object.position
offset.copy(position).sub(this.target)
var targetDistance = offset.length()
// half of the fov is center to top of screen
targetDistance *= Math.tan((this.object.fov / 2) * Math.PI / 180.0)
targetDistance = distance || targetDistance
// we use only clientHeight here so aspect ratio does not distort speed
this.panLeft(2 * deltaX * targetDistance / this.element.clientHeight, this.object.matrix)
this.panUp(2 * deltaY * targetDistance / this.element.clientHeight, this.object.matrix)
} else if (this.object.isOrthographicCamera) {
// orthographic
this.panLeft(deltaX * (this.object.right - this.object.left) / this.object.zoom / this.element.clientWidth, this.object.matrix)
this.panUp(deltaY * (this.object.top - this.object.bottom) / this.object.zoom / this.element.clientHeight, this.object.matrix)
} else {
// camera neither orthographic nor perspective
console.warn('WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.')
this.enablePan = false
}
}
dollyOut(dollyScale) {
if (this.object.isPerspectiveCamera) {
this.scale /= dollyScale
} else if (this.object.isOrthographicCamera) {
this.object.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.object.zoom * dollyScale))
this.object.updateProjectionMatrix()
this.zoomChanged = true
} else {
console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.')
this.enableZoom = false
}
}
dollyIn(dollyScale) {
if (this.object.isPerspectiveCamera) {
this.scale *= dollyScale
} else if (this.object.isOrthographicCamera) {
this.object.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.object.zoom / dollyScale))
this.object.updateProjectionMatrix()
this.zoomChanged = true
} else {
console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.')
this.enableZoom = false
}
}
//#endregion
//#region Event Callbacks - update the object state
handleMouseDownRotate(event) {
this.rotateStart.set(event.clientX, event.clientY)
}
handleMouseDownDolly(event) {
this.dollyStart.set(event.clientX, event.clientY)
}
handleMouseDownPan(event) {
this.panStart.set(event.clientX, event.clientY)
}
handleMouseMoveRotate(event) {
this.rotateEnd.set(event.clientX, event.clientY)
this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart).multiplyScalar(this.rotateSpeed)
this.rotateLeft(2 * Math.PI * this.rotateDelta.x / this.element.clientHeight) // yes, height
this.rotateUp(2 * Math.PI * this.rotateDelta.y / this.element.clientHeight)
this.rotateStart.copy(this.rotateEnd)
this.update(true)
}
handleMouseMoveDolly(event) {
this.dollyEnd.set(event.clientX, event.clientY)
this.dollyDelta.subVectors(this.dollyEnd, this.dollyStart)
if (this.dollyDelta.y > 0) {
this.dollyOut(this.getZoomScale())
} else if (this.dollyDelta.y < 0) {
this.dollyIn(this.getZoomScale())
}
this.dollyStart.copy(this.dollyEnd)
this.update(true)
}
handleMouseMovePan(event) {
this.panEnd.set(event.clientX, event.clientY)
this.panDelta.subVectors(this.panEnd, this.panStart).multiplyScalar(this.panSpeed)
this.pan(this.panDelta.x, this.panDelta.y)
this.panStart.copy(this.panEnd)
this.update(true)
}
handleMouseUp(/*event*/) {
// no-op
}
handleMouseWheel(event) {
if (event.deltaY < 0) {
this.dollyIn(this.getZoomScale())
} else if (event.deltaY > 0) {
this.dollyOut(this.getZoomScale())
}
this.update(true)
}
//#endregion
//#region Mouse/Keyboard handlers
// Called when the cursor location has moved
onPointerMove(event) {
if (!this.enabled || (this.state == STATE.NONE)) return
switch (event.pointerType) {
case 'mouse':
case 'pen':
this.onMouseMove(event)
break
// TODO touch
}
}
// Called when the cursor is no longer behind held
onPointerUp(event) {
if (!this.enabled) return
switch (event.pointerType) {
case 'mouse':
case 'pen':
this.onMouseUp(event)
break
// TODO touch
}
}
// On left click or tap
onPointerDown(event) {
if (!this.enabled) return
switch (event.pointerType) {
case 'mouse':
case 'pen':
this.onMouseDown(event)
break
// TODO touch
}
}
onMouseDown(event) {
// Prevent the browser from scrolling.
event.preventDefault()
// Manually set the focus since calling preventDefault above
// prevents the browser from setting it automatically.
this.element.focus ? this.element.focus() : window.focus()
var mouseAction
switch (event.button) {
case 0:
mouseAction = this.mouseButtons.LEFT
break
case 1:
mouseAction = this.mouseButtons.MIDDLE
break
case 2:
mouseAction = this.mouseButtons.RIGHT
break
default:
mouseAction = - 1
}
switch (mouseAction) {
case THREE.MOUSE.DOLLY:
if (this.enableZoom === false) return
this.handleMouseDownDolly(event)
this.state = STATE.DOLLY
break
case THREE.MOUSE.ROTATE:
if (event.ctrlKey || event.metaKey || event.shiftKey) {
if (this.enablePan === false) return
this.handleMouseDownPan(event)
this.state = STATE.PAN
} else {
if (this.enableRotate === false) return
this.handleMouseDownRotate(event)
this.state = STATE.ROTATE
}
break
case THREE.MOUSE.PAN:
if (event.ctrlKey || event.metaKey || event.shiftKey) {
if (this.enableRotate === false) return
this.handleMouseDownRotate(event)
this.state = STATE.ROTATE
} else {
if (this.enablePan === false) return
this.handleMouseDownPan(event)
this.state = STATE.PAN
}
break
default:
this.state = STATE.NONE
}
}
onMouseMove(event) {
if (this.enabled === false) return
event.preventDefault()
switch (this.state) {
case STATE.ROTATE:
if (this.enableRotate === false) return
this.handleMouseMoveRotate(event)
break
case STATE.DOLLY:
if (this.enableZoom === false) return
this.handleMouseMoveDolly(event)
break
case STATE.PAN:
if (this.enablePan === false) return
this.handleMouseMovePan(event)
break
}
}
onMouseUp(event) {
this.state = STATE.NONE
}
onMouseWheel(event) {
if (this.enabled === false || this.enableZoom === false || (this.state !== STATE.NONE && this.state !== STATE.ROTATE)) return
event.preventDefault()
event.stopPropagation()
this.dispatchEvent(this.startEvent)
this.handleMouseWheel(event)
this.dispatchEvent(this.endEvent)
}
//#endregion
//#region Touch handlers
handleTouchStartRotate(event) {
if (event.touches.length == 1) {
this.rotateStart.set(event.touches[0].pageX, event.touches[0].pageY)
} else {
var x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX)
var y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY)
this.rotateStart.set(x, y)
}
}
handleTouchStartPan(event) {
if (event.touches.length == 1) {
this.panStart.set(event.touches[0].pageX, event.touches[0].pageY)
} else {
var x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX)
var y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY)
this.panStart.set(x, y)
}
}
handleTouchStartDolly(event) {
var dx = event.touches[0].pageX - event.touches[1].pageX
var dy = event.touches[0].pageY - event.touches[1].pageY
var distance = Math.sqrt(dx * dx + dy * dy)
this.dollyStart.set(0, distance)
}
handleTouchStartDollyPan(event) {
if (this.enableTouchZoom) this.handleTouchStartDolly(event)
if (this.enableTouchPan) this.handleTouchStartPan(event)
}
handleTouchStartDollyRotate(event) {
if (this.enableTouchZoom) this.handleTouchStartDolly(event)
if (this.enableTouchRotate) this.handleTouchStartRotate(event)
}
handleTouchMoveRotate(event) {
if (event.touches.length == 1) {
this.rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY)
} else {
var x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX)
var y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY)
this.rotateEnd.set(x, y)
}
this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart).multiplyScalar(this.rotateSpeed)
this.rotateLeft(2 * Math.PI * this.rotateDelta.x / this.element.clientHeight) // yes, height
this.rotateUp(2 * Math.PI * this.rotateDelta.y / this.element.clientHeight)
this.rotateStart.copy(this.rotateEnd)
}
handleTouchMovePan(event) {
if (event.touches.length == 1) {
this.panEnd.set(event.touches[0].pageX, event.touches[0].pageY)
} else {
var x = 0.5 * (event.touches[0].pageX + event.touches[1].pageX)
var y = 0.5 * (event.touches[0].pageY + event.touches[1].pageY)
this.panEnd.set(x, y)
}
this.panDelta.subVectors(this.panEnd, this.panStart).multiplyScalar(this.panSpeed)
this.pan(this.panDelta.x, this.panDelta.y)
this.panStart.copy(this.panEnd)
}
handleTouchMoveDolly(event) {
var dx = event.touches[0].pageX - event.touches[1].pageX
var dy = event.touches[0].pageY - event.touches[1].pageY
var distance = Math.sqrt(dx * dx + dy * dy)
this.dollyEnd.set(0, distance)
this.dollyDelta.set(0, Math.pow(this.dollyEnd.y / this.dollyStart.y, this.zoomSpeed))
this.dollyOut(this.dollyDelta.y)
this.dollyStart.copy(this.dollyEnd)
}
handleTouchMoveDollyPan(event) {
if (this.enableTouchZoom) this.handleTouchMoveDolly(event)
if (this.enableTouchPan) this.handleTouchMovePan(event)
}
handleTouchMoveDollyRotate(event) {
if (this.enableTouchZoom) this.handleTouchMoveDolly(event)
if (this.enableTouchRotate) this.handleTouchMoveRotate(event)
}
handleTouchEnd( /*event*/) {
// no-op
}
//#endregion
tickControls() {
const control = this.controlMap
for (var keyCode of this.keyDowns) {
if (control.MOVE_FORWARD.includes(keyCode)) {
this.pan(0, this.keyPanSpeed, this.keyPanDistance)
} else if (control.MOVE_BACKWARD.includes(keyCode)) {
this.pan(0, -this.keyPanSpeed, this.keyPanDistance)
} else if (control.MOVE_LEFT.includes(keyCode)) {
this.pan(this.keyPanSpeed, 0, this.keyPanDistance)
} else if (control.MOVE_RIGHT.includes(keyCode)) {
this.pan(-this.keyPanSpeed, 0, this.keyPanDistance)
} else if (control.MOVE_UP.includes(keyCode)) {
this.translateY(+this.verticalTranslationSpeed)
} else if (control.MOVE_DOWN.includes(keyCode)) {
this.translateY(-this.verticalTranslationSpeed)
}
}
}
onKeyDown(e) {
if (!this.enabled) return
if (e.code && !this.keyDowns.includes(e.code)) {
this.keyDowns.push(e.code)
// console.debug('[control] Key down: ', this.keyDowns)
}
}
onKeyUp(event) {
// console.log('[control] Key up: ', event.code, this.keyDowns)
this.keyDowns = this.keyDowns.filter(code => code != event.code)
}
onTouchStart(event) {
if (this.enabled === false) return
event.preventDefault() // prevent scrolling
switch (event.touches.length) {
case 1:
switch (this.touches.ONE) {
case THREE.TOUCH.ROTATE:
if (this.enableTouchRotate === false) return
this.handleTouchStartRotate(event)
this.state = STATE.TOUCH_ROTATE
break
case THREE.TOUCH.PAN:
if (this.enableTouchPan === false) return
this.handleTouchStartPan(event)
this.state = STATE.TOUCH_PAN
break
default:
this.state = STATE.NONE
}
break
case 2:
switch (this.touches.TWO) {
case THREE.TOUCH.DOLLY_PAN:
if (this.enableTouchZoom === false && this.enableTouchPan === false) return
this.handleTouchStartDollyPan(event)
this.state = STATE.TOUCH_DOLLY_PAN
break
case THREE.TOUCH.DOLLY_ROTATE:
if (this.enableTouchZoom === false && this.enableTouchRotate === false) return
this.handleTouchStartDollyRotate(event)
this.state = STATE.TOUCH_DOLLY_ROTATE
break
default:
this.state = STATE.NONE
}
break
default:
this.state = STATE.NONE
}
if (this.state !== STATE.NONE) {
this.dispatchEvent(this.startEvent)
}
}
onTouchMove(event) {
if (this.enabled === false) return
event.preventDefault() // prevent scrolling
event.stopPropagation()
switch (this.state) {
case STATE.TOUCH_ROTATE:
if (this.enableTouchRotate === false) return
this.handleTouchMoveRotate(event)
this.update()
break
case STATE.TOUCH_PAN:
if (this.enableTouchPan === false) return
this.handleTouchMovePan(event)
this.update()
break
case STATE.TOUCH_DOLLY_PAN:
if (this.enableTouchZoom === false && this.enableTouchPan === false) return
this.handleTouchMoveDollyPan(event)
this.update()
break
case STATE.TOUCH_DOLLY_ROTATE:
if (this.enableTouchZoom === false && this.enableTouchRotate === false) return
this.handleTouchMoveDollyRotate(event)
this.update()
break
default:
this.state = STATE.NONE
}
}
onTouchEnd(event) {
if (this.enabled === false) return
this.handleTouchEnd(event)
this.dispatchEvent(this.endEvent)
this.state = STATE.NONE
}
onContextMenu(event) {
// Disable context menu
if (this.enabled) event.preventDefault()
}
registerHandlers() {
this.element.addEventListener('pointermove', this.onPointerMove, false, {passive: true})
this.element.addEventListener('pointerup', this.onPointerUp, false, {passive: true})
this.element.addEventListener('pointerdown', this.onPointerDown, false, {passive: true})
this.element.addEventListener('wheel', this.onMouseWheel, true, {passive: true})
this.element.addEventListener('touchstart', this.onTouchStart, false, {passive: true})
this.element.addEventListener('touchend', this.onTouchEnd, false, {passive: true})
this.element.addEventListener('touchmove', this.onTouchMove, false, {passive: true})
this.element.ownerDocument.addEventListener('contextmenu', this.onContextMenu, false, {passive: true})
this.element.ownerDocument.addEventListener('keydown', this.onKeyDown, false, {passive: true})
this.element.ownerDocument.addEventListener('keyup', this.onKeyUp, false, {passive: true})
console.log('[controls] registered handlers', this.element)
}
unregisterHandlers() {
this.element.removeEventListener('pointermove', this.onPointerMove, false, {passive: true})
this.element.removeEventListener('pointerup', this.onPointerUp, false, {passive: true})
this.element.removeEventListener('pointerdown', this.onPointerDown, false, {passive: true})
this.element.removeEventListener('wheel', this.onMouseWheel, true, {passive: true})
this.element.removeEventListener('touchstart', this.onTouchStart, false, {passive: true})
this.element.removeEventListener('touchend', this.onTouchEnd, false, {passive: true})
this.element.removeEventListener('touchmove', this.onTouchMove, false, {passive: true})
this.element.ownerDocument.removeEventListener('contextmenu', this.onContextMenu, false, {passive: true})
this.element.ownerDocument.removeEventListener('keydown', this.onKeyDown, false, {passive: true})
this.element.ownerDocument.removeEventListener('keyup', this.onKeyUp, false, {passive: true})
console.log('[controls] unregistered handlers', this.element)
}
dispatchEvent() {
// no-op
}
}
module.exports = { MapControls }

View file

@ -1,23 +1,26 @@
//@ts-check
import * as THREE from 'three'
import * as TWEEN from '@tweenjs/tween.js'
import * as Entity from './entity/EntityMesh'
import nbt from 'prismarine-nbt'
import EventEmitter from 'events'
import nbt from 'prismarine-nbt'
import * as TWEEN from '@tweenjs/tween.js'
import * as THREE from 'three'
import { PlayerObject, PlayerAnimation } from 'skinview3d'
import { loadSkinToCanvas, loadEarsToCanvasFromSkin, inferModelType, loadCapeToCanvas, loadImage } from 'skinview-utils'
// todo replace with url
import stevePng from 'mc-assets/dist/other-textures/latest/entity/player/wide/steve.png'
import { WalkingGeneralSwing } from './entity/animations'
import { NameTagObject } from 'skinview3d/libs/nametag'
import { flat, fromFormattedString } from '@xmcl/text-component'
import mojangson from 'mojangson'
import * as Entity from './entity/EntityMesh'
import { WalkingGeneralSwing } from './entity/animations'
import externalTexturesJson from './entity/externalTextures.json'
import { disposeObject } from './threeJsUtils'
export const TWEEN_DURATION = 50 // todo should be 100
function getUsernameTexture (username, { fontFamily = 'sans-serif' }) {
/**
* @param {string} username
*/
function getUsernameTexture(username, { fontFamily = 'sans-serif' }) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (!ctx) throw new Error('Could not get 2d context')
@ -43,7 +46,7 @@ function getUsernameTexture (username, { fontFamily = 'sans-serif' }) {
const addNametag = (entity, options, mesh) => {
if (entity.username !== undefined) {
if (mesh.children.find(c => c.name === 'nametag')) return // todo update
if (mesh.children.some(c => c.name === 'nametag')) return // todo update
const canvas = getUsernameTexture(entity.username, options)
const tex = new THREE.Texture(canvas)
tex.needsUpdate = true
@ -61,7 +64,7 @@ const addNametag = (entity, options, mesh) => {
// todo cleanup
const nametags = {}
function getEntityMesh (entity, scene, options, overrides) {
function getEntityMesh(entity, scene, options, overrides) {
if (entity.name) {
try {
// https://github.com/PrismarineJS/prismarine-viewer/pull/410
@ -107,7 +110,7 @@ export class Entities extends EventEmitter {
this.getItemUv = undefined
}
clear () {
clear() {
for (const mesh of Object.values(this.entities)) {
this.scene.remove(mesh)
disposeObject(mesh)
@ -115,7 +118,7 @@ export class Entities extends EventEmitter {
this.entities = {}
}
setDebugMode (mode, /** @type {THREE.Object3D?} */entity = null) {
setDebugMode(mode, /** @type {THREE.Object3D?} */entity = null) {
this.debugMode = mode
for (const mesh of entity ? [entity] : Object.values(this.entities)) {
const boxHelper = mesh.children.find(c => c.name === 'debug')
@ -127,7 +130,7 @@ export class Entities extends EventEmitter {
}
}
setRendering (rendering, /** @type {THREE.Object3D?} */entity = null) {
setRendering(rendering, /** @type {THREE.Object3D?} */entity = null) {
this.rendering = rendering
for (const ent of entity ? [entity] : Object.values(this.entities)) {
if (rendering) {
@ -138,7 +141,7 @@ export class Entities extends EventEmitter {
}
}
render () {
render() {
const dt = this.clock.getDelta()
for (const entityId of Object.keys(this.entities)) {
const playerObject = this.getPlayerObject(entityId)
@ -148,7 +151,7 @@ export class Entities extends EventEmitter {
}
}
getPlayerObject (entityId) {
getPlayerObject(entityId) {
/** @type {(PlayerObject & { animation?: PlayerAnimation }) | undefined} */
const playerObject = this.entities[entityId]?.playerObject
return playerObject
@ -158,7 +161,7 @@ export class Entities extends EventEmitter {
defaultSteveTexture
// true means use default skin url
updatePlayerSkin (entityId, username, /** @type {string | true} */skinUrl, /** @type {string | true | undefined} */capeUrl = undefined) {
updatePlayerSkin(entityId, username, /** @type {string | true} */skinUrl, /** @type {string | true | undefined} */capeUrl = undefined) {
let playerObject = this.getPlayerObject(entityId)
if (!playerObject) return
// const username = this.entities[entityId].username
@ -167,7 +170,7 @@ export class Entities extends EventEmitter {
skinUrl = `https://mulv.tycrek.dev/api/lookup?username=${username}&type=skin`
if (!username) return
}
loadImage(skinUrl).then((image) => {
loadImage(skinUrl).then(image => {
playerObject = this.getPlayerObject(entityId)
if (!playerObject) return
/** @type {THREE.CanvasTexture} */
@ -185,28 +188,28 @@ export class Entities extends EventEmitter {
skinTexture.magFilter = THREE.NearestFilter
skinTexture.minFilter = THREE.NearestFilter
skinTexture.needsUpdate = true
//@ts-ignore
//@ts-expect-error
playerObject.skin.map = skinTexture
playerObject.skin.modelType = inferModelType(skinTexture.image)
const earsCanvas = document.createElement('canvas')
loadEarsToCanvasFromSkin(earsCanvas, image)
if (!isCanvasBlank(earsCanvas)) {
if (isCanvasBlank(earsCanvas)) {
playerObject.ears.map = null
playerObject.ears.visible = false
} else {
const earsTexture = new THREE.CanvasTexture(earsCanvas)
earsTexture.magFilter = THREE.NearestFilter
earsTexture.minFilter = THREE.NearestFilter
earsTexture.needsUpdate = true
//@ts-ignore
//@ts-expect-error
playerObject.ears.map = earsTexture
playerObject.ears.visible = true
} else {
playerObject.ears.map = null
playerObject.ears.visible = false
}
this.onSkinUpdate?.()
if (capeUrl) {
if (capeUrl === true) capeUrl = `https://mulv.tycrek.dev/api/lookup?username=${username}&type=cape`
loadImage(capeUrl).then((capeImage) => {
loadImage(capeUrl).then(capeImage => {
playerObject = this.getPlayerObject(entityId)
if (!playerObject) return
const capeCanvas = document.createElement('canvas')
@ -216,10 +219,10 @@ export class Entities extends EventEmitter {
capeTexture.magFilter = THREE.NearestFilter
capeTexture.minFilter = THREE.NearestFilter
capeTexture.needsUpdate = true
//@ts-ignore
//@ts-expect-error
playerObject.cape.map = capeTexture
playerObject.cape.visible = true
//@ts-ignore
//@ts-expect-error
playerObject.elytra.map = capeTexture
this.onSkinUpdate?.()
@ -241,14 +244,14 @@ export class Entities extends EventEmitter {
playerObject.cape.map = null
}
function isCanvasBlank (canvas) {
function isCanvasBlank(canvas) {
return !canvas.getContext('2d')
.getImageData(0, 0, canvas.width, canvas.height).data
.some(channel => channel !== 0)
}
}
playAnimation (entityPlayerId, /** @type {'walking' | 'running' | 'oneSwing' | 'idle'} */animation) {
playAnimation(entityPlayerId, /** @type {'walking' | 'running' | 'oneSwing' | 'idle'} */animation) {
const playerObject = this.getPlayerObject(entityPlayerId)
if (!playerObject) return
@ -268,7 +271,7 @@ export class Entities extends EventEmitter {
}
parseEntityLabel (jsonLike) {
parseEntityLabel(jsonLike) {
if (!jsonLike) return
try {
const parsed = typeof jsonLike === 'string' ? mojangson.simplify(mojangson.parse(jsonLike)) : nbt.simplify(jsonLike)
@ -279,7 +282,7 @@ export class Entities extends EventEmitter {
}
}
update (/** @type {import('prismarine-entity').Entity & {delete?, pos}} */entity, overrides) {
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') {
isPlayerModel = true
@ -290,8 +293,8 @@ export class Entities extends EventEmitter {
let mesh
if (entity.name === 'item') {
/** @type {any} */
//@ts-ignore
const item = entity.metadata?.find(m => typeof m === 'object' && m !== null && m.itemCount)
//@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) {
@ -318,11 +321,11 @@ export class Entities extends EventEmitter {
transparent: true,
alphaTest: 0.1,
})
mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0.0), [
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: 0x000000 }), new THREE.MeshBasicMaterial({ color: 0x000000 }),
new THREE.MeshBasicMaterial({ color: 0x000000 }), new THREE.MeshBasicMaterial({ color: 0x000000 }),
material, materialFlipped
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)
@ -334,7 +337,7 @@ export class Entities extends EventEmitter {
const delta = clock.getDelta()
mesh.rotation.y += delta
}
//@ts-ignore
//@ts-expect-error
group.additionalCleanup = () => {
// important: avoid texture memory leak and gpu slowdown
itemsTexture.dispose()
@ -349,7 +352,7 @@ export class Entities extends EventEmitter {
const playerObject = new PlayerObject()
playerObject.position.set(0, 16, 0)
//@ts-ignore
//@ts-expect-error
wrapper.add(playerObject)
const scale = 1 / 16
wrapper.scale.set(scale, scale, scale)
@ -362,16 +365,16 @@ export class Entities extends EventEmitter {
nameTag.position.y = playerObject.position.y + playerObject.scale.y * 16 + 3
nameTag.renderOrder = 1000
//@ts-ignore
//@ts-expect-error
wrapper.add(nameTag)
}
//@ts-ignore
//@ts-expect-error
group.playerObject = playerObject
wrapper.rotation.set(0, Math.PI, 0)
mesh = wrapper
playerObject.animation = new WalkingGeneralSwing()
//@ts-ignore
//@ts-expect-error
playerObject.animation.isMoving = false
} else {
mesh = getEntityMesh(entity, this.scene, this.entitiesOptions, overrides)
@ -382,11 +385,12 @@ export class Entities extends EventEmitter {
group.position.set(entity.pos.x, entity.pos.y, entity.pos.z)
// todo use width and height instead
const boxHelper = new THREE.BoxHelper(mesh,
entity.type === 'hostile' ? 0xff0000 :
entity.type === 'mob' ? 0x00ff00 :
entity.type === "player" ? 0x0000ff :
0xffa500
const boxHelper = new THREE.BoxHelper(
mesh,
entity.type === 'hostile' ? 0xff_00_00 :
entity.type === 'mob' ? 0x00_ff_00 :
entity.type === 'player' ? 0x00_00_ff :
0xff_a5_00,
)
boxHelper.name = 'debug'
group.add(mesh)
@ -405,7 +409,7 @@ export class Entities extends EventEmitter {
this.setRendering(this.rendering, group)
}
//@ts-ignore
//@ts-expect-error
// set visibility
const isInvisible = entity.metadata?.[0] & 0x20
for (const child of this.entities[entity.id]?.children.find(c => c.name === 'mesh')?.children ?? []) {
@ -456,6 +460,7 @@ export class Entities extends EventEmitter {
if (e?.playerObject && overrides?.rotation?.head) {
/** @type {PlayerObject} */
// eslint-disable-next-line prefer-destructuring
const playerObject = e.playerObject
const headRotationDiff = overrides.rotation.head.y ? overrides.rotation.head.y - entity.yaw : 0
playerObject.skin.head.rotation.y = -headRotationDiff

View file

@ -106,7 +106,7 @@ function addCube(attr, boneId, bone, cube, texWidth = 64, texHeight = 64) {
const u = (cube.uv[0] + dot(pos[3] ? u1 : u0, cube.size)) / texWidth
const v = (cube.uv[1] + dot(pos[4] ? v1 : v0, cube.size)) / texHeight
const inflate = cube.inflate ? cube.inflate : 0
const inflate = cube.inflate ?? 0
let vecPos = new THREE.Vector3(
cube.origin[0] + pos[0] * cube.size[0] + (pos[0] ? inflate : -inflate),
cube.origin[1] + pos[1] * cube.size[1] + (pos[1] ? inflate : -inflate),
@ -125,10 +125,7 @@ function addCube(attr, boneId, bone, cube, texWidth = 64, texHeight = 64) {
attr.skinWeights.push(1, 0, 0, 0)
}
attr.indices.push(
ndx, ndx + 1, ndx + 2,
ndx + 2, ndx + 1, ndx + 3
)
attr.indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3)
}
}
@ -178,8 +175,7 @@ function getMesh(texture, jsonModel, overrides = {}) {
const rootBones = []
for (const jsonBone of jsonModel.bones) {
if (jsonBone.parent && bones[jsonBone.parent]) bones[jsonBone.parent].add(bones[jsonBone.name])
else {
if (jsonBone.parent && bones[jsonBone.parent]) { bones[jsonBone.parent].add(bones[jsonBone.name]) } else {
rootBones.push(bones[jsonBone.name])
}
}
@ -294,9 +290,10 @@ const getEntity = (name) => {
// zombie_villager: 'zombie_villager/zombie_villager'
// }
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class EntityMesh {
constructor(version, type, scene, /** @type {{textures?, rotation?: Record<string, {x,y,z}>}} */overrides = {}) {
let originalType = type
const originalType = type
const mappedValue = temporaryMap[type]
if (mappedValue) type = mappedValue
@ -356,12 +353,12 @@ export class EntityMesh {
const texture = overrides.textures?.[name] ?? e.textures[name]
if (!texture) continue
// console.log(JSON.stringify(jsonModel, null, 2))
const mesh = getMesh(texture + '.png', jsonModel, overrides,)
const mesh = getMesh(texture + '.png', jsonModel, overrides)
mesh.name = `geometry_${name}`
this.mesh.add(mesh)
const skeletonHelper = new THREE.SkeletonHelper(mesh)
//@ts-ignore
//@ts-expect-error
skeletonHelper.material.linewidth = 2
skeletonHelper.visible = false
this.mesh.add(skeletonHelper)

View file

@ -9,11 +9,11 @@ export class WalkingGeneralSwing extends PlayerAnimation {
_startArmSwing
swingArm () {
swingArm() {
this._startArmSwing = this.progress
}
animate (player) {
animate(player) {
// Multiply by animation's natural speed
let t
const updateT = () => {
@ -49,7 +49,7 @@ export class WalkingGeneralSwing extends PlayerAnimation {
}
if (this._startArmSwing) {
let tHand = (this.progress - this._startArmSwing) * 18 + Math.PI * 0.5
const tHand = (this.progress - this._startArmSwing) * 18 + Math.PI * 0.5
player.skin.rightArm.rotation.x = Math.cos(tHand) * 1.5
const basicArmRotationZ = Math.PI * 0.1
player.skin.rightArm.rotation.z = Math.cos(t + Math.PI) * 0.3 - basicArmRotationZ

View file

@ -1,3 +1 @@
import * as externalModels from './exportedModels'
export { externalModels }
export * as externalModels from './exportedModels'

View file

@ -1,5 +1,5 @@
import { World } from './world'
import { Vec3 } from 'vec3'
import { World } from './world'
import { getSectionGeometry, setBlockStatesData as setMesherData } from './models'
if (module.require) {
@ -12,7 +12,7 @@ if (module.require) {
}
let world: World
let dirtySections: Map<string, number> = new Map()
let dirtySections = new Map<string, number>()
let allDataReady = false
function sectionKey (x, y, z) {
@ -83,32 +83,54 @@ const handleMessage = data => {
globalThis.world = world
}
if (data.type === 'mesherData') {
setMesherData(data.blockstatesModels, data.blocksAtlas)
allDataReady = true
} else if (data.type === 'dirty') {
const loc = new Vec3(data.x, data.y, data.z)
setSectionDirty(loc, data.value)
} else if (data.type === 'chunk') {
world.addColumn(data.x, data.z, data.chunk)
} else if (data.type === 'unloadChunk') {
world.removeColumn(data.x, data.z)
if (Object.keys(world.columns).length === 0) softCleanup()
} else if (data.type === 'blockUpdate') {
const loc = new Vec3(data.pos.x, data.pos.y, data.pos.z).floored()
world.setBlockStateId(loc, data.stateId)
} else if (data.type === 'reset') {
world = undefined as any
// blocksStates = null
dirtySections = new Map()
// todo also remove cached
globalVar.mcData = null
allDataReady = false
switch (data.type) {
case 'mesherData': {
setMesherData(data.blockstatesModels, data.blocksAtlas)
allDataReady = true
break
}
case 'dirty': {
const loc = new Vec3(data.x, data.y, data.z)
setSectionDirty(loc, data.value)
break
}
case 'chunk': {
world.addColumn(data.x, data.z, data.chunk)
break
}
case 'unloadChunk': {
world.removeColumn(data.x, data.z)
if (Object.keys(world.columns).length === 0) softCleanup()
break
}
case 'blockUpdate': {
const loc = new Vec3(data.pos.x, data.pos.y, data.pos.z).floored()
world.setBlockStateId(loc, data.stateId)
break
}
case 'reset': {
world = undefined as any
// blocksStates = null
dirtySections = new Map()
// todo also remove cached
globalVar.mcData = null
allDataReady = false
break
}
// No default
}
}
// eslint-disable-next-line no-restricted-globals -- TODO
self.onmessage = ({ data }) => {
if (Array.isArray(data)) {
// eslint-disable-next-line unicorn/no-array-for-each
data.forEach(handleMessage)
return
}
@ -124,12 +146,12 @@ setInterval(() => {
// const start = performance.now()
for (const key of dirtySections.keys()) {
let [x, y, z] = key.split(',').map(v => parseInt(v, 10))
const [x, y, z] = key.split(',').map(v => parseInt(v, 10))
const chunk = world.getColumn(x, z)
if (chunk?.getSection(new Vec3(x, y, z))) {
const geometry = getSectionGeometry(x, y, z, world)
const transferable = [geometry.positions.buffer, geometry.normals.buffer, geometry.colors.buffer, geometry.uvs.buffer]
//@ts-ignore
//@ts-expect-error
postMessage({ type: 'geometry', key, geometry }, transferable)
} else {
// console.info('[mesher] Missing section', x, y, z)

View file

@ -1,8 +1,7 @@
import { Vec3 } from 'vec3'
import { World, BlockModelPartsResolved } from './world'
import { WorldBlock as Block } from './world'
import legacyJson from '../../../../src/preflatMap.json'
import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
import legacyJson from '../../../../src/preflatMap.json'
import { World, BlockModelPartsResolved, WorldBlock as Block } from './world'
import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
let blockProvider: WorldBlockProvider
@ -14,7 +13,7 @@ let tintsData
try {
tintsData = require('esbuild-data').tints
} catch (err) {
tintsData = require("minecraft-data/minecraft-data/data/pc/1.16.2/tints.json")
tintsData = require('minecraft-data/minecraft-data/data/pc/1.16.2/tints.json')
}
for (const key of Object.keys(tintsData)) {
tints[key] = prepareTints(tintsData[key])
@ -30,7 +29,7 @@ function prepareTints (tints) {
}
}
return new Proxy(map, {
get: (target, key) => {
get (target, key) {
return target.has(key) ? target.get(key) : defaultValue
}
})
@ -72,7 +71,7 @@ export function preflatBlockCalculation (block: Block, world: World, position: V
}
case 'door': {
// upper half matches lower in
const half = block.getProperties().half
const { half } = block.getProperties()
if (half === 'upper') {
// copy other properties
const lower = world.getBlock(position.offset(0, -1, 0))
@ -130,6 +129,7 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ
Math.max(Math.max(heights[4], heights[5]), Math.max(heights[7], heights[8]))
]
// eslint-disable-next-line guard-for-in
for (const face in elemFaces) {
const { dir, corners } = elemFaces[face]
const isUp = dir[1] === 1
@ -162,17 +162,18 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ
})
}
const u = texture.u
const v = texture.v
const su = texture.su
const sv = texture.sv
const { u } = texture
const { v } = texture
const { su } = texture
const { sv } = texture
for (const pos of corners) {
const height = cornerHeights[pos[2] * 2 + pos[0]]
attr.t_positions.push(
(pos[0] ? 0.999 : 0.001) + (cursor.x & 15) - 8,
(pos[1] ? height - 0.001 : 0.001) + (cursor.y & 15) - 8,
(pos[2] ? 0.999 : 0.001) + (cursor.z & 15) - 8)
(pos[2] ? 0.999 : 0.001) + (cursor.z & 15) - 8
)
attr.t_normals.push(...dir)
attr.t_uvs.push(pos[3] * su + u, pos[4] * sv * (pos[1] ? 1 : height) + v)
attr.t_colors.push(tint[0], tint[1], tint[2])
@ -186,8 +187,9 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO:
const position = cursor
// const key = `${position.x},${position.y},${position.z}`
// if (!globalThis.allowedBlocks.includes(key)) return
const cullIfIdentical = block.name.indexOf('glass') >= 0
const cullIfIdentical = block.name.includes('glass')
// eslint-disable-next-line guard-for-in
for (const face in element.faces) {
const eFace = element.faces[face]
const { corners, mask1, mask2 } = elemFaces[face]
@ -212,10 +214,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO:
const maxz = element.to[2]
const texture = eFace.texture as any
const u = texture.u
const v = texture.v
const su = texture.su
const sv = texture.sv
const { u, v, su, sv } = texture
const ndx = Math.floor(attr.positions.length / 3)
@ -299,6 +298,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO:
const corner = world.getBlock(cursor.offset(...cornerDir))
let cornerLightResult = 15
// eslint-disable-next-line no-constant-condition, sonarjs/no-gratuitous-expressions
if (/* world.config.smoothLighting */false) { // todo fix
const side1Light = world.getLight(cursor.plus(new Vec3(...side1Dir)), true)
const side2Light = world.getLight(cursor.plus(new Vec3(...side2Dir)), true)
@ -337,11 +337,13 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO:
if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) {
attr.indices.push(
// eslint-disable-next-line @stylistic/function-call-argument-newline
ndx, ndx + 3, ndx + 2,
ndx, ndx + 1, ndx + 3
)
} else {
attr.indices.push(
// eslint-disable-next-line @stylistic/function-call-argument-newline
ndx, ndx + 1, ndx + 2,
ndx + 2, ndx + 1, ndx + 3
)
@ -349,14 +351,14 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO:
}
}
const invisibleBlocks = ['air', 'cave_air', 'void_air', 'barrier']
const invisibleBlocks = new Set(['air', 'cave_air', 'void_air', 'barrier'])
const isBlockWaterlogged = (block: Block) => block.getProperties().waterlogged === true || block.getProperties().waterlogged === 'true'
let unknownBlockModel: BlockModelPartsResolved
let erroredBlockModel: BlockModelPartsResolved
export function getSectionGeometry (sx, sy, sz, world: World) {
let delayedRender = [] as (() => void)[]
let delayedRender = [] as Array<() => void>
const attr = {
sx: sx + 8,
@ -382,15 +384,15 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) {
for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
const block = world.getBlock(cursor)!
if (invisibleBlocks.includes(block.name)) continue
if (invisibleBlocks.has(block.name)) continue
if (block.name.includes('_sign') || block.name === 'sign') {
const key = `${cursor.x},${cursor.y},${cursor.z}`
const props: any = block.getProperties()
const facingRotationMap = {
"north": 2,
"south": 0,
"west": 1,
"east": 3
'north': 2,
'south': 0,
'west': 1,
'east': 3
}
const isWall = block.name.endsWith('wall_sign') || block.name.endsWith('wall_hanging_sign')
const isHanging = block.name.endsWith('hanging_sign')
@ -406,15 +408,15 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
if (world.preflat) {
const patchProperties = preflatBlockCalculation(block, world, cursor)
if (patchProperties) {
//@ts-ignore
//@ts-expect-error
block._originalProperties ??= block._properties
//@ts-ignore
//@ts-expect-error
block._properties = { ...block._originalProperties, ...patchProperties }
preflatRecomputeVariant = true
} else {
//@ts-ignore
//@ts-expect-error
block._properties = block._originalProperties ?? block._properties
//@ts-ignore
//@ts-expect-error
block._originalProperties = undefined
}
}
@ -422,15 +424,16 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
const isWaterlogged = isBlockWaterlogged(block)
if (block.name === 'water' || isWaterlogged) {
const pos = cursor.clone()
// eslint-disable-next-line @typescript-eslint/no-loop-func
delayedRender.push(() => {
renderLiquid(world, pos, blockProvider.getTextureInfo('water_still'), block.type, biome, true, attr)
})
} else if (block.name === 'lava') {
renderLiquid(world, cursor, blockProvider.getTextureInfo('lava_still'), block.type, biome, false, attr)
}
if (block.name !== "water" && block.name !== "lava" && !invisibleBlocks.includes(block.name)) {
if (block.name !== 'water' && block.name !== 'lava' && !invisibleBlocks.has(block.name)) {
// cache
let models = block.models
let { models } = block
if (block.models === undefined || preflatRecomputeVariant) {
try {
models = blockProvider.getAllResolvedModels0_1({
@ -463,8 +466,9 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
let globalShift = null as any
for (const axis of ['x', 'y', 'z'] as const) {
if (axis in model) {
if (!globalMatrix) globalMatrix = buildRotationMatrix(axis, -(model[axis] ?? 0))
else globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -(model[axis] ?? 0)))
globalMatrix = globalMatrix ?
matmulmat3(globalMatrix, buildRotationMatrix(axis, -(model[axis] ?? 0))) :
buildRotationMatrix(axis, -(model[axis] ?? 0))
}
}
if (globalMatrix) {
@ -498,11 +502,10 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
let ndx = attr.positions.length / 3
for (let i = 0; i < attr.t_positions.length / 12; i++) {
attr.indices.push(
ndx, ndx + 1, ndx + 2,
ndx + 2, ndx + 1, ndx + 3,
ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3,
// eslint-disable-next-line @stylistic/function-call-argument-newline
// back face
ndx, ndx + 2, ndx + 1,
ndx + 2, ndx + 3, ndx + 1
ndx, ndx + 2, ndx + 1, ndx + 2, ndx + 3, ndx + 1
)
ndx += 4
}

View file

@ -4,139 +4,139 @@ export type BlockElement = NonNullable<BlockModelPartsResolved[0][0]['elements']
export function buildRotationMatrix (axis, degree) {
const radians = degree / 180 * Math.PI
const cos = Math.cos(radians)
const sin = Math.sin(radians)
const radians = degree / 180 * Math.PI
const cos = Math.cos(radians)
const sin = Math.sin(radians)
const axis0 = { x: 0, y: 1, z: 2 }[axis]
const axis1 = (axis0 + 1) % 3
const axis2 = (axis0 + 2) % 3
const axis0 = { x: 0, y: 1, z: 2 }[axis]
const axis1 = (axis0 + 1) % 3
const axis2 = (axis0 + 2) % 3
const matrix = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
]
const matrix = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
]
matrix[axis0][axis0] = 1
matrix[axis1][axis1] = cos
matrix[axis1][axis2] = -sin
matrix[axis2][axis1] = +sin
matrix[axis2][axis2] = cos
matrix[axis0][axis0] = 1
matrix[axis1][axis1] = cos
matrix[axis1][axis2] = -sin
matrix[axis2][axis1] = +sin
matrix[axis2][axis2] = cos
return matrix
return matrix
}
export function vecadd3 (a, b) {
if (!b) return a
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
if (!b) return a
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
}
export function vecsub3 (a, b) {
if (!b) return a
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
if (!b) return a
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
export function matmul3 (matrix, vector): [number, number, number] {
if (!matrix) return vector
return [
matrix[0][0] * vector[0] + matrix[0][1] * vector[1] + matrix[0][2] * vector[2],
matrix[1][0] * vector[0] + matrix[1][1] * vector[1] + matrix[1][2] * vector[2],
matrix[2][0] * vector[0] + matrix[2][1] * vector[1] + matrix[2][2] * vector[2]
]
if (!matrix) return vector
return [
matrix[0][0] * vector[0] + matrix[0][1] * vector[1] + matrix[0][2] * vector[2],
matrix[1][0] * vector[0] + matrix[1][1] * vector[1] + matrix[1][2] * vector[2],
matrix[2][0] * vector[0] + matrix[2][1] * vector[1] + matrix[2][2] * vector[2]
]
}
export function matmulmat3 (a, b) {
const te = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
const te = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
const a11 = a[0][0]; const a12 = a[1][0]; const a13 = a[2][0]
const a21 = a[0][1]; const a22 = a[1][1]; const a23 = a[2][1]
const a31 = a[0][2]; const a32 = a[1][2]; const a33 = a[2][2]
const a11 = a[0][0]; const a12 = a[1][0]; const a13 = a[2][0]
const a21 = a[0][1]; const a22 = a[1][1]; const a23 = a[2][1]
const a31 = a[0][2]; const a32 = a[1][2]; const a33 = a[2][2]
const b11 = b[0][0]; const b12 = b[1][0]; const b13 = b[2][0]
const b21 = b[0][1]; const b22 = b[1][1]; const b23 = b[2][1]
const b31 = b[0][2]; const b32 = b[1][2]; const b33 = b[2][2]
const b11 = b[0][0]; const b12 = b[1][0]; const b13 = b[2][0]
const b21 = b[0][1]; const b22 = b[1][1]; const b23 = b[2][1]
const b31 = b[0][2]; const b32 = b[1][2]; const b33 = b[2][2]
te[0][0] = a11 * b11 + a12 * b21 + a13 * b31
te[1][0] = a11 * b12 + a12 * b22 + a13 * b32
te[2][0] = a11 * b13 + a12 * b23 + a13 * b33
te[0][0] = a11 * b11 + a12 * b21 + a13 * b31
te[1][0] = a11 * b12 + a12 * b22 + a13 * b32
te[2][0] = a11 * b13 + a12 * b23 + a13 * b33
te[0][1] = a21 * b11 + a22 * b21 + a23 * b31
te[1][1] = a21 * b12 + a22 * b22 + a23 * b32
te[2][1] = a21 * b13 + a22 * b23 + a23 * b33
te[0][1] = a21 * b11 + a22 * b21 + a23 * b31
te[1][1] = a21 * b12 + a22 * b22 + a23 * b32
te[2][1] = a21 * b13 + a22 * b23 + a23 * b33
te[0][2] = a31 * b11 + a32 * b21 + a33 * b31
te[1][2] = a31 * b12 + a32 * b22 + a33 * b32
te[2][2] = a31 * b13 + a32 * b23 + a33 * b33
te[0][2] = a31 * b11 + a32 * b21 + a33 * b31
te[1][2] = a31 * b12 + a32 * b22 + a33 * b32
te[2][2] = a31 * b13 + a32 * b23 + a33 * b33
return te
return te
}
export const elemFaces = {
up: {
dir: [0, 1, 0],
mask1: [1, 1, 0],
mask2: [0, 1, 1],
corners: [
[0, 1, 1, 0, 1],
[1, 1, 1, 1, 1],
[0, 1, 0, 0, 0],
[1, 1, 0, 1, 0]
]
},
down: {
dir: [0, -1, 0],
mask1: [1, 1, 0],
mask2: [0, 1, 1],
corners: [
[1, 0, 1, 0, 1],
[0, 0, 1, 1, 1],
[1, 0, 0, 0, 0],
[0, 0, 0, 1, 0]
]
},
east: {
dir: [1, 0, 0],
mask1: [1, 1, 0],
mask2: [1, 0, 1],
corners: [
[1, 1, 1, 0, 0],
[1, 0, 1, 0, 1],
[1, 1, 0, 1, 0],
[1, 0, 0, 1, 1]
]
},
west: {
dir: [-1, 0, 0],
mask1: [1, 1, 0],
mask2: [1, 0, 1],
corners: [
[0, 1, 0, 0, 0],
[0, 0, 0, 0, 1],
[0, 1, 1, 1, 0],
[0, 0, 1, 1, 1]
]
},
north: {
dir: [0, 0, -1],
mask1: [1, 0, 1],
mask2: [0, 1, 1],
corners: [
[1, 0, 0, 0, 1],
[0, 0, 0, 1, 1],
[1, 1, 0, 0, 0],
[0, 1, 0, 1, 0]
]
},
south: {
dir: [0, 0, 1],
mask1: [1, 0, 1],
mask2: [0, 1, 1],
corners: [
[0, 0, 1, 0, 1],
[1, 0, 1, 1, 1],
[0, 1, 1, 0, 0],
[1, 1, 1, 1, 0]
]
}
up: {
dir: [0, 1, 0],
mask1: [1, 1, 0],
mask2: [0, 1, 1],
corners: [
[0, 1, 1, 0, 1],
[1, 1, 1, 1, 1],
[0, 1, 0, 0, 0],
[1, 1, 0, 1, 0]
]
},
down: {
dir: [0, -1, 0],
mask1: [1, 1, 0],
mask2: [0, 1, 1],
corners: [
[1, 0, 1, 0, 1],
[0, 0, 1, 1, 1],
[1, 0, 0, 0, 0],
[0, 0, 0, 1, 0]
]
},
east: {
dir: [1, 0, 0],
mask1: [1, 1, 0],
mask2: [1, 0, 1],
corners: [
[1, 1, 1, 0, 0],
[1, 0, 1, 0, 1],
[1, 1, 0, 1, 0],
[1, 0, 0, 1, 1]
]
},
west: {
dir: [-1, 0, 0],
mask1: [1, 1, 0],
mask2: [1, 0, 1],
corners: [
[0, 1, 0, 0, 0],
[0, 0, 0, 0, 1],
[0, 1, 1, 1, 0],
[0, 0, 1, 1, 1]
]
},
north: {
dir: [0, 0, -1],
mask1: [1, 0, 1],
mask2: [0, 1, 1],
corners: [
[1, 0, 0, 0, 1],
[0, 0, 0, 1, 1],
[1, 1, 0, 0, 0],
[0, 1, 0, 1, 0]
]
},
south: {
dir: [0, 0, 1],
mask1: [1, 0, 1],
mask2: [0, 1, 1],
corners: [
[0, 0, 1, 0, 1],
[1, 0, 1, 1, 1],
[0, 1, 1, 0, 0],
[1, 1, 1, 1, 0]
]
}
}

View file

@ -1,273 +1,274 @@
/* eslint-disable @stylistic/function-call-argument-newline */
import { Vec3 } from 'vec3'
import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
import { Block } from 'prismarine-block'
import { BlockModelPartsResolved } from './world'
import { IndexedData } from 'minecraft-data'
import * as THREE from 'three'
import { BlockModelPartsResolved } from './world'
import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
type NeighborSide = 'up' | 'down' | 'east' | 'west' | 'north' | 'south'
function tintToGl (tint) {
const r = (tint >> 16) & 0xff
const g = (tint >> 8) & 0xff
const b = tint & 0xff
return [r / 255, g / 255, b / 255]
const r = (tint >> 16) & 0xff
const g = (tint >> 8) & 0xff
const b = tint & 0xff
return [r / 255, g / 255, b / 255]
}
type Neighbors = Partial<Record<NeighborSide, boolean>>
function renderElement (element: BlockElement, doAO: boolean, attr, globalMatrix, globalShift, block: Block | undefined, biome: string, neighbors: Neighbors) {
const cursor = new Vec3(0, 0, 0)
const cursor = new Vec3(0, 0, 0)
// const key = `${position.x},${position.y},${position.z}`
// if (!globalThis.allowedBlocks.includes(key)) return
// const cullIfIdentical = block.name.indexOf('glass') >= 0
// const key = `${position.x},${position.y},${position.z}`
// if (!globalThis.allowedBlocks.includes(key)) return
// const cullIfIdentical = block.name.indexOf('glass') >= 0
for (const face in element.faces) {
const eFace = element.faces[face]
const { corners, mask1, mask2 } = elemFaces[face]
const dir = matmul3(globalMatrix, elemFaces[face].dir)
// eslint-disable-next-line guard-for-in
for (const face in element.faces) {
const eFace = element.faces[face]
const { corners, mask1, mask2 } = elemFaces[face]
const dir = matmul3(globalMatrix, elemFaces[face].dir)
if (eFace.cullface) {
if (neighbors[face]) continue
}
if (eFace.cullface) {
if (neighbors[face]) continue
}
const minx = element.from[0]
const miny = element.from[1]
const minz = element.from[2]
const maxx = element.to[0]
const maxy = element.to[1]
const maxz = element.to[2]
const minx = element.from[0]
const miny = element.from[1]
const minz = element.from[2]
const maxx = element.to[0]
const maxy = element.to[1]
const maxz = element.to[2]
const texture = eFace.texture as any
const u = texture.u
const v = texture.v
const su = texture.su
const sv = texture.sv
const texture = eFace.texture as any
const { u } = texture
const { v } = texture
const { su } = texture
const { sv } = texture
const ndx = Math.floor(attr.positions.length / 3)
const ndx = Math.floor(attr.positions.length / 3)
let tint = [1, 1, 1]
if (eFace.tintindex !== undefined) {
if (eFace.tintindex === 0) {
// TODO
// if (block.name === 'redstone_wire') {
// tint = tints.redstone[`${block.getProperties().power}`]
// } else if (block.name === 'birch_leaves' ||
// block.name === 'spruce_leaves' ||
// block.name === 'lily_pad') {
// tint = tints.constant[block.name]
// } else if (block.name.includes('leaves') || block.name === 'vine') {
// tint = tints.foliage[biome]
// } else {
// tint = tints.grass[biome]
// }
}
}
const tint = [1, 1, 1]
if (eFace.tintindex !== undefined) {
if (eFace.tintindex === 0) {
// TODO
// if (block.name === 'redstone_wire') {
// tint = tints.redstone[`${block.getProperties().power}`]
// } else if (block.name === 'birch_leaves' ||
// block.name === 'spruce_leaves' ||
// block.name === 'lily_pad') {
// tint = tints.constant[block.name]
// } else if (block.name.includes('leaves') || block.name === 'vine') {
// tint = tints.foliage[biome]
// } else {
// tint = tints.grass[biome]
// }
}
}
// UV rotation
const r = eFace.rotation || 0
const uvcs = Math.cos(r * Math.PI / 180)
const uvsn = -Math.sin(r * Math.PI / 180)
// UV rotation
const r = eFace.rotation || 0
const uvcs = Math.cos(r * Math.PI / 180)
const uvsn = -Math.sin(r * Math.PI / 180)
let localMatrix = null as any
let localShift = null as any
let localMatrix = null as any
let localShift = null as any
if (element.rotation) {
// todo do we support rescale?
localMatrix = buildRotationMatrix(
element.rotation.axis,
element.rotation.angle
)
if (element.rotation) {
// todo do we support rescale?
localMatrix = buildRotationMatrix(
element.rotation.axis,
element.rotation.angle
)
localShift = vecsub3(
element.rotation.origin,
matmul3(
localMatrix,
element.rotation.origin
)
)
}
localShift = vecsub3(
element.rotation.origin,
matmul3(
localMatrix,
element.rotation.origin
)
)
}
const aos: number[] = []
// const neighborPos = position.plus(new Vec3(...dir))
// const baseLight = world.getLight(neighborPos, undefined, undefined, block.name) / 15
const baseLight = 1
for (const pos of corners) {
let vertex = [
(pos[0] ? maxx : minx),
(pos[1] ? maxy : miny),
(pos[2] ? maxz : minz)
]
const aos: number[] = []
// const neighborPos = position.plus(new Vec3(...dir))
// const baseLight = world.getLight(neighborPos, undefined, undefined, block.name) / 15
const baseLight = 1
for (const pos of corners) {
let vertex = [
(pos[0] ? maxx : minx),
(pos[1] ? maxy : miny),
(pos[2] ? maxz : minz)
]
vertex = vecadd3(matmul3(localMatrix, vertex), localShift)
vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift)
vertex = vertex.map(v => v / 16)
vertex = vecadd3(matmul3(localMatrix, vertex), localShift)
vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift)
vertex = vertex.map(v => v / 16)
attr.positions.push(
vertex[0]/* + (cursor.x & 15) - 8 */,
vertex[1]/* + (cursor.y & 15) x */,
vertex[2]/* + (cursor.z & 15) - 8 */
)
attr.positions.push(
vertex[0]/* + (cursor.x & 15) - 8 */,
vertex[1]/* + (cursor.y & 15) x */,
vertex[2]/* + (cursor.z & 15) - 8 */
)
attr.normals.push(...dir)
attr.normals.push(...dir)
const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5
const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5
attr.uvs.push(baseu * su + u, basev * sv + v)
const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5
const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5
attr.uvs.push(baseu * su + u, basev * sv + v)
let light = 1
if (doAO) {
const dx = pos[0] * 2 - 1
const dy = pos[1] * 2 - 1
const dz = pos[2] * 2 - 1
const cornerDir = matmul3(globalMatrix, [dx, dy, dz])
const side1Dir = matmul3(globalMatrix, [dx * mask1[0], dy * mask1[1], dz * mask1[2]])
const side2Dir = matmul3(globalMatrix, [dx * mask2[0], dy * mask2[1], dz * mask2[2]])
// const side1 = world.getBlock(cursor.offset(...side1Dir))
// const side2 = world.getBlock(cursor.offset(...side2Dir))
// const corner = world.getBlock(cursor.offset(...cornerDir))
let light = 1
if (doAO) {
const dx = pos[0] * 2 - 1
const dy = pos[1] * 2 - 1
const dz = pos[2] * 2 - 1
const cornerDir = matmul3(globalMatrix, [dx, dy, dz])
const side1Dir = matmul3(globalMatrix, [dx * mask1[0], dy * mask1[1], dz * mask1[2]])
const side2Dir = matmul3(globalMatrix, [dx * mask2[0], dy * mask2[1], dz * mask2[2]])
// const side1 = world.getBlock(cursor.offset(...side1Dir))
// const side2 = world.getBlock(cursor.offset(...side2Dir))
// const corner = world.getBlock(cursor.offset(...cornerDir))
let cornerLightResult = 15
// if (/* world.config.smoothLighting */false) { // todo fix
// const side1Light = world.getLight(cursor.plus(new Vec3(...side1Dir)), true)
// const side2Light = world.getLight(cursor.plus(new Vec3(...side2Dir)), true)
// const cornerLight = world.getLight(cursor.plus(new Vec3(...cornerDir)), true)
// // interpolate
// cornerLightResult = (side1Light + side2Light + cornerLight) / 3
// }
// const side1Block = world.shouldMakeAo(side1) ? 1 : 0
// const side2Block = world.shouldMakeAo(side2) ? 1 : 0
// const cornerBlock = world.shouldMakeAo(corner) ? 1 : 0
const side1Block = 0
const side2Block = 0
const cornerBlock = 0
// TODO: correctly interpolate ao light based on pos (evaluate once for each corner of the block)
const ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock))
// todo light should go upper on lower blocks
light = (ao + 1) / 4 * (cornerLightResult / 15)
aos.push(ao)
}
attr.colors.push(baseLight * tint[0] * light, baseLight * tint[1] * light, baseLight * tint[2] * light)
}
// if (needTiles) {
// attr.tiles[`${cursor.x},${cursor.y},${cursor.z}`] ??= {
// block: block.name,
// faces: [],
// }
// attr.tiles[`${cursor.x},${cursor.y},${cursor.z}`].faces.push({
// face,
// neighbor: `${neighborPos.x},${neighborPos.y},${neighborPos.z}`,
// light: baseLight
// // texture: eFace.texture.name,
// })
const cornerLightResult = 15
// if (/* world.config.smoothLighting */false) { // todo fix
// const side1Light = world.getLight(cursor.plus(new Vec3(...side1Dir)), true)
// const side2Light = world.getLight(cursor.plus(new Vec3(...side2Dir)), true)
// const cornerLight = world.getLight(cursor.plus(new Vec3(...cornerDir)), true)
// // interpolate
// cornerLightResult = (side1Light + side2Light + cornerLight) / 3
// }
if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) {
attr.indices.push(
ndx, ndx + 3, ndx + 2,
ndx, ndx + 1, ndx + 3
)
} else {
attr.indices.push(
ndx, ndx + 1, ndx + 2,
ndx + 2, ndx + 1, ndx + 3
)
}
// const side1Block = world.shouldMakeAo(side1) ? 1 : 0
// const side2Block = world.shouldMakeAo(side2) ? 1 : 0
// const cornerBlock = world.shouldMakeAo(corner) ? 1 : 0
const side1Block = 0
const side2Block = 0
const cornerBlock = 0
// TODO: correctly interpolate ao light based on pos (evaluate once for each corner of the block)
const ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock))
// todo light should go upper on lower blocks
light = (ao + 1) / 4 * (cornerLightResult / 15)
aos.push(ao)
}
attr.colors.push(baseLight * tint[0] * light, baseLight * tint[1] * light, baseLight * tint[2] * light)
}
// if (needTiles) {
// attr.tiles[`${cursor.x},${cursor.y},${cursor.z}`] ??= {
// block: block.name,
// faces: [],
// }
// attr.tiles[`${cursor.x},${cursor.y},${cursor.z}`].faces.push({
// face,
// neighbor: `${neighborPos.x},${neighborPos.y},${neighborPos.z}`,
// light: baseLight
// // texture: eFace.texture.name,
// })
// }
if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) {
attr.indices.push(
ndx, ndx + 3, ndx + 2,
ndx, ndx + 1, ndx + 3
)
} else {
attr.indices.push(
ndx, ndx + 1, ndx + 2,
ndx + 2, ndx + 1, ndx + 3
)
}
}
}
export const renderBlockThreeAttr = (models: BlockModelPartsResolved, block: Block | undefined, biome: string, mcData: IndexedData, variants = [], neighbors: Neighbors = {}) => {
const sx = 0
const sy = 0
const sz = 0
const sx = 0
const sy = 0
const sz = 0
const attr = {
sx: sx + 0.5,
sy: sy + 0.5,
sz: sz + 0.5,
positions: [],
normals: [],
colors: [],
uvs: [],
t_positions: [],
t_normals: [],
t_colors: [],
t_uvs: [],
indices: [],
tiles: {},
} as Record<string, any>
const attr = {
sx: sx + 0.5,
sy: sy + 0.5,
sz: sz + 0.5,
positions: [],
normals: [],
colors: [],
uvs: [],
t_positions: [],
t_normals: [],
t_colors: [],
t_uvs: [],
indices: [],
tiles: {},
} as Record<string, any>
for (const [i, modelVars] of models.entries()) {
const model = modelVars[variants[i]] ?? modelVars[0]
if (!model) continue
let globalMatrix = null as any
let globalShift = null as any
for (const axis of ['x', 'y', 'z'] as const) {
if (axis in model) {
if (!globalMatrix) globalMatrix = buildRotationMatrix(axis, -(model[axis] ?? 0))
else globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -(model[axis] ?? 0)))
}
}
if (globalMatrix) {
globalShift = [8, 8, 8]
globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift))
}
const ao = model.ao ?? true
for (const element of model.elements ?? []) {
renderElement(element, ao, attr, globalMatrix, globalShift, block, biome, neighbors)
}
for (const [i, modelVars] of models.entries()) {
const model = modelVars[variants[i]] ?? modelVars[0]
if (!model) continue
let globalMatrix = null as any
let globalShift = null as any
for (const axis of ['x', 'y', 'z'] as const) {
if (axis in model) {
if (globalMatrix) { globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -(model[axis] ?? 0))) } else { globalMatrix = buildRotationMatrix(axis, -(model[axis] ?? 0)) }
}
}
if (globalMatrix) {
globalShift = [8, 8, 8]
globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift))
}
let ndx = attr.positions.length / 3
for (let i = 0; i < attr.t_positions.length / 12; i++) {
attr.indices.push(
ndx, ndx + 1, ndx + 2,
ndx + 2, ndx + 1, ndx + 3,
// back face
ndx, ndx + 2, ndx + 1,
ndx + 2, ndx + 3, ndx + 1
)
ndx += 4
const ao = model.ao ?? true
for (const element of model.elements ?? []) {
renderElement(element, ao, attr, globalMatrix, globalShift, block, biome, neighbors)
}
}
attr.positions.push(...attr.t_positions)
attr.normals.push(...attr.t_normals)
attr.colors.push(...attr.t_colors)
attr.uvs.push(...attr.t_uvs)
let ndx = attr.positions.length / 3
for (let i = 0; i < attr.t_positions.length / 12; i++) {
attr.indices.push(
ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3,
// back face
ndx, ndx + 2, ndx + 1, ndx + 2, ndx + 3, ndx + 1
)
ndx += 4
}
delete attr.t_positions
delete attr.t_normals
delete attr.t_colors
delete attr.t_uvs
attr.positions.push(...attr.t_positions)
attr.normals.push(...attr.t_normals)
attr.colors.push(...attr.t_colors)
attr.uvs.push(...attr.t_uvs)
attr.positions = new Float32Array(attr.positions) as any
attr.normals = new Float32Array(attr.normals) as any
attr.colors = new Float32Array(attr.colors) as any
attr.uvs = new Float32Array(attr.uvs) as any
delete attr.t_positions
delete attr.t_normals
delete attr.t_colors
delete attr.t_uvs
return attr
attr.positions = new Float32Array(attr.positions) as any
attr.normals = new Float32Array(attr.normals) as any
attr.colors = new Float32Array(attr.colors) as any
attr.uvs = new Float32Array(attr.uvs) as any
return attr
}
export const renderBlockThree = (...args: Parameters<typeof renderBlockThreeAttr>) => {
const attr = renderBlockThreeAttr(...args)
const data = {
geometry: attr
}
const attr = renderBlockThreeAttr(...args)
const data = {
geometry: attr
}
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))
geometry.setAttribute('color', new THREE.BufferAttribute(data.geometry.colors, 3))
geometry.setAttribute('uv', new THREE.BufferAttribute(data.geometry.uvs, 2))
geometry.setIndex(data.geometry.indices)
geometry.name = 'block-geometry'
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))
geometry.setAttribute('color', new THREE.BufferAttribute(data.geometry.colors, 3))
geometry.setAttribute('uv', new THREE.BufferAttribute(data.geometry.uvs, 2))
geometry.setIndex(data.geometry.indices)
geometry.name = 'block-geometry'
return geometry
return geometry
}

View file

@ -1,76 +1,76 @@
import { setBlockStatesData, getSectionGeometry } from '../models'
import { World as MesherWorld } from '../world'
import ChunkLoader, { PCChunk } from 'prismarine-chunk'
import { Vec3 } from 'vec3'
import MinecraftData from 'minecraft-data'
import blocksAtlasesJson from 'mc-assets/dist/blocksAtlases.json'
import { World as MesherWorld } from '../world'
import { setBlockStatesData, getSectionGeometry } from '../models'
export const setup = (version, initialBlocks: [number[], string][]) => {
const mcData = MinecraftData(version)
const blockStatesModels = require(`mc-assets/dist/blockStatesModels.json`)
const mesherWorld = new MesherWorld(version)
const Chunk = ChunkLoader(version)
const chunk1 = new Chunk(undefined as any)
export const setup = (version, initialBlocks: Array<[number[], string]>) => {
const mcData = MinecraftData(version)
const blockStatesModels = require(`mc-assets/dist/blockStatesModels.json`)
const mesherWorld = new MesherWorld(version)
const Chunk = ChunkLoader(version)
const chunk1 = new Chunk(undefined as any)
const pos = new Vec3(2, 5, 2)
for (const [addPos, name] of initialBlocks) {
chunk1.setBlockStateId(pos.offset(addPos[0], addPos[1], addPos[2]), mcData.blocksByName[name].defaultState!)
}
const getGeometry = () => {
const sectionGeometry = getSectionGeometry(0, 0, 0, mesherWorld)
const centerFaces = sectionGeometry.tiles[`${pos.x},${pos.y},${pos.z}`]?.faces.length ?? 0
const totalTiles = Object.values(sectionGeometry.tiles).reduce((acc, val: any) => acc + val.faces.length, 0)
const centerTileNeighbors = Object.entries(sectionGeometry.tiles).reduce((acc, [key, val]: any) => {
return acc + val.faces.filter((face: any) => face.neighbor === `${pos.x},${pos.y},${pos.z}`).length
}, 0)
return {
centerFaces,
totalTiles,
centerTileNeighbors,
faces: sectionGeometry.tiles[`${pos.x},${pos.y},${pos.z}`]?.faces ?? [],
attr: sectionGeometry
}
}
setBlockStatesData(blockStatesModels, blocksAtlasesJson, true, false)
const reload = () => {
mesherWorld.removeColumn(0, 0)
mesherWorld.addColumn(0, 0, chunk1.toJson())
}
reload()
const getLights = () => {
return Object.fromEntries(getGeometry().faces.map(({ face, light }) => ([face, light * 15 - 2])))
}
const setLight = (x: number, y: number, z: number, val = 0) => {
// create columns first
chunk1.setBlockLight(pos.offset(x, y, z), 15)
chunk1.setSkyLight(pos.offset(x, y, z), 15)
chunk1.setBlockLight(pos.offset(x, y, z), val)
chunk1.setSkyLight(pos.offset(x, y, z), 0)
}
const pos = new Vec3(2, 5, 2)
for (const [addPos, name] of initialBlocks) {
chunk1.setBlockStateId(pos.offset(addPos[0], addPos[1], addPos[2]), mcData.blocksByName[name].defaultState!)
}
const getGeometry = () => {
const sectionGeometry = getSectionGeometry(0, 0, 0, mesherWorld)
const centerFaces = sectionGeometry.tiles[`${pos.x},${pos.y},${pos.z}`]?.faces.length ?? 0
const totalTiles = Object.values(sectionGeometry.tiles).reduce((acc, val: any) => acc + val.faces.length, 0)
const centerTileNeighbors = Object.entries(sectionGeometry.tiles).reduce((acc, [key, val]: any) => {
return acc + val.faces.filter((face: any) => face.neighbor === `${pos.x},${pos.y},${pos.z}`).length
}, 0)
return {
mesherWorld,
setLight,
getLights,
getGeometry,
pos,
mcData,
reload,
chunk: chunk1 as PCChunk
centerFaces,
totalTiles,
centerTileNeighbors,
faces: sectionGeometry.tiles[`${pos.x},${pos.y},${pos.z}`]?.faces ?? [],
attr: sectionGeometry
}
}
setBlockStatesData(blockStatesModels, blocksAtlasesJson, true, false)
const reload = () => {
mesherWorld.removeColumn(0, 0)
mesherWorld.addColumn(0, 0, chunk1.toJson())
}
reload()
const getLights = () => {
return Object.fromEntries(getGeometry().faces.map(({ face, light }) => ([face, light * 15 - 2])))
}
const setLight = (x: number, y: number, z: number, val = 0) => {
// create columns first
chunk1.setBlockLight(pos.offset(x, y, z), 15)
chunk1.setSkyLight(pos.offset(x, y, z), 15)
chunk1.setBlockLight(pos.offset(x, y, z), val)
chunk1.setSkyLight(pos.offset(x, y, z), 0)
}
return {
mesherWorld,
setLight,
getLights,
getGeometry,
pos,
mcData,
reload,
chunk: chunk1 as PCChunk
}
}
// surround it
const addPositions = [
// [[0, 0, 0], 'diamond_block'],
[[1, 0, 0], 'stone'],
[[-1, 0, 0], 'stone'],
[[0, 1, 0], 'stone'],
[[0, -1, 0], 'stone'],
[[0, 0, 1], 'stone'],
[[0, 0, -1], 'stone'],
// [[0, 0, 0], 'diamond_block'],
[[1, 0, 0], 'stone'],
[[-1, 0, 0], 'stone'],
[[0, 1, 0], 'stone'],
[[0, -1, 0], 'stone'],
[[0, 0, 1], 'stone'],
[[0, 0, -1], 'stone'],
]

View file

@ -1,13 +1,13 @@
import { setup } from './mesherTester'
const addPositions = [
// [[0, 0, 0], 'diamond_block'],
[[1, 0, 0], 'stone'],
[[-1, 0, 0], 'stone'],
[[0, 1, 0], 'stone'],
[[0, -1, 0], 'stone'],
[[0, 0, 1], 'stone'],
[[0, 0, -1], 'stone'],
// [[0, 0, 0], 'diamond_block'],
[[1, 0, 0], 'stone'],
[[-1, 0, 0], 'stone'],
[[0, 1, 0], 'stone'],
[[0, -1, 0], 'stone'],
[[0, 0, 1], 'stone'],
[[0, 0, -1], 'stone'],
] as const
const { mesherWorld, getGeometry, pos, mcData } = setup('1.18.1', addPositions as any)

View file

@ -1,6 +1,6 @@
import { test, expect } from 'vitest'
import { setup } from './mesherTester'
import supportedVersions from '../../../../../src/supportedVersions.mjs'
import { setup } from './mesherTester'
const lastVersion = supportedVersions.at(-1)
@ -16,14 +16,14 @@ const addPositions = [
test('Known blocks are not rendered', () => {
const { mesherWorld, getGeometry, pos, mcData } = setup(lastVersion, addPositions as any)
const ignoreAsExpected = ['air', 'cave_air', 'void_air', 'barrier', 'water', 'lava', 'moving_piston', 'light']
const ignoreAsExpected = new Set(['air', 'cave_air', 'void_air', 'barrier', 'water', 'lava', 'moving_piston', 'light'])
let time = 0
let times = 0
const missingBlocks = {}/* as {[number, number]} */
const erroredBlocks = {}/* as {[number, number]} */
for (const block of mcData.blocksArray) {
if (ignoreAsExpected.includes(block.name)) continue
if (ignoreAsExpected.has(block.name)) continue
// if (block.maxStateId! - block.minStateId! > 100) continue
// for (let i = block.minStateId!; i <= block.maxStateId!; i++) {
for (let i = block.defaultState!; i <= block.defaultState!; i++) {

View file

@ -1,11 +1,11 @@
import Chunks from 'prismarine-chunk'
import mcData from 'minecraft-data'
import { Block } from "prismarine-block"
import { Block } from 'prismarine-block'
import { Vec3 } from 'vec3'
import moreBlockDataGeneratedJson from '../moreBlockDataGenerated.json'
import { defaultMesherConfig } from './shared'
import legacyJson from '../../../../src/preflatMap.json'
import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
import moreBlockDataGeneratedJson from '../moreBlockDataGenerated.json'
import legacyJson from '../../../../src/preflatMap.json'
import { defaultMesherConfig } from './shared'
const ignoreAoBlocks = Object.keys(moreBlockDataGeneratedJson.noOcclusions)
@ -59,7 +59,7 @@ export class World {
) + 2
)
// lightsCache.set(key, result)
if (result === 2 && [this.getBlock(pos)?.name ?? '', curBlockName].some(x => x.match(/_stairs|slab|glass_pane/)) && !skipMoreChecks) { // todo this is obviously wrong
if (result === 2 && [this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong
const lights = [
this.getLight(pos.offset(0, 1, 0), undefined, true),
this.getLight(pos.offset(0, -1, 0), undefined, true),
@ -130,7 +130,7 @@ export class World {
}
})
if (this.preflat) {
//@ts-ignore
//@ts-expect-error
b._properties = {}
const namePropsStr = legacyJson.blocks[b.type + ':' + b.metadata] || findClosestLegacyBlockFallback(b.type, b.metadata, pos)
@ -139,11 +139,11 @@ export class World {
const propsStr = namePropsStr.split('[')?.[1]?.split(']')
if (propsStr) {
const newProperties = Object.fromEntries(propsStr.join('').split(',').map(x => {
let [key, val] = x.split('=') as any
if (!isNaN(val)) val = parseInt(val)
let [key, val] = x.split('=')
if (!isNaN(val)) val = parseInt(val, 10)
return [key, val]
}))
//@ts-ignore
//@ts-expect-error
b._properties = newProperties
}
}

View file

@ -1,7 +1,9 @@
/* eslint-disable unicorn/no-abusive-eslint-disable */
/* eslint-disable */
const THREE = require('three')
const { MeshLine, MeshLineMaterial } = require('three.meshline')
function getMesh (primitive, camera) {
function getMesh(primitive, camera) {
if (primitive.type === 'line') {
const color = primitive.color ? primitive.color : 0xff0000
const resolution = new THREE.Vector2(window.innerWidth / camera.zoom, window.innerHeight / camera.zoom)
@ -53,7 +55,7 @@ class Primitives {
this.primitives = {}
}
clear () {
clear() {
for (const mesh of Object.values(this.primitives)) {
this.scene.remove(mesh)
disposeObject(mesh)
@ -61,7 +63,7 @@ class Primitives {
this.primitives = {}
}
update (primitive) {
update(primitive) {
if (this.primitives[primitive.id]) {
this.scene.remove(this.primitives[primitive.id])
disposeObject(this.primitives[primitive.id])
@ -75,7 +77,7 @@ class Primitives {
}
}
function GridBoxGeometry (geometry, independent) {
function GridBoxGeometry(geometry, independent) {
if (!(geometry instanceof THREE.BoxBufferGeometry)) {
console.log("GridBoxGeometry: the parameter 'geometry' has to be of the type THREE.BoxBufferGeometry")
return geometry
@ -113,7 +115,7 @@ function GridBoxGeometry (geometry, independent) {
newGeometry.setIndex(fullIndices)
function indexSide (x, y, shift) {
function indexSide(x, y, shift) {
const indices = []
for (let i = 0; i < y + 1; i++) {
let index11 = 0

View file

@ -2,7 +2,7 @@ import { fromFormattedString } from '@xmcl/text-component'
export const formattedStringToSimpleString = (str) => {
const result = fromFormattedString(str)
let str = result.text
str = result.text
// todo recursive
for (const extra of result.extra) {
str += extra.text

View file

@ -1,14 +1,12 @@
export function getBufferFromStream (stream) {
return new Promise(
(resolve, reject) => {
let buffer = Buffer.from([])
stream.on('data', buf => {
buffer = Buffer.concat([buffer, buf])
})
stream.on('end', () => resolve(buffer))
stream.on('error', reject)
}
)
export async function getBufferFromStream (stream) {
return new Promise((resolve, reject) => {
let buffer = Buffer.from([])
stream.on('data', buf => {
buffer = Buffer.concat([buffer, buf])
})
stream.on('end', () => resolve(buffer))
stream.on('error', reject)
})
}
export function openURL (url, newTab = true) {

View file

@ -7,6 +7,7 @@ export const disposeObject = (obj: THREE.Object3D) => {
obj.material?.dispose?.()
}
if (obj.children) {
// eslint-disable-next-line unicorn/no-array-for-each
obj.children.forEach(disposeObject)
}
}

View file

@ -1,8 +1,8 @@
const THREE = require('three')
const path = require('path')
const THREE = require('three')
const textureCache = {}
function loadTexture (texture, cb) {
function loadTexture(texture, cb) {
if (!textureCache[texture]) {
const url = path.resolve(__dirname, '../../public/' + texture)
textureCache[texture] = new THREE.TextureLoader().load(url)
@ -10,7 +10,7 @@ function loadTexture (texture, cb) {
cb(textureCache[texture])
}
function loadJSON (json, cb) {
function loadJSON(json, cb) {
cb(require(path.resolve(__dirname, '../../public/' + json)))
}

View file

@ -1,4 +1,4 @@
function safeRequire (path) {
function safeRequire(path) {
try {
return require(path)
} catch (e) {
@ -11,7 +11,7 @@ const THREE = require('three')
const textureCache = {}
// todo not ideal, export different functions for browser and node
export function loadTexture (texture, cb) {
export function loadTexture(texture, cb) {
if (process.platform === 'browser') {
return require('./utils.web').loadTexture(texture, cb)
}
@ -26,7 +26,7 @@ export function loadTexture (texture, cb) {
}
}
export function loadJSON (json, cb) {
export function loadJSON(json, cb) {
if (process.platform === 'browser') {
return require('./utils.web').loadJSON(json, cb)
}

View file

@ -2,7 +2,7 @@
const THREE = require('three')
const textureCache = {}
function loadTexture (texture, cb, onLoad) {
function loadTexture(texture, cb, onLoad) {
const cached = textureCache[texture]
if (!cached) {
textureCache[texture] = new THREE.TextureLoader().load(texture, onLoad)
@ -11,12 +11,12 @@ function loadTexture (texture, cb, onLoad) {
if (cached) onLoad?.()
}
function loadJSON (url, callback) {
function loadJSON(url, callback) {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.responseType = 'json'
xhr.onload = function () {
const status = xhr.status
const { status } = xhr
if (status === 200) {
callback(xhr.response)
} else {

View file

@ -1,13 +1,12 @@
import EventEmitter from 'events'
import * as THREE from 'three'
import { Vec3 } from 'vec3'
import { generateSpiralMatrix } from 'flying-squid/dist/utils'
import worldBlockProvider from 'mc-assets/dist/worldBlockProvider'
import { Entities } from './entities'
import { Primitives } from './primitives'
import EventEmitter from 'events'
import { WorldRendererThree } from './worldrendererThree'
import { generateSpiralMatrix } from 'flying-squid/dist/utils'
import { WorldRendererCommon, WorldRendererConfig, defaultWorldRendererConfig } from './worldrendererCommon'
import { versionToNumber } from '../prepare/utils'
import worldBlockProvider from 'mc-assets/dist/worldBlockProvider'
import { renderBlockThree } from './mesher/standaloneRenderer'
export class Viewer {
@ -29,6 +28,7 @@ export class Viewer {
get camera () {
return this.world.camera
}
set camera (camera) {
this.world.camera = camera
}
@ -79,7 +79,7 @@ export class Viewer {
setVersion (userVersion: string, texturesVersion = userVersion) {
console.log('[viewer] Using version:', userVersion, 'textures:', texturesVersion)
this.world.setVersion(userVersion, texturesVersion).then(() => {
void this.world.setVersion(userVersion, texturesVersion).then(async () => {
return new THREE.TextureLoader().loadAsync(this.world.itemsAtlasParser!.latestImage)
}).then((texture) => {
this.entities.itemsTexture = texture
@ -109,11 +109,11 @@ export class Viewer {
}
})
const geometry = renderBlockThree(models, undefined, 'plains', loadedData)
const material = this.world.material
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 helper = new THREE.BoxHelper(mesh, 0xffff00)
const helper = new THREE.BoxHelper(mesh, 0xff_ff_00)
mesh.add(helper)
this.scene.add(mesh)
}
@ -148,7 +148,7 @@ export class Viewer {
const sound = new THREE.PositionalAudio(this.audioListener)
const audioLoader = new THREE.AudioLoader()
let start = Date.now()
const start = Date.now()
audioLoader.loadAsync(path).then((buffer) => {
if (Date.now() - start > 500) return
// play
@ -213,21 +213,21 @@ export class Viewer {
this.world.timeUpdated?.(timeOfDay)
let skyLight = 15
if (timeOfDay < 0 || timeOfDay > 24000) {
throw new Error("Invalid time of day. It should be between 0 and 24000.")
} else if (timeOfDay <= 6000 || timeOfDay >= 18000) {
if (timeOfDay < 0 || timeOfDay > 24_000) {
throw new Error('Invalid time of day. It should be between 0 and 24000.')
} else if (timeOfDay <= 6000 || timeOfDay >= 18_000) {
skyLight = 15
} else if (timeOfDay > 6000 && timeOfDay < 12000) {
} else if (timeOfDay > 6000 && timeOfDay < 12_000) {
skyLight = 15 - ((timeOfDay - 6000) / 6000) * 15
} else if (timeOfDay >= 12000 && timeOfDay < 18000) {
skyLight = ((timeOfDay - 12000) / 6000) * 15
} else if (timeOfDay >= 12_000 && timeOfDay < 18_000) {
skyLight = ((timeOfDay - 12_000) / 6000) * 15
}
skyLight = Math.floor(skyLight) // todo: remove this after optimization
if (this.world.mesherConfig.skyLight === skyLight) return
this.world.mesherConfig.skyLight = skyLight
; (this.world as WorldRendererThree).rerenderAllChunks?.()
this.world.mesherConfig.skyLight = skyLight;
(this.world as WorldRendererThree).rerenderAllChunks?.()
})
emitter.emit('listening')

View file

@ -1,118 +1,120 @@
import * as THREE from 'three'
import { statsEnd, statsStart } from '../../../src/topRightStats'
// wrapper for now
export class ViewerWrapper {
previousWindowWidth: number
previousWindowHeight: number
globalObject = globalThis as any
stopRenderOnBlur = false
addedToPage = false
renderInterval = 0
renderIntervalUnfocused: number | undefined
fpsInterval
previousWindowWidth: number
previousWindowHeight: number
globalObject = globalThis as any
stopRenderOnBlur = false
addedToPage = false
renderInterval = 0
renderIntervalUnfocused: number | undefined
fpsInterval
constructor (public canvas: HTMLCanvasElement, public renderer?: THREE.WebGLRenderer) {
if (this.renderer) this.globalObject.renderer = this.renderer
constructor (public canvas: HTMLCanvasElement, public renderer?: THREE.WebGLRenderer) {
if (this.renderer) this.globalObject.renderer = this.renderer
}
addToPage (startRendering = true) {
if (this.addedToPage) throw new Error('Already added to page')
let pixelRatio = window.devicePixelRatio || 1 // todo this value is too high on ios, need to check, probably we should use avg, also need to make it configurable
if (this.renderer) {
if (!this.renderer.capabilities.isWebGL2) pixelRatio = 1 // webgl1 has issues with high pixel ratio (sometimes screen is clipped)
this.renderer.setPixelRatio(pixelRatio)
this.renderer.setSize(window.innerWidth, window.innerHeight)
} else {
this.canvas.width = window.innerWidth * pixelRatio
this.canvas.height = window.innerHeight * pixelRatio
}
addToPage (startRendering = true) {
if (this.addedToPage) throw new Error('Already added to page')
let pixelRatio = window.devicePixelRatio || 1 // todo this value is too high on ios, need to check, probably we should use avg, also need to make it configurable
if (this.renderer) {
if (!this.renderer.capabilities.isWebGL2) pixelRatio = 1 // webgl1 has issues with high pixel ratio (sometimes screen is clipped)
this.renderer.setPixelRatio(pixelRatio)
this.renderer.setSize(window.innerWidth, window.innerHeight)
} else {
this.canvas.width = window.innerWidth * pixelRatio
this.canvas.height = window.innerHeight * pixelRatio
}
this.previousWindowWidth = window.innerWidth
this.previousWindowHeight = window.innerHeight
this.previousWindowWidth = window.innerWidth
this.previousWindowHeight = window.innerHeight
this.canvas.id = 'viewer-canvas'
document.body.appendChild(this.canvas)
this.canvas.id = 'viewer-canvas'
document.body.appendChild(this.canvas)
this.addedToPage = true
this.addedToPage = true
let max = 0
this.fpsInterval = setInterval(() => {
if (max > 0) {
viewer.world.droppedFpsPercentage = this.renderedFps / max
}
max = Math.max(this.renderedFps, max)
this.renderedFps = 0
}, 1000)
if (startRendering) {
this.globalObject.requestAnimationFrame(this.render.bind(this))
}
if (typeof window !== 'undefined') {
this.trackWindowFocus()
}
let max = 0
this.fpsInterval = setInterval(() => {
if (max > 0) {
viewer.world.droppedFpsPercentage = this.renderedFps / max
}
max = Math.max(this.renderedFps, max)
this.renderedFps = 0
}, 1000)
if (startRendering) {
this.globalObject.requestAnimationFrame(this.render.bind(this))
}
windowFocused = true
trackWindowFocus () {
window.addEventListener('focus', () => {
this.windowFocused = true
})
window.addEventListener('blur', () => {
this.windowFocused = false
})
if (typeof window !== 'undefined') {
this.trackWindowFocus()
}
}
dispose () {
if (!this.addedToPage) throw new Error('Not added to page')
document.body.removeChild(this.canvas)
this.renderer?.dispose()
// this.addedToPage = false
clearInterval(this.fpsInterval)
windowFocused = true
trackWindowFocus () {
window.addEventListener('focus', () => {
this.windowFocused = true
})
window.addEventListener('blur', () => {
this.windowFocused = false
})
}
dispose () {
if (!this.addedToPage) throw new Error('Not added to page')
this.canvas.remove()
this.renderer?.dispose()
// this.addedToPage = false
clearInterval(this.fpsInterval)
}
renderedFps = 0
lastTime = performance.now()
delta = 0
preRender = () => { }
postRender = () => { }
render (time: DOMHighResTimeStamp) {
if (this.globalObject.stopLoop) return
for (const fn of beforeRenderFrame) fn()
this.globalObject.requestAnimationFrame(this.render.bind(this))
if (this.globalObject.stopRender || this.renderer?.xr.isPresenting || (this.stopRenderOnBlur && !this.windowFocused)) return
const renderInterval = (this.windowFocused ? this.renderInterval : this.renderIntervalUnfocused) ?? this.renderInterval
if (renderInterval) {
this.delta += time - this.lastTime
this.lastTime = time
if (this.delta > renderInterval) {
this.delta %= renderInterval
// continue rendering
} else {
return
}
}
renderedFps = 0
lastTime = performance.now()
delta = 0
preRender = () => { }
postRender = () => { }
render (time: DOMHighResTimeStamp) {
if (this.globalObject.stopLoop) return
for (const fn of beforeRenderFrame) fn()
this.globalObject.requestAnimationFrame(this.render.bind(this))
if (this.globalObject.stopRender || this.renderer?.xr.isPresenting || (this.stopRenderOnBlur && !this.windowFocused)) return
const renderInterval = (this.windowFocused ? this.renderInterval : this.renderIntervalUnfocused) ?? this.renderInterval
if (renderInterval) {
this.delta += time - this.lastTime
this.lastTime = time
if (this.delta > renderInterval) {
this.delta %= renderInterval
// continue rendering
} else {
return
}
}
this.preRender()
statsStart()
// ios bug: viewport dimensions are updated after the resize event
if (this.previousWindowWidth !== window.innerWidth || this.previousWindowHeight !== window.innerHeight) {
this.resizeHandler()
this.previousWindowWidth = window.innerWidth
this.previousWindowHeight = window.innerHeight
}
viewer.render()
this.renderedFps++
statsEnd()
this.postRender()
this.preRender()
statsStart()
// ios bug: viewport dimensions are updated after the resize event
if (this.previousWindowWidth !== window.innerWidth || this.previousWindowHeight !== window.innerHeight) {
this.resizeHandler()
this.previousWindowWidth = window.innerWidth
this.previousWindowHeight = window.innerHeight
}
viewer.render()
this.renderedFps++
statsEnd()
this.postRender()
}
resizeHandler () {
const width = window.innerWidth
const height = window.innerHeight
resizeHandler () {
const width = window.innerWidth
const height = window.innerHeight
viewer.camera.aspect = width / height
viewer.camera.updateProjectionMatrix()
viewer.camera.aspect = width / height
viewer.camera.updateProjectionMatrix()
if (this.renderer) {
this.renderer.setSize(width, height)
}
viewer.world.handleResize()
if (this.renderer) {
this.renderer.setSize(width, height)
}
viewer.world.handleResize()
}
}

View file

@ -1,11 +1,11 @@
export function createWorkerProxy<T extends Record<string, (...args: any[]) => void>> (handlers: T): { __workerProxy: T } {
addEventListener('message', (event) => {
const { type, args } = event.data
if (handlers[type]) {
handlers[type](...args)
}
})
return null as any
addEventListener('message', (event) => {
const { type, args } = event.data
if (handlers[type]) {
handlers[type](...args)
}
})
return null as any
}
/**
@ -20,32 +20,34 @@ export function createWorkerProxy<T extends Record<string, (...args: any[]) => v
* ```
*/
export const useWorkerProxy = <T extends { __workerProxy: Record<string, (...args: any[]) => void> }> (worker: Worker, autoTransfer = true): T['__workerProxy'] & {
transfer: (...args: Transferable[]) => T['__workerProxy']
transfer: (...args: Transferable[]) => T['__workerProxy']
} => {
// in main thread
return new Proxy({} as any, {
get: (target, prop) => {
if (prop === 'transfer') return (...transferable: Transferable[]) => {
return new Proxy({}, {
get: (target, prop) => {
return (...args: any[]) => {
worker.postMessage({
type: prop,
args,
}, transferable)
}
}
})
}
return (...args: any[]) => {
const transfer = autoTransfer ? args.filter(arg => arg instanceof ArrayBuffer || arg instanceof MessagePort || arg instanceof ImageBitmap || arg instanceof OffscreenCanvas) : []
// in main thread
return new Proxy({} as any, {
get (target, prop) {
if (prop === 'transfer') {
return (...transferable: Transferable[]) => {
return new Proxy({}, {
get (target, prop) {
return (...args: any[]) => {
worker.postMessage({
type: prop,
args,
}, transfer)
type: prop,
args,
}, transferable)
}
}
})
}
})
}
return (...args: any[]) => {
const transfer = autoTransfer ? args.filter(arg => arg instanceof ArrayBuffer || arg instanceof MessagePort || arg instanceof ImageBitmap || arg instanceof OffscreenCanvas) : []
worker.postMessage({
type: prop,
args,
}, transfer)
}
}
})
}
// const workerProxy = createWorkerProxy({

View file

@ -1,10 +1,11 @@
import { chunkPos } from './simpleUtils'
/* eslint-disable guard-for-in */
// todo refactor into its own commons module
import { EventEmitter } from 'events'
import { generateSpiralMatrix, ViewRect } from 'flying-squid/dist/utils'
import { Vec3 } from 'vec3'
import { EventEmitter } from 'events'
import { BotEvents } from 'mineflayer'
import { chunkPos } from './simpleUtils'
export type ChunkPosKey = string
type ChunkPos = { x: number, z: number }
@ -15,9 +16,9 @@ type ChunkPos = { x: number, z: number }
*/
export class WorldDataEmitter extends EventEmitter {
private loadedChunks: Record<ChunkPosKey, boolean>
private lastPos: Vec3
private eventListeners: Record<string, any> = {};
private emitter: WorldDataEmitter
private readonly lastPos: Vec3
private eventListeners: Record<string, any> = {}
private readonly emitter: WorldDataEmitter
constructor (public world: typeof __type_bot['world'], public viewDistance: number, position: Vec3 = new Vec3(0, 0, 0)) {
super()
@ -31,7 +32,6 @@ export class WorldDataEmitter extends EventEmitter {
const dir = new Vec3(click.direction.x, click.direction.y, click.direction.z)
const block = this.world.raycast(ori, dir, 256)
if (!block) return
//@ts-ignore
this.emit('blockClicked', block, block.face, click.button)
})
}
@ -56,13 +56,13 @@ export class WorldDataEmitter extends EventEmitter {
this.eventListeners[bot.username] = {
// 'move': botPosition,
entitySpawn: (e: any) => {
entitySpawn (e: any) {
emitEntity(e)
},
entityUpdate: (e: any) => {
entityUpdate (e: any) {
emitEntity(e)
},
entityMoved: (e: any) => {
entityMoved (e: any) {
emitEntity(e)
},
entityGone: (e: any) => {
@ -72,7 +72,7 @@ export class WorldDataEmitter extends EventEmitter {
this.loadChunk(pos)
},
blockUpdate: (oldBlock: any, newBlock: any) => {
const stateId = newBlock.stateId ? newBlock.stateId : ((newBlock.type << 4) | newBlock.metadata)
const stateId = newBlock.stateId ?? ((newBlock.type << 4) | newBlock.metadata)
this.emitter.emit('blockUpdate', { pos: oldBlock.position, stateId })
},
time: () => {
@ -82,7 +82,7 @@ export class WorldDataEmitter extends EventEmitter {
bot._client.on('update_light', ({ chunkX, chunkZ }) => {
const chunkPos = new Vec3(chunkX * 16, 0, chunkZ * 16)
this.loadChunk(chunkPos, true)
void this.loadChunk(chunkPos, true)
})
this.emitter.on('listening', () => {
@ -126,17 +126,17 @@ export class WorldDataEmitter extends EventEmitter {
const positions = generateSpiralMatrix(this.viewDistance).map(([x, z]) => new Vec3((botX + x) * 16, 0, (botZ + z) * 16))
this.lastPos.update(pos)
await this._loadChunks(positions)
this._loadChunks(positions)
}
async _loadChunks (positions: Vec3[], sliceSize = 5, waitTime = 0) {
_loadChunks (positions: Vec3[], sliceSize = 5, waitTime = 0) {
let i = 0
const interval = setInterval(() => {
if (i >= positions.length) {
clearInterval(interval)
return
}
this.loadChunk(positions[i])
void this.loadChunk(positions[i])
i++
}, 1)
}
@ -146,7 +146,7 @@ export class WorldDataEmitter extends EventEmitter {
this.unloadAllChunks()
for (const loadedChunk in clonedLoadedChunks) {
const [x, z] = loadedChunk.split(',').map(Number)
this.loadChunk(new Vec3(x, 0, z))
void this.loadChunk(new Vec3(x, 0, z))
}
}
@ -155,6 +155,7 @@ export class WorldDataEmitter extends EventEmitter {
const dx = Math.abs(botX - Math.floor(pos.x / 16))
const dz = Math.abs(botZ - Math.floor(pos.z / 16))
if (dx <= this.viewDistance && dz <= this.viewDistance) {
// eslint-disable-next-line @typescript-eslint/await-thenable -- todo allow to use async world provider but not sure if needed
const column = await this.world.getColumnAt(pos['y'] ? pos as Vec3 : new Vec3(pos.x, 0, pos.z))
if (column) {
// todo optimize toJson data, make it clear why it is used
@ -164,7 +165,7 @@ export class WorldDataEmitter extends EventEmitter {
minY: column['minY'] ?? 0,
worldHeight: column['worldHeight'] ?? 256,
}
//@ts-ignore
//@ts-expect-error
this.emitter.emit('loadChunk', { x: pos.x, z: pos.z, chunk, blockEntities: column.blockEntities, worldConfig, isLightUpdate })
this.loadedChunks[`${pos.x},${pos.z}`] = true
}
@ -193,8 +194,8 @@ export class WorldDataEmitter extends EventEmitter {
const newView = new ViewRect(botX, botZ, this.viewDistance)
const chunksToUnload: Vec3[] = []
for (const coords of Object.keys(this.loadedChunks)) {
const x = parseInt(coords.split(',')[0])
const z = parseInt(coords.split(',')[1])
const x = parseInt(coords.split(',')[0], 10)
const z = parseInt(coords.split(',')[1], 10)
const p = new Vec3(x, 0, z)
const [chunkX, chunkZ] = chunkPos(p)
if (!newView.contains(chunkX, chunkZ)) {
@ -211,7 +212,7 @@ export class WorldDataEmitter extends EventEmitter {
return undefined!
}).filter(a => !!a)
this.lastPos.update(pos)
await this._loadChunks(positions)
this._loadChunks(positions)
} else {
this.emitter.emit('chunkPosUpdate', { pos }) // todo-low
this.lastPos.update(pos)

View file

@ -1,13 +1,8 @@
import * as THREE from 'three'
import { Vec3 } from 'vec3'
import { loadJSON } from './utils'
import { loadTexture } from './utils.web'
/* eslint-disable guard-for-in */
import { EventEmitter } from 'events'
import { Vec3 } from 'vec3'
import * as THREE from 'three'
import mcDataRaw from 'minecraft-data/data.js' // handled correctly in esbuild plugin
import { dynamicMcDataFiles } from '../../buildMesherConfig.mjs'
import { chunkPos } from './simpleUtils'
import { defaultMesherConfig } from './mesher/shared'
import { buildCleanupDecorator } from './cleanupDecorator'
import blocksAtlases from 'mc-assets/dist/blocksAtlases.json'
import blocksAtlasLatest from 'mc-assets/dist/blocksAtlasLatest.png'
import blocksAtlasLegacy from 'mc-assets/dist/blocksAtlasLegacy.png'
@ -15,8 +10,14 @@ import itemsAtlases from 'mc-assets/dist/itemsAtlases.json'
import itemsAtlasLatest from 'mc-assets/dist/itemsAtlasLatest.png'
import itemsAtlasLegacy from 'mc-assets/dist/itemsAtlasLegacy.png'
import { AtlasParser } from 'mc-assets'
import { dynamicMcDataFiles } from '../../buildMesherConfig.mjs'
import { getResourcepackTiles } from '../../../src/resourcePack'
import { toMajorVersion } from '../../../src/utils'
import { buildCleanupDecorator } from './cleanupDecorator'
import { defaultMesherConfig } from './mesher/shared'
import { loadTexture } from './utils.web'
import { loadJSON } from './utils'
import { chunkPos } from './simpleUtils'
function mod (x, n) {
return ((x % n) + n) % n
@ -42,15 +43,20 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
@worldCleanup()
active = false
version = undefined as string | undefined
@worldCleanup()
loadedChunks = {} as Record<string, boolean>
@worldCleanup()
finishedChunks = {} as Record<string, boolean>
@worldCleanup()
sectionsOutstanding = new Map<string, number>()
@worldCleanup()
renderUpdateEmitter = new EventEmitter()
customTexturesDataUrl = undefined as string | undefined
currentTextureImage = undefined as any
workers: any[] = []
@ -64,6 +70,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
chunksLength = 0
@worldCleanup()
allChunksFinished = false
handleResize = () => { }
mesherConfig = defaultMesherConfig
camera: THREE.PerspectiveCamera
@ -94,15 +101,13 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
for (let i = 0; i < numWorkers; i++) {
// Node environment needs an absolute path, but browser needs the url of the file
const workerName = 'mesher.js'
// eslint-disable-next-line node/no-path-concat
const src = typeof window === 'undefined' ? `${__dirname}/${workerName}` : workerName
const worker: any = new Worker(src)
const handleMessage = (data) => {
if (!this.active) return
this.handleWorkerMessage(data)
new Promise(resolve => {
setTimeout(resolve, 0)
})
if (data.type === 'sectionFinished') {
if (!this.sectionsOutstanding.get(data.key)) throw new Error(`sectionFinished event for non-outstanding section ${data.key}`)
this.sectionsOutstanding.set(data.key, this.sectionsOutstanding.get(data.key)! - 1)
@ -131,6 +136,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
}
worker.onmessage = ({ data }) => {
if (Array.isArray(data)) {
// eslint-disable-next-line unicorn/no-array-for-each
data.forEach(handleMessage)
return
}
@ -150,11 +156,11 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
/**
* Optionally update data that are depedendent on the viewer position
*/
updatePosDataChunk?(key: string): void
updatePosDataChunk? (key: string): void
allChunksLoaded?(): void
allChunksLoaded? (): void
timeUpdated?(newTime: number): void
timeUpdated? (newTime: number): void
updateViewerPosition (pos: Vec3) {
this.viewerPosition = pos
@ -236,11 +242,11 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
texture.minFilter = THREE.NearestFilter
texture.flipY = false
this.material.map = texture
this.currentTextureImage = this.material.map!.image
this.mesherConfig.textureSize = this.material.map!.image.width
this.currentTextureImage = this.material.map.image
this.mesherConfig.textureSize = this.material.map.image.width
for (const worker of this.workers) {
const blockstatesModels = this.blockstatesModels
const { blockstatesModels } = this
if (this.customBlockStates) {
// TODO! remove from other versions as well
blockstatesModels.blockstates.latest = {

View file

@ -2,394 +2,399 @@ import * as THREE from 'three'
import { Vec3 } from 'vec3'
import nbt from 'prismarine-nbt'
import PrismarineChatLoader from 'prismarine-chat'
import { renderSign } from '../sign-renderer/'
import { chunkPos, sectionPos } from './simpleUtils'
import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon'
import * as tweenJs from '@tweenjs/tween.js'
import { BloomPass, RenderPass, UnrealBloomPass, EffectComposer, WaterPass, GlitchPass } from 'three-stdlib'
import { renderSign } from '../sign-renderer'
import { chunkPos, sectionPos } from './simpleUtils'
import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon'
import { disposeObject } from './threeJsUtils'
export class WorldRendererThree extends WorldRendererCommon {
outputFormat = 'threeJs' as const
blockEntities = {}
sectionObjects: Record<string, THREE.Object3D> = {}
chunkTextures = new Map<string, { [pos: string]: THREE.Texture }>()
signsCache = new Map<string, any>()
starField: StarField
cameraSectionPos: Vec3 = new Vec3(0, 0, 0)
outputFormat = 'threeJs' as const
blockEntities = {}
sectionObjects: Record<string, THREE.Object3D> = {}
chunkTextures = new Map<string, { [pos: string]: THREE.Texture }>()
signsCache = new Map<string, any>()
starField: StarField
cameraSectionPos: Vec3 = new Vec3(0, 0, 0)
get tilesRendered () {
return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0)
get tilesRendered () {
return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0)
}
constructor (public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public config: WorldRendererConfig) {
super(config)
this.starField = new StarField(scene)
}
timeUpdated (newTime: number): void {
const nightTime = 13_500
const morningStart = 23_000
const displayStars = newTime > nightTime && newTime < morningStart
if (displayStars && !this.starField.points) {
this.starField.addToScene()
} else if (!displayStars && this.starField.points) {
this.starField.remove()
}
}
/**
* Optionally update data that are depedendent on the viewer position
*/
updatePosDataChunk (key: string) {
const [x, y, z] = key.split(',').map(x => Math.floor(+x / 16))
// sum of distances: x + y + z
const chunkDistance = Math.abs(x - this.cameraSectionPos.x) + Math.abs(y - this.cameraSectionPos.y) + Math.abs(z - this.cameraSectionPos.z)
const section = this.sectionObjects[key].children.find(child => child.name === 'mesh')!
section.renderOrder = 500 - chunkDistance
}
updateViewerPosition (pos: Vec3): void {
this.viewerPosition = pos
const cameraPos = this.camera.position.toArray().map(x => Math.floor(x / 16)) as [number, number, number]
this.cameraSectionPos = new Vec3(...cameraPos)
// eslint-disable-next-line guard-for-in
for (const key in this.sectionObjects) {
const value = this.sectionObjects[key]
if (!value) continue
this.updatePosDataChunk(key)
}
}
handleWorkerMessage (data: any): void {
if (data.type !== 'geometry') return
let object: THREE.Object3D = this.sectionObjects[data.key]
if (object) {
this.scene.remove(object)
disposeObject(object)
delete this.sectionObjects[data.key]
}
constructor (public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public config: WorldRendererConfig) {
super(config)
this.starField = new StarField(scene)
const chunkCoords = data.key.split(',')
if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || !data.geometry.positions.length || !this.active) return
// if (!this.initialChunksLoad && this.enableChunksLoadDelay) {
// const newPromise = new Promise(resolve => {
// if (this.droppedFpsPercentage > 0.5) {
// setTimeout(resolve, 1000 / 50 * this.droppedFpsPercentage)
// } else {
// setTimeout(resolve)
// }
// })
// this.promisesQueue.push(newPromise)
// for (const promise of this.promisesQueue) {
// await promise
// }
// }
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))
geometry.setAttribute('color', new THREE.BufferAttribute(data.geometry.colors, 3))
geometry.setAttribute('uv', new THREE.BufferAttribute(data.geometry.uvs, 2))
geometry.setIndex(data.geometry.indices)
const mesh = new THREE.Mesh(geometry, this.material)
mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz)
mesh.name = 'mesh'
object = new THREE.Group()
object.add(mesh)
// mesh with static dimensions: 16x16x16
const staticChunkMesh = new THREE.Mesh(new THREE.BoxGeometry(16, 16, 16), new THREE.MeshBasicMaterial({ color: 0x00_00_00, transparent: true, opacity: 0 }))
staticChunkMesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz)
const boxHelper = new THREE.BoxHelper(staticChunkMesh, 0xff_ff_00)
boxHelper.name = 'helper'
object.add(boxHelper)
object.name = 'chunk'
//@ts-expect-error
object.tilesCount = data.geometry.positions.length / 3 / 4
if (!this.config.showChunkBorders) {
boxHelper.visible = false
}
// should not compute it once
if (Object.keys(data.geometry.signs).length) {
for (const [posKey, { isWall, isHanging, rotation }] of Object.entries(data.geometry.signs)) {
const [x, y, z] = posKey.split(',')
const signBlockEntity = this.blockEntities[posKey]
if (!signBlockEntity) continue
const sign = this.renderSign(new Vec3(+x, +y, +z), rotation, isWall, isHanging, nbt.simplify(signBlockEntity))
if (!sign) continue
object.add(sign)
}
}
this.sectionObjects[data.key] = object
this.updatePosDataChunk(data.key)
object.matrixAutoUpdate = false
mesh.onAfterRender = (renderer, scene, camera, geometry, material, group) => {
// mesh.matrixAutoUpdate = false
}
timeUpdated (newTime: number): void {
const nightTime = 13_500
const morningStart = 23_000
const displayStars = newTime > nightTime && newTime < morningStart
if (displayStars && !this.starField.points) {
this.starField.addToScene()
} else if (!displayStars && this.starField.points) {
this.starField.remove()
this.scene.add(object)
}
getSignTexture (position: Vec3, blockEntity, backSide = false) {
const chunk = chunkPos(position)
let textures = this.chunkTextures.get(`${chunk[0]},${chunk[1]}`)
if (!textures) {
textures = {}
this.chunkTextures.set(`${chunk[0]},${chunk[1]}`, textures)
}
const texturekey = `${position.x},${position.y},${position.z}`
// todo investigate bug and remove this so don't need to clean in section dirty
if (textures[texturekey]) return textures[texturekey]
const PrismarineChat = PrismarineChatLoader(this.version!)
const canvas = renderSign(blockEntity, PrismarineChat)
if (!canvas) return
const tex = new THREE.Texture(canvas)
tex.magFilter = THREE.NearestFilter
tex.minFilter = THREE.NearestFilter
tex.needsUpdate = true
textures[texturekey] = tex
return tex
}
updateCamera (pos: Vec3 | null, yaw: number, pitch: number): void {
if (pos) {
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')
}
render () {
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.renderer.render(this.scene, cam)
}
renderSign (position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) {
const tex = this.getSignTexture(position, blockEntity)
if (!tex) return
// todo implement
// const key = JSON.stringify({ position, rotation, isWall })
// if (this.signsCache.has(key)) {
// console.log('cached', key)
// } else {
// this.signsCache.set(key, tex)
// }
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ map: tex, transparent: true }))
mesh.renderOrder = 999
const lineHeight = 7 / 16
const scaleFactor = isHanging ? 1.3 : 1
mesh.scale.set(1 * scaleFactor, lineHeight * scaleFactor, 1 * scaleFactor)
const thickness = (isHanging ? 2 : 1.5) / 16
const wallSpacing = 0.25 / 16
if (isWall && !isHanging) {
mesh.position.set(0, 0, -0.5 + thickness + wallSpacing + 0.0001)
} else {
mesh.position.set(0, 0, thickness / 2 + 0.0001)
}
const group = new THREE.Group()
group.rotation.set(
0,
-THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)),
0
)
group.add(mesh)
const height = (isHanging ? 10 : 8) / 16
const heightOffset = (isHanging ? 0 : isWall ? 4.333 : 9.333) / 16
const textPosition = height / 2 + heightOffset
group.position.set(position.x + 0.5, position.y + textPosition, position.z + 0.5)
return group
}
updateLight (chunkX: number, chunkZ: number) {
// set all sections in the chunk dirty
for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) {
this.setSectionDirty(new Vec3(chunkX, y, chunkZ))
}
}
async doHmr () {
const oldSections = { ...this.sectionObjects }
this.sectionObjects = {} // skip clearing
worldView!.unloadAllChunks()
void this.setVersion(this.version, this.texturesVersion)
this.sectionObjects = oldSections
// this.rerenderAllChunks()
// supply new data
await worldView!.updatePosition(bot.entity.position, true)
}
rerenderAllChunks () { // todo not clear what to do with loading chunks
for (const key of Object.keys(this.sectionObjects)) {
const [x, y, z] = key.split(',').map(Number)
this.setSectionDirty(new Vec3(x, y, z))
}
}
updateShowChunksBorder (value: boolean) {
this.config.showChunkBorders = value
for (const object of Object.values(this.sectionObjects)) {
for (const child of object.children) {
if (child.name === 'helper') {
child.visible = value
}
}
}
}
/**
* Optionally update data that are depedendent on the viewer position
*/
updatePosDataChunk (key: string) {
const [x, y, z] = key.split(',').map(x => Math.floor(+x / 16))
// sum of distances: x + y + z
const chunkDistance = Math.abs(x - this.cameraSectionPos.x) + Math.abs(y - this.cameraSectionPos.y) + Math.abs(z - this.cameraSectionPos.z)
const section = this.sectionObjects[key].children.find(child => child.name === 'mesh')!
section.renderOrder = 500 - chunkDistance
resetWorld () {
super.resetWorld()
for (const mesh of Object.values(this.sectionObjects)) {
this.scene.remove(mesh)
}
}
updateViewerPosition (pos: Vec3): void {
this.viewerPosition = pos
const cameraPos = this.camera.position.toArray().map(x => Math.floor(x / 16)) as [number, number, number]
this.cameraSectionPos = new Vec3(...cameraPos)
for (const key in this.sectionObjects) {
const value = this.sectionObjects[key]
if (!value) continue
this.updatePosDataChunk(key)
}
getLoadedChunksRelative (pos: Vec3, includeY = false) {
const [currentX, currentY, currentZ] = sectionPos(pos)
return Object.fromEntries(Object.entries(this.sectionObjects).map(([key, o]) => {
const [xRaw, yRaw, zRaw] = key.split(',').map(Number)
const [x, y, z] = sectionPos({ x: xRaw, y: yRaw, z: zRaw })
const setKey = includeY ? `${x - currentX},${y - currentY},${z - currentZ}` : `${x - currentX},${z - currentZ}`
return [setKey, o]
}))
}
cleanChunkTextures (x, z) {
const textures = this.chunkTextures.get(`${Math.floor(x / 16)},${Math.floor(z / 16)}`) ?? {}
for (const key of Object.keys(textures)) {
textures[key].dispose()
delete textures[key]
}
}
handleWorkerMessage (data: any): void {
if (data.type !== 'geometry') return
let object: THREE.Object3D = this.sectionObjects[data.key]
if (object) {
this.scene.remove(object)
disposeObject(object)
delete this.sectionObjects[data.key]
}
const chunkCoords = data.key.split(',')
if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || !data.geometry.positions.length || !this.active) return
// if (!this.initialChunksLoad && this.enableChunksLoadDelay) {
// const newPromise = new Promise(resolve => {
// if (this.droppedFpsPercentage > 0.5) {
// setTimeout(resolve, 1000 / 50 * this.droppedFpsPercentage)
// } else {
// setTimeout(resolve)
// }
// })
// this.promisesQueue.push(newPromise)
// for (const promise of this.promisesQueue) {
// await promise
// }
// }
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))
geometry.setAttribute('color', new THREE.BufferAttribute(data.geometry.colors, 3))
geometry.setAttribute('uv', new THREE.BufferAttribute(data.geometry.uvs, 2))
geometry.setIndex(data.geometry.indices)
const mesh = new THREE.Mesh(geometry, this.material)
mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz)
mesh.name = 'mesh'
object = new THREE.Group()
object.add(mesh)
// mesh with static dimensions: 16x16x16
const staticChunkMesh = new THREE.Mesh(new THREE.BoxGeometry(16, 16, 16), new THREE.MeshBasicMaterial({ color: 0x000000, transparent: true, opacity: 0 }))
staticChunkMesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz)
const boxHelper = new THREE.BoxHelper(staticChunkMesh, 0xffff00)
boxHelper.name = 'helper'
object.add(boxHelper)
object.name = 'chunk'
//@ts-ignore
object.tilesCount = data.geometry.positions.length / 3 / 4
if (!this.config.showChunkBorders) {
boxHelper.visible = false
}
// should not compute it once
if (Object.keys(data.geometry.signs).length) {
for (const [posKey, { isWall, isHanging, rotation }] of Object.entries(data.geometry.signs)) {
const [x, y, z] = posKey.split(',')
const signBlockEntity = this.blockEntities[posKey]
if (!signBlockEntity) continue
const sign = this.renderSign(new Vec3(+x, +y, +z), rotation, isWall, isHanging, nbt.simplify(signBlockEntity))
if (!sign) continue
object.add(sign)
}
}
this.sectionObjects[data.key] = object
this.updatePosDataChunk(data.key)
object.matrixAutoUpdate = false
mesh.onAfterRender = (renderer, scene, camera, geometry, material, group) => {
// mesh.matrixAutoUpdate = false
}
this.scene.add(object)
readdChunks () {
for (const key of Object.keys(this.sectionObjects)) {
this.scene.remove(this.sectionObjects[key])
}
setTimeout(() => {
for (const key of Object.keys(this.sectionObjects)) {
this.scene.add(this.sectionObjects[key])
}
}, 500)
}
getSignTexture (position: Vec3, blockEntity, backSide = false) {
const chunk = chunkPos(position)
let textures = this.chunkTextures.get(`${chunk[0]},${chunk[1]}`)
if (!textures) {
textures = {}
this.chunkTextures.set(`${chunk[0]},${chunk[1]}`, textures)
}
const texturekey = `${position.x},${position.y},${position.z}`
// todo investigate bug and remove this so don't need to clean in section dirty
if (textures[texturekey]) return textures[texturekey]
const PrismarineChat = PrismarineChatLoader(this.version!)
const canvas = renderSign(blockEntity, PrismarineChat)
if (!canvas) return
const tex = new THREE.Texture(canvas)
tex.magFilter = THREE.NearestFilter
tex.minFilter = THREE.NearestFilter
tex.needsUpdate = true
textures[texturekey] = tex
return tex
disableUpdates (children = this.scene.children) {
for (const child of children) {
child.matrixWorldNeedsUpdate = false
this.disableUpdates(child.children ?? [])
}
}
updateCamera (pos: Vec3 | null, yaw: number, pitch: number): void {
if (pos) {
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')
removeColumn (x, z) {
super.removeColumn(x, z)
this.cleanChunkTextures(x, z)
for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) {
this.setSectionDirty(new Vec3(x, y, z), false)
const key = `${x},${y},${z}`
const mesh = this.sectionObjects[key]
if (mesh) {
this.scene.remove(mesh)
disposeObject(mesh)
}
delete this.sectionObjects[key]
}
}
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)
}
renderSign (position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) {
const tex = this.getSignTexture(position, blockEntity)
if (!tex) return
// todo implement
// const key = JSON.stringify({ position, rotation, isWall })
// if (this.signsCache.has(key)) {
// console.log('cached', key)
// } else {
// this.signsCache.set(key, tex)
// }
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ map: tex, transparent: true, }))
mesh.renderOrder = 999
const lineHeight = 7 / 16
const scaleFactor = isHanging ? 1.3 : 1
mesh.scale.set(1 * scaleFactor, lineHeight * scaleFactor, 1 * scaleFactor)
const thickness = (isHanging ? 2 : 1.5) / 16
const wallSpacing = 0.25 / 16
if (isWall && !isHanging) {
mesh.position.set(0, 0, -0.5 + thickness + wallSpacing + 0.0001)
} else {
mesh.position.set(0, 0, thickness / 2 + 0.0001)
}
const group = new THREE.Group()
group.rotation.set(0, -THREE.MathUtils.degToRad(
rotation * (isWall ? 90 : 45 / 2)
), 0)
group.add(mesh)
const height = (isHanging ? 10 : 8) / 16
const heightOffset = (isHanging ? 0 : isWall ? 4.333 : 9.333) / 16
const textPosition = height / 2 + heightOffset
group.position.set(position.x + 0.5, position.y + textPosition, position.z + 0.5)
return group
}
updateLight (chunkX: number, chunkZ: number) {
// set all sections in the chunk dirty
for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) {
this.setSectionDirty(new Vec3(chunkX, y, chunkZ))
}
}
async doHmr () {
const oldSections = { ...this.sectionObjects }
this.sectionObjects = {} // skip clearing
worldView!.unloadAllChunks()
this.setVersion(this.version, this.texturesVersion)
this.sectionObjects = oldSections
// this.rerenderAllChunks()
// supply new data
await worldView!.updatePosition(bot.entity.position, true)
}
rerenderAllChunks () { // todo not clear what to do with loading chunks
for (const key of Object.keys(this.sectionObjects)) {
const [x, y, z] = key.split(',').map(Number)
this.setSectionDirty(new Vec3(x, y, z))
}
}
updateShowChunksBorder (value: boolean) {
this.config.showChunkBorders = value
for (const object of Object.values(this.sectionObjects)) {
for (const child of object.children) {
if (child.name === 'helper') {
child.visible = value
}
}
}
}
resetWorld () {
super.resetWorld()
for (const mesh of Object.values(this.sectionObjects)) {
this.scene.remove(mesh)
}
}
getLoadedChunksRelative (pos: Vec3, includeY = false) {
const [currentX, currentY, currentZ] = sectionPos(pos)
return Object.fromEntries(Object.entries(this.sectionObjects).map(([key, o]) => {
const [xRaw, yRaw, zRaw] = key.split(',').map(Number)
const [x, y, z] = sectionPos({ x: xRaw, y: yRaw, z: zRaw })
const setKey = includeY ? `${x - currentX},${y - currentY},${z - currentZ}` : `${x - currentX},${z - currentZ}`
return [setKey, o]
}))
}
cleanChunkTextures (x, z) {
const textures = this.chunkTextures.get(`${Math.floor(x / 16)},${Math.floor(z / 16)}`) ?? {}
for (const key of Object.keys(textures)) {
textures[key].dispose()
delete textures[key]
}
}
readdChunks () {
for (const key of Object.keys(this.sectionObjects)) {
this.scene.remove(this.sectionObjects[key])
}
setTimeout(() => {
for (const key of Object.keys(this.sectionObjects)) {
this.scene.add(this.sectionObjects[key])
}
}, 500)
}
disableUpdates (children = this.scene.children) {
for (const child of children) {
child.matrixWorldNeedsUpdate = false
this.disableUpdates(child.children ?? [])
}
}
removeColumn (x, z) {
super.removeColumn(x, z)
this.cleanChunkTextures(x, z)
for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) {
this.setSectionDirty(new Vec3(x, y, z), false)
const key = `${x},${y},${z}`
const mesh = this.sectionObjects[key]
if (mesh) {
this.scene.remove(mesh)
disposeObject(mesh)
}
delete this.sectionObjects[key]
}
}
setSectionDirty (pos, value = true) {
this.cleanChunkTextures(pos.x, pos.z) // todo don't do this!
super.setSectionDirty(pos, value)
}
setSectionDirty (pos, value = true) {
this.cleanChunkTextures(pos.x, pos.z) // todo don't do this!
super.setSectionDirty(pos, value)
}
}
class StarField {
points?: THREE.Points
private _enabled = true
get enabled () {
return this._enabled
points?: THREE.Points
private _enabled = true
get enabled () {
return this._enabled
}
set enabled (value) {
this._enabled = value
if (this.points) {
this.points.visible = value
}
set enabled (value) {
this._enabled = value
if (this.points) {
this.points.visible = value
}
}
constructor (private readonly scene: THREE.Scene) {
}
addToScene () {
if (this.points || !this.enabled) return
const radius = 80
const depth = 50
const count = 7000
const factor = 7
const saturation = 10
const speed = 0.2
const geometry = new THREE.BufferGeometry()
const genStar = r => new THREE.Vector3().setFromSpherical(new THREE.Spherical(r, Math.acos(1 - Math.random() * 2), Math.random() * 2 * Math.PI))
const positions = [] as number[]
const colors = [] as number[]
const sizes = Array.from({ length: count }, () => (0.5 + 0.5 * Math.random()) * factor)
const color = new THREE.Color()
let r = radius + depth
const increment = depth / count
for (let i = 0; i < count; i++) {
r -= increment * Math.random()
positions.push(...genStar(r).toArray())
color.setHSL(i / count, saturation, 0.9)
colors.push(color.r, color.g, color.b)
}
constructor (private scene: THREE.Scene) {
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3))
geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1))
// Create a material
const material = new StarfieldMaterial()
material.blending = THREE.AdditiveBlending
material.depthTest = false
material.transparent = true
// Create points and add them to the scene
this.points = new THREE.Points(geometry, material)
this.scene.add(this.points)
const clock = new THREE.Clock()
this.points.onBeforeRender = (renderer, scene, camera) => {
this.points?.position.copy?.(camera.position)
material.uniforms.time.value = clock.getElapsedTime() * speed
}
}
addToScene () {
if (this.points || !this.enabled) return
remove () {
if (this.points) {
this.points.geometry.dispose();
(this.points.material as THREE.Material).dispose()
this.scene.remove(this.points)
const radius = 80
const depth = 50
const count = 7000
const factor = 7
const saturation = 10
const speed = 0.2
const geometry = new THREE.BufferGeometry()
const genStar = r => new THREE.Vector3().setFromSpherical(new THREE.Spherical(r, Math.acos(1 - Math.random() * 2), Math.random() * 2 * Math.PI))
const positions = [] as number[]
const colors = [] as number[]
const sizes = Array.from({ length: count }, () => (0.5 + 0.5 * Math.random()) * factor)
const color = new THREE.Color()
let r = radius + depth
const increment = depth / count
for (let i = 0; i < count; i++) {
r -= increment * Math.random()
positions.push(...genStar(r).toArray())
color.setHSL(i / count, saturation, 0.9)
colors.push(color.r, color.g, color.b)
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3))
geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1))
// Create a material
const material = new StarfieldMaterial()
material.blending = THREE.AdditiveBlending
material.depthTest = false
material.transparent = true
// Create points and add them to the scene
this.points = new THREE.Points(geometry, material)
this.scene.add(this.points)
const clock = new THREE.Clock()
this.points.onBeforeRender = (renderer, scene, camera) => {
this.points?.position.copy?.(camera.position)
material.uniforms.time.value = clock.getElapsedTime() * speed
}
}
remove () {
if (this.points) {
this.points.geometry.dispose();
(this.points.material as THREE.Material).dispose()
this.scene.remove(this.points)
this.points = undefined
}
this.points = undefined
}
}
}
const version = parseInt(THREE.REVISION.replace(/\D+/g, ''))
const version = parseInt(THREE.REVISION.replaceAll(/\D+/g, ''), 10)
class StarfieldMaterial extends THREE.ShaderMaterial {
constructor () {
super({
uniforms: { time: { value: 0.0 }, fade: { value: 1.0 } },
vertexShader: /* glsl */ `
constructor () {
super({
uniforms: { time: { value: 0 }, fade: { value: 1 } },
vertexShader: /* glsl */ `
uniform float time;
attribute float size;
varying vec3 vColor;
@ -400,7 +405,7 @@ class StarfieldMaterial extends THREE.ShaderMaterial {
gl_PointSize = size * (30.0 / -mvPosition.z) * (3.0 + sin(time + 100.0));
gl_Position = projectionMatrix * mvPosition;
}`,
fragmentShader: /* glsl */ `
fragmentShader: /* glsl */ `
uniform sampler2D pointTexture;
uniform float fade;
varying vec3 vColor;
@ -415,6 +420,6 @@ class StarfieldMaterial extends THREE.ShaderMaterial {
#include <tonemapping_fragment>
#include <${version >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
}`,
})
}
})
}
}

View file

@ -1,49 +0,0 @@
{
"variants": {
"facing=north,type=single": {
"model": "chest"
},
"facing=east,type=single": {
"model": "chest",
"y": 90
},
"facing=south,type=single": {
"model": "chest",
"y": 180
},
"facing=west,type=single": {
"model": "chest",
"y": 270
},
"facing=north,type=left": {
"model": "chest_left"
},
"facing=east,type=left": {
"model": "chest_left",
"y": 90
},
"facing=south,type=left": {
"model": "chest_left",
"y": 180
},
"facing=west,type=left": {
"model": "chest_left",
"y": 270
},
"facing=north,type=right": {
"model": "chest_right"
},
"facing=east,type=right": {
"model": "chest_right",
"y": 90
},
"facing=south,type=right": {
"model": "chest_right",
"y": 180
},
"facing=west,type=right": {
"model": "chest_right",
"y": 270
}
}
}

View file

@ -1,19 +0,0 @@
{
"variants": {
"facing=north": {
"model": "chest"
},
"facing=east": {
"model": "chest",
"y": 90
},
"facing=south": {
"model": "chest",
"y": 180
},
"facing=west": {
"model": "chest",
"y": 270
}
}
}

View file

@ -1,49 +0,0 @@
{
"variants": {
"facing=north,type=single": {
"model": "chest"
},
"facing=east,type=single": {
"model": "chest",
"y": 90
},
"facing=south,type=single": {
"model": "chest",
"y": 180
},
"facing=west,type=single": {
"model": "chest",
"y": 270
},
"facing=north,type=left": {
"model": "chest_left"
},
"facing=east,type=left": {
"model": "chest_left",
"y": 90
},
"facing=south,type=left": {
"model": "chest_left",
"y": 180
},
"facing=west,type=left": {
"model": "chest_left",
"y": 270
},
"facing=north,type=right": {
"model": "chest_right"
},
"facing=east,type=right": {
"model": "chest_right",
"y": 90
},
"facing=south,type=right": {
"model": "chest_right",
"y": 180
},
"facing=west,type=right": {
"model": "chest_right",
"y": 270
}
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign",
"textures": {
"sign": "entity/sign"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign_wall",
"textures": {
"sign": "entity/sign"
}
}

View file

@ -1,140 +0,0 @@
{
"elements": [
{
"from": [
7.25,
0,
7.25
],
"to": [
8.75,
9.333,
8.75
],
"faces": {
"north": {
"uv": [
1.5,
8,
2,
15
],
"texture": "#sign"
},
"east": {
"uv": [
1,
8,
1.5,
15
],
"texture": "#sign"
},
"south": {
"uv": [
0.5,
8,
1,
15
],
"texture": "#sign"
},
"west": {
"uv": [
0,
8,
0.5,
15
],
"texture": "#sign"
},
"up": {
"uv": [
0.5,
7,
1,
8
],
"texture": "#sign"
},
"down": {
"uv": [
1,
7,
1.5,
8
],
"texture": "#sign"
}
}
},
{
"from": [
0,
9.333,
7.25
],
"to": [
16,
17.333,
8.75
],
"faces": {
"north": {
"uv": [
7,
1,
13,
7
],
"texture": "#sign"
},
"east": {
"uv": [
6.5,
1,
7,
7
],
"texture": "#sign"
},
"south": {
"uv": [
0.5,
1,
6.5,
7
],
"texture": "#sign"
},
"west": {
"uv": [
0,
1,
0.5,
7
],
"texture": "#sign"
},
"up": {
"uv": [
0.5,
0,
6.5,
1
],
"texture": "#sign"
},
"down": {
"uv": [
6.5,
1,
12.5,
0
],
"texture": "#sign"
}
}
}
]
}

View file

@ -1,72 +0,0 @@
{
"elements": [
{
"from": [
0,
4.333,
0.25
],
"to": [
16,
12.333,
1.75
],
"faces": {
"north": {
"uv": [
7,
1,
13,
7
],
"texture": "#sign"
},
"east": {
"uv": [
6.5,
1,
7,
7
],
"texture": "#sign"
},
"south": {
"uv": [
0.5,
1,
6.5,
7
],
"texture": "#sign"
},
"west": {
"uv": [
0,
1,
0.5,
7
],
"texture": "#sign"
},
"up": {
"uv": [
0.5,
0,
6.5,
1
],
"texture": "#sign"
},
"down": {
"uv": [
6.5,
1,
12.5,
0
],
"texture": "#sign"
}
}
}
]
}

View file

@ -1,67 +0,0 @@
{
"variants": {
"rotation=0": {
"model": "sign/oak"
},
"rotation=1": {
"model": "sign/oak",
"y": 22.5
},
"rotation=2": {
"model": "sign/oak",
"y": 45
},
"rotation=3": {
"model": "sign/oak",
"y": 67.5
},
"rotation=4": {
"model": "sign/oak",
"y": 90
},
"rotation=5": {
"model": "sign/oak",
"y": 112.5
},
"rotation=6": {
"model": "sign/oak",
"y": 135
},
"rotation=7": {
"model": "sign/oak",
"y": 157.5
},
"rotation=8": {
"model": "sign/oak",
"y": 180
},
"rotation=9": {
"model": "sign/oak",
"y": 202.5
},
"rotation=10": {
"model": "sign/oak",
"y": 225
},
"rotation=11": {
"model": "sign/oak",
"y": 247.5
},
"rotation=12": {
"model": "sign/oak",
"y": 270
},
"rotation=13": {
"model": "sign/oak",
"y": 292.5
},
"rotation=14": {
"model": "sign/oak",
"y": 315
},
"rotation=15": {
"model": "sign/oak",
"y": 337.5
}
}
}

View file

@ -1,19 +0,0 @@
{
"variants": {
"facing=south": {
"model": "sign/oak_wall"
},
"facing=west": {
"model": "sign/oak_wall",
"y": 90
},
"facing=north": {
"model": "sign/oak_wall",
"y": 180
},
"facing=east": {
"model": "sign/oak_wall",
"y": 270
}
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign",
"textures": {
"sign": "entity/signs/acacia"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign_wall",
"textures": {
"sign": "entity/signs/acacia"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign",
"textures": {
"sign": "entity/signs/birch"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign_wall",
"textures": {
"sign": "entity/signs/birch"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign",
"textures": {
"sign": "entity/signs/dark_oak"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign_wall",
"textures": {
"sign": "entity/signs/dark_oak"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign",
"textures": {
"sign": "entity/signs/jungle"
}
}

View file

@ -1,7 +0,0 @@
{
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
"parent": "sign/sign_wall",
"textures": {
"sign": "entity/signs/jungle"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign",
"textures": {
"sign": "entity/signs/spruce"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign_wall",
"textures": {
"sign": "entity/signs/spruce"
}
}

View file

@ -1,67 +0,0 @@
{
"variants": {
"rotation=0": {
"model": "sign/acacia"
},
"rotation=1": {
"model": "sign/acacia",
"y": 22.5
},
"rotation=2": {
"model": "sign/acacia",
"y": 45
},
"rotation=3": {
"model": "sign/acacia",
"y": 67.5
},
"rotation=4": {
"model": "sign/acacia",
"y": 90
},
"rotation=5": {
"model": "sign/acacia",
"y": 112.5
},
"rotation=6": {
"model": "sign/acacia",
"y": 135
},
"rotation=7": {
"model": "sign/acacia",
"y": 157.5
},
"rotation=8": {
"model": "sign/acacia",
"y": 180
},
"rotation=9": {
"model": "sign/acacia",
"y": 202.5
},
"rotation=10": {
"model": "sign/acacia",
"y": 225
},
"rotation=11": {
"model": "sign/acacia",
"y": 247.5
},
"rotation=12": {
"model": "sign/acacia",
"y": 270
},
"rotation=13": {
"model": "sign/acacia",
"y": 292.5
},
"rotation=14": {
"model": "sign/acacia",
"y": 315
},
"rotation=15": {
"model": "sign/acacia",
"y": 337.5
}
}
}

View file

@ -1,19 +0,0 @@
{
"variants": {
"facing=south": {
"model": "sign/acacia_wall"
},
"facing=west": {
"model": "sign/acacia_wall",
"y": 90
},
"facing=north": {
"model": "sign/acacia_wall",
"y": 180
},
"facing=east": {
"model": "sign/acacia_wall",
"y": 270
}
}
}

View file

@ -1,67 +0,0 @@
{
"variants": {
"rotation=0": {
"model": "sign/birch"
},
"rotation=1": {
"model": "sign/birch",
"y": 22.5
},
"rotation=2": {
"model": "sign/birch",
"y": 45
},
"rotation=3": {
"model": "sign/birch",
"y": 67.5
},
"rotation=4": {
"model": "sign/birch",
"y": 90
},
"rotation=5": {
"model": "sign/birch",
"y": 112.5
},
"rotation=6": {
"model": "sign/birch",
"y": 135
},
"rotation=7": {
"model": "sign/birch",
"y": 157.5
},
"rotation=8": {
"model": "sign/birch",
"y": 180
},
"rotation=9": {
"model": "sign/birch",
"y": 202.5
},
"rotation=10": {
"model": "sign/birch",
"y": 225
},
"rotation=11": {
"model": "sign/birch",
"y": 247.5
},
"rotation=12": {
"model": "sign/birch",
"y": 270
},
"rotation=13": {
"model": "sign/birch",
"y": 292.5
},
"rotation=14": {
"model": "sign/birch",
"y": 315
},
"rotation=15": {
"model": "sign/birch",
"y": 337.5
}
}
}

View file

@ -1,19 +0,0 @@
{
"variants": {
"facing=south": {
"model": "sign/birch_wall"
},
"facing=west": {
"model": "sign/birch_wall",
"y": 90
},
"facing=north": {
"model": "sign/birch_wall",
"y": 180
},
"facing=east": {
"model": "sign/birch_wall",
"y": 270
}
}
}

View file

@ -1,67 +0,0 @@
{
"variants": {
"rotation=0": {
"model": "sign/dark_oak"
},
"rotation=1": {
"model": "sign/dark_oak",
"y": 22.5
},
"rotation=2": {
"model": "sign/dark_oak",
"y": 45
},
"rotation=3": {
"model": "sign/dark_oak",
"y": 67.5
},
"rotation=4": {
"model": "sign/dark_oak",
"y": 90
},
"rotation=5": {
"model": "sign/dark_oak",
"y": 112.5
},
"rotation=6": {
"model": "sign/dark_oak",
"y": 135
},
"rotation=7": {
"model": "sign/dark_oak",
"y": 157.5
},
"rotation=8": {
"model": "sign/dark_oak",
"y": 180
},
"rotation=9": {
"model": "sign/dark_oak",
"y": 202.5
},
"rotation=10": {
"model": "sign/dark_oak",
"y": 225
},
"rotation=11": {
"model": "sign/dark_oak",
"y": 247.5
},
"rotation=12": {
"model": "sign/dark_oak",
"y": 270
},
"rotation=13": {
"model": "sign/dark_oak",
"y": 292.5
},
"rotation=14": {
"model": "sign/dark_oak",
"y": 315
},
"rotation=15": {
"model": "sign/dark_oak",
"y": 337.5
}
}
}

View file

@ -1,19 +0,0 @@
{
"variants": {
"facing=south": {
"model": "sign/dark_oak_wall"
},
"facing=west": {
"model": "sign/dark_oak_wall",
"y": 90
},
"facing=north": {
"model": "sign/dark_oak_wall",
"y": 180
},
"facing=east": {
"model": "sign/dark_oak_wall",
"y": 270
}
}
}

View file

@ -1,67 +0,0 @@
{
"variants": {
"rotation=0": {
"model": "sign/jungle"
},
"rotation=1": {
"model": "sign/jungle",
"y": 22.5
},
"rotation=2": {
"model": "sign/jungle",
"y": 45
},
"rotation=3": {
"model": "sign/jungle",
"y": 67.5
},
"rotation=4": {
"model": "sign/jungle",
"y": 90
},
"rotation=5": {
"model": "sign/jungle",
"y": 112.5
},
"rotation=6": {
"model": "sign/jungle",
"y": 135
},
"rotation=7": {
"model": "sign/jungle",
"y": 157.5
},
"rotation=8": {
"model": "sign/jungle",
"y": 180
},
"rotation=9": {
"model": "sign/jungle",
"y": 202.5
},
"rotation=10": {
"model": "sign/jungle",
"y": 225
},
"rotation=11": {
"model": "sign/jungle",
"y": 247.5
},
"rotation=12": {
"model": "sign/jungle",
"y": 270
},
"rotation=13": {
"model": "sign/jungle",
"y": 292.5
},
"rotation=14": {
"model": "sign/jungle",
"y": 315
},
"rotation=15": {
"model": "sign/jungle",
"y": 337.5
}
}
}

View file

@ -1,19 +0,0 @@
{
"variants": {
"facing=south": {
"model": "sign/jungle_wall"
},
"facing=west": {
"model": "sign/jungle_wall",
"y": 90
},
"facing=north": {
"model": "sign/jungle_wall",
"y": 180
},
"facing=east": {
"model": "sign/jungle_wall",
"y": 270
}
}
}

View file

@ -1,67 +0,0 @@
{
"variants": {
"rotation=0": {
"model": "sign/oak"
},
"rotation=1": {
"model": "sign/oak",
"y": 22.5
},
"rotation=2": {
"model": "sign/oak",
"y": 45
},
"rotation=3": {
"model": "sign/oak",
"y": 67.5
},
"rotation=4": {
"model": "sign/oak",
"y": 90
},
"rotation=5": {
"model": "sign/oak",
"y": 112.5
},
"rotation=6": {
"model": "sign/oak",
"y": 135
},
"rotation=7": {
"model": "sign/oak",
"y": 157.5
},
"rotation=8": {
"model": "sign/oak",
"y": 180
},
"rotation=9": {
"model": "sign/oak",
"y": 202.5
},
"rotation=10": {
"model": "sign/oak",
"y": 225
},
"rotation=11": {
"model": "sign/oak",
"y": 247.5
},
"rotation=12": {
"model": "sign/oak",
"y": 270
},
"rotation=13": {
"model": "sign/oak",
"y": 292.5
},
"rotation=14": {
"model": "sign/oak",
"y": 315
},
"rotation=15": {
"model": "sign/oak",
"y": 337.5
}
}
}

View file

@ -1,19 +0,0 @@
{
"variants": {
"facing=south": {
"model": "sign/oak_wall"
},
"facing=west": {
"model": "sign/oak_wall",
"y": 90
},
"facing=north": {
"model": "sign/oak_wall",
"y": 180
},
"facing=east": {
"model": "sign/oak_wall",
"y": 270
}
}
}

View file

@ -1,67 +0,0 @@
{
"variants": {
"rotation=0": {
"model": "sign/spruce"
},
"rotation=1": {
"model": "sign/spruce",
"y": 22.5
},
"rotation=2": {
"model": "sign/spruce",
"y": 45
},
"rotation=3": {
"model": "sign/spruce",
"y": 67.5
},
"rotation=4": {
"model": "sign/spruce",
"y": 90
},
"rotation=5": {
"model": "sign/spruce",
"y": 112.5
},
"rotation=6": {
"model": "sign/spruce",
"y": 135
},
"rotation=7": {
"model": "sign/spruce",
"y": 157.5
},
"rotation=8": {
"model": "sign/spruce",
"y": 180
},
"rotation=9": {
"model": "sign/spruce",
"y": 202.5
},
"rotation=10": {
"model": "sign/spruce",
"y": 225
},
"rotation=11": {
"model": "sign/spruce",
"y": 247.5
},
"rotation=12": {
"model": "sign/spruce",
"y": 270
},
"rotation=13": {
"model": "sign/spruce",
"y": 292.5
},
"rotation=14": {
"model": "sign/spruce",
"y": 315
},
"rotation=15": {
"model": "sign/spruce",
"y": 337.5
}
}
}

View file

@ -1,19 +0,0 @@
{
"variants": {
"facing=south": {
"model": "sign/spruce_wall"
},
"facing=west": {
"model": "sign/spruce_wall",
"y": 90
},
"facing=north": {
"model": "sign/spruce_wall",
"y": 180
},
"facing=east": {
"model": "sign/spruce_wall",
"y": 270
}
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign",
"textures": {
"sign": "entity/signs/crimson"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign_wall",
"textures": {
"sign": "entity/signs/crimson"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign",
"textures": {
"sign": "entity/signs/warped"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign_wall",
"textures": {
"sign": "entity/signs/warped"
}
}

View file

@ -1,67 +0,0 @@
{
"variants": {
"rotation=0": {
"model": "sign/crimson"
},
"rotation=1": {
"model": "sign/crimson",
"y": 22.5
},
"rotation=2": {
"model": "sign/crimson",
"y": 45
},
"rotation=3": {
"model": "sign/crimson",
"y": 67.5
},
"rotation=4": {
"model": "sign/crimson",
"y": 90
},
"rotation=5": {
"model": "sign/crimson",
"y": 112.5
},
"rotation=6": {
"model": "sign/crimson",
"y": 135
},
"rotation=7": {
"model": "sign/crimson",
"y": 157.5
},
"rotation=8": {
"model": "sign/crimson",
"y": 180
},
"rotation=9": {
"model": "sign/crimson",
"y": 202.5
},
"rotation=10": {
"model": "sign/crimson",
"y": 225
},
"rotation=11": {
"model": "sign/crimson",
"y": 247.5
},
"rotation=12": {
"model": "sign/crimson",
"y": 270
},
"rotation=13": {
"model": "sign/crimson",
"y": 292.5
},
"rotation=14": {
"model": "sign/crimson",
"y": 315
},
"rotation=15": {
"model": "sign/crimson",
"y": 337.5
}
}
}

View file

@ -1,19 +0,0 @@
{
"variants": {
"facing=south": {
"model": "sign/crimson_wall"
},
"facing=west": {
"model": "sign/crimson_wall",
"y": 90
},
"facing=north": {
"model": "sign/crimson_wall",
"y": 180
},
"facing=east": {
"model": "sign/crimson_wall",
"y": 270
}
}
}

View file

@ -1,67 +0,0 @@
{
"variants": {
"rotation=0": {
"model": "sign/warped"
},
"rotation=1": {
"model": "sign/warped",
"y": 22.5
},
"rotation=2": {
"model": "sign/warped",
"y": 45
},
"rotation=3": {
"model": "sign/warped",
"y": 67.5
},
"rotation=4": {
"model": "sign/warped",
"y": 90
},
"rotation=5": {
"model": "sign/warped",
"y": 112.5
},
"rotation=6": {
"model": "sign/warped",
"y": 135
},
"rotation=7": {
"model": "sign/warped",
"y": 157.5
},
"rotation=8": {
"model": "sign/warped",
"y": 180
},
"rotation=9": {
"model": "sign/warped",
"y": 202.5
},
"rotation=10": {
"model": "sign/warped",
"y": 225
},
"rotation=11": {
"model": "sign/warped",
"y": 247.5
},
"rotation=12": {
"model": "sign/warped",
"y": 270
},
"rotation=13": {
"model": "sign/warped",
"y": 292.5
},
"rotation=14": {
"model": "sign/warped",
"y": 315
},
"rotation=15": {
"model": "sign/warped",
"y": 337.5
}
}
}

View file

@ -1,19 +0,0 @@
{
"variants": {
"facing=south": {
"model": "sign/warped_wall"
},
"facing=west": {
"model": "sign/warped_wall",
"y": 90
},
"facing=north": {
"model": "sign/warped_wall",
"y": 180
},
"facing=east": {
"model": "sign/warped_wall",
"y": 270
}
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign",
"textures": {
"sign": "entity/signs/mangrove"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign_wall",
"textures": {
"sign": "entity/signs/mangrove"
}
}

View file

@ -1,67 +0,0 @@
{
"variants": {
"rotation=0": {
"model": "sign/mangrove"
},
"rotation=1": {
"model": "sign/mangrove",
"y": 22.5
},
"rotation=2": {
"model": "sign/mangrove",
"y": 45
},
"rotation=3": {
"model": "sign/mangrove",
"y": 67.5
},
"rotation=4": {
"model": "sign/mangrove",
"y": 90
},
"rotation=5": {
"model": "sign/mangrove",
"y": 112.5
},
"rotation=6": {
"model": "sign/mangrove",
"y": 135
},
"rotation=7": {
"model": "sign/mangrove",
"y": 157.5
},
"rotation=8": {
"model": "sign/mangrove",
"y": 180
},
"rotation=9": {
"model": "sign/mangrove",
"y": 202.5
},
"rotation=10": {
"model": "sign/mangrove",
"y": 225
},
"rotation=11": {
"model": "sign/mangrove",
"y": 247.5
},
"rotation=12": {
"model": "sign/mangrove",
"y": 270
},
"rotation=13": {
"model": "sign/mangrove",
"y": 292.5
},
"rotation=14": {
"model": "sign/mangrove",
"y": 315
},
"rotation=15": {
"model": "sign/mangrove",
"y": 337.5
}
}
}

View file

@ -1,19 +0,0 @@
{
"variants": {
"facing=south": {
"model": "sign/mangrove_wall"
},
"facing=west": {
"model": "sign/mangrove_wall",
"y": 90
},
"facing=north": {
"model": "sign/mangrove_wall",
"y": 180
},
"facing=east": {
"model": "sign/mangrove_wall",
"y": 270
}
}
}

View file

@ -1,47 +0,0 @@
{
"texture_size": [32, 32],
"textures": {
"0": "entity/decorated_pot/decorated_pot_base"
},
"elements": [
{
"name": "Body",
"from": [1, 0, 1],
"to": [15, 16, 15],
"faces": {
"north": {"uv": [0, 6.5, 7, 13.5], "texture": "#0"},
"east": {"uv": [0, 6.5, 7, 13.5], "texture": "#0"},
"south": {"uv": [0, 6.5, 7, 13.5], "texture": "#0"},
"west": {"uv": [0, 6.5, 7, 13.5], "texture": "#0"},
"up": {"uv": [7, 6.5, 14, 13.5], "texture": "#0"},
"down": {"uv": [7, 6.5, 14, 13.5], "texture": "#0"}
}
},
{
"name": "Neck",
"from": [5, 16, 5],
"to": [11, 17, 11],
"faces": {
"north": {"uv": [6, 5.5, 9, 6], "texture": "#0"},
"east": {"uv": [9, 5.5, 12, 6], "texture": "#0"},
"south": {"uv": [2.5, 5.5, 5.5, 6], "texture": "#0"},
"west": {"uv": [0, 5.5, 3, 6], "texture": "#0"},
"up": {"uv": [0, 0, 3, 3], "texture": "#0"},
"down": {"uv": [0, 0, 3, 3], "texture": "#0"}
}
},
{
"name": "Head",
"from": [4, 17, 4],
"to": [12, 20, 12],
"faces": {
"north": {"uv": [0, 4, 4, 5.5], "texture": "#0"},
"east": {"uv": [4, 4, 8, 5.5], "texture": "#0"},
"south": {"uv": [8, 4, 12, 5.5], "texture": "#0"},
"west": {"uv": [12, 4, 16, 5.5], "texture": "#0"},
"up": {"uv": [4, 0, 8, 4], "texture": "#0"},
"down": {"uv": [8, 0, 12, 4], "texture": "#0"}
}
}
]
}

View file

@ -1,7 +0,0 @@
{
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
"parent": "sign/hanging",
"textures": {
"wood": "entity/signs/hanging/acacia"
}
}

View file

@ -1,7 +0,0 @@
{
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
"parent": "sign/wall_hanging",
"textures": {
"wood": "entity/signs/hanging/acacia"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign",
"textures": {
"sign": "entity/signs/bamboo"
}
}

View file

@ -1,7 +0,0 @@
{
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
"parent": "sign/hanging",
"textures": {
"wood": "entity/signs/hanging/bamboo"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign_wall",
"textures": {
"sign": "entity/signs/bamboo"
}
}

View file

@ -1,7 +0,0 @@
{
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
"parent": "sign/wall_hanging",
"textures": {
"wood": "entity/signs/hanging/bamboo"
}
}

View file

@ -1,7 +0,0 @@
{
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
"parent": "sign/hanging",
"textures": {
"wood": "entity/signs/hanging/birch"
}
}

View file

@ -1,7 +0,0 @@
{
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
"parent": "sign/wall_hanging",
"textures": {
"wood": "entity/signs/hanging/birch"
}
}

View file

@ -1,6 +0,0 @@
{
"parent": "sign/sign",
"textures": {
"sign": "entity/signs/cherry"
}
}

Some files were not shown because too many files have changed in this diff Show more