minecraft-renderer 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mesher.js +22 -22
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +17 -17
- package/dist/minecraft-renderer.js +65 -619
- package/dist/minecraft-renderer.js.meta.json +1 -0
- package/dist/threeWorker.js +453 -1007
- package/package.json +1 -1
- package/src/lib/guiRenderer.ts +0 -1
- package/src/lib/moreBlockDataGenerated.json +8 -0
- package/src/lib/skyLight.ts +125 -0
- package/src/lib/worldrendererCommon.ts +2 -12
- package/src/mesher/models.ts +28 -2
- package/src/mesher/test/mesherTester.ts +1 -1
- package/src/sign-renderer/index.ts +4 -1
- package/src/three/bannerRenderer.ts +5 -4
- package/src/three/cinimaticScript.ts +2 -2
- package/src/three/entities.ts +42 -11
- package/src/three/entity/EntityMesh.ts +1 -1
- package/src/three/entity/entities.json +108 -2
- package/src/three/entity/exportedModels.js +0 -1
- package/src/three/entity/externalTextures.json +1 -1
- package/src/three/fireworks.ts +18 -5
- package/src/three/fireworksRenderer.ts +15 -13
- package/src/three/modules/rain.ts +6 -1
- package/src/three/modules/sciFiWorldReveal.ts +14 -10
- package/src/three/modules/starfield.ts +1 -1
- package/src/three/sceneOrigin.ts +215 -0
- package/src/three/skyboxRenderer.ts +3 -3
- package/src/three/threeJsMedia.ts +12 -6
- package/src/three/threeJsParticles.ts +42 -14
- package/src/three/threeJsSound.ts +3 -3
- package/src/three/waypointSprite.ts +45 -23
- package/src/three/waypoints.ts +12 -4
- package/src/three/world/cursorBlock.ts +5 -5
- package/src/three/worldBlockGeometry.ts +14 -5
- package/src/three/worldGeometryExport.ts +4 -3
- package/src/three/worldRendererThree.ts +155 -30
package/package.json
CHANGED
package/src/lib/guiRenderer.ts
CHANGED
|
@@ -268,7 +268,6 @@ const generateAtlas = async (appViewer: AppViewer, images: Record<string, HTMLIm
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
export const generateGuiAtlas = async (appViewer: AppViewer) => {
|
|
271
|
-
console.trace('generateGuiAtlas')
|
|
272
271
|
const { blockModelsResolved, itemsModelsResolved } = getNonFullBlocksModels(appViewer)
|
|
273
272
|
|
|
274
273
|
// Generate blocks atlas
|
|
@@ -718,5 +718,13 @@
|
|
|
718
718
|
"barrier": true,
|
|
719
719
|
"light": true,
|
|
720
720
|
"moving_piston": true
|
|
721
|
+
},
|
|
722
|
+
"hasSemiTransparentTextuersRegex": {
|
|
723
|
+
"_stained_glass$": true,
|
|
724
|
+
"_stained_glass_pane$": true,
|
|
725
|
+
"^ice$": true,
|
|
726
|
+
"^tinted_glass$": true,
|
|
727
|
+
"^slime_block$": true,
|
|
728
|
+
"^honey_block$": true
|
|
721
729
|
}
|
|
722
730
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Calculates sky light level based on Minecraft time of day.
|
|
4
|
+
*
|
|
5
|
+
* Minecraft time reference:
|
|
6
|
+
* - 0 ticks = 6:00 AM (sunrise complete)
|
|
7
|
+
* - 6000 ticks = 12:00 PM (noon) - brightest
|
|
8
|
+
* - 12000 ticks = 6:00 PM (sunset begins)
|
|
9
|
+
* - 13000 ticks = 7:00 PM (dusk/night begins)
|
|
10
|
+
* - 18000 ticks = 12:00 AM (midnight) - darkest
|
|
11
|
+
* - 23000 ticks = 5:00 AM (dawn begins)
|
|
12
|
+
* - 24000 ticks = 6:00 AM (same as 0)
|
|
13
|
+
*
|
|
14
|
+
* Sky light ranges from 4 (night) to 15 (day).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Calculate celestial angle from time of day (0-1 range representing sun position)
|
|
19
|
+
*/
|
|
20
|
+
export const getCelestialAngle = (timeOfDay: number): number => {
|
|
21
|
+
// Normalize time to 0-1 range
|
|
22
|
+
let angle = ((timeOfDay % 24_000) / 24_000) - 0.25
|
|
23
|
+
|
|
24
|
+
if (angle < 0) angle += 1
|
|
25
|
+
if (angle > 1) angle -= 1
|
|
26
|
+
|
|
27
|
+
// Vanilla Minecraft applies a smoothing curve
|
|
28
|
+
const smoothedAngle = angle + (1 - Math.cos(angle * Math.PI)) / 2
|
|
29
|
+
return smoothedAngle
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Calculate sky light level (0-15) based on time of day in ticks.
|
|
34
|
+
* Matches Minecraft vanilla behavior.
|
|
35
|
+
*
|
|
36
|
+
* @param timeOfDay - Time in ticks (0-24000)
|
|
37
|
+
* @returns Sky light level (4-15, where 15 is brightest day, 4 is darkest night)
|
|
38
|
+
*/
|
|
39
|
+
export const calculateSkyLight = (timeOfDay: number): number => {
|
|
40
|
+
// Normalize time to 0-24000 range
|
|
41
|
+
const normalizedTime = ((timeOfDay % 24_000) + 24_000) % 24_000
|
|
42
|
+
|
|
43
|
+
// Calculate celestial angle (0-1, where 0.25 is noon, 0.75 is midnight)
|
|
44
|
+
const celestialAngle = getCelestialAngle(normalizedTime)
|
|
45
|
+
|
|
46
|
+
// Calculate brightness factor based on celestial angle
|
|
47
|
+
// cos gives us smooth day/night transition
|
|
48
|
+
const cos = Math.cos(celestialAngle * Math.PI * 2)
|
|
49
|
+
|
|
50
|
+
// Map cos (-1 to 1) to brightness (0 to 1)
|
|
51
|
+
// At noon (celestialAngle ~0.25): cos(0.5π) = 0, but we want max brightness
|
|
52
|
+
// At midnight (celestialAngle ~0.75): cos(1.5π) = 0, but we want min brightness
|
|
53
|
+
|
|
54
|
+
// Vanilla-like calculation:
|
|
55
|
+
// brightness goes from 0 (dark) to 1 (bright)
|
|
56
|
+
const brightness = cos * 0.5 + 0.5
|
|
57
|
+
|
|
58
|
+
// Apply threshold - night should be darker
|
|
59
|
+
// Vanilla has minimum sky light of 4 during night
|
|
60
|
+
const skyLight = Math.round(4 + brightness * 11)
|
|
61
|
+
|
|
62
|
+
return Math.max(4, Math.min(15, skyLight))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Simplified sky light calculation that more closely matches vanilla behavior.
|
|
67
|
+
* Uses piecewise linear interpolation based on known Minecraft light levels.
|
|
68
|
+
*
|
|
69
|
+
* @param timeOfDay - Time in ticks (0-24000)
|
|
70
|
+
* @returns Sky light level (4-15)
|
|
71
|
+
*/
|
|
72
|
+
export const calculateSkyLightSimple = (timeOfDay: number): number => {
|
|
73
|
+
// Normalize to 0-24000
|
|
74
|
+
const time = ((timeOfDay % 24_000) + 24_000) % 24_000
|
|
75
|
+
|
|
76
|
+
// Vanilla Minecraft approximate sky light levels:
|
|
77
|
+
// 0-12000 (6AM-6PM): Day, sky light = 15
|
|
78
|
+
// 12000-13000 (6PM-7PM): Sunset transition, 15 -> 4
|
|
79
|
+
// 13000-23000 (7PM-5AM): Night, sky light = 4
|
|
80
|
+
// 23000-24000 (5AM-6AM): Sunrise transition, 4 -> 15
|
|
81
|
+
|
|
82
|
+
if (time >= 0 && time < 12_000) {
|
|
83
|
+
// Day time - full brightness
|
|
84
|
+
return 15
|
|
85
|
+
} else if (time >= 12_000 && time < 13_000) {
|
|
86
|
+
// Sunset transition (6PM to 7PM)
|
|
87
|
+
const progress = (time - 12_000) / 1000
|
|
88
|
+
return Math.round(15 - progress * 11)
|
|
89
|
+
} else if (time >= 13_000 && time < 23_000) {
|
|
90
|
+
// Night time - minimum brightness
|
|
91
|
+
return 4
|
|
92
|
+
} else {
|
|
93
|
+
// Sunrise transition (5AM to 6AM)
|
|
94
|
+
const progress = (time - 23_000) / 1000
|
|
95
|
+
return Math.round(4 + progress * 11)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Test/debug helper - run this to see values at different times
|
|
100
|
+
export const debugSkyLight = () => {
|
|
101
|
+
const testTimes = [
|
|
102
|
+
{ ticks: 0, label: '6:00 AM (sunrise)' },
|
|
103
|
+
{ ticks: 6000, label: '12:00 PM (noon)' },
|
|
104
|
+
{ ticks: 12_000, label: '6:00 PM (sunset starts)' },
|
|
105
|
+
{ ticks: 12_500, label: '6:30 PM (sunset mid)' },
|
|
106
|
+
{ ticks: 13_000, label: '7:00 PM (night begins)' },
|
|
107
|
+
{ ticks: 18_000, label: '12:00 AM (midnight)' },
|
|
108
|
+
{ ticks: 19_000, label: '1:00 AM' },
|
|
109
|
+
{ ticks: 23_000, label: '5:00 AM (dawn begins)' },
|
|
110
|
+
{ ticks: 23_500, label: '5:30 AM (dawn mid)' },
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
console.log('Sky Light Debug:')
|
|
114
|
+
console.log('================')
|
|
115
|
+
for (const { ticks, label } of testTimes) {
|
|
116
|
+
const smooth = calculateSkyLight(ticks)
|
|
117
|
+
const simple = calculateSkyLightSimple(ticks)
|
|
118
|
+
console.log(`${ticks.toString().padStart(5)} ticks (${label}): smooth=${smooth}, simple=${simple}`)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Export for global access in console
|
|
123
|
+
if (typeof window !== 'undefined') {
|
|
124
|
+
(window as any).debugSkyLight = debugSkyLight
|
|
125
|
+
}
|
|
@@ -17,6 +17,7 @@ import { getPlayerStateUtils } from '../graphicsBackend/playerState'
|
|
|
17
17
|
type PlayerStateUtils = ReturnType<typeof getPlayerStateUtils>
|
|
18
18
|
import { MesherLogReader } from './mesherlogReader'
|
|
19
19
|
import { setSkinsConfig } from './utils/skins'
|
|
20
|
+
import { calculateSkyLightSimple } from './skyLight'
|
|
20
21
|
import { WorldViewWorker } from '../worldView'
|
|
21
22
|
import { generateSpiralMatrix } from './spiral'
|
|
22
23
|
import { PlayerStateReactive } from '../playerState/playerState'
|
|
@@ -540,19 +541,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
540
541
|
}
|
|
541
542
|
|
|
542
543
|
getMesherConfig(): MesherConfig {
|
|
543
|
-
let skyLight = 15
|
|
544
544
|
const timeOfDay = this.timeOfTheDay
|
|
545
|
-
|
|
546
|
-
//
|
|
547
|
-
} else if (timeOfDay <= 6000 || timeOfDay >= 18_000) {
|
|
548
|
-
skyLight = 15
|
|
549
|
-
} else if (timeOfDay > 6000 && timeOfDay < 12_000) {
|
|
550
|
-
skyLight = 15 - ((timeOfDay - 6000) / 6000) * 15
|
|
551
|
-
} else if (timeOfDay >= 12_000 && timeOfDay < 18_000) {
|
|
552
|
-
skyLight = ((timeOfDay - 12_000) / 6000) * 15
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
skyLight = Math.floor(skyLight)
|
|
545
|
+
const skyLight = (timeOfDay < 0 || timeOfDay > 24_000) ? 15 : calculateSkyLightSimple(timeOfDay)
|
|
556
546
|
return {
|
|
557
547
|
version: this.version,
|
|
558
548
|
enableLighting: this.worldRendererConfig.enableLighting,
|
package/src/mesher/models.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import { Vec3 } from 'vec3'
|
|
3
3
|
import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
|
4
|
+
import moreBlockDataGeneratedJson from '../lib/moreBlockDataGenerated.json'
|
|
4
5
|
import legacyJson from '../lib/preflatMap.json'
|
|
5
6
|
import { BlockType } from '../playground/shared'
|
|
6
7
|
import { World, BlockModelPartsResolved, WorldBlock as Block, WorldBlock, worldColumnKey } from './world'
|
|
@@ -16,6 +17,7 @@ let blockProvider: WorldBlockProvider
|
|
|
16
17
|
|
|
17
18
|
const tints: any = {}
|
|
18
19
|
let needTiles = false
|
|
20
|
+
let semiTransparentBlocks: string[] = []
|
|
19
21
|
|
|
20
22
|
let tintsData
|
|
21
23
|
try {
|
|
@@ -700,7 +702,7 @@ export function getSectionGeometry(sx: number, sy: number, sz: number, world: Wo
|
|
|
700
702
|
|
|
701
703
|
for (const element of model.elements ?? []) {
|
|
702
704
|
const ao = model.ao ?? block.boundingBox !== 'empty'
|
|
703
|
-
if (block.transparent) {
|
|
705
|
+
if (block.transparent && semiTransparentBlocks.includes(block.name)) {
|
|
704
706
|
const pos = cursor.clone()
|
|
705
707
|
delayedRender.push(() => {
|
|
706
708
|
renderElement(world, pos, element, ao, attr, globalMatrix, globalShift, block, biome)
|
|
@@ -794,7 +796,7 @@ function arrayNeedsUint32(array) {
|
|
|
794
796
|
|
|
795
797
|
}
|
|
796
798
|
|
|
797
|
-
export const setBlockStatesData = (blockstatesModels, blocksAtlas: any, _needTiles = false, useUnknownBlockModel = true, version = 'latest') => {
|
|
799
|
+
export const setBlockStatesData = (blockstatesModels, blocksAtlas: any, _needTiles = false, useUnknownBlockModel = true, version = 'latest', mcData = (globalThis as any).mcData) => {
|
|
798
800
|
blockProvider = worldBlockProvider(blockstatesModels, blocksAtlas, version)
|
|
799
801
|
globalThis.blockProvider = blockProvider
|
|
800
802
|
if (useUnknownBlockModel) {
|
|
@@ -802,4 +804,28 @@ export const setBlockStatesData = (blockstatesModels, blocksAtlas: any, _needTil
|
|
|
802
804
|
}
|
|
803
805
|
|
|
804
806
|
needTiles = _needTiles
|
|
807
|
+
|
|
808
|
+
// Cache semi-transparent blocks based on regex patterns from moreBlockDataGenerated.json
|
|
809
|
+
const regexPatterns = Object.keys(moreBlockDataGeneratedJson.hasSemiTransparentTextuersRegex || {})
|
|
810
|
+
semiTransparentBlocks = []
|
|
811
|
+
|
|
812
|
+
// Get all block names from blockstatesModels
|
|
813
|
+
if (!Array.isArray(mcData.blocks)) throw new Error('mcData.blocks is not an array')
|
|
814
|
+
const allBlockNames = mcData.blocks.map(block => block.name)
|
|
815
|
+
|
|
816
|
+
// Filter blocks that match any of the regex patterns
|
|
817
|
+
for (const blockName of allBlockNames) {
|
|
818
|
+
for (const pattern of regexPatterns) {
|
|
819
|
+
try {
|
|
820
|
+
const regex = new RegExp(pattern)
|
|
821
|
+
if (regex.test(blockName)) {
|
|
822
|
+
semiTransparentBlocks.push(blockName)
|
|
823
|
+
break // Only add once per block
|
|
824
|
+
}
|
|
825
|
+
} catch (err) {
|
|
826
|
+
// Invalid regex pattern, skip
|
|
827
|
+
console.warn('Invalid regex pattern in hasSemiTransparentTextuersRegex:', pattern)
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
805
831
|
}
|
|
@@ -39,7 +39,7 @@ export const setup = (version, initialBlocks: Array<[number[], string]>, options
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
setBlockStatesData(blockStatesModels, blocksAtlasesJson, !options?.noDebugTiles, false, version)
|
|
42
|
+
setBlockStatesData(blockStatesModels, blocksAtlasesJson, !options?.noDebugTiles, false, version, { blocks: mcData.blocksArray })
|
|
43
43
|
const reload = () => {
|
|
44
44
|
mesherWorld.removeColumn(0, 0)
|
|
45
45
|
mesherWorld.addColumn(0, 0, chunk1.toJson())
|
|
@@ -137,7 +137,10 @@ export const renderComponent = (
|
|
|
137
137
|
const textWidths: number[] = []
|
|
138
138
|
|
|
139
139
|
const renderText = (component: Message, parentFormatting?: Formatting | undefined) => {
|
|
140
|
-
|
|
140
|
+
if (component.text !== null && component.text !== undefined && typeof component.text !== 'string') {
|
|
141
|
+
console.warn('renderText received non-string text value:', typeof component.text, component.text)
|
|
142
|
+
}
|
|
143
|
+
const text = component.text === null || component.text === undefined ? undefined : String(component.text)
|
|
141
144
|
const formatting = {
|
|
142
145
|
color: component.color ?? parentFormatting?.color,
|
|
143
146
|
underlined: component.underlined ?? parentFormatting?.underlined,
|
|
@@ -144,10 +144,11 @@ export const renderBanner = (
|
|
|
144
144
|
|
|
145
145
|
ctx.imageSmoothingEnabled = false
|
|
146
146
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
ctx.
|
|
147
|
+
// Base color rendering disabled
|
|
148
|
+
// // Always render base color first (even if no patterns)
|
|
149
|
+
// const baseColorHex = BANNER_COLORS[baseColor] || BANNER_COLORS[15]
|
|
150
|
+
// ctx.fillStyle = baseColorHex
|
|
151
|
+
// ctx.fillRect(0, 0, BANNER_WIDTH * scale, BANNER_HEIGHT * scale)
|
|
151
152
|
|
|
152
153
|
// Render patterns on top of base color (if any)
|
|
153
154
|
if (blockEntity?.Patterns && blockEntity.Patterns.length > 0) {
|
|
@@ -97,8 +97,8 @@ export class CinimaticScriptRunner {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
runExampleScripts(index: number) {
|
|
100
|
-
const
|
|
101
|
-
const playerPos = new Vec3(
|
|
100
|
+
const cameraWorldPos = this.worldRenderer.getCameraPosition()
|
|
101
|
+
const playerPos = new Vec3(cameraWorldPos.x, cameraWorldPos.y, cameraWorldPos.z)
|
|
102
102
|
|
|
103
103
|
// Circular flyby around current position
|
|
104
104
|
const circular = CinimaticScriptRunner.createCircularFlyby(playerPos, 30, 20, 15_000)
|
package/src/three/entities.ts
CHANGED
|
@@ -423,6 +423,9 @@ export class Entities {
|
|
|
423
423
|
|
|
424
424
|
// Update position and rotation
|
|
425
425
|
if (playerData.position) {
|
|
426
|
+
if (!this.worldRenderer.sceneOrigin.getWorldPosition(this.playerEntity)) {
|
|
427
|
+
this.worldRenderer.sceneOrigin.track(this.playerEntity)
|
|
428
|
+
}
|
|
426
429
|
this.playerEntity.position.set(playerData.position.x, playerData.position.y, playerData.position.z)
|
|
427
430
|
}
|
|
428
431
|
if (playerData.yaw !== undefined) {
|
|
@@ -434,7 +437,7 @@ export class Entities {
|
|
|
434
437
|
|
|
435
438
|
clear() {
|
|
436
439
|
for (const mesh of Object.values(this.entities)) {
|
|
437
|
-
this.worldRenderer.
|
|
440
|
+
this.worldRenderer.sceneOrigin.removeAndUntrack(mesh)
|
|
438
441
|
disposeObject(mesh)
|
|
439
442
|
}
|
|
440
443
|
this.entities = {}
|
|
@@ -446,7 +449,7 @@ export class Entities {
|
|
|
446
449
|
|
|
447
450
|
// Clean up player entity
|
|
448
451
|
if (this.playerEntity) {
|
|
449
|
-
this.worldRenderer.
|
|
452
|
+
this.worldRenderer.sceneOrigin.removeAndUntrack(this.playerEntity)
|
|
450
453
|
disposeObject(this.playerEntity)
|
|
451
454
|
this.playerEntity = null
|
|
452
455
|
}
|
|
@@ -534,22 +537,29 @@ export class Entities {
|
|
|
534
537
|
|
|
535
538
|
if (thirdPersonNow) {
|
|
536
539
|
const yOffset = this.worldRenderer.playerStateReactive.eyeHeight
|
|
537
|
-
|
|
538
|
-
entity.position.set(
|
|
540
|
+
// Set world position — proxy auto-converts to scene coords
|
|
541
|
+
entity.position.set(
|
|
542
|
+
this.worldRenderer.cameraWorldPos.x,
|
|
543
|
+
this.worldRenderer.cameraWorldPos.y - yOffset,
|
|
544
|
+
this.worldRenderer.cameraWorldPos.z
|
|
545
|
+
)
|
|
539
546
|
|
|
540
547
|
const p: any = (this.worldRenderer.playerStateReactive as any).position
|
|
541
548
|
if (p && typeof p.x === 'number') {
|
|
542
549
|
this.updateAutoWalkFlags(entityKey, entity, dtRaw, new THREE.Vector3(p.x, p.y, p.z))
|
|
543
550
|
} else {
|
|
544
|
-
this.
|
|
551
|
+
const wp = this.worldRenderer.sceneOrigin.getWorldPosition(entity)
|
|
552
|
+
this.updateAutoWalkFlags(entityKey, entity, dtRaw, wp ? new THREE.Vector3(wp.x, wp.y, wp.z) : entity.position)
|
|
545
553
|
}
|
|
546
554
|
|
|
547
555
|
this.updateThirdPersonHeadAndBody(entity, dt)
|
|
548
556
|
} else {
|
|
549
|
-
this.
|
|
557
|
+
const wp = this.worldRenderer.sceneOrigin.getWorldPosition(entity)
|
|
558
|
+
this.updateAutoWalkFlags(entityKey, entity, dtRaw, wp ? new THREE.Vector3(wp.x, wp.y, wp.z) : entity.position)
|
|
550
559
|
}
|
|
551
560
|
} else {
|
|
552
|
-
this.
|
|
561
|
+
const wp = this.worldRenderer.sceneOrigin.getWorldPosition(entity)
|
|
562
|
+
this.updateAutoWalkFlags(entityKey, entity, dtRaw, wp ? new THREE.Vector3(wp.x, wp.y, wp.z) : entity.position)
|
|
553
563
|
}
|
|
554
564
|
|
|
555
565
|
const { playerObject } = entity
|
|
@@ -790,6 +800,7 @@ export class Entities {
|
|
|
790
800
|
skinTexture.needsUpdate = true
|
|
791
801
|
playerObject.skin.map = skinTexture as any
|
|
792
802
|
playerObject.skin.modelType = inferModelType(skinCanvas)
|
|
803
|
+
playerObject.skin['isCustom'] = skinUrl !== stevePngUrl
|
|
793
804
|
|
|
794
805
|
let earsCanvas: HTMLCanvasElement | undefined
|
|
795
806
|
if (!playerCustomSkinImage) {
|
|
@@ -1031,12 +1042,13 @@ export class Entities {
|
|
|
1031
1042
|
|
|
1032
1043
|
if (entity.delete) {
|
|
1033
1044
|
if (!e) return
|
|
1045
|
+
e.userData._posTween?.stop()
|
|
1034
1046
|
if (e.additionalCleanup) e.additionalCleanup()
|
|
1035
1047
|
e.traverse(c => {
|
|
1036
1048
|
if (c['additionalCleanup']) c['additionalCleanup']()
|
|
1037
1049
|
})
|
|
1038
1050
|
this.onRemoveEntity(entity)
|
|
1039
|
-
this.worldRenderer.
|
|
1051
|
+
this.worldRenderer.sceneOrigin.removeAndUntrack(e)
|
|
1040
1052
|
disposeObject(e)
|
|
1041
1053
|
// todo dispose textures as well ?
|
|
1042
1054
|
delete this.entities[entity.id]
|
|
@@ -1109,6 +1121,7 @@ export class Entities {
|
|
|
1109
1121
|
mesh.name = 'mesh'
|
|
1110
1122
|
// set initial position so there are no weird jumps update after
|
|
1111
1123
|
const pos = entity.pos ?? entity.position
|
|
1124
|
+
this.worldRenderer.sceneOrigin.track(group)
|
|
1112
1125
|
group.position.set(pos.x, pos.y, pos.z)
|
|
1113
1126
|
|
|
1114
1127
|
// todo use width and height instead
|
|
@@ -1326,7 +1339,20 @@ export class Entities {
|
|
|
1326
1339
|
if (!e) return
|
|
1327
1340
|
const ANIMATION_DURATION = justAdded ? 0 : TWEEN_DURATION
|
|
1328
1341
|
if (entity.position) {
|
|
1329
|
-
|
|
1342
|
+
// Initialize tween target from current world position
|
|
1343
|
+
const currentWorld = this.worldRenderer.sceneOrigin.getWorldPosition(e) ?? { x: entity.position.x, y: entity.position.y, z: entity.position.z }
|
|
1344
|
+
if (!e.userData._tweenTarget) {
|
|
1345
|
+
e.userData._tweenTarget = { x: currentWorld.x, y: currentWorld.y, z: currentWorld.z }
|
|
1346
|
+
}
|
|
1347
|
+
// Stop previous position tween to prevent accumulation
|
|
1348
|
+
e.userData._posTween?.stop()
|
|
1349
|
+
// Tween a separate target object, apply via proxy on each update
|
|
1350
|
+
e.userData._posTween = new TWEEN.Tween(e.userData._tweenTarget)
|
|
1351
|
+
.to({ x: entity.position.x, y: entity.position.y, z: entity.position.z }, ANIMATION_DURATION)
|
|
1352
|
+
.onUpdate(() => {
|
|
1353
|
+
e.position.set(e.userData._tweenTarget.x, e.userData._tweenTarget.y, e.userData._tweenTarget.z)
|
|
1354
|
+
})
|
|
1355
|
+
.start()
|
|
1330
1356
|
}
|
|
1331
1357
|
if (entity.yaw) {
|
|
1332
1358
|
const da = (entity.yaw - e.rotation.y) % (Math.PI * 2)
|
|
@@ -1366,8 +1392,13 @@ export class Entities {
|
|
|
1366
1392
|
if (!mesh.visible) return
|
|
1367
1393
|
|
|
1368
1394
|
const MAX_DISTANCE_SKIN_LOAD = 128
|
|
1369
|
-
const cameraPos = this.worldRenderer.
|
|
1370
|
-
|
|
1395
|
+
const cameraPos = this.worldRenderer.getCameraPosition()
|
|
1396
|
+
// Use world positions for accurate distance calculation
|
|
1397
|
+
const wp = this.worldRenderer.sceneOrigin.getWorldPosition(mesh)
|
|
1398
|
+
const entityWorldPos = wp
|
|
1399
|
+
? new THREE.Vector3(wp.x, wp.y, wp.z)
|
|
1400
|
+
: mesh.position.clone().add(new THREE.Vector3(this.worldRenderer.sceneOrigin.x, this.worldRenderer.sceneOrigin.y, this.worldRenderer.sceneOrigin.z))
|
|
1401
|
+
const distance = entityWorldPos.distanceTo(cameraPos)
|
|
1371
1402
|
if (distance < MAX_DISTANCE_SKIN_LOAD && distance < (this.worldRenderer.viewDistance * 16)) {
|
|
1372
1403
|
if (this.loadedSkinEntityIds.has(String(entityId))) return
|
|
1373
1404
|
void this.updatePlayerSkin(entityId, mesh.playerObject.realUsername, mesh.playerObject.realPlayerUuid, true, true)
|
|
@@ -331,7 +331,7 @@ export function getMesh(
|
|
|
331
331
|
geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(geoData.skinWeights, 4))
|
|
332
332
|
geometry.setIndex(geoData.indices)
|
|
333
333
|
|
|
334
|
-
const material = new THREE.MeshLambertMaterial({ transparent: true, alphaTest: 0.1 })
|
|
334
|
+
const material = new THREE.MeshLambertMaterial({ transparent: true, alphaTest: 0.1, side: THREE.DoubleSide })
|
|
335
335
|
const mesh = new THREE.SkinnedMesh(geometry, material)
|
|
336
336
|
mesh.add(...rootBones)
|
|
337
337
|
mesh.bind(skeleton)
|
|
@@ -125,11 +125,14 @@
|
|
|
125
125
|
"render_controllers": ["controller.render.arrow"]
|
|
126
126
|
},
|
|
127
127
|
"bat": {
|
|
128
|
+
"_comment": "Wing tip mirror flags are swapped vs vanilla Bedrock model: our addCube() mirror implementation flips UVs in the opposite direction, so rightWingTip needs mirror=true and leftWingTip needs mirror=false to render correctly.",
|
|
128
129
|
"identifier": "minecraft:bat",
|
|
129
130
|
"materials": {"default": "bat"},
|
|
130
|
-
"textures": {"default": "
|
|
131
|
+
"textures": {"default": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAALHSURBVHja7VrBSiQxFJybHvci6G1gF2TBwyoKelnWy55kWYS9ePDmD+in+KcevLb7GgpqipeeTkzPpGdewSNJmx6nKtXJS3oWiwTOv33pbs6Ou9vr731YHddQ5/ZiAHa/9cXnjLln62DyLALIL48P+4AIQ+RZAP2cnO909/PH4uvRQcdh1zSqwCMPIiDPIqwT8uHufMVJuDfnOxnhq9OT7vHPZR9WZxEgSjUBUi7IcQBG++XpphfB6rgvVwCQ//f7og8WAeL8F6Kr6oD7X8s+PPuOmQNYAAsIUOIAkDeSLIK1LeCMagIweZQ8iRkBlJuYl5gwSlxjZ2R9KM/2XBphsyxEQGhfOCEVNQXQ0UZdo2i21+edR57DmxjZFRpTCaD2/7QAXsAFcEKq3zYEGHJBtgCeC97f3vtnG0uY1e1aavQ3/QgMuaBoDtCsDyWv3yqSlxhpTClAygXZArAILIaS8ezOo52TIJVA1/sqAswRnhCfEoAnuqGkZwpbZxJfyflTQuysAN7GxxNiZ62fEoCFMBGqpcK8h8ckqIkRoiU3VPkHIM/2R4rMaTLK0pGr9oVrw9v7Iy/QDLFEAH5umxTAW9utreTHOuD1+e/KaQ4mrGrb1ykE0L07HODFmKMsJs1b2GYfAc0MdYPEThiTvIA8DjWayNxSs3pJePPDOvJNCKDL27r22L95AlRJXaewuh57cVuPx7Acen1VlNkIoKR0++udCer84PXn42wj660EW38BooS99rqTn1R/FkCTnibyAH3l5bW9GOqjx+Wa8TWVCXpn/NzW63oIote1bH6HlSKUK0BKkFkIgDSX01+taz9+vTV0X/MC6Hu+1OFmSb9tHp6MRopIrQgBAjMDtr2bfOvb3KTIP2vZy9HnvH6vXJD6ZdheOYAJl/yOZ/YCMOFYygKBQCAQCAQCgUAgEAgEAoFAIBDYKXwAXSs8dJ1Ggb4AAAAASUVORK5CYII="},
|
|
131
132
|
"geometry": {
|
|
132
133
|
"default": {
|
|
134
|
+
"texturewidth": 64,
|
|
135
|
+
"textureheight": 64,
|
|
133
136
|
"visible_bounds_width": 1,
|
|
134
137
|
"visible_bounds_height": 1,
|
|
135
138
|
"visible_bounds_offset": [0, 0.5, 0],
|
|
@@ -174,6 +177,7 @@
|
|
|
174
177
|
},
|
|
175
178
|
{
|
|
176
179
|
"name": "rightWingTip",
|
|
180
|
+
"mirror": true,
|
|
177
181
|
"pivot": [-12, 23, 1.5],
|
|
178
182
|
"cubes": [
|
|
179
183
|
{"origin": [-20, 10, 1.5], "size": [8, 12, 1], "uv": [24, 16]}
|
|
@@ -191,7 +195,6 @@
|
|
|
191
195
|
},
|
|
192
196
|
{
|
|
193
197
|
"name": "leftWingTip",
|
|
194
|
-
"mirror": true,
|
|
195
198
|
"pivot": [12, 23, 1.5],
|
|
196
199
|
"cubes": [
|
|
197
200
|
{"origin": [12, 10, 1.5], "size": [8, 12, 1], "uv": [24, 16]}
|
|
@@ -3541,6 +3544,109 @@
|
|
|
3541
3544
|
"render_controllers": ["controller.render.salmon"],
|
|
3542
3545
|
"spawn_egg": {"texture": "spawn_egg", "texture_index": 47}
|
|
3543
3546
|
},
|
|
3547
|
+
"sheep": {
|
|
3548
|
+
"identifier": "minecraft:sheep",
|
|
3549
|
+
"materials": {"default": "sheep"},
|
|
3550
|
+
"textures": {"default": "textures/entity/sheep/sheep", "wool": "textures/entity/sheep/sheep_fur"},
|
|
3551
|
+
"geometry": {
|
|
3552
|
+
"default": {
|
|
3553
|
+
"visible_bounds_width": 2,
|
|
3554
|
+
"visible_bounds_height": 1.75,
|
|
3555
|
+
"visible_bounds_offset": [0, 0.5, 0],
|
|
3556
|
+
"texturewidth": 64,
|
|
3557
|
+
"textureheight": 32,
|
|
3558
|
+
"bones": [
|
|
3559
|
+
{
|
|
3560
|
+
"name": "body",
|
|
3561
|
+
"pivot": [0, 19, 2],
|
|
3562
|
+
"bind_pose_rotation": [90, 0, 0],
|
|
3563
|
+
"cubes": [
|
|
3564
|
+
{"origin": [-4, 13, -5], "size": [8, 16, 6], "uv": [28, 8]}
|
|
3565
|
+
]
|
|
3566
|
+
},
|
|
3567
|
+
{
|
|
3568
|
+
"name": "head",
|
|
3569
|
+
"pivot": [0, 18, -8],
|
|
3570
|
+
"cubes": [
|
|
3571
|
+
{"origin": [-3, 16, -14], "size": [6, 6, 8], "uv": [0, 0]}
|
|
3572
|
+
]
|
|
3573
|
+
},
|
|
3574
|
+
{
|
|
3575
|
+
"name": "leg0",
|
|
3576
|
+
"parent": "body",
|
|
3577
|
+
"pivot": [-3, 12, 7],
|
|
3578
|
+
"cubes": [{"origin": [-5, 0, 5], "size": [4, 12, 4], "uv": [0, 16]}]
|
|
3579
|
+
},
|
|
3580
|
+
{
|
|
3581
|
+
"name": "leg1",
|
|
3582
|
+
"parent": "body",
|
|
3583
|
+
"pivot": [3, 12, 7],
|
|
3584
|
+
"cubes": [{"origin": [1, 0, 5], "size": [4, 12, 4], "uv": [0, 16]}]
|
|
3585
|
+
},
|
|
3586
|
+
{
|
|
3587
|
+
"name": "leg2",
|
|
3588
|
+
"parent": "body",
|
|
3589
|
+
"pivot": [-3, 12, -5],
|
|
3590
|
+
"cubes": [{"origin": [-5, 0, -7], "size": [4, 12, 4], "uv": [0, 16]}]
|
|
3591
|
+
},
|
|
3592
|
+
{
|
|
3593
|
+
"name": "leg3",
|
|
3594
|
+
"parent": "body",
|
|
3595
|
+
"pivot": [3, 12, -5],
|
|
3596
|
+
"cubes": [{"origin": [1, 0, -7], "size": [4, 12, 4], "uv": [0, 16]}]
|
|
3597
|
+
}
|
|
3598
|
+
]
|
|
3599
|
+
},
|
|
3600
|
+
"wool": {
|
|
3601
|
+
"visible_bounds_width": 2,
|
|
3602
|
+
"visible_bounds_height": 1.75,
|
|
3603
|
+
"visible_bounds_offset": [0, 0.5, 0],
|
|
3604
|
+
"texturewidth": 64,
|
|
3605
|
+
"textureheight": 64,
|
|
3606
|
+
"bones": [
|
|
3607
|
+
{
|
|
3608
|
+
"name": "head",
|
|
3609
|
+
"pivot": [0, 18, -8],
|
|
3610
|
+
"cubes": [
|
|
3611
|
+
{"origin": [-3, 16, -12], "size": [6, 6, 6], "uv": [0, 32], "inflate": 0.6}
|
|
3612
|
+
]
|
|
3613
|
+
},
|
|
3614
|
+
{
|
|
3615
|
+
"name": "body",
|
|
3616
|
+
"pivot": [0, 19, 2],
|
|
3617
|
+
"bind_pose_rotation": [90, 0, 0],
|
|
3618
|
+
"cubes": [
|
|
3619
|
+
{"origin": [-4, 13, -5], "size": [8, 16, 6], "uv": [28, 40], "inflate": 1.75}
|
|
3620
|
+
]
|
|
3621
|
+
},
|
|
3622
|
+
{
|
|
3623
|
+
"name": "leg0",
|
|
3624
|
+
"parent": "body",
|
|
3625
|
+
"pivot": [-3, 12, 7],
|
|
3626
|
+
"cubes": [{"origin": [-5, 6, 5], "size": [4, 6, 4], "uv": [0, 48], "inflate": 0.5}]
|
|
3627
|
+
},
|
|
3628
|
+
{
|
|
3629
|
+
"name": "leg1",
|
|
3630
|
+
"parent": "body",
|
|
3631
|
+
"pivot": [3, 12, 7],
|
|
3632
|
+
"cubes": [{"origin": [1, 6, 5], "size": [4, 6, 4], "uv": [0, 48], "inflate": 0.5}]
|
|
3633
|
+
},
|
|
3634
|
+
{
|
|
3635
|
+
"name": "leg2",
|
|
3636
|
+
"parent": "body",
|
|
3637
|
+
"pivot": [-3, 12, -5],
|
|
3638
|
+
"cubes": [{"origin": [-5, 6, -7], "size": [4, 6, 4], "uv": [0, 48], "inflate": 0.5}]
|
|
3639
|
+
},
|
|
3640
|
+
{
|
|
3641
|
+
"name": "leg3",
|
|
3642
|
+
"parent": "body",
|
|
3643
|
+
"pivot": [3, 12, -5],
|
|
3644
|
+
"cubes": [{"origin": [1, 6, -7], "size": [4, 6, 4], "uv": [0, 48], "inflate": 0.5}]
|
|
3645
|
+
}
|
|
3646
|
+
]
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
},
|
|
3544
3650
|
"shulker_bullet": {
|
|
3545
3651
|
"identifier": "minecraft:shulker_bullet",
|
|
3546
3652
|
"materials": {"default": "shulker_bullet"},
|
|
@@ -22,7 +22,6 @@ export { default as parrot } from './models/parrot.obj'
|
|
|
22
22
|
export { default as piglin } from './models/piglin.obj'
|
|
23
23
|
export { default as pillager } from './models/pillager.obj'
|
|
24
24
|
export { default as rabbit } from './models/rabbit.obj'
|
|
25
|
-
export { default as sheep } from './models/sheep.obj'
|
|
26
25
|
export { default as arrow } from './models/arrow.obj'
|
|
27
26
|
export { default as shulker } from './models/shulker.obj'
|
|
28
27
|
export { default as sniffer } from './models/sniffer.obj'
|