|
| 1 | +const [readVarInt, writeVarInt, sizeOfVarInt] = require('protodef').types.varint |
| 2 | + |
| 3 | +const DATA_BITS_MASK = 32767 |
| 4 | +const MAX_QUANTIZED_VALUE = 32766.0 |
| 5 | +const ABS_MIN_VALUE = 3.051944088384301e-5 |
| 6 | +const ABS_MAX_VALUE = 1.7179869183e10 |
| 7 | + |
| 8 | +function sanitize (value) { |
| 9 | + if (isNaN(value)) return 0.0 |
| 10 | + return Math.max(-ABS_MAX_VALUE, Math.min(value, ABS_MAX_VALUE)) |
| 11 | +} |
| 12 | + |
| 13 | +function pack (value) { |
| 14 | + return Math.round((value * 0.5 + 0.5) * MAX_QUANTIZED_VALUE) |
| 15 | +} |
| 16 | + |
| 17 | +function unpack (packed, shift) { |
| 18 | + // We use division by power of 2 to simulate a 64-bit right shift |
| 19 | + const val = Math.floor(packed / Math.pow(2, shift)) & DATA_BITS_MASK |
| 20 | + const clamped = val > 32766 ? 32766 : val |
| 21 | + return (clamped * 2.0) / 32766.0 - 1.0 |
| 22 | +} |
| 23 | + |
| 24 | +function readLpVec3 (buffer, offset) { |
| 25 | + const a = buffer[offset] |
| 26 | + if (a === 0) { |
| 27 | + return { value: { x: 0, y: 0, z: 0 }, size: 1 } |
| 28 | + } |
| 29 | + |
| 30 | + const b = buffer[offset + 1] |
| 31 | + const c = buffer.readUInt32LE(offset + 2) |
| 32 | + |
| 33 | + // Combine into 48-bit safe integer (up to 2^53 is safe in JS) |
| 34 | + const packed = (c * 65536) + (b << 8) + a |
| 35 | + |
| 36 | + let scale = a & 3 |
| 37 | + let size = 6 |
| 38 | + |
| 39 | + if ((a & 4) === 4) { |
| 40 | + const { value: varIntVal, size: varIntSize } = readVarInt(buffer, offset + 6) |
| 41 | + scale = (varIntVal * 4) + scale |
| 42 | + size += varIntSize |
| 43 | + } |
| 44 | + |
| 45 | + return { |
| 46 | + value: { |
| 47 | + x: unpack(packed, 3) * scale, |
| 48 | + y: unpack(packed, 18) * scale, |
| 49 | + z: unpack(packed, 33) * scale |
| 50 | + }, |
| 51 | + size |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +function writeLpVec3 (value, buffer, offset) { |
| 56 | + const x = sanitize(value.x) |
| 57 | + const y = sanitize(value.y) |
| 58 | + const z = sanitize(value.z) |
| 59 | + |
| 60 | + const max = Math.max(Math.abs(x), Math.abs(y), Math.abs(z)) |
| 61 | + |
| 62 | + if (max < ABS_MIN_VALUE) { |
| 63 | + buffer[offset] = 0 |
| 64 | + return offset + 1 |
| 65 | + } |
| 66 | + |
| 67 | + const scale = Math.ceil(max) |
| 68 | + const needsContinuation = (scale & 3) !== scale |
| 69 | + const scaleByte = needsContinuation ? ((scale & 3) | 4) : (scale & 3) |
| 70 | + |
| 71 | + const pX = pack(x / scale) |
| 72 | + const pY = pack(y / scale) |
| 73 | + const pZ = pack(z / scale) |
| 74 | + |
| 75 | + // Layout: |
| 76 | + // [Z (15)] [Y (15)] [X (15)] [Flags (3)] |
| 77 | + |
| 78 | + // low32 contains Flags(3), X(15), and the first 14 bits of Y (3+15+14 = 32) |
| 79 | + const low32 = (scaleByte | (pX << 3) | (pY << 18)) >>> 0 |
| 80 | + |
| 81 | + // high16 contains the 15th bit of Y and all 15 bits of Z |
| 82 | + const high16 = ((pY >> 14) & 0x01) | (pZ << 1) |
| 83 | + |
| 84 | + buffer.writeUInt32LE(low32, offset) |
| 85 | + buffer.writeUInt16LE(high16, offset + 4) |
| 86 | + |
| 87 | + if (needsContinuation) { |
| 88 | + return writeVarInt(Math.floor(scale / 4), buffer, offset + 6) |
| 89 | + } |
| 90 | + |
| 91 | + return offset + 6 |
| 92 | +} |
| 93 | + |
| 94 | +function sizeOfLpVec3 (value) { |
| 95 | + const max = Math.max(Math.abs(value.x), Math.abs(value.y), Math.abs(value.z)) |
| 96 | + if (max < ABS_MIN_VALUE) return 1 |
| 97 | + |
| 98 | + const scale = Math.ceil(max) |
| 99 | + if ((scale & 3) !== scale) { |
| 100 | + return 6 + sizeOfVarInt(Math.floor(scale / 4)) |
| 101 | + } |
| 102 | + return 6 |
| 103 | +} |
| 104 | + |
| 105 | +module.exports = [readLpVec3, writeLpVec3, sizeOfLpVec3] |
0 commit comments