You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
262 lines
10 KiB
262 lines
10 KiB
import math as _m, random as _r
|
|
|
|
_FLOAT=0; _GATHER=1; _LINES=2; _LFADE=3; _EXPAND=4; _ELLIPSE=5
|
|
_CHAR_W_LATIN=10.0; _CHAR_W_CJK=18.0; _SLOTS_PER_LBL=20
|
|
|
|
def _cjk(ch): return '\u4e00'<=ch<='\u9fff'
|
|
|
|
def _tier():
|
|
x=_r.random()
|
|
return 0 if x<0.3 else (1 if x<0.7 else 2)
|
|
|
|
def _spd(t,s1,s2,s3):
|
|
if t==0: return _r.uniform(s1*.8,s1*1.2)
|
|
if t==1: return _r.uniform(s2*.8,s2*1.2)
|
|
return _r.uniform(s3*.8,s3*1.2)
|
|
|
|
def _dir(): return -1 if _r.random()>0.1 else 1
|
|
|
|
def _cpos(i,W,H):
|
|
a=i/18*2*_m.pi-_m.pi/2
|
|
r=min(W,H)*.28
|
|
return W/2+r*_m.cos(a), H/2+r*_m.sin(a)
|
|
|
|
def _epos(i,W,H):
|
|
a=i/18*2*_m.pi-_m.pi/2
|
|
return W/2+W*.47*_m.cos(a), H/2+H*.43*_m.sin(a)
|
|
|
|
def _ease(t):
|
|
t=max(0.,min(1.,t))
|
|
return 2*t*t if t<.5 else -1+(4-2*t)*t
|
|
|
|
def _init_bg(W,H,s1,s2,s3):
|
|
out=[]
|
|
for i in range(80):
|
|
t=_tier()
|
|
out.append({'x':_r.uniform(0,W),'y':i/80*H,'speed':_spd(t,s1,s2,s3)*_dir()})
|
|
return out
|
|
|
|
def _init_cd(W,H,s1,s2,s3,cw):
|
|
out=[]
|
|
for i,w in enumerate(cw):
|
|
t=_tier()
|
|
out.append({'x':_r.uniform(0,W),'y':_r.uniform(0,H),'speed':_spd(t,s1,s2,s3)*_dir(),'idx':i,'word':w})
|
|
return out
|
|
|
|
def _build_rows(nr,rs,wpr,W,H,aw,s1,s2,s3):
|
|
fs=max(12,min(80,int(W*.85/max(1,wpr*12)/(10/18)*18)))
|
|
cw=fs*.6; out=[]
|
|
for i in range(nr):
|
|
t=_tier(); sp=_spd(t,s1,s2,s3)*_dir()
|
|
sel=_r.choices(aw,k=wpr) if aw else []
|
|
gaps=[_r.uniform(60,140) for _ in range(max(0,wpr-1))]
|
|
wws=[max(40,len(w.get('english',''))*cw) for w in sel]
|
|
tw=sum(wws)+sum(gaps)
|
|
sx=_r.uniform(-tw*.5,W-tw*.3)
|
|
wr=[]; x=sx
|
|
for j,w in enumerate(sel):
|
|
wr.append({'id':w['id'],'text':w['english'],'x':x,'width':wws[j]})
|
|
x+=wws[j]+(gaps[j] if j<len(gaps) else 0)
|
|
out.append({'words':wr,'y':120+i*rs,'speed':sp,'total_w':tw})
|
|
return out
|
|
|
|
def frameEnd(frame):
|
|
try: _run(frame)
|
|
except Exception as e: print('FE err:',e)
|
|
|
|
def _run(frame):
|
|
root=op('/project1/deep_sound')
|
|
W=root.par.Resw.val; H=root.par.Resh.val; dt=1
|
|
s1=root.par.Speedslow.val; s2=root.par.Speedmid.val; s3=root.par.Speedfast.val
|
|
wpr=int(root.par.Wordsperrow.val)
|
|
es=root.par.Expandspd.val; ds=root.par.Drawspd.val; ms=root.par.Marqueespd.val
|
|
gw=root.par.Gatherwait.val; lp=root.par.Linepause.val; fo=root.par.Fadeout.val
|
|
mcs=root.par.Marqueecharspacing.val; mcsz=root.par.Marqueesize.val
|
|
|
|
# Read state from stored dict
|
|
_DEF={'appState':_FLOAT,'textAlpha':1.,'gatherTimer':-1.,'pauseTimer':-1.,
|
|
'dissolving':0,'ellipseAlpha':1.,'currentSeg':0,'labelFadeProgress':0.,
|
|
'expandT':0.,'ellipseDrawT':0.,'marqueeAngle':0.,'marqueeAlpha':0.,
|
|
'lineAlpha':0.,'pid0':0,'pid1':0,'pid2':0,'pid3':0}
|
|
st=root.fetch('state_dict', None)
|
|
if st is None: st=dict(_DEF)
|
|
|
|
ast=int(round(st['appState']))
|
|
ta=float(st['textAlpha']); gt=float(st['gatherTimer']); pt=float(st['pauseTimer'])
|
|
dv=int(round(st['dissolving'])); ea=float(st['ellipseAlpha']); cs=int(round(st['currentSeg']))
|
|
lfp=float(st['labelFadeProgress']); et=float(st['expandT']); edt=float(st['ellipseDrawT'])
|
|
mang=float(st['marqueeAngle']); malp=float(st['marqueeAlpha'])
|
|
pid0=int(round(st['pid0'])); pid1=int(round(st['pid1']))
|
|
pid2=int(round(st['pid2'])); pid3=int(round(st['pid3']))
|
|
pids=[pid0,pid1,pid2,pid3]
|
|
|
|
bg=root.fetch('bg_dots',None); cd=root.fetch('circle_dots',None)
|
|
rows=root.fetch('rows',None); segs=root.fetch('line_segs',[])
|
|
las=root.fetch('label_alphas',[0.,0.,0.,0.])
|
|
|
|
wdat=op('words_table'); aw=[]; cw=[]
|
|
for r in range(1,wdat.numRows):
|
|
w={'id':int(wdat[r,'id'].val),'english':str(wdat[r,'english'].val),
|
|
'chinese':str(wdat[r,'chinese'].val),'circle':int(wdat[r,'circle'].val)}
|
|
aw.append(w)
|
|
if w['circle']==1: cw.append(w)
|
|
|
|
ts_div=[1,2,3][min(2,max(0,root.par.Textsize.menuIndex))]
|
|
fs=max(12,min(80,int(W*.85/max(1,wpr*12)/(10/18)*18)))//ts_div
|
|
nr=max(8,int((H-120)/max(1,fs*1.6))); rs2=(H-120)/max(1,nr-1)
|
|
|
|
pw=root.fetch('prev_wpr',-1); ps=root.fetch('prev_s1',-999.); pts=root.fetch('prev_ts',-1)
|
|
if bg is None or rows is None or cd is None or pw!=wpr or abs(ps-s1)>.001 or pts!=ts_div:
|
|
bg=_init_bg(W,H,s1,s2,s3)
|
|
cd=_init_cd(W,H,s1,s2,s3,cw)
|
|
rows=_build_rows(nr,rs2,wpr,W,H,aw,s1,s2,s3)
|
|
root.store('prev_wpr',wpr); root.store('prev_s1',s1); root.store('prev_ts',ts_div); root.store('font_size',fs)
|
|
|
|
fi_spd=root.par.Fadein.val; fo_spd=root.par.Fadeoutspd.val
|
|
ta=max(0.,ta-fo_spd) if ast!=_FLOAT else min(1.,ta+fi_spd)
|
|
|
|
for row in rows:
|
|
for wd in row['words']: wd['x']+=row['speed']*dt
|
|
if row['words']:
|
|
lft=min(wd['x'] for wd in row['words'])
|
|
rgt=max(wd['x']+wd['width'] for wd in row['words'])
|
|
sh=W+row['total_w']+160
|
|
if row['speed']<0 and rgt<-80:
|
|
for wd in row['words']: wd['x']+=sh
|
|
elif row['speed']>0 and lft>W+80:
|
|
for wd in row['words']: wd['x']-=sh
|
|
|
|
for d in bg:
|
|
d['x']+=d['speed']*dt
|
|
if d['speed']<0 and d['x']<-80: d['x']+=W+160
|
|
elif d['speed']>0 and d['x']>W+80: d['x']-=W+160
|
|
|
|
for i,d in enumerate(cd):
|
|
if ast==_FLOAT:
|
|
d['x']+=d['speed']*dt
|
|
if d['speed']<0 and d['x']<-80: d['x']+=W+160
|
|
elif d['speed']>0 and d['x']>W+80: d['x']-=W+160
|
|
elif ast in (_GATHER,_LINES,_LFADE):
|
|
tx,ty=_cpos(i,W,H)
|
|
d['x']+=(tx-d['x'])*.04*dt; d['y']+=(ty-d['y'])*.04*dt
|
|
elif ast==_EXPAND:
|
|
tx,ty=_cpos(i,W,H); ex,ey=_epos(i,W,H); t=_ease(et)
|
|
d['x']=tx+(ex-tx)*t; d['y']=ty+(ey-ty)*t
|
|
elif ast==_ELLIPSE:
|
|
ex,ey=_epos(i,W,H); d['x']=ex; d['y']=ey
|
|
|
|
if ast==_GATHER:
|
|
gt-=dt/60.
|
|
if gt<=0:
|
|
p=sorted(_r.sample(range(18),4)); pid0,pid1,pid2,pid3=p; pids=p
|
|
segs=[{'from_idx':p[k],'to_idx':p[k+1],'progress':0.,'done':False} for k in range(3)]
|
|
las=[0.,0.,0.,0.]; cs=0; ast=_LINES
|
|
elif ast==_LINES:
|
|
if cs<len(segs):
|
|
segs[cs]['progress']=min(1.,segs[cs]['progress']+.012*dt)
|
|
if segs[cs]['progress']>=1. and not segs[cs]['done']:
|
|
segs[cs]['done']=True; cs+=1
|
|
las[0]=min(1.,las[0]+.02*dt)
|
|
if len(segs)>0 and segs[0]['done']: las[1]=min(1.,las[1]+.02*dt)
|
|
if len(segs)>1 and segs[1]['done']: las[2]=min(1.,las[2]+.02*dt)
|
|
if len(segs)>2 and segs[2]['done']: las[3]=min(1.,las[3]+.02*dt)
|
|
if all(s['done'] for s in segs) and all(a>=1. for a in las) and pt<0:
|
|
pt=lp
|
|
if pt>=0:
|
|
pt-=dt/60.
|
|
if pt<=0: pt=-1; ast=_LFADE; lfp=0.
|
|
elif ast==_LFADE:
|
|
lfp=min(1.,lfp+.015*dt)
|
|
if lfp>=1.: ast=_EXPAND; et=0.
|
|
elif ast==_EXPAND:
|
|
et=min(1.,et+es*dt)
|
|
if et>=1.: ast=_ELLIPSE
|
|
elif ast==_ELLIPSE:
|
|
edt=min(1.,edt+ds*dt)
|
|
mang-=ms*dt
|
|
if edt>=1.: malp=min(1.,malp+.012*dt)
|
|
|
|
if dv:
|
|
ea=max(0.,ea-dt/max(.001,fo*60.))
|
|
if ea<=0.:
|
|
ast=_FLOAT; dv=0; ea=1.; edt=0.; mang=0.; malp=0.; ta=0.
|
|
et=0.; lfp=0.; gt=-1.; pt=-1.; pids=[0,0,0,0]; segs=[]; las=[0.,0.,0.,0.]; cs=0
|
|
pid0=pid1=pid2=pid3=0
|
|
bg=_init_bg(W,H,s1,s2,s3); cd=_init_cd(W,H,s1,s2,s3,cw)
|
|
|
|
prox=[]
|
|
if ast==_FLOAT:
|
|
for i in range(len(bg)):
|
|
for j in range(i+1,len(bg)):
|
|
dx=bg[i]['x']-bg[j]['x']; dy=bg[i]['y']-bg[j]['y']
|
|
dist=_m.sqrt(dx*dx+dy*dy)
|
|
if dist<90: prox.append((dist,i,j))
|
|
prox.sort(); prox=prox[:int(root.par.Maxlinks.val)]
|
|
|
|
# Line alpha for Level TOP (no Python alpha math in render)
|
|
line_alpha=1. if ast==_LINES else (1.-lfp if ast==_LFADE else 0.)
|
|
|
|
# Update line-label Text TOPs
|
|
for i in range(4):
|
|
did=pids[i] if i<len(pids) else 0
|
|
chi=''; eng=''
|
|
if 0<=did<len(cd):
|
|
w=cd[did].get('word',{}); chi=w.get('chinese',''); eng=w.get('english','')
|
|
ch_t=root.op(f'lbl_ch_{i}'); en_t=root.op(f'lbl_en_{i}')
|
|
if ch_t: ch_t.par.text=chi
|
|
if en_t: en_t.par.text=eng
|
|
|
|
# Update chr_sop fontsizex if Marqueesize changed
|
|
pmcsz=root.fetch('prev_mcsz',-1.)
|
|
if abs(pmcsz-mcsz)>0.1:
|
|
for idx in range(80):
|
|
s=root.op(f'marq_geo/chr_sop_{idx:02d}')
|
|
if s: s.par.fontsizex=mcsz
|
|
root.store('prev_mcsz',mcsz)
|
|
|
|
# Per-character marquee placement along ellipse
|
|
rx_m=W*0.47-34; ry_m=H*0.43-24
|
|
for qi in range(4):
|
|
ang=mang+qi*_m.pi/2
|
|
did_q=pids[qi] if qi<len(pids) else 0
|
|
lbl=''
|
|
if 0<=did_q<len(cd):
|
|
w=cd[did_q].get('word',{}); chi=w.get('chinese',''); eng=w.get('english','')
|
|
lbl=f'{chi} {eng}'.strip() if chi else eng
|
|
t=ang; base_slot=qi*_SLOTS_PER_LBL
|
|
cw_lat=mcsz*0.56; cw_cjk=mcsz
|
|
for ci in range(_SLOTS_PER_LBL):
|
|
idx=base_slot+ci
|
|
sop=root.op(f'marq_geo/chr_sop_{idx:02d}')
|
|
tf=root.op(f'marq_geo/chr_tf_{idx:02d}')
|
|
if ci<len(lbl):
|
|
ch=lbl[ci]
|
|
cw=cw_cjk if _cjk(ch) else cw_lat
|
|
x=rx_m*_m.cos(t); y=ry_m*_m.sin(t)
|
|
rot=_m.degrees(_m.atan2(ry_m*_m.cos(t),-rx_m*_m.sin(t)))
|
|
if sop: sop.par.text=ch
|
|
if tf: tf.par.tx=round(x,3); tf.par.ty=round(y,3); tf.par.rz=round(rot,3)
|
|
ds_dt=_m.sqrt((rx_m*_m.sin(t))**2+(ry_m*_m.cos(t))**2)
|
|
t+=(cw+2)*mcs/max(1.,ds_dt)
|
|
else:
|
|
if sop: sop.par.text=''
|
|
if tf: tf.par.tx=0.; tf.par.ty=0.; tf.par.rz=0.
|
|
|
|
# Update custom params for Level TOP opacity drivers
|
|
root.par.Textalpha=round(ta,4)
|
|
root.par.Marqalpha=round(malp*ea,4)
|
|
root.par.Linealpha=round(line_alpha,4)
|
|
|
|
# Write all state back to dict (picked up by state_chop Script CHOP each frame)
|
|
root.store('state_dict',{'appState':ast,'textAlpha':ta,'gatherTimer':gt,'pauseTimer':pt,
|
|
'dissolving':dv,'ellipseAlpha':ea,'currentSeg':cs,'labelFadeProgress':lfp,
|
|
'expandT':et,'ellipseDrawT':edt,'marqueeAngle':mang,'marqueeAlpha':malp,
|
|
'lineAlpha':line_alpha,'marqAlpha':malp*ea,
|
|
'pid0':pid0,'pid1':pid1,'pid2':pid2,'pid3':pid3})
|
|
root.store('bg_dots',bg); root.store('circle_dots',cd); root.store('rows',rows)
|
|
root.store('line_segs',segs); root.store('label_alphas',las)
|
|
root.store('prox_pairs',prox)
|
|
|
|
def onStart(): pass
|
|
def onCreate(): pass
|
|
def onExit(): pass
|
|
|