The 45 VSTRIP vertices captured off the i860 sort back into an exact 9x5 model-space grid (x,z in even 2-unit steps, y = height at every node; all 45 cells filled). cap7's death-camera views it nearly edge-on, which is why the raw screen projection looks like a folded sliver. gridsurf.py rebuilds the true grid connectivity (2 tris/quad) and shades it from the firmware's own per-vertex normals -> a clean solid surface. render-readout.html now leads with that true-3D reconstruction and shows the grazing projection + wireframe as "how the death-camera saw it". Also resolved a long-standing red herring: the (1,1,10,10) extent bounds that earlier sessions chased as the "empty-bins bug" appear identically in the working frame -- they're the per-frame marker rect, not the geometry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
96 lines
3.9 KiB
Python
96 lines
3.9 KiB
Python
"""The captured object is a complete 9x5 height-field grid (verified: all 45 cells
|
|
filled, x in {0..16 step 2}, z in {0..-8 step 2}, y = height). Reconstruct the FULL
|
|
grid connectivity (2 tris per quad) and render a clean solid surface from a chosen
|
|
3/4 view, Gouraud-lit by the captured normals. Real data; only the camera is ours."""
|
|
import sys, pickle, math, json
|
|
S = r'C:\Users\cyd\AppData\Local\Temp\claude\c--VWE-TeslaRel410\4e848c76-6e89-4034-8047-d8d491cb32d8\scratchpad'
|
|
objs = pickle.load(open(S + r'\vfull.pkl', 'rb'))['objs']
|
|
YAW = float(sys.argv[1]) if len(sys.argv) > 1 else 40.0
|
|
PITCH = float(sys.argv[2]) if len(sys.argv) > 2 else 28.0
|
|
OUT = sys.argv[3] if len(sys.argv) > 3 else 'gridsurf'
|
|
|
|
allv = [v for o in objs for v in o]
|
|
xs = sorted(set(round(v['mx'], 2) for v in allv))
|
|
zs = sorted(set(round(v['mz'], 2) for v in allv))
|
|
grid = {(round(v['mx'], 2), round(v['mz'], 2)): v for v in allv}
|
|
cx = sum(xs)/len(xs); cz = sum(zs)/len(zs)
|
|
cy = sum(v['my'] for v in allv)/len(allv)
|
|
|
|
ry = math.radians(YAW); rp = math.radians(PITCH)
|
|
cyw, syw = math.cos(ry), math.sin(ry); cp, sp = math.cos(rp), math.sin(rp)
|
|
def rot(x, y, z):
|
|
x, z = x*cyw + z*syw, -x*syw + z*cyw
|
|
y, z = y*cp - z*sp, y*sp + z*cp
|
|
return x, y, z
|
|
def _n(a, b, c):
|
|
m = math.sqrt(a*a+b*b+c*c) or 1.0
|
|
return a/m, b/m, c/m
|
|
LIGHT = _n(0.35, 0.55, 0.72)
|
|
|
|
# rotate every grid vertex + normal, cache projected + intensity
|
|
P = {}
|
|
for (x, z), v in grid.items():
|
|
X, Y, Z = rot(v['mx']-cx, (v['my']-cy)*1.8, v['mz']-cz) # exaggerate height 1.8x for relief
|
|
nx, ny, nz = rot(v['nx'], v['ny'], v['nz'])
|
|
n = _n(nx, ny, nz)
|
|
d = n[0]*LIGHT[0] + n[1]*LIGHT[1] + n[2]*LIGHT[2]
|
|
inten = max(0.16, min(1.0, 0.22 + 0.85*abs(d)))
|
|
P[(x, z)] = [X, Y, Z, inten]
|
|
|
|
allp = list(P.values())
|
|
mnx = min(p[0] for p in allp); mxx = max(p[0] for p in allp)
|
|
mny = min(p[1] for p in allp); mxy = max(p[1] for p in allp)
|
|
spanx = (mxx-mnx) or 1; spany = (mxy-mny) or 1
|
|
IW, IH = 620, 560
|
|
PADf = 0.1
|
|
sc = min(IW*(1-2*PADf)/spanx, IH*(1-2*PADf)/spany)
|
|
oxp = (IW - spanx*sc)/2; oyp = (IH - spany*sc)/2
|
|
def SX(p): return int((p[0]-mnx)*sc + oxp)
|
|
def SY(p): return int((mxy-p[1])*sc + oyp) # flip Y (model up -> screen up)
|
|
|
|
fb = [[0.0]*IW for _ in range(IH)]
|
|
zb = [[-1e9]*IW for _ in range(IH)]
|
|
def tri(a, b, c):
|
|
ax, ay, bx, by, cx2, cy2 = SX(a), SY(a), SX(b), SY(b), SX(c), SY(c)
|
|
ia, ib, ic = a[3], b[3], c[3]; za, zbv, zc = a[2], b[2], c[2]
|
|
area = (bx-ax)*(cy2-ay) - (cx2-ax)*(by-ay)
|
|
if abs(area) < 1e-6: return
|
|
x0 = max(0, min(ax, bx, cx2)); x1 = min(IW-1, max(ax, bx, cx2))
|
|
y0 = max(0, min(ay, by, cy2)); y1 = min(IH-1, max(ay, by, cy2))
|
|
for py in range(y0, y1+1):
|
|
for px in range(x0, x1+1):
|
|
w0 = ((bx-px)*(cy2-py) - (cx2-px)*(by-py))/area
|
|
w1 = ((cx2-px)*(ay-py) - (ax-px)*(cy2-py))/area
|
|
w2 = 1 - w0 - w1
|
|
if w0 < -0.004 or w1 < -0.004 or w2 < -0.004: continue
|
|
z = w0*za + w1*zbv + w2*zc
|
|
if z > zb[py][px]:
|
|
zb[py][px] = z; fb[py][px] = w0*ia + w1*ib + w2*ic
|
|
|
|
nt = 0
|
|
for i in range(len(xs)-1):
|
|
for j in range(len(zs)-1):
|
|
a = P[(xs[i], zs[j])]; b = P[(xs[i+1], zs[j])]
|
|
c = P[(xs[i], zs[j+1])]; d = P[(xs[i+1], zs[j+1])]
|
|
tri(a, b, c); tri(b, d, c); nt += 2
|
|
print("yaw=%.0f pitch=%.0f grid %dx%d %d tris" % (YAW, PITCH, len(xs), len(zs), nt))
|
|
|
|
ramp = " .:-=+*#%@"
|
|
for ry2 in range(0, IH, IH//38):
|
|
line = " "
|
|
for rx in range(0, IW, IW//74):
|
|
v = fb[ry2][rx]; line += ramp[min(9, int(v*9.99))] if v > 0 else ' '
|
|
print(line)
|
|
|
|
img = bytearray(IW*IH*3)
|
|
for y in range(IH):
|
|
for x in range(IW):
|
|
v = fb[y][x]
|
|
if v > 0:
|
|
r = int(min(255, 28+v*168)); g = int(min(255, 50+v*205)); b = int(min(255, 54+v*122))
|
|
else:
|
|
r, g, b = 7, 11, 17
|
|
o = (y*IW+x)*3; img[o], img[o+1], img[o+2] = r, g, b
|
|
open(S + '\\' + OUT + '.ppm', 'wb').write(b'P6\n%d %d\n255\n' % (IW, IH) + bytes(img))
|
|
print("wrote %s.ppm (%dx%d)" % (OUT, IW, IH))
|