lint prismarine-viewer & better ci checks (#171)
This commit is contained in:
parent
c680c6a7d1
commit
d4f06aa1a4
204 changed files with 2096 additions and 6242 deletions
|
|
@ -1,4 +1,6 @@
|
|||
node_modules
|
||||
rsbuild.config.ts
|
||||
*.module.css.d.ts
|
||||
*.generated.ts
|
||||
generated
|
||||
public
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
.github/workflows/next-deploy.yml
vendored
2
.github/workflows/next-deploy.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
.github/workflows/preview.yml
vendored
2
.github/workflows/preview.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
|
|
@ -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
1
.vscode/launch.json
vendored
|
|
@ -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
|
||||
|
|
|
|||
12
package.json
12
package.json
|
|
@ -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
459
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = {
|
||||
mineflayer: require('./lib/mineflayer'),
|
||||
standalone: require('./lib/standalone'),
|
||||
headless: require('./lib/headless'),
|
||||
viewer: require('./viewer'),
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
923
prismarine-viewer/viewer/lib/controls.js
vendored
923
prismarine-viewer/viewer/lib/controls.js
vendored
|
|
@ -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 }
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,3 +1 @@
|
|||
import * as externalModels from './exportedModels'
|
||||
|
||||
export { externalModels }
|
||||
export * as externalModels from './exportedModels'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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++) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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'}>
|
||||
}`,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign",
|
||||
"textures": {
|
||||
"sign": "entity/sign"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign_wall",
|
||||
"textures": {
|
||||
"sign": "entity/sign"
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign",
|
||||
"textures": {
|
||||
"sign": "entity/signs/acacia"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign_wall",
|
||||
"textures": {
|
||||
"sign": "entity/signs/acacia"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign",
|
||||
"textures": {
|
||||
"sign": "entity/signs/birch"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign_wall",
|
||||
"textures": {
|
||||
"sign": "entity/signs/birch"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign",
|
||||
"textures": {
|
||||
"sign": "entity/signs/dark_oak"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign_wall",
|
||||
"textures": {
|
||||
"sign": "entity/signs/dark_oak"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign",
|
||||
"textures": {
|
||||
"sign": "entity/signs/jungle"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
|
||||
"parent": "sign/sign_wall",
|
||||
"textures": {
|
||||
"sign": "entity/signs/jungle"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign",
|
||||
"textures": {
|
||||
"sign": "entity/signs/spruce"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign_wall",
|
||||
"textures": {
|
||||
"sign": "entity/signs/spruce"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign",
|
||||
"textures": {
|
||||
"sign": "entity/signs/crimson"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign_wall",
|
||||
"textures": {
|
||||
"sign": "entity/signs/crimson"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign",
|
||||
"textures": {
|
||||
"sign": "entity/signs/warped"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign_wall",
|
||||
"textures": {
|
||||
"sign": "entity/signs/warped"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign",
|
||||
"textures": {
|
||||
"sign": "entity/signs/mangrove"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign_wall",
|
||||
"textures": {
|
||||
"sign": "entity/signs/mangrove"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
|
||||
"parent": "sign/hanging",
|
||||
"textures": {
|
||||
"wood": "entity/signs/hanging/acacia"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
|
||||
"parent": "sign/wall_hanging",
|
||||
"textures": {
|
||||
"wood": "entity/signs/hanging/acacia"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign",
|
||||
"textures": {
|
||||
"sign": "entity/signs/bamboo"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
|
||||
"parent": "sign/hanging",
|
||||
"textures": {
|
||||
"wood": "entity/signs/hanging/bamboo"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"parent": "sign/sign_wall",
|
||||
"textures": {
|
||||
"sign": "entity/signs/bamboo"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
|
||||
"parent": "sign/wall_hanging",
|
||||
"textures": {
|
||||
"wood": "entity/signs/hanging/bamboo"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
|
||||
"parent": "sign/hanging",
|
||||
"textures": {
|
||||
"wood": "entity/signs/hanging/birch"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"credit": "Made with Blockbench by TyBraniff for Bluemaps support.",
|
||||
"parent": "sign/wall_hanging",
|
||||
"textures": {
|
||||
"wood": "entity/signs/hanging/birch"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue