From 59ec99809affc4ad2d3977e642f0cbf9f8967075 Mon Sep 17 00:00:00 2001 From: Salad Dais Date: Sat, 16 Jul 2022 23:17:34 +0000 Subject: [PATCH] Correct TE rotation quantization Literally everything has its own special float quantization. Argh. --- hippolyzer/lib/base/templates.py | 18 +++++++++++- tests/proxy/test_templates.py | 49 ++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/hippolyzer/lib/base/templates.py b/hippolyzer/lib/base/templates.py index a7ff18a..f4d2da9 100644 --- a/hippolyzer/lib/base/templates.py +++ b/hippolyzer/lib/base/templates.py @@ -7,6 +7,7 @@ import dataclasses import enum import importlib import logging +import math import zlib from typing import * @@ -1045,6 +1046,21 @@ _TE_FIELD_KEY = Optional[Sequence[int]] TE_S16_COORD = se.QuantizedFloat(se.S16, -1.000030518509476, 1.0, False) +class PackedTERotation(se.QuantizedFloat): + """Another weird one, packed TE rotations have their own special quantization""" + + def __init__(self): + super().__init__(se.S16, math.pi * -2, math.pi * 2, zero_median=False) + self.step_mag = 1.0 / (se.U16.max_val + 1) + + def _float_to_quantized(self, val: float, lower: float, upper: float): + val = math.fmod(val, upper) + val = super()._float_to_quantized(val, lower, upper) + if val == se.S16.max_val + 1: + val = self.prim_min + return val + + @dataclasses.dataclass class TextureEntry: Textures: Dict[_TE_FIELD_KEY, UUID] = _te_field( @@ -1056,7 +1072,7 @@ class TextureEntry: ScalesT: Dict[_TE_FIELD_KEY, float] = _te_field(se.F32, default=1.0) OffsetsS: Dict[_TE_FIELD_KEY, float] = _te_field(TE_S16_COORD, default=0.0) OffsetsT: Dict[_TE_FIELD_KEY, float] = _te_field(TE_S16_COORD, default=0.0) - Rotation: Dict[_TE_FIELD_KEY, float] = _te_field(TE_S16_COORD, default=0.0) + Rotation: Dict[_TE_FIELD_KEY, float] = _te_field(PackedTERotation(), default=0.0) BasicMaterials: Dict[_TE_FIELD_KEY, "BasicMaterials"] = _te_field( BUMP_SHINY_FULLBRIGHT, default_factory=lambda: BasicMaterials(Bump=0, FullBright=False, Shiny=0), ) diff --git a/tests/proxy/test_templates.py b/tests/proxy/test_templates.py index 97785bc..6a7afba 100644 --- a/tests/proxy/test_templates.py +++ b/tests/proxy/test_templates.py @@ -1,9 +1,10 @@ +import math import unittest import hippolyzer.lib.base.serialization as se from hippolyzer.lib.base.datatypes import UUID from hippolyzer.lib.base.message.message_formatting import HumanMessageSerializer -from hippolyzer.lib.base.templates import TextureEntrySubfieldSerializer, TEFaceBitfield, TextureEntry +from hippolyzer.lib.base.templates import TextureEntrySubfieldSerializer, TEFaceBitfield, TextureEntry, PackedTERotation EXAMPLE_TE = b'\x89UgG$\xcbC\xed\x92\x0bG\xca\xed\x15F_\x08\xca*\x98:\x18\x02,\r\xf4\x1e\xc6\xf5\x91\x01]\x83\x014' \ b'\x00\x90i+\x10\x80\xa1\xaa\xa2g\x11o\xa8]\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x80?' \ @@ -68,8 +69,52 @@ class TemplateTests(unittest.TestCase): # Serialization order and format should match indra's exactly self.assertEqual(EXAMPLE_TE, data_field) deser = spec.deserialize(None, data_field, pod=True) - self.assertEqual(deser, pod_te) + self.assertEqual(pod_te, deser) def test_textureentry_defaults(self): te = TextureEntry() self.assertEqual(UUID('89556747-24cb-43ed-920b-47caed15465f'), te.Textures[None]) + + def test_textureentry_rotation_packing(self): + writer = se.BufferWriter("!") + writer.write(PackedTERotation(), math.pi * 2) + # fmod() makes this loop back around to 0 + self.assertEqual(b"\x00\x00", writer.copy_buffer()) + writer.clear() + + writer.write(PackedTERotation(), -math.pi * 2) + # fmod() makes this loop back around to 0 + self.assertEqual(b"\x00\x00", writer.copy_buffer()) + writer.clear() + + writer.write(PackedTERotation(), 0) + self.assertEqual(b"\x00\x00", writer.copy_buffer()) + writer.clear() + + # These both map to -32768 because of overflow in the positive case + # that isn't caught by exact equality to math.pi * 2 + writer.write(PackedTERotation(), math.pi * 1.999999) + self.assertEqual(b"\x80\x00", writer.copy_buffer()) + writer.clear() + + writer.write(PackedTERotation(), math.pi * -1.999999) + self.assertEqual(b"\x80\x00", writer.copy_buffer()) + writer.clear() + + def test_textureentry_rotation_unpacking(self): + reader = se.BufferReader("!", b"\x00\x00") + self.assertEqual(0, reader.read(PackedTERotation())) + + reader = se.BufferReader("!", b"\x80\x00") + self.assertEqual(-math.pi * 2, reader.read(PackedTERotation())) + + # This quantization method does not allow for any representation of + # F_TWO_PI itself, just a value slightly below it! The float representation + # is ever so slightly different from the C++ version, but it should still + # round-trip correctly. + reader = se.BufferReader("!", b"\x7f\xff") + self.assertEqual(6.282993559581101, reader.read(PackedTERotation())) + + writer = se.BufferWriter("!") + writer.write(PackedTERotation(), 6.282993559581101) + self.assertEqual(b"\x7f\xff", writer.copy_buffer())