minecraft-renderer 0.1.94 → 0.1.96
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.map +2 -2
- package/dist/mesherWasm.js +369 -196
- package/dist/minecraft-renderer.js +72 -73
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +420 -421
- package/package.json +1 -1
- package/src/mesher-shared/exportedGeometryTypes.ts +3 -1
- package/src/mesher-shared/shaderCubeFormat.ts +3 -0
- package/src/mesher-shared/shared.ts +2 -1
- package/src/three/entities.ts +3 -4
- package/src/three/entity/interpolationPolicy.test.ts +23 -1
- package/src/three/entity/interpolationPolicy.ts +66 -11
- package/src/three/entity/localVehicleRendering.integration.test.ts +30 -10
- package/src/three/entity/minecartLocalPassengerRendering.test.ts +4 -2
- package/src/three/entity/networkHeadPitchRendering.test.ts +1 -1
- package/src/three/entity/remoteBoatPassengerRotation.integration.test.ts +3 -1
- package/src/three/entity/vehiclePassengerProduction.test.ts +20 -5
- package/src/three/entity/vehiclePassengerUpdate.ts +11 -2
- package/src/three/shaders/cubeBlockShader.ts +75 -14
- package/src/three/worldGeometryExport.ts +18 -3
- package/src/wasm-mesher/bridge/shaderCubeBridge.ts +83 -30
- package/src/wasm-mesher/tests/shaderCubeUvParity.test.ts +226 -0
- package/src/wasm-mesher/worker/mesherWasm.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
/** Shared geometry export shapes (worker bridge + main-thread viewer). */
|
|
3
3
|
|
|
4
|
+
import { SHADER_CUBES_FORMAT_VERSION } from './shaderCubeFormat'
|
|
5
|
+
|
|
4
6
|
export interface ExportedSection {
|
|
5
7
|
key: string
|
|
6
8
|
position: { x: number; y: number; z: number }
|
|
@@ -25,7 +27,7 @@ export interface ExportedSection {
|
|
|
25
27
|
shaderCubes?: {
|
|
26
28
|
words: Uint32Array
|
|
27
29
|
count: number
|
|
28
|
-
formatVersion:
|
|
30
|
+
formatVersion: typeof SHADER_CUBES_FORMAT_VERSION
|
|
29
31
|
}
|
|
30
32
|
visibilitySet?: number
|
|
31
33
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import { BlockType } from '../playground/shared'
|
|
3
|
+
import { SHADER_CUBES_FORMAT_VERSION } from './shaderCubeFormat'
|
|
3
4
|
|
|
4
5
|
export const SECTION_HEIGHT = 16
|
|
5
6
|
|
|
@@ -80,7 +81,7 @@ export type MesherGeometryOutput = {
|
|
|
80
81
|
shaderCubes?: {
|
|
81
82
|
words: Uint32Array
|
|
82
83
|
count: number
|
|
83
|
-
formatVersion:
|
|
84
|
+
formatVersion: typeof SHADER_CUBES_FORMAT_VERSION
|
|
84
85
|
}
|
|
85
86
|
/** Packed VisibilitySet (36 bits) for section occlusion / smart cull. */
|
|
86
87
|
visibilitySet?: number
|
package/src/three/entities.ts
CHANGED
|
@@ -447,9 +447,7 @@ export class Entities {
|
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
private setSceneAttached(attached: boolean, entity: THREE.Object3D | null = null): void {
|
|
450
|
-
const targets = entity
|
|
451
|
-
? [entity]
|
|
452
|
-
: [...Object.values(this.entities), ...(this.playerEntity ? [this.playerEntity] : [])]
|
|
450
|
+
const targets = entity ? [entity] : [...Object.values(this.entities), ...(this.playerEntity ? [this.playerEntity] : [])]
|
|
453
451
|
for (const ent of targets) {
|
|
454
452
|
if (attached) {
|
|
455
453
|
if (!this.worldRenderer.scene.children.includes(ent)) this.worldRenderer.scene.add(ent)
|
|
@@ -655,7 +653,8 @@ export class Entities {
|
|
|
655
653
|
applyVehiclePassengerPositions({
|
|
656
654
|
entities: this.entities,
|
|
657
655
|
localPlayer: this.playerEntity,
|
|
658
|
-
getWorldPosition: target => this.worldRenderer.sceneOrigin.getWorldPosition(target as SceneEntity)
|
|
656
|
+
getWorldPosition: target => this.worldRenderer.sceneOrigin.getWorldPosition(target as SceneEntity),
|
|
657
|
+
version: this.worldRenderer.version
|
|
659
658
|
})
|
|
660
659
|
}
|
|
661
660
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
getHorsePassengerWorldPosition,
|
|
11
11
|
getLocalVehicleWorldPosition,
|
|
12
12
|
getMinecartPassengerWorldPosition,
|
|
13
|
+
getVehiclePassengerFeetOffsetY,
|
|
13
14
|
resolveLocalVehicleWorldPosition,
|
|
14
15
|
samePosition,
|
|
15
16
|
shouldRestartCameraPositionTween,
|
|
@@ -244,7 +245,28 @@ test('horse vertical camera lock falls back to raw Y when result is non-finite',
|
|
|
244
245
|
})
|
|
245
246
|
|
|
246
247
|
test('minecart passenger feet Y is vehicleY - 0.35', () => {
|
|
247
|
-
expect(getMinecartPassengerWorldPosition({ x: 0, y: 64, z: 0 }).y).toBeCloseTo(63.65, 5)
|
|
248
|
+
expect(getMinecartPassengerWorldPosition({ x: 0, y: 64, z: 0 }, '1.17.1', 'minecart', 0.7).y).toBeCloseTo(63.65, 5)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
test.each([
|
|
252
|
+
['1.17.1', 'boat', -0.45],
|
|
253
|
+
['1.20.1', 'boat', -0.45],
|
|
254
|
+
['1.20.2', 'boat', -0.4125],
|
|
255
|
+
['1.21.4', 'boat', -0.4125],
|
|
256
|
+
['1.17.1', 'minecart', -0.35],
|
|
257
|
+
['1.20.1', 'minecart', -0.35],
|
|
258
|
+
['1.20.2', 'minecart', -0.4125],
|
|
259
|
+
['1.21.4', 'minecart', -0.4125],
|
|
260
|
+
['1.21.4', 'bamboo_raft', -0.1]
|
|
261
|
+
])('vehicle %s %s seat offset matches vanilla', (version, vehicleName, expected) => {
|
|
262
|
+
const layout = vehicleName === 'minecart' ? 'minecart' : 'boat'
|
|
263
|
+
const height = layout === 'boat' ? 0.5625 : 0.7
|
|
264
|
+
expect(getVehiclePassengerFeetOffsetY(layout, version, vehicleName, height)).toBeCloseTo(expected, 5)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
test('vehicle seat offset falls back to the default vehicle height', () => {
|
|
268
|
+
expect(getVehiclePassengerFeetOffsetY('boat', '1.20.2', 'oak_boat', undefined)).toBeCloseTo(-0.4125, 5)
|
|
269
|
+
expect(getVehiclePassengerFeetOffsetY('minecart', '1.20.2', 'minecart', undefined)).toBeCloseTo(-0.4125, 5)
|
|
248
270
|
})
|
|
249
271
|
|
|
250
272
|
test.each([
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
|
+
import { versionToNumber } from '../../lib/utils'
|
|
3
|
+
|
|
2
4
|
export const LOCAL_MOVEMENT_TWEEN_DURATION_MS = 50
|
|
3
5
|
export const ENTITY_TWEEN_DURATION_MS = 120
|
|
4
6
|
export const SPECTATING_CAMERA_TWEEN_DURATION_MS = 150
|
|
@@ -116,11 +118,6 @@ export function resolveLocalVehicleWorldPosition(args: {
|
|
|
116
118
|
return getLocalVehicleWorldPosition(cameraWorldPos, rawVehicleY)
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
const BOAT_PASSENGER_RIDING_OFFSET_Y = -0.1
|
|
120
|
-
const PLAYER_RIDING_OFFSET_Y = -0.35
|
|
121
|
-
/** Vanilla 1.17.1 AbstractMinecart#getPassengersRidingOffset() */
|
|
122
|
-
const MINECART_PASSENGER_RIDING_OFFSET_Y = 0
|
|
123
|
-
|
|
124
121
|
const RIDEABLE_MINECART_ENTITY_NAMES = new Set([
|
|
125
122
|
'minecart',
|
|
126
123
|
'chest_minecart',
|
|
@@ -148,21 +145,79 @@ export function getBoatPassengerSeatOffset(passengerIndex: number, passengerCoun
|
|
|
148
145
|
return passengerIndex === 0 ? 0.2 : -0.6
|
|
149
146
|
}
|
|
150
147
|
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
const DEFAULT_BOAT_HEIGHT = 0.5625
|
|
149
|
+
const DEFAULT_MINECART_HEIGHT = 0.7
|
|
150
|
+
|
|
151
|
+
// <= 1.20.1: Entity#positionRider = y + vehicle.getPassengersRidingOffset() + passenger.getMyRidingOffset()
|
|
152
|
+
// Vanilla 1.17.1 sources: vehicle/Boat#getPassengersRidingOffset() = -0.1,
|
|
153
|
+
// vehicle/AbstractMinecart#getPassengersRidingOffset() = 0, player/Player#getMyRidingOffset() = -0.35.
|
|
154
|
+
const LEGACY_PLAYER_RIDING_OFFSET_Y = -0.35 // Player#getMyRidingOffset(), <= 1.20.1
|
|
155
|
+
const LEGACY_BOAT_RIDING_OFFSET_Y = -0.1 // Boat#getPassengersRidingOffset(), <= 1.20.1
|
|
156
|
+
const LEGACY_MINECART_RIDING_OFFSET_Y = 0 // AbstractMinecart#getPassengersRidingOffset(), <= 1.20.1
|
|
157
|
+
|
|
158
|
+
// >= 1.20.2: y is based on the vehicle's PASSENGER attachment point.
|
|
159
|
+
// The player's contribution is numerically the same, but the mechanism changed:
|
|
160
|
+
// 1.20.2-1.20.4: PASSENGER.y + Player#ridingOffset() (-0.6)
|
|
161
|
+
// >= 1.20.5: PASSENGER.y - Player.DEFAULT_VEHICLE_ATTACHMENT.y (0.6)
|
|
162
|
+
// Vanilla sources: Entity#positionRider, Boat#getPassengerAttachmentPoint,
|
|
163
|
+
// EntityType.*_MINECART#passengerAttachments and Player#ridingOffset / DEFAULT_VEHICLE_ATTACHMENT.
|
|
164
|
+
const MODERN_PLAYER_RIDING_OFFSET_Y = -0.6
|
|
165
|
+
const MODERN_MINECART_PASSENGER_ATTACHMENT_Y = 0.1875 // EntityType.*_MINECART.passengerAttachments()
|
|
166
|
+
const MODERN_BOAT_RIDE_HEIGHT_FACTOR = 1 / 3 // (Abstract)Boat#rideHeight(), >= 1.20.2
|
|
167
|
+
const MODERN_RAFT_RIDE_HEIGHT_FACTOR = 0.8888889 // Raft#rideHeight(), >= 1.21.2
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Vanilla seat offset for a player passenger in a boat or minecart.
|
|
171
|
+
* Rafts before 1.21.2 are intentionally treated as boats because their entity
|
|
172
|
+
* name is 'boat' and their metadata variant is not read by this project.
|
|
173
|
+
*/
|
|
174
|
+
export function getVehiclePassengerFeetOffsetY(
|
|
175
|
+
layout: 'boat' | 'minecart',
|
|
176
|
+
version: string,
|
|
177
|
+
vehicleName: string | undefined,
|
|
178
|
+
vehicleHeight: number | undefined
|
|
179
|
+
): number {
|
|
180
|
+
const height = Number.isFinite(vehicleHeight) ? vehicleHeight! : layout === 'boat' ? DEFAULT_BOAT_HEIGHT : DEFAULT_MINECART_HEIGHT
|
|
181
|
+
|
|
182
|
+
if (versionToNumber(version) >= versionToNumber('1.20.2')) {
|
|
183
|
+
const isRaft = typeof vehicleName === 'string' && vehicleName.toLowerCase().endsWith('_raft')
|
|
184
|
+
const passengerAttachmentY =
|
|
185
|
+
layout === 'boat' ? height * (isRaft ? MODERN_RAFT_RIDE_HEIGHT_FACTOR : MODERN_BOAT_RIDE_HEIGHT_FACTOR) : MODERN_MINECART_PASSENGER_ATTACHMENT_Y
|
|
186
|
+
return passengerAttachmentY + MODERN_PLAYER_RIDING_OFFSET_Y
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const vehicleRidingOffset = layout === 'boat' ? LEGACY_BOAT_RIDING_OFFSET_Y : LEGACY_MINECART_RIDING_OFFSET_Y
|
|
190
|
+
return vehicleRidingOffset + LEGACY_PLAYER_RIDING_OFFSET_Y
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Vanilla Boat#positionRider position, without applying the passenger pose. */
|
|
194
|
+
export function getBoatPassengerWorldPosition(
|
|
195
|
+
boatWorldPos: Vec3Like,
|
|
196
|
+
boatYaw: number,
|
|
197
|
+
passengerIndex: number,
|
|
198
|
+
passengerCount: number,
|
|
199
|
+
version: string,
|
|
200
|
+
vehicleName: string | undefined,
|
|
201
|
+
vehicleHeight: number | undefined
|
|
202
|
+
): Vec3Like {
|
|
153
203
|
const seatOffset = getBoatPassengerSeatOffset(passengerIndex, passengerCount)
|
|
154
204
|
return {
|
|
155
205
|
x: boatWorldPos.x - Math.sin(boatYaw) * seatOffset,
|
|
156
|
-
y: boatWorldPos.y +
|
|
206
|
+
y: boatWorldPos.y + getVehiclePassengerFeetOffsetY('boat', version, vehicleName, vehicleHeight),
|
|
157
207
|
z: boatWorldPos.z + Math.cos(boatYaw) * seatOffset
|
|
158
208
|
}
|
|
159
209
|
}
|
|
160
210
|
|
|
161
|
-
/** Vanilla
|
|
162
|
-
export function getMinecartPassengerWorldPosition(
|
|
211
|
+
/** Vanilla minecart positionRider for a centered player passenger. */
|
|
212
|
+
export function getMinecartPassengerWorldPosition(
|
|
213
|
+
minecartWorldPos: Vec3Like,
|
|
214
|
+
version: string,
|
|
215
|
+
vehicleName: string | undefined,
|
|
216
|
+
vehicleHeight: number | undefined
|
|
217
|
+
): Vec3Like {
|
|
163
218
|
return {
|
|
164
219
|
x: minecartWorldPos.x,
|
|
165
|
-
y: minecartWorldPos.y +
|
|
220
|
+
y: minecartWorldPos.y + getVehiclePassengerFeetOffsetY('minecart', version, vehicleName, vehicleHeight),
|
|
166
221
|
z: minecartWorldPos.z
|
|
167
222
|
}
|
|
168
223
|
}
|
|
@@ -14,6 +14,10 @@ import {
|
|
|
14
14
|
|
|
15
15
|
type Vec3 = { x: number; y: number; z: number }
|
|
16
16
|
|
|
17
|
+
const TEST_VERSION = '1.17.1'
|
|
18
|
+
const BOAT_HEIGHT = 0.5625
|
|
19
|
+
const MINECART_HEIGHT = 0.7
|
|
20
|
+
|
|
17
21
|
function staleOffsetVehiclePosition(cameraWorldPos: Vec3, vehiclePosition: Vec3, stalePassengerPosition: Vec3): Vec3 {
|
|
18
22
|
return {
|
|
19
23
|
x: cameraWorldPos.x + (vehiclePosition.x - stalePassengerPosition.x),
|
|
@@ -74,23 +78,23 @@ test('render frames between press and release keep zero horizontal delta to came
|
|
|
74
78
|
test('boat passenger positions use vanilla 1.17.1 riding and seat offsets', () => {
|
|
75
79
|
const boat = { x: 10, y: 64, z: 20 }
|
|
76
80
|
|
|
77
|
-
expect(getBoatPassengerWorldPosition(boat, 0, 0, 1)).toEqual({
|
|
81
|
+
expect(getBoatPassengerWorldPosition(boat, 0, 0, 1, TEST_VERSION, 'oak_boat', BOAT_HEIGHT)).toEqual({
|
|
78
82
|
x: 10,
|
|
79
83
|
y: 63.55,
|
|
80
84
|
z: 20
|
|
81
85
|
})
|
|
82
|
-
expect(getBoatPassengerWorldPosition(boat, 0, 0, 2)).toEqual({
|
|
86
|
+
expect(getBoatPassengerWorldPosition(boat, 0, 0, 2, TEST_VERSION, 'oak_boat', BOAT_HEIGHT)).toEqual({
|
|
83
87
|
x: 10,
|
|
84
88
|
y: 63.55,
|
|
85
89
|
z: 20.2
|
|
86
90
|
})
|
|
87
|
-
expect(getBoatPassengerWorldPosition(boat, 0, 1, 2)).toEqual({
|
|
91
|
+
expect(getBoatPassengerWorldPosition(boat, 0, 1, 2, TEST_VERSION, 'oak_boat', BOAT_HEIGHT)).toEqual({
|
|
88
92
|
x: 10,
|
|
89
93
|
y: 63.55,
|
|
90
94
|
z: 19.4
|
|
91
95
|
})
|
|
92
96
|
|
|
93
|
-
const rotated = getBoatPassengerWorldPosition(boat, Math.PI / 2, 0, 2)
|
|
97
|
+
const rotated = getBoatPassengerWorldPosition(boat, Math.PI / 2, 0, 2, TEST_VERSION, 'oak_boat', BOAT_HEIGHT)
|
|
94
98
|
expect(rotated.x).toBeCloseTo(9.8)
|
|
95
99
|
expect(rotated.y).toBe(63.55)
|
|
96
100
|
expect(rotated.z).toBeCloseTo(20)
|
|
@@ -110,7 +114,15 @@ test('remote player follows the tracked boat each frame and releases from an emp
|
|
|
110
114
|
sceneOrigin.track(passenger)
|
|
111
115
|
passenger.position.set(90, 64, 190)
|
|
112
116
|
|
|
113
|
-
const firstPassengerPosition = getBoatPassengerWorldPosition(
|
|
117
|
+
const firstPassengerPosition = getBoatPassengerWorldPosition(
|
|
118
|
+
sceneOrigin.getWorldPosition(boat)!,
|
|
119
|
+
boat.rotation.y,
|
|
120
|
+
0,
|
|
121
|
+
1,
|
|
122
|
+
TEST_VERSION,
|
|
123
|
+
'oak_boat',
|
|
124
|
+
BOAT_HEIGHT
|
|
125
|
+
)
|
|
114
126
|
anchorVehiclePassengerPosition(passenger, firstPassengerPosition, '10')
|
|
115
127
|
|
|
116
128
|
expect(stopPositionTween).toHaveBeenCalledOnce()
|
|
@@ -119,7 +131,15 @@ test('remote player follows the tracked boat each frame and releases from an emp
|
|
|
119
131
|
|
|
120
132
|
boat.position.set(102, 64.2, 203)
|
|
121
133
|
boat.rotation.y = Math.PI / 2
|
|
122
|
-
const movedPassengerPosition = getBoatPassengerWorldPosition(
|
|
134
|
+
const movedPassengerPosition = getBoatPassengerWorldPosition(
|
|
135
|
+
sceneOrigin.getWorldPosition(boat)!,
|
|
136
|
+
boat.rotation.y,
|
|
137
|
+
0,
|
|
138
|
+
1,
|
|
139
|
+
TEST_VERSION,
|
|
140
|
+
'oak_boat',
|
|
141
|
+
BOAT_HEIGHT
|
|
142
|
+
)
|
|
123
143
|
anchorVehiclePassengerPosition(passenger, movedPassengerPosition, '10')
|
|
124
144
|
expect(sceneOrigin.getWorldPosition(passenger)?.x).toBe(102)
|
|
125
145
|
expect(sceneOrigin.getWorldPosition(passenger)?.y).toBeCloseTo(63.75)
|
|
@@ -142,7 +162,7 @@ test('remote player follows the tracked boat each frame and releases from an emp
|
|
|
142
162
|
|
|
143
163
|
test('minecart passenger uses vanilla 1.17.1 riding Y offset', () => {
|
|
144
164
|
const minecart = { x: 10, y: 64, z: 20 }
|
|
145
|
-
expect(getMinecartPassengerWorldPosition(minecart)).toEqual({
|
|
165
|
+
expect(getMinecartPassengerWorldPosition(minecart, TEST_VERSION, 'minecart', MINECART_HEIGHT)).toEqual({
|
|
146
166
|
x: 10,
|
|
147
167
|
y: 63.65,
|
|
148
168
|
z: 20
|
|
@@ -163,13 +183,13 @@ test('minecart passenger anchor follows vehicle world position without inheritin
|
|
|
163
183
|
sceneOrigin.track(passenger)
|
|
164
184
|
passenger.position.set(90, 64, 190)
|
|
165
185
|
|
|
166
|
-
const firstPosition = getMinecartPassengerWorldPosition(sceneOrigin.getWorldPosition(minecart)
|
|
186
|
+
const firstPosition = getMinecartPassengerWorldPosition(sceneOrigin.getWorldPosition(minecart)!, TEST_VERSION, 'minecart', MINECART_HEIGHT)
|
|
167
187
|
anchorVehiclePassengerPosition(passenger, firstPosition, '42')
|
|
168
188
|
const anchoredYaw = passenger.rotation.y
|
|
169
189
|
|
|
170
190
|
minecart.position.set(103, 64.5, 205)
|
|
171
191
|
minecart.rotation.y = Math.PI
|
|
172
|
-
const movedPosition = getMinecartPassengerWorldPosition(sceneOrigin.getWorldPosition(minecart)
|
|
192
|
+
const movedPosition = getMinecartPassengerWorldPosition(sceneOrigin.getWorldPosition(minecart)!, TEST_VERSION, 'minecart', MINECART_HEIGHT)
|
|
173
193
|
anchorVehiclePassengerPosition(passenger, movedPosition, '42')
|
|
174
194
|
|
|
175
195
|
expect(sceneOrigin.getWorldPosition(passenger)).toEqual({
|
|
@@ -234,7 +254,7 @@ test('local minecart passenger anchors to camera-synced vehicle position', () =>
|
|
|
234
254
|
for (const camera of cameraFrames) {
|
|
235
255
|
const syncedMinecart = getLocalVehicleWorldPosition(camera, vehicleY)
|
|
236
256
|
minecart.position.set(syncedMinecart.x, syncedMinecart.y, syncedMinecart.z)
|
|
237
|
-
const seatPosition = getMinecartPassengerWorldPosition(syncedMinecart)
|
|
257
|
+
const seatPosition = getMinecartPassengerWorldPosition(syncedMinecart, TEST_VERSION, 'minecart', MINECART_HEIGHT)
|
|
238
258
|
anchorVehiclePassengerPosition(passenger, seatPosition, '42')
|
|
239
259
|
const passengerWorld = sceneOrigin.getWorldPosition(passenger)!
|
|
240
260
|
|
|
@@ -10,6 +10,8 @@ const REMOTE_PLAYER_ID = 8
|
|
|
10
10
|
const MINECART_ID = '42'
|
|
11
11
|
const EYE_HEIGHT = 1.62
|
|
12
12
|
const PLAYER_HEIGHT = 1.8
|
|
13
|
+
const TEST_VERSION = '1.17.1'
|
|
14
|
+
const MINECART_HEIGHT = 0.7
|
|
13
15
|
|
|
14
16
|
type PassengerLike = THREE.Group & {
|
|
15
17
|
playerObject: Record<string, unknown>
|
|
@@ -42,7 +44,7 @@ function anchorMinecartPassengers(
|
|
|
42
44
|
remotePlayers: Record<number, PassengerLike>
|
|
43
45
|
) {
|
|
44
46
|
const vehicleWorldPos = sceneOrigin.getWorldPosition(minecart)!
|
|
45
|
-
const seatPosition = getMinecartPassengerWorldPosition(vehicleWorldPos)
|
|
47
|
+
const seatPosition = getMinecartPassengerWorldPosition(vehicleWorldPos, TEST_VERSION, 'minecart', MINECART_HEIGHT)
|
|
46
48
|
for (const passengerId of passengerIds) {
|
|
47
49
|
const passenger = resolveMinecartPassenger(passengerId, localPlayer, remotePlayers)
|
|
48
50
|
if (!passenger?.playerObject) continue
|
|
@@ -196,7 +198,7 @@ test('local boat passenger uses the same anchor as remote passenger', () => {
|
|
|
196
198
|
sceneOrigin.track(localPlayer)
|
|
197
199
|
sceneOrigin.track(remotePlayer)
|
|
198
200
|
|
|
199
|
-
const boatSeat = getBoatPassengerWorldPosition(sceneOrigin.getWorldPosition(boat)!, boat.rotation.y, 0, 1)
|
|
201
|
+
const boatSeat = getBoatPassengerWorldPosition(sceneOrigin.getWorldPosition(boat)!, boat.rotation.y, 0, 1, TEST_VERSION, 'oak_boat', 0.5625)
|
|
200
202
|
if (shouldAnchorPassenger(localPlayer, 'boat', true)) {
|
|
201
203
|
anchorVehiclePassengerPosition(localPlayer, boatSeat, '10')
|
|
202
204
|
}
|
|
@@ -74,7 +74,7 @@ test('minecart passenger anchor preserves head rotation', () => {
|
|
|
74
74
|
storeNetworkHeadPitch(userData, 0.45)
|
|
75
75
|
applyNetworkHeadPitch(playerObject, userData)
|
|
76
76
|
|
|
77
|
-
const seat = getMinecartPassengerWorldPosition({ x: 10, y: 64, z: 20 })
|
|
77
|
+
const seat = getMinecartPassengerWorldPosition({ x: 10, y: 64, z: 20 }, '1.17.1', 'minecart', 0.7)
|
|
78
78
|
anchorVehiclePassengerPosition(passenger, seat, '42')
|
|
79
79
|
simulateAnimationFrames(playerObject, userData, 4)
|
|
80
80
|
|
|
@@ -21,6 +21,7 @@ const BOAT_YAW = -0.5645049299419149
|
|
|
21
21
|
const REMOTE_PLAYER_A = 8
|
|
22
22
|
const REMOTE_PLAYER_B = 9
|
|
23
23
|
const BOAT_ID = 10
|
|
24
|
+
const TEST_VERSION = '1.17.1'
|
|
24
25
|
|
|
25
26
|
function makePlayerObject(): PlayerObjectType {
|
|
26
27
|
return new PlayerObject() as PlayerObjectType
|
|
@@ -71,7 +72,8 @@ function createHarness(passengerIds: number[]) {
|
|
|
71
72
|
updateVehiclePassengerPositions({
|
|
72
73
|
entities: entities as Parameters<typeof updateVehiclePassengerPositions>[0]['entities'],
|
|
73
74
|
localPlayer: null,
|
|
74
|
-
getWorldPosition: target => sceneOrigin.getWorldPosition(target as THREE.Object3D)
|
|
75
|
+
getWorldPosition: target => sceneOrigin.getWorldPosition(target as THREE.Object3D),
|
|
76
|
+
version: TEST_VERSION
|
|
75
77
|
})
|
|
76
78
|
|
|
77
79
|
const runRemoteRotation = () =>
|
|
@@ -8,6 +8,7 @@ import { updateVehiclePassengerPositions, type VehiclePassengerSceneEntity, type
|
|
|
8
8
|
|
|
9
9
|
const LOCAL_PLAYER_ID = 7
|
|
10
10
|
const REMOTE_PLAYER_ID = 8
|
|
11
|
+
const TEST_VERSION = '1.17.1'
|
|
11
12
|
|
|
12
13
|
type PassengerLike = THREE.Group & VehiclePassengerSceneEntity
|
|
13
14
|
|
|
@@ -43,7 +44,8 @@ function createHarness() {
|
|
|
43
44
|
updateVehiclePassengerPositions({
|
|
44
45
|
entities,
|
|
45
46
|
localPlayer,
|
|
46
|
-
getWorldPosition: target => sceneOrigin.getWorldPosition(target as THREE.Object3D)
|
|
47
|
+
getWorldPosition: target => sceneOrigin.getWorldPosition(target as THREE.Object3D),
|
|
48
|
+
version: TEST_VERSION
|
|
47
49
|
})
|
|
48
50
|
|
|
49
51
|
return { sceneOrigin, localPlayer, entities, cameraWorldPos, runUpdate }
|
|
@@ -65,7 +67,15 @@ test('updateVehiclePassengerPositions anchors local boat and minecart passengers
|
|
|
65
67
|
|
|
66
68
|
runUpdate()
|
|
67
69
|
|
|
68
|
-
const expectedBoatSeat = getBoatPassengerWorldPosition(
|
|
70
|
+
const expectedBoatSeat = getBoatPassengerWorldPosition(
|
|
71
|
+
sceneOrigin.getWorldPosition(boat)!,
|
|
72
|
+
boat.rotation.y,
|
|
73
|
+
0,
|
|
74
|
+
1,
|
|
75
|
+
TEST_VERSION,
|
|
76
|
+
'oak_boat',
|
|
77
|
+
boat.originalEntity.height
|
|
78
|
+
)
|
|
69
79
|
expect(localPlayer.userData._passengerVehicleId).toBe('10')
|
|
70
80
|
expect(sceneOrigin.getWorldPosition(localPlayer)).toEqual(expectedBoatSeat)
|
|
71
81
|
|
|
@@ -77,7 +87,12 @@ test('updateVehiclePassengerPositions anchors local boat and minecart passengers
|
|
|
77
87
|
|
|
78
88
|
runUpdate()
|
|
79
89
|
|
|
80
|
-
const expectedMinecartSeat = getMinecartPassengerWorldPosition(
|
|
90
|
+
const expectedMinecartSeat = getMinecartPassengerWorldPosition(
|
|
91
|
+
sceneOrigin.getWorldPosition(minecart)!,
|
|
92
|
+
TEST_VERSION,
|
|
93
|
+
'minecart',
|
|
94
|
+
minecart.originalEntity.height
|
|
95
|
+
)
|
|
81
96
|
expect(localPlayer.userData._passengerVehicleId).toBe('42')
|
|
82
97
|
expect(sceneOrigin.getWorldPosition(localPlayer)).toEqual(expectedMinecartSeat)
|
|
83
98
|
})
|
|
@@ -119,8 +134,8 @@ test('local and remote boat passengers use the same seat algorithm per index', (
|
|
|
119
134
|
const vehicleWorldPos = sceneOrigin.getWorldPosition(boat)!
|
|
120
135
|
const localWorld = sceneOrigin.getWorldPosition(localPlayer)!
|
|
121
136
|
const remoteWorld = sceneOrigin.getWorldPosition(remotePlayer)!
|
|
122
|
-
expect(localWorld).toEqual(getBoatPassengerWorldPosition(vehicleWorldPos, boat.rotation.y, 0, 2))
|
|
123
|
-
expect(remoteWorld).toEqual(getBoatPassengerWorldPosition(vehicleWorldPos, boat.rotation.y, 1, 2))
|
|
137
|
+
expect(localWorld).toEqual(getBoatPassengerWorldPosition(vehicleWorldPos, boat.rotation.y, 0, 2, TEST_VERSION, 'oak_boat', boat.originalEntity.height))
|
|
138
|
+
expect(remoteWorld).toEqual(getBoatPassengerWorldPosition(vehicleWorldPos, boat.rotation.y, 1, 2, TEST_VERSION, 'oak_boat', boat.originalEntity.height))
|
|
124
139
|
expect(localWorld.y).toBeCloseTo(63.55, 5)
|
|
125
140
|
expect(remoteWorld.y).toBeCloseTo(63.55, 5)
|
|
126
141
|
})
|
|
@@ -33,6 +33,7 @@ export function updateVehiclePassengerPositions(args: {
|
|
|
33
33
|
entities: Record<string, VehiclePassengerSceneEntity | VehiclePassengerVehicle>
|
|
34
34
|
localPlayer: VehiclePassengerSceneEntity | null
|
|
35
35
|
getWorldPosition: (target: unknown) => Vec3Like | undefined
|
|
36
|
+
version: string
|
|
36
37
|
}): Set<VehiclePassengerSceneEntity> {
|
|
37
38
|
const attachedPassengers = new Set<VehiclePassengerSceneEntity>()
|
|
38
39
|
|
|
@@ -61,10 +62,18 @@ export function updateVehiclePassengerPositions(args: {
|
|
|
61
62
|
|
|
62
63
|
const passengerWorldPos =
|
|
63
64
|
layout === 'minecart'
|
|
64
|
-
? getMinecartPassengerWorldPosition(vehicleWorldPos)
|
|
65
|
+
? getMinecartPassengerWorldPosition(vehicleWorldPos, args.version, vehicleName, vehicle.originalEntity.height)
|
|
65
66
|
: layout === 'horse'
|
|
66
67
|
? getHorsePassengerWorldPosition(vehicleWorldPos, vehicleName, vehicle.originalEntity.height ?? 1.6)
|
|
67
|
-
: getBoatPassengerWorldPosition(
|
|
68
|
+
: getBoatPassengerWorldPosition(
|
|
69
|
+
vehicleWorldPos,
|
|
70
|
+
vehicle.rotation.y,
|
|
71
|
+
passengerIndex,
|
|
72
|
+
passengerIds.length,
|
|
73
|
+
args.version,
|
|
74
|
+
vehicleName,
|
|
75
|
+
vehicle.originalEntity.height
|
|
76
|
+
)
|
|
68
77
|
|
|
69
78
|
if (!isFiniteVec3(passengerWorldPos)) continue
|
|
70
79
|
anchorVehiclePassengerPosition(passenger, passengerWorldPos, vehicleId)
|
|
@@ -7,6 +7,64 @@ export type { BlockLightmapParams }
|
|
|
7
7
|
// Face order: UP=0, DOWN=1, EAST=2, WEST=3, SOUTH=4, NORTH=5
|
|
8
8
|
// matches WASM mesher face order (mesher.rs FACE_NAMES)
|
|
9
9
|
|
|
10
|
+
/** Term of tile-local UV in quad-corner coordinates (u, v ∈ {0,1}). */
|
|
11
|
+
type FaceUvTerm = 'u' | '1-u' | 'v' | '1-v'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Canonical tile-local UV per face for su>0, sv>0. Matches legacy elemFaces
|
|
15
|
+
* (+ implicit +180° on down; see render-from-wasm.ts `r += 180`).
|
|
16
|
+
* Index = faceId: UP, DOWN, EAST, WEST, SOUTH, NORTH.
|
|
17
|
+
* Single source of truth — GLSL and shaderCubeFaceUv() are generated from this.
|
|
18
|
+
*/
|
|
19
|
+
export const FACE_UV_TERMS: ReadonlyArray<readonly [FaceUvTerm, FaceUvTerm]> = [
|
|
20
|
+
['u', '1-v'], // UP
|
|
21
|
+
['1-u', 'v'], // DOWN
|
|
22
|
+
['v', 'u'], // EAST
|
|
23
|
+
['v', 'u'], // WEST
|
|
24
|
+
['u', 'v'], // SOUTH
|
|
25
|
+
['1-u', 'v'] // NORTH
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
const glslTerm = (t: FaceUvTerm) =>
|
|
29
|
+
({
|
|
30
|
+
u: 'u',
|
|
31
|
+
v: 'v',
|
|
32
|
+
'1-u': '1.0 - u',
|
|
33
|
+
'1-v': '1.0 - v'
|
|
34
|
+
})[t]
|
|
35
|
+
|
|
36
|
+
/** GLSL if/else-if chain for per-face UV — must stay in sync with FACE_UV_TERMS. */
|
|
37
|
+
export function buildFaceUvGlsl(): string {
|
|
38
|
+
return FACE_UV_TERMS.map(([ut, vt], i) => ` ${i === 0 ? 'if' : 'else if'} (faceId == ${i}u) uv = vec2(${glslTerm(ut)}, ${glslTerm(vt)});`).join('\n')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function evalFaceUvTerm(t: FaceUvTerm, cu: number, cv: number): number {
|
|
42
|
+
switch (t) {
|
|
43
|
+
case 'u':
|
|
44
|
+
return cu
|
|
45
|
+
case '1-u':
|
|
46
|
+
return 1 - cu
|
|
47
|
+
case 'v':
|
|
48
|
+
return cv
|
|
49
|
+
case '1-v':
|
|
50
|
+
return 1 - cv
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** vi — quad corner in shader order (0..3): u = vi & 1, v = (vi >> 1) & 1. */
|
|
55
|
+
export function shaderCubeFaceUv(faceId: number, vi: number, flipU: boolean, flipV: boolean): [number, number] {
|
|
56
|
+
const [ut, vt] = FACE_UV_TERMS[faceId] ?? FACE_UV_TERMS[0]
|
|
57
|
+
const cu = vi & 1
|
|
58
|
+
const cv = (vi >> 1) & 1
|
|
59
|
+
let uLocal = evalFaceUvTerm(ut, cu, cv)
|
|
60
|
+
let vLocal = evalFaceUvTerm(vt, cu, cv)
|
|
61
|
+
if (flipU) uLocal = 1 - uLocal
|
|
62
|
+
if (flipV) vLocal = 1 - vLocal
|
|
63
|
+
return [uLocal, vLocal]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const faceUvGlsl = buildFaceUvGlsl()
|
|
67
|
+
|
|
10
68
|
const vertexShader = /* glsl */ `
|
|
11
69
|
precision highp float;
|
|
12
70
|
precision highp int;
|
|
@@ -143,18 +201,16 @@ void main() {
|
|
|
143
201
|
v_tintIndex = int(tint);
|
|
144
202
|
v_faceId = int(faceId);
|
|
145
203
|
|
|
146
|
-
// --- Per-face UV
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
v_uv = vec2(1.0 - u, v);
|
|
157
|
-
}
|
|
204
|
+
// --- Per-face UV: canonical tile-local UV (su>0, sv>0), generated from
|
|
205
|
+
// FACE_UV_TERMS — same source as shaderCubeFaceUv() ---
|
|
206
|
+
vec2 uv;
|
|
207
|
+
${faceUvGlsl}
|
|
208
|
+
|
|
209
|
+
// su/sv sign from resolved model: negative scale = mirror on axis.
|
|
210
|
+
// Bit offsets mirror WORD2.FLIP_U_SHIFT / WORD0.FLIP_V_SHIFT (GLSL cannot import TS).
|
|
211
|
+
if (((a_w2 >> 31u) & 0x1u) != 0u) uv.x = 1.0 - uv.x;
|
|
212
|
+
if (((a_w0 >> 31u) & 0x1u) != 0u) uv.y = 1.0 - uv.y;
|
|
213
|
+
v_uv = uv;
|
|
158
214
|
|
|
159
215
|
// --- Position: section base (multiples of 16) + face quad + block-local 0..15 ---
|
|
160
216
|
// Must mirror WORD2/WORD3 constants in TS (GLSL cannot import them).
|
|
@@ -321,6 +377,9 @@ void main() {
|
|
|
321
377
|
}
|
|
322
378
|
`
|
|
323
379
|
|
|
380
|
+
/** Vertex shader source (includes generated face UV table). Exported for parity tests. */
|
|
381
|
+
export const cubeBlockVertexShader = vertexShader
|
|
382
|
+
|
|
324
383
|
export function createCubeBlockMaterial(): THREE.ShaderMaterial {
|
|
325
384
|
return new THREE.ShaderMaterial({
|
|
326
385
|
vertexShader,
|
|
@@ -417,7 +476,8 @@ export const WORD0 = {
|
|
|
417
476
|
FACE_SHIFT: 12,
|
|
418
477
|
TINT_SHIFT: 15,
|
|
419
478
|
AO_SHIFT: 23,
|
|
420
|
-
|
|
479
|
+
/** sv<0 mirror flag — no free bits remain in word0 after this. */
|
|
480
|
+
FLIP_V_SHIFT: 31
|
|
421
481
|
} as const
|
|
422
482
|
|
|
423
483
|
export const WORD1 = {
|
|
@@ -434,7 +494,8 @@ export const WORD2 = {
|
|
|
434
494
|
SECTION_X_HI_SHIFT: 19,
|
|
435
495
|
SECTION_Z_HI_SHIFT: 25,
|
|
436
496
|
SECTION_HI_BITS: 6,
|
|
437
|
-
|
|
497
|
+
/** su<0 mirror flag — no free bits remain in word2 after this. */
|
|
498
|
+
FLIP_U_SHIFT: 31
|
|
438
499
|
} as const
|
|
439
500
|
|
|
440
501
|
/** Section base X/Z: low 16 bits in a_w3, high 6 in a_w2 (22-bit biased section index). */
|
|
@@ -5,6 +5,7 @@ import type { ExportedSection, ExportedWorldGeometry } from '../mesher-shared/ex
|
|
|
5
5
|
import { calculateSkyLightSimple } from '../lib/skyLight'
|
|
6
6
|
import { bakeLegacyVertexColors } from '../lib/bakeLegacyLight'
|
|
7
7
|
import { getShaderCubeResources } from '../wasm-mesher/bridge/shaderCubeBridge'
|
|
8
|
+
import { SHADER_CUBES_FORMAT_VERSION } from '../mesher-shared/shaderCubeFormat'
|
|
8
9
|
import { createCubeBlockMaterial, setCubeSkyLevel } from './shaders/cubeBlockShader'
|
|
9
10
|
import { createShaderCubeMesh } from './shaderCubeMesh'
|
|
10
11
|
|
|
@@ -181,6 +182,8 @@ export function createMeshesFromExport(
|
|
|
181
182
|
): THREE.Group[] {
|
|
182
183
|
const groups: THREE.Group[] = []
|
|
183
184
|
const resolvedShaderMat = shaderMaterial ?? shaderMaterialForExport(material, skyLevel)
|
|
185
|
+
let skippedShaderCubeSections = 0
|
|
186
|
+
let skippedShaderCubeFormatVersion: number | undefined
|
|
184
187
|
|
|
185
188
|
for (const section of exportData.sections) {
|
|
186
189
|
const group = new THREE.Group()
|
|
@@ -213,9 +216,14 @@ export function createMeshesFromExport(
|
|
|
213
216
|
|
|
214
217
|
const shaderCubes = section.shaderCubes
|
|
215
218
|
if (shaderCubes && shaderCubes.count > 0 && resolvedShaderMat) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
+
if (shaderCubes.formatVersion !== SHADER_CUBES_FORMAT_VERSION) {
|
|
220
|
+
skippedShaderCubeSections++
|
|
221
|
+
skippedShaderCubeFormatVersion ??= shaderCubes.formatVersion
|
|
222
|
+
} else {
|
|
223
|
+
const shaderMesh = createShaderCubeMesh(shaderCubes, resolvedShaderMat)
|
|
224
|
+
shaderMesh.position.set(section.position.x, section.position.y, section.position.z)
|
|
225
|
+
group.add(shaderMesh)
|
|
226
|
+
}
|
|
219
227
|
}
|
|
220
228
|
|
|
221
229
|
if (group.children.length > 0) {
|
|
@@ -223,6 +231,13 @@ export function createMeshesFromExport(
|
|
|
223
231
|
}
|
|
224
232
|
}
|
|
225
233
|
|
|
234
|
+
if (skippedShaderCubeSections > 0) {
|
|
235
|
+
console.warn(
|
|
236
|
+
`[worldGeometryExport] Skipped ${skippedShaderCubeSections} shader-cube section(s): ` +
|
|
237
|
+
`export format ${skippedShaderCubeFormatVersion} ≠ current ${SHADER_CUBES_FORMAT_VERSION}`
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
226
241
|
return groups
|
|
227
242
|
}
|
|
228
243
|
|