Combat: AUTHENTIC weapon groups -- streamed per-mech button bindings + the real fire chain (task #5)
The recovered system: fire channels = LBE4ControlsManager buttonGroups (0x40/0x45/0x46/0x47); default groups = the per-mech type-6 controls-map resource in BTL4.RES, installed by the T0 CreateStreamedMappings the port already called -- it needed only the TriggerState attribute (id 0x13 PINNED to the binary value; fireImpulse@0x31C is the binary's TriggerState) and an input feed. Keyboard/harness now push press/release edges into the button groups; the gBT*Trigger bypasses, per-type keyboard split and 1,0 pulse hack are retired -- weapons sharing a button fire TOGETHER (madcat Trigger = 4 weapons). Myomers @4b9550/@4b95b8 misattribution corrected (they are MechWeapon ConfigureMappables/ChooseButton). Verified 2-node: kill through the authentic chain (12 hits vs ~36 pre-groups). Config-mode session (regrouping UI) = the remaining stage, KB-scoped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 276 KiB |
@@ -0,0 +1,9 @@
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
a=np.array(Image.open('scratchpad/cop_fwd_s.png').convert('RGB').resize((360,270)))
|
||||
b=np.array(Image.open('scratchpad/cop_flip_s.png').convert('RGB').resize((360,270)))
|
||||
gap=np.full((270,6,3),255,np.uint8)
|
||||
Image.fromarray(np.concatenate([a,gap,b],axis=1)).save('scratchpad/cop_ab.png')
|
||||
def stat(x): return "mean=%s greenfrac=%.2f"%(x.reshape(-1,3).mean(0).round(0), ((x[:,:,1]>100)&(x[:,:,0]<80)).mean())
|
||||
print("forward:",stat(a)); print("flip: ",stat(b))
|
||||
print("saved cop_ab.png [forward | flip]")
|
||||
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 140 KiB |
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Robust BGF (DIV-BIZ2) parser for the cockpit-shell reconstruction harness.
|
||||
Handles the vertex-format chunks the engine loader (bgfload.cpp) does:
|
||||
0x80 XYZ(12) 0x81 XYZ_N(24) 0x82 XYZ_RGBA(28)
|
||||
0x88 XYZ_UV(20) 0x89 XYZ_N_UV(32) 0x8A XYZ_RGBA_UV(36)
|
||||
Faces: 0x47 index list (u32 tri indices), 0x4d poly list (ppf-prefixed u32).
|
||||
Tree: 0x40 container / 0x41 object / 0x42 patch / 0x46 mesh; 0x2037 SV_SPECIAL
|
||||
(carries "PUNCH"); 0x2030 material name. Face indices are patch-local (base=patch
|
||||
vert start). Returns per-patch geometry so we see frame vs glass vs punch."""
|
||||
import struct
|
||||
|
||||
VSTRIDE = {0x80:12, 0x81:24, 0x82:28, 0x88:20, 0x89:32, 0x8A:36}
|
||||
VHASN = {0x81, 0x89}
|
||||
VHASUV = {0x88, 0x89, 0x8A}
|
||||
CONTAINERS = {0x40,0x41,0x42,0x46,0x48,0x70,0x10,0x20,0x30,0x3}
|
||||
|
||||
def parse(path):
|
||||
d = open(path,"rb").read()
|
||||
patches = [] # each: dict(material, punch, verts[list (x,y,z,has_n,nx,ny,nz,u,v)], faces[list (a,b,c)])
|
||||
cur = {"material":"", "punch":False}
|
||||
def read_chunk(p, end):
|
||||
if p+2 > end: return None
|
||||
tw = struct.unpack_from("<H", d, p)[0]
|
||||
cid = tw & 0x2fff
|
||||
lw = 4 if (tw & 0x8000) else (2 if (tw & 0x4000) else 1)
|
||||
if p+2+lw > end: return None
|
||||
if lw==1: ln = d[p+2]
|
||||
elif lw==2: ln = struct.unpack_from("<H", d, p+2)[0]
|
||||
else: ln = struct.unpack_from("<I", d, p+2)[0]
|
||||
body = p+2+lw
|
||||
return cid, body, ln, body+ln
|
||||
def walk(p, end, patch):
|
||||
while p < end:
|
||||
r = read_chunk(p, end)
|
||||
if not r: break
|
||||
cid, body, ln, nxt = r
|
||||
if cid == 0x42: # new patch
|
||||
patch = {"material":"", "punch":False, "verts":[], "faces":[]}
|
||||
patches.append(patch)
|
||||
walk(body, nxt, patch)
|
||||
elif cid == 0x2030: # material name (string)
|
||||
if patch is not None:
|
||||
patch["material"] = d[body:body+ln].split(b'\0')[0].decode('latin1','ignore')
|
||||
elif cid == 0x2037: # SV_SPECIAL -> PUNCH?
|
||||
if patch is not None and b"PUNCH" in d[body:body+ln]:
|
||||
patch["punch"] = True
|
||||
elif cid in VSTRIDE: # vertex chunk -> this submesh's face indices are
|
||||
st = VSTRIDE[cid]; hasn = cid in VHASN; hasuv = cid in VHASUV
|
||||
patch["_base"] = len(patch["verts"]) # local to THIS chunk (base offset)
|
||||
n = ln // st
|
||||
for i in range(n):
|
||||
o = body + i*st
|
||||
x,y,z = struct.unpack_from("<fff", d, o)
|
||||
nx=ny=nz=0.0; u=v=0.0; off=12
|
||||
if hasn:
|
||||
nx,ny,nz = struct.unpack_from("<fff", d, o+off); off+=12
|
||||
if hasuv:
|
||||
u,v = struct.unpack_from("<ff", d, o+off)
|
||||
patch["verts"].append((x,y,z,hasn,nx,ny,nz,u,v))
|
||||
elif cid == 0x47: # tri index list (indices local to last vertex chunk)
|
||||
b = patch.get("_base",0); n = ln//4
|
||||
idx = [struct.unpack_from("<I", d, body+i*4)[0] for i in range(n)]
|
||||
for f in range(0,(n//3)*3,3):
|
||||
patch["faces"].append((b+idx[f],b+idx[f+1],b+idx[f+2]))
|
||||
elif cid == 0x4d: # poly list (ppf-prefixed)
|
||||
if ln>=1:
|
||||
b = patch.get("_base",0); ppf = d[body]; n=(ln-1)//4
|
||||
idx=[struct.unpack_from("<I",d,body+1+i*4)[0] for i in range(n)]
|
||||
for f in range(0,(n//ppf)*ppf,ppf):
|
||||
poly=[idx[f+j] for j in range(ppf)]
|
||||
for k in range(1,ppf-1):
|
||||
patch["faces"].append((b+poly[0],b+poly[k],b+poly[k+1]))
|
||||
elif cid in CONTAINERS:
|
||||
walk(body, nxt, patch)
|
||||
p = nxt
|
||||
walk(8, len(d), None)
|
||||
return [pt for pt in patches if pt["verts"] and pt["faces"]]
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys, numpy as np
|
||||
from collections import Counter
|
||||
path = sys.argv[1] if len(sys.argv)>1 else "content/VIDEO/GEO/MAX_COP.BGF"
|
||||
P = parse(path)
|
||||
print(f"{path}: {len(P)} patches")
|
||||
allV=[]; ec=Counter(); tot_faces=0; punch_faces=0
|
||||
for i,pt in enumerate(P):
|
||||
V=np.array([(v[0],v[1],v[2]) for v in pt["verts"]])
|
||||
hasuv = any(v[7]!=0 or v[8]!=0 for v in pt["verts"])
|
||||
hasn = any(v[3] for v in pt["verts"])
|
||||
F=pt["faces"]
|
||||
lo=V.min(0); hi=V.max(0); cen=V.mean(0)
|
||||
for a,b,c in F:
|
||||
if max(a,b,c)>=len(V): continue
|
||||
for e in ((a,b),(b,c),(c,a)): ec[tuple(sorted(e))]+=1
|
||||
tot_faces+=len(F)
|
||||
if pt["punch"]: punch_faces+=len(F)
|
||||
print(f" patch{i}: mtl='{pt['material'][:30]}' punch={pt['punch']} "
|
||||
f"verts={len(V)} faces={len(F)} uv={hasuv} n={hasn} "
|
||||
f"cen=({cen[0]:.2f},{cen[1]:.2f},{cen[2]:.2f}) "
|
||||
f"bbox=[{lo[0]:.2f},{lo[1]:.2f},{lo[2]:.2f}]..[{hi[0]:.2f},{hi[1]:.2f},{hi[2]:.2f}]")
|
||||
bound=sum(1 for k,vv in ec.items() if vv==1)
|
||||
print(f"TOTAL faces={tot_faces} punch_faces={punch_faces} boundary_edges={bound}/{len(ec)}")
|
||||
@@ -0,0 +1,51 @@
|
||||
import struct,sys
|
||||
|
||||
def rdU16(b,p): return struct.unpack_from('<H',b,p)[0]
|
||||
def rdU32(b,p): return struct.unpack_from('<I',b,p)[0]
|
||||
def rdF32(b,p): return struct.unpack_from('<f',b,p)[0]
|
||||
|
||||
TAGNAME={0x0003:'HEADER',0x0005:'BIZ_DONE',0x0040:'OBJECT',0x0041:'LOD',0x0042:'PATCH',
|
||||
0x0046:'PMESH',0x0047:'CONN_LIST',0x0048:'SPHERE_LIST',0x004d:'PCONN_LIST',0x0070:'BOUND',
|
||||
0x0080:'VTX_XYZ',0x0081:'VTX_XYZ_N',0x0082:'VTX_XYZ_RGBA',0x0088:'VTX_XYZ_UV',
|
||||
0x0089:'VTX_XYZ_N_UV',0x008A:'VTX_XYZ_RGBA_UV',0x2030:'SV_F_MATERIAL',0x2037:'SV_SPECIAL',
|
||||
0x0010:'TEXTURE',0x0011:'TEXTURE_MAP',0x0018:'BITSLICE',0x0020:'MATERIAL',
|
||||
0x0021:'MAT_TEXTURE',0x0023:'AMBIENT',0x0024:'DIFFUSE',0x0026:'EMISSIVE',
|
||||
0x0028:'RAMP_REF',0x0030:'RAMP',0x0031:'RAMP_DATA',0x2008:'NAME',
|
||||
0x0043:'FACE',0x0044:'FACE?',0x0045:'FACE?'}
|
||||
CONTAINERS={0x0003,0x0070,0x0040,0x0041,0x0042,0x0046,0x0048,0x0010,0x0020,0x0030}
|
||||
|
||||
def parse(b,p,end,depth,out):
|
||||
while p+3<=end:
|
||||
tw=rdU16(b,p); p+=2
|
||||
idv=tw & 0x2fff
|
||||
lw=4 if (tw&0x8000) else (2 if (tw&0x4000) else 1)
|
||||
if p+lw>end: break
|
||||
ln = b[p] if lw==1 else (rdU16(b,p) if lw==2 else rdU32(b,p))
|
||||
p+=lw
|
||||
if p+ln>end: break
|
||||
out.append((depth,idv,p,ln))
|
||||
if idv in CONTAINERS:
|
||||
parse(b,p,p+ln,depth+1,out)
|
||||
p+=ln
|
||||
if idv==0x0005: break
|
||||
return
|
||||
|
||||
fn=sys.argv[1]
|
||||
b=open(fn,'rb').read()
|
||||
assert b[:8]==b'DIV-BIZ2', b[:8]
|
||||
out=[]
|
||||
parse(b,8,len(b),0,out)
|
||||
for depth,idv,p,ln in out:
|
||||
nm=TAGNAME.get(idv,'?%04x'%idv)
|
||||
extra=''
|
||||
if idv==0x2008 or idv==0x0011 or idv==0x0028:
|
||||
s=b[p:p+ln].split(b'\0')[0].decode('latin1',errors='replace')
|
||||
extra=' "%s"'%s
|
||||
if idv==0x2037: # SV_SPECIAL
|
||||
s=b[p:p+ln].split(b'\0')[0].decode('latin1',errors='replace')
|
||||
extra=' SPECIAL="%s"'%s
|
||||
if idv==0x2030: # SV_F_MATERIAL
|
||||
s=b[p:p+ln].split(b'\0')[0].decode('latin1',errors='replace')
|
||||
extra=' MAT="%s" (len %d)'%(s,ln)
|
||||
if idv==0x0018 and ln>=1: extra=' slice=%d'%b[p]
|
||||
print(' '*depth + '%-14s off=0x%04x len=%d%s'%(nm,p,ln,extra))
|
||||
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 331 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 231 KiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 292 KiB |
|
After Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 432 KiB |
|
After Width: | Height: | Size: 358 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 252 KiB |
|
After Width: | Height: | Size: 365 KiB |
|
After Width: | Height: | Size: 368 KiB |
|
After Width: | Height: | Size: 368 KiB |
|
After Width: | Height: | Size: 459 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 432 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 117 KiB |
@@ -0,0 +1,53 @@
|
||||
import struct, glob, os, numpy as np
|
||||
from collections import Counter
|
||||
def load(path):
|
||||
data=open(path,"rb").read()
|
||||
verts=[];faces=[];cb=[0]
|
||||
def addpoly(idx,b):
|
||||
for k in range(1,len(idx)-1): faces.append((b+idx[0],b+idx[k],b+idx[k+1]))
|
||||
def parse(p,end):
|
||||
while p+3<=end:
|
||||
tw=struct.unpack_from("<H",data,p)[0];p+=2
|
||||
cid=tw&0x2fff;lw=4 if(tw&0x8000)else(2 if(tw&0x4000)else 1)
|
||||
if p+lw>end:return
|
||||
ln=data[p] if lw==1 else(struct.unpack_from("<H",data,p)[0] if lw==2 else struct.unpack_from("<I",data,p)[0])
|
||||
p+=lw
|
||||
if p+ln>end:return
|
||||
if cid==0x88:
|
||||
cb[0]=len(verts)
|
||||
for i in range(ln//20):
|
||||
o=p+i*20;x,y,z=struct.unpack_from("<fff",data,o);verts.append((x,y,z))
|
||||
elif cid==0x47:
|
||||
n=ln//4;idx=[struct.unpack_from("<I",data,p+i*4)[0] for i in range(n)]
|
||||
for f in range(0,(n//3)*3,3): faces.append((cb[0]+idx[f],cb[0]+idx[f+1],cb[0]+idx[f+2]))
|
||||
elif cid==0x4d:
|
||||
if ln>=1:
|
||||
ppf=data[p];n=(ln-1)//4
|
||||
idx=[struct.unpack_from("<I",data,p+1+i*4)[0] for i in range(n)]
|
||||
for f in range(0,(n//ppf)*ppf,ppf): addpoly([idx[f+j] for j in range(ppf)],cb[0])
|
||||
if cid in {0x3,0x70,0x40,0x41,0x42,0x46,0x48,0x10,0x20,0x30}: parse(p,p+ln)
|
||||
p+=ln
|
||||
if cid==0x5:break
|
||||
parse(8,len(data))
|
||||
return np.array(verts),faces
|
||||
for path in sorted(glob.glob("content/VIDEO/GEO/*_COP.BGF")):
|
||||
V,faces=load(path)
|
||||
if len(V)==0: print(os.path.basename(path),'-- no verts parsed'); continue
|
||||
nv=len(V); faces=[(a,b,c) for (a,b,c) in faces if a<nv and b<nv and c<nv]
|
||||
# boundary edges
|
||||
ec=Counter()
|
||||
for a,b,c in faces:
|
||||
for edg in ((a,b),(b,c),(c,a)): ec[tuple(sorted(edg))]+=1
|
||||
bound=sum(1 for v in ec.values() if v==1)
|
||||
# normal dominant-axis area (weighted by face area)
|
||||
axarea={'+x':0.,'-x':0.,'+y':0.,'-y':0.,'+z':0.,'-z':0.}
|
||||
for a,b,c in faces:
|
||||
n=np.cross(V[b]-V[a],V[c]-V[a]); area=np.linalg.norm(n)
|
||||
if area<1e-9: continue
|
||||
nn=n/area; ax=int(np.argmax(np.abs(nn))); s='+' if nn[ax]>0 else '-'
|
||||
axarea[s+'xyz'[ax]]+=area
|
||||
tot=sum(axarea.values()) or 1
|
||||
dom=max(axarea,key=axarea.get)
|
||||
name=os.path.basename(path)
|
||||
frac={k:round(v/tot,2) for k,v in axarea.items()}
|
||||
print(f"{name:14s} verts={len(V):4d} faces={len(faces):4d} boundary={bound:4d} | area-by-axis {frac} | DOMINANT={dom}")
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,60 @@
|
||||
import struct, math, numpy as np
|
||||
from PIL import Image
|
||||
data=open("content/VIDEO/GEO/BLX_COP.BGF","rb").read()
|
||||
verts=[]; faces=[]
|
||||
def addpoly(idx,b):
|
||||
for k in range(1,len(idx)-1): faces.append((b+idx[0],b+idx[k],b+idx[k+1]))
|
||||
curbase=[0]
|
||||
def parse(p,end):
|
||||
while p+3<=end:
|
||||
tw=struct.unpack_from("<H",data,p)[0]; p+=2
|
||||
cid=tw&0x2fff; lw=4 if(tw&0x8000)else(2 if(tw&0x4000)else 1)
|
||||
if p+lw>end:return
|
||||
ln=data[p] if lw==1 else(struct.unpack_from("<H",data,p)[0] if lw==2 else struct.unpack_from("<I",data,p)[0])
|
||||
p+=lw
|
||||
if p+ln>end:return
|
||||
if cid==0x88:
|
||||
curbase[0]=len(verts)
|
||||
for i in range(ln//20):
|
||||
o=p+i*20; x,y,z=struct.unpack_from("<fff",data,o); verts.append((x,y,z))
|
||||
elif cid==0x47:
|
||||
n=ln//4; idx=[struct.unpack_from("<I",data,p+i*4)[0] for i in range(n)]
|
||||
for f in range(0,(n//3)*3,3): faces.append((curbase[0]+idx[f],curbase[0]+idx[f+1],curbase[0]+idx[f+2]))
|
||||
elif cid==0x4d:
|
||||
if ln>=1:
|
||||
ppf=data[p]; n=(ln-1)//4
|
||||
idx=[struct.unpack_from("<I",data,p+1+i*4)[0] for i in range(n)]
|
||||
for f in range(0,(n//ppf)*ppf,ppf): addpoly([idx[f+j] for j in range(ppf)],curbase[0])
|
||||
if cid in {0x3,0x70,0x40,0x41,0x42,0x46,0x48,0x10,0x20,0x30}: parse(p,p+ln)
|
||||
p+=ln
|
||||
if cid==0x5:break
|
||||
parse(8,len(data))
|
||||
V=np.array(verts,dtype=float)
|
||||
|
||||
def cover(eye,look,fov=95,W=360,H=280):
|
||||
eye=np.array(eye,float); fwd=np.array(look,float)-eye; fwd/=np.linalg.norm(fwd)
|
||||
up=np.array([0,1,0.0]); right=np.cross(fwd,up); right/=np.linalg.norm(right); upv=np.cross(right,fwd)
|
||||
f=(H/2)/math.tan(math.radians(fov)/2); img=np.zeros((H,W))
|
||||
for (a,b,c) in faces:
|
||||
tri=V[[a,b,c]]
|
||||
cam=[(np.dot(pt-eye,right),np.dot(pt-eye,upv),np.dot(pt-eye,fwd)) for pt in tri]
|
||||
if any(cz<=0.03 for _,_,cz in cam):continue
|
||||
pts=[(W/2+f*cx/cz,H/2-f*cy/cz) for cx,cy,cz in cam]
|
||||
xs=[p[0] for p in pts];ys=[p[1] for p in pts]
|
||||
mnx=max(int(min(xs)),0);mxx=min(int(max(xs))+1,W-1);mny=max(int(min(ys)),0);mxy=min(int(max(ys))+1,H-1)
|
||||
if mnx>=mxx or mny>=mxy:continue
|
||||
(x0,y0),(x1,y1),(x2,y2)=pts
|
||||
den=((y1-y2)*(x0-x2)+(x2-x1)*(y0-y2))
|
||||
if abs(den)<1e-9:continue
|
||||
ys2,xs2=np.mgrid[mny:mxy+1,mnx:mxx+1];xf=xs2+.5;yf=ys2+.5
|
||||
l0=((y1-y2)*(xf-x2)+(x2-x1)*(yf-y2))/den;l1=((y2-y0)*(xf-x2)+(x0-x2)*(yf-y2))/den;l2=1-l0-l1
|
||||
m=(l0>=0)&(l1>=0)&(l2>=0)
|
||||
img[ys2[m],xs2[m]]=1
|
||||
return (img*255).astype(np.uint8)
|
||||
# eye near origin (rear), try forward -Z and +Z, and from just inside each end
|
||||
tiles=[]
|
||||
for name,eye,look in (("O->-Z",(0,-0.3,0),(0,-0.3,-4)),("O->+Z",(0,-0.3,0),(0,-0.3,4)),
|
||||
("rear-Z",(0,-0.3,-0.9),(0,-0.3,-4)),("rear+Z",(0,-0.3,-3.7),(0,-0.3,-1))):
|
||||
c=cover(eye,look); tiles.append(np.stack([c,c,c],axis=-1))
|
||||
Image.fromarray(np.concatenate(tiles,axis=1)).save("scratchpad/cop_cover.png")
|
||||
print("saved cop_cover.png [O->-Z | O->+Z | rear-Z | rear+Z] (white=covered by a face, black=open)")
|
||||