| 12345678910111213141516171819202122232425262728293031323334353637383940414243 |
- """Blender-free contract for an estimated shared compact window-frame image."""
- import hashlib
- import json
- import math
- from pathlib import Path
- import struct
- def load_window_texture(manifest_path):
- manifest_path = Path(manifest_path).resolve()
- record = json.loads(manifest_path.read_text(encoding='utf-8'))
- if record.get('kind') != 'estimated-window-frame' or record.get('version') not in (1, 2):
- raise ValueError('unsupported compact window texture manifest')
- path = (manifest_path.parent / record['image']).resolve()
- data = path.read_bytes()
- if len(data) > 8192 or len(data) < 33 or data[:8] != b'\x89PNG\r\n\x1a\n':
- raise ValueError('window texture must be a bounded PNG')
- width, height = struct.unpack('>II', data[16:24])
- size_valid = (width, height) == (64, 64) if record['version'] == 1 else (
- width == height and width in (32, 64) and record.get('dimensions') == [width, height])
- if not size_valid or data[24:26] != bytes((8, 2)):
- raise ValueError('window texture must be bounded opaque RGB8; v1=64px, v2=declared 32/64px')
- if hashlib.sha256(data).hexdigest() != record['sha256']:
- raise ValueError('window texture source hash mismatch')
- mip_bytes = sum(max(1, width >> level) * max(1, height >> level) * 4
- for level in range(width.bit_length()))
- return {**record, 'path': str(path), 'rasterBytes': len(data),
- 'width': width, 'height': height, 'rgba8FullMipEstimate': mip_bytes}
- def window_quad_uv(points):
- """Map a vertical window quad to one tile; never reuse geographic roof UVs."""
- if len(points) != 4:
- raise ValueError('window face is not a quad')
- pairs = [(b[0]-a[0], b[1]-a[1]) for a in points for b in points]
- dx, dy = max(pairs, key=lambda p: math.hypot(*p))
- length = math.hypot(dx, dy)
- low_z, high_z = min(p[2] for p in points), max(p[2] for p in points)
- if length < 1e-6 or high_z-low_z < 1e-6:
- raise ValueError('window quad has no width or height')
- along = [(p[0]-points[0][0])*dx/length + (p[1]-points[0][1])*dy/length for p in points]
- low, high = min(along), max(along)
- return [((u-low)/(high-low), (p[2]-low_z)/(high_z-low_z)) for u,p in zip(along,points)]
|