Pinch cause: SmoothStep taper drove the underside radius to zero at one point. Version 2: depth 0.75x diameter (thin-pad control at 0.30 fails), 3-4 seeded roots, bounded contour warp + roughness, one off-route side island, 3 m rock spans. Bounds/route/W1-W8-W16 pass. Open: legacy probe metadata says 25 cm but production legacy is 12.5 cm -- round 3 must fix the import scale contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
287 lines
13 KiB
Python
287 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
"""Small CPU-only matched views of the legacy and native FloatingIslands fields."""
|
|
from __future__ import annotations
|
|
|
|
import ctypes
|
|
import json
|
|
import math
|
|
import struct
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
RESULTS = ROOT / "results"
|
|
CAPTURES = RESULTS / "captures"
|
|
CAPTURES.mkdir(parents=True, exist_ok=True)
|
|
WIDTH, HEIGHT = 320, 226
|
|
BACKGROUND = (236, 240, 242)
|
|
LEGACY_COLOR = (133, 127, 105)
|
|
NATIVE_COLOR = (78, 135, 151)
|
|
ROUTE_COLOR = (223, 72, 45)
|
|
TILE_COLOR = (178, 60, 52)
|
|
LOD_COLOR = (211, 136, 45)
|
|
Q = 256.0
|
|
|
|
|
|
def lower_priority() -> None:
|
|
if hasattr(ctypes, "windll"):
|
|
ctypes.windll.kernel32.SetPriorityClass(ctypes.windll.kernel32.GetCurrentProcess(), 0x00004000)
|
|
|
|
|
|
def read_mesh(path: Path):
|
|
raw = path.read_bytes()
|
|
if len(raw) < 8 or raw[:4] != b"VFMI":
|
|
raise RuntimeError(f"invalid field mesh: {path}")
|
|
count = struct.unpack_from("<I", raw, 4)[0]
|
|
stride = struct.calcsize("<12f")
|
|
if len(raw) != 8 + count * stride:
|
|
raise RuntimeError(f"truncated field mesh: {path}")
|
|
triangles = [struct.unpack_from("<12f", raw, 8 + i * stride) for i in range(count)]
|
|
vertices = [tri[0:3] for tri in triangles]
|
|
vertices.extend(tri[3:6] for tri in triangles)
|
|
vertices.extend(tri[6:9] for tri in triangles)
|
|
return triangles, vertices
|
|
|
|
|
|
def normalize(vector):
|
|
size = math.sqrt(sum(value * value for value in vector))
|
|
return tuple(value / size for value in vector) if size > 1.0e-12 else (0.0, 0.0, 1.0)
|
|
|
|
|
|
def dot(a, b):
|
|
return sum(x * y for x, y in zip(a, b))
|
|
|
|
|
|
def cross(a, b):
|
|
return (a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0])
|
|
|
|
|
|
def project(points, target, view_dir, scale=None):
|
|
direction = normalize(view_dir)
|
|
forward = tuple(-value for value in direction)
|
|
reference_up = (0.0, 1.0, 0.0) if abs(forward[2]) > 0.98 else (0.0, 0.0, 1.0)
|
|
right = normalize(cross(forward, reference_up))
|
|
up = normalize(cross(right, forward))
|
|
camera_points = []
|
|
for point in points:
|
|
delta = tuple(point[i] - target[i] for i in range(3))
|
|
camera_points.append((dot(delta, right), dot(delta, up), dot(delta, forward)))
|
|
if scale is None:
|
|
span_x = max((p[0] for p in camera_points), default=1.0) - min((p[0] for p in camera_points), default=0.0)
|
|
span_y = max((p[1] for p in camera_points), default=1.0) - min((p[1] for p in camera_points), default=0.0)
|
|
scale = 0.86 * min((WIDTH - 30) / max(span_x, 1.0e-6), (HEIGHT - 30) / max(span_y, 1.0e-6))
|
|
pixels = [
|
|
(WIDTH * 0.5 + point[0] * scale, 31 + (HEIGHT - 31) * 0.5 - point[1] * scale, point[2])
|
|
for point in camera_points
|
|
]
|
|
return pixels, scale, right, up, forward
|
|
|
|
|
|
def common_scale(mesh_pair, target, view_dir):
|
|
vertices = mesh_pair[0][1] + mesh_pair[1][1]
|
|
_, scale, _, _, _ = project(vertices, target, view_dir)
|
|
return scale
|
|
|
|
|
|
def projected_line(a, b, target, basis, scale):
|
|
right, up, forward = basis
|
|
points = []
|
|
for point in (a, b):
|
|
delta = tuple(point[i] - target[i] for i in range(3))
|
|
points.append((WIDTH * 0.5 + dot(delta, right) * scale,
|
|
31 + (HEIGHT - 31) * 0.5 - dot(delta, up) * scale))
|
|
return points
|
|
|
|
|
|
def render(mesh, mesh_pair, method, target, view_dir, title, subtitle, *, route=None,
|
|
guide=None, camera_eye=None, framing_radius=None):
|
|
triangles, vertices = mesh
|
|
perspective = camera_eye is not None
|
|
if perspective:
|
|
forward = normalize(tuple(target[i] - camera_eye[i] for i in range(3)))
|
|
reference_up = (0.0, 1.0, 0.0) if abs(forward[2]) > 0.98 else (0.0, 0.0, 1.0)
|
|
right = normalize(cross(forward, reference_up))
|
|
up = normalize(cross(right, forward))
|
|
scale = WIDTH * 0.91
|
|
origin = camera_eye
|
|
else:
|
|
if framing_radius is None:
|
|
scale = common_scale(mesh_pair, target, view_dir)
|
|
else:
|
|
scale = 0.86 * min((WIDTH - 30) / (2.0 * framing_radius),
|
|
(HEIGHT - 30) / (2.0 * framing_radius))
|
|
_, _, right, up, forward = project((), target, view_dir, scale)
|
|
origin = target
|
|
|
|
def screen(point):
|
|
delta = tuple(point[i] - origin[i] for i in range(3))
|
|
cx, cy, depth = dot(delta, right), dot(delta, up), dot(delta, forward)
|
|
if perspective:
|
|
if depth <= 1.0:
|
|
return None
|
|
return (WIDTH * 0.5 + cx * scale / depth,
|
|
31 + (HEIGHT - 31) * 0.5 - cy * scale / depth, depth)
|
|
return (WIDTH * 0.5 + cx * scale,
|
|
31 + (HEIGHT - 31) * 0.5 - cy * scale, depth)
|
|
|
|
background = Image.new("RGB", (WIDTH, HEIGHT + 32), BACKGROUND)
|
|
draw = ImageDraw.Draw(background)
|
|
draw.rectangle((0, 0, WIDTH - 1, 31), fill=(34, 46, 54))
|
|
draw.text((8, 4), title, fill=(250, 252, 253))
|
|
draw.text((8, 17), subtitle, fill=(190, 207, 215))
|
|
pixels = background.load()
|
|
zbuffer = [float("inf")] * (WIDTH * (HEIGHT - 31))
|
|
camera_fill = forward if perspective else normalize(view_dir)
|
|
light = normalize((-0.25 + 1.2 * camera_fill[0],
|
|
-0.35 + 1.2 * camera_fill[1],
|
|
0.45 + 1.2 * camera_fill[2]))
|
|
base = LEGACY_COLOR if method == "legacy" else NATIVE_COLOR
|
|
for tri in triangles:
|
|
points = [screen(tri[offset:offset + 3]) for offset in (0, 3, 6)]
|
|
if any(point is None for point in points):
|
|
continue
|
|
x0 = max(0, int(math.floor(min(p[0] for p in points))))
|
|
x1 = min(WIDTH - 1, int(math.ceil(max(p[0] for p in points))))
|
|
y0 = max(31, int(math.floor(min(p[1] for p in points))))
|
|
y1 = min(HEIGHT - 1, int(math.ceil(max(p[1] for p in points))))
|
|
if x0 > x1 or y0 > y1:
|
|
continue
|
|
(ax, ay, az), (bx, by, bz), (cx, cy, cz) = points
|
|
denominator = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy)
|
|
if abs(denominator) < 1.0e-10:
|
|
continue
|
|
normal = normalize(tri[9:12])
|
|
shade = 0.50 + 0.50 * max(0.0, dot(normal, light))
|
|
color = tuple(max(0, min(255, int(channel * shade))) for channel in base)
|
|
for y in range(y0, y1 + 1):
|
|
row = (y - 31) * WIDTH
|
|
for x in range(x0, x1 + 1):
|
|
sx, sy = x + 0.5, y + 0.5
|
|
u = ((by - cy) * (sx - cx) + (cx - bx) * (sy - cy)) / denominator
|
|
v = ((cy - ay) * (sx - cx) + (ax - cx) * (sy - cy)) / denominator
|
|
w = 1.0 - u - v
|
|
if u < -1e-8 or v < -1e-8 or w < -1e-8:
|
|
continue
|
|
if perspective:
|
|
depth = 1.0 / (u / az + v / bz + w / cz)
|
|
else:
|
|
depth = u * az + v * bz + w * cz
|
|
index = row + x
|
|
if depth < zbuffer[index]:
|
|
zbuffer[index] = depth
|
|
pixels[x, y] = color
|
|
|
|
basis = (right, up, forward)
|
|
if guide:
|
|
origin = guide["origin_voxels"]
|
|
top = guide["top_voxels"]
|
|
for x_delta, color, label in ((32.0, TILE_COLOR, "32v tile"), (64.0, LOD_COLOR, "64v LOD1")):
|
|
line = projected_line((origin[0] + x_delta, origin[1] - 190.0, top - 65.0),
|
|
(origin[0] + x_delta, origin[1] + 190.0, top + 14.0),
|
|
target, basis, scale)
|
|
draw.line(line, fill=color, width=2)
|
|
draw.text((max(2, min(WIDTH - 55, int(line[1][0]) + 2)),
|
|
max(33, min(HEIGHT - 11, int(line[1][1]) - 12))), label, fill=color)
|
|
if route:
|
|
points = [screen(point) for point in route]
|
|
points = [point[:2] for point in points if point is not None]
|
|
if len(points) >= 2:
|
|
draw.line(points, fill=(250, 248, 239), width=5, joint="curve")
|
|
draw.line(points, fill=ROUTE_COLOR, width=3, joint="curve")
|
|
for index in (0, len(points) - 1):
|
|
x, y = points[index]
|
|
draw.ellipse((x - 4, y - 4, x + 4, y + 4), fill=(250, 248, 239), outline=ROUTE_COLOR, width=2)
|
|
return background
|
|
|
|
|
|
def titled_pair(left, right, category):
|
|
pair = Image.new("RGB", (WIDTH * 2, HEIGHT + 32), (220, 226, 229))
|
|
pair.paste(left, (0, 0))
|
|
pair.paste(right, (WIDTH, 0))
|
|
draw = ImageDraw.Draw(pair)
|
|
draw.rectangle((0, 0, WIDTH * 2 - 1, HEIGHT + 31), outline=(72, 84, 91), width=1)
|
|
draw.text((8, HEIGHT + 14), f"{category} | same target, camera and scale", fill=(45, 55, 61))
|
|
return pair
|
|
|
|
|
|
def main():
|
|
lower_priority()
|
|
data = json.loads((RESULTS / "audit.json").read_text(encoding="utf-8"))
|
|
anchor = data["comparison_anchor_core_voxels"]
|
|
route = data["route_points_core_voxels"]
|
|
profiles = data["island_profiles"]
|
|
middle_island = profiles[1]
|
|
cluster = {
|
|
"legacy": read_mesh(RESULTS / "cluster_legacy.vfmesh"),
|
|
"native": read_mesh(RESULTS / "cluster_native.vfmesh"),
|
|
}
|
|
detail = {
|
|
"legacy": read_mesh(RESULTS / "detail_legacy.vfmesh"),
|
|
"native": read_mesh(RESULTS / "detail_native.vfmesh"),
|
|
}
|
|
a = anchor
|
|
scenes = (
|
|
("overview", "Cluster overview", cluster, (a[0], a[1], a[2] - 28.0), (8.0, -10.0, 7.0), None, None),
|
|
("underside", "Cluster underside and lobed roots", detail,
|
|
(middle_island["center_voxels"][0], middle_island["center_voxels"][1],
|
|
middle_island["bottom_voxels"] - 6.0), (0.12, -0.08, -1.0), None, None),
|
|
("edge_cliff", "Thick crust and bounded rock detail", detail,
|
|
(middle_island["center_voxels"][0], middle_island["center_voxels"][1],
|
|
middle_island["top_voxels"] - middle_island["radius_voxels"] * 0.65),
|
|
(1.0, -0.25, 0.18), None, None),
|
|
("route", "Supported route across the cluster", cluster,
|
|
(middle_island["center_voxels"][0], middle_island["center_voxels"][1],
|
|
middle_island["top_voxels"] - 18.0), (8.0, -10.0, 7.0), route, None),
|
|
)
|
|
pairs = []
|
|
for key, title, meshes, target, camera, path, grid in scenes:
|
|
framing_radius = 28.0 if key in ("underside", "edge_cliff") else None
|
|
left = render(meshes["legacy"], (meshes["legacy"], meshes["native"]), "legacy",
|
|
target, camera, title, "Legacy bridge | exact owner field", route=None,
|
|
guide=grid, framing_radius=framing_radius)
|
|
route_caption = "Core native | planner route" if path else "Core native | matched field view"
|
|
right = render(meshes["native"], (meshes["legacy"], meshes["native"]), "native",
|
|
target, camera, title, route_caption, route=path, guide=grid,
|
|
framing_radius=framing_radius)
|
|
pair = titled_pair(left, right, title)
|
|
out = CAPTURES / f"{key}_matched.jpg"
|
|
pair.save(out, quality=82, optimize=True)
|
|
pairs.append(pair)
|
|
print(f"capture={out.name} bytes={out.stat().st_size} size={pair.size[0]}x{pair.size[1]}")
|
|
|
|
eye_index = 5
|
|
eye_route = route[eye_index]
|
|
eye = (eye_route[0], eye_route[1], eye_route[2] + 3.68)
|
|
next_island = profiles[1]
|
|
look = (next_island["center_voxels"][0], next_island["center_voxels"][1],
|
|
next_island["top_voxels"] + 5.0)
|
|
perspective_pair = []
|
|
for method, meshes in (("legacy", cluster), ("native", cluster)):
|
|
perspective_pair.append(render(meshes[method], (meshes["legacy"], meshes["native"]), method,
|
|
look, (0.0, 0.0, -1.0), "Island-edge player view", "Eye at 1.8 m | route span to next island",
|
|
route=None, camera_eye=eye))
|
|
eye_pair = titled_pair(perspective_pair[0], perspective_pair[1], "Island-edge player view")
|
|
eye_path = CAPTURES / "player_eye_matched.jpg"
|
|
eye_pair.save(eye_path, quality=80, optimize=True)
|
|
pairs.insert(3, eye_pair)
|
|
print(f"capture={eye_path.name} bytes={eye_path.stat().st_size} size={eye_pair.size[0]}x{eye_pair.size[1]}")
|
|
|
|
sheet = Image.new("RGB", (WIDTH * 2, (HEIGHT + 32) * len(pairs)), (218, 225, 228))
|
|
for row, pair in enumerate(pairs):
|
|
sheet.paste(pair, (0, row * (HEIGHT + 32)))
|
|
sheet_path = CAPTURES / "contact_sheet.jpg"
|
|
sheet.save(sheet_path, quality=76, optimize=True)
|
|
oversized = [(path.name, path.stat().st_size) for path in sorted(CAPTURES.glob("*.jpg"))
|
|
if path.stat().st_size >= 300_000]
|
|
for path in sorted(CAPTURES.glob("*.jpg")):
|
|
print(f"capture_size={path.name} bytes={path.stat().st_size}")
|
|
if oversized:
|
|
raise RuntimeError(f"captures exceed the 300 KB limit: {oversized}")
|
|
print(f"contact_sheet={sheet_path.name} bytes={sheet_path.stat().st_size} views=5 matched_pairs=5")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|