# Implementation of {raster}. # Last edited on 2021-10-02 02:29:52 by stolfi import raster import path import path_hp import contact import move import move_parms import rootray import rootray_shape import rootray_cart import hacks import rn import rmxn import sys from math import sqrt, sin, cos, atan2, log, exp, floor, ceil, inf, nan, pi def get_spacing_and_phase(OPHS, xdir, ydir): nph = len(OPHS) assert nph > 0, "no rasters given" # Guess the scanline spacing {ystep}: totwd = 0 for oph in OPHS: assert path.nelems(oph) == 1 mv = path.elem(oph, 0) assert not move.is_jump(mv) totwd += move.width(mv) ystep = totwd/nph assert ystep > 1.0e-6 # Find the lowest raster in {ydir} direction and its projection {ymin}: ymin = +inf for oph in OPHS: xm, ym = path.mean_projections(oph, xdir, ydir) ymin = min(ymin, ym) if nph == 1: # We can use {ymin} as the phase: yphase = ymin else: # Take {ymin} as the guesed phase and refine it so that the mean dev is zero: sum_yr = 0 isc_min = +inf for oph in OPHS: xm, ym = path.mean_projections(oph, xdir, ydir) yd = ym - ymin isc = floor(yd/ystep + 0.5) yr = yd - ystep*isc assert abs(yr) < 0.4*ystep, "raster deviation from scanline too large" isc_min = min (isc, isc_min) sum_yr += yr yphase = ymin + sum_yr/nph + isc_min*ystep # Check for quantized spacing: for oph in OPHS: xm, ym = path.mean_projections(oph, xdir, ydir) yd = ym - yphase yr = yd - ystep*floor(yd/ystep + 0.5) # Deviation from ideal position. assert abs(yr) < 0.2*ystep return ystep, yphase # ---------------------------------------------------------------------- def scanline_index(oph, xdir, ydir, ystep, yphase): xm, ym = path.mean_projections(oph, xdir, ydir) yd = ym - yphase isc = int(floor(yd/ystep + 0.5)) return isc # ---------------------------------------------------------------------- def sort_by_scanline(OPHS, xdir, ydir, ystep, yphase): nph = len(OPHS) if nph <= 1: return OPHS.copy() def scanpos(oph): # Returns a pair {(isc, xm)} where {isc} is the index # of the scanline nearest to the raster fill element {oph}, # and {xm} is its mean coordinate along the direction {xdir}. xm, ym = path.mean_projections(oph, xdir, ydir) isc = int(floor((ym - yphase)/ystep + 0.5)) return (isc, xm) OPHS_srt = sorted(OPHS, key = scanpos) return OPHS_srt # ---------------------------------------------------------------------- def separate_by_scanline(OPHS, xdir, ydir, ystep, yphase): SCS = [] # List of lists of indices of fillers in each scan-line. isc_prev = None # Scanline index of {oph_prev}. for iph in range(len(OPHS)): oph_this = OPHS[iph] # Get scanline index: isc_this = scanline_index(oph_this, xdir, ydir, ystep, yphase) assert isc_this >= 0, "negative scanline index -- check {yphase}" if isc_this == isc_prev : # Looks like they are on the same scanline: assert len(SCS) > isc_this assert len(SCS[isc_this]) > 0 elif isc_prev == None or isc_this > isc_prev: # Looks like we got to a new scanline. Extend {SCS}: while len(SCS) <= isc_this: SCS.append([].copy()) else: assert False, "rasters are not in scanline order" SCS[isc_this].append(iph) isc_prev = isc_this return SCS # ---------------------------------------------------------------------- def make_raster_raster_link(oph0, oph1, mp_link, name): omv0 = path.elem(oph0,0) omv1 = path.elem(oph1,0) mlk = move.connector(move.rev(omv0), omv1, False, False, None) if mlk != None and not move.is_jump(mlk): move.set_name(mlk, "M" + name) plk = path.from_moves((mlk,)) path.set_name(plk, "P" + name, False) path.clear_links(plk) path.add_link(oph1, plk) path.add_link(oph0, path.rev(plk)) else: plk = None return plk # ...................................................................... def create_all_raster_raster_links(OPHS, xdir, ydir, mp_link): debug = False LKS = [] if len(OPHS) > 0: # Paranoia: mp_fill = move.parameters(path.elem(OPHS[0],0)) for oph in OPHS: assert path.nelems(oph) == 1 omv = path.elem(oph, 0) assert not move.is_jump(omv) assert move.parameters(omv) == mp_fill wdf = move_parms.width(mp_fill) ystep, yphase = get_spacing_and_phase(OPHS, xdir, ydir) OPHS = sort_by_scanline(OPHS, xdir, ydir, ystep, yphase) SCS = separate_by_scanline(OPHS, xdir, ydir, ystep, yphase) nsc = len(SCS) # Number of scanlines. def report(plk, isc0, ir0, isc1, ir1, iend): # Reports creation of link. xend = ("pini", "pfin")[iend] sys.stderr.write(" created link %s" % path.get_name(plk)) sys.stderr.write(" from %s of scanline %d raster %d" % (xend, isc0, ir0)) sys.stderr.write(" to %s of scanline %d raster %d" % (xend, isc1, ir1)) sys.stderr.write("\n") return # ...................................................................... for isc in range(nsc-1): # Get the raster lists of two conseutive scanlines: SC0 = SCS[isc]; SC1 = SCS[isc+1]; if len(SC0) > 0 and len(SC1) > 0: if debug: path.show_list(sys.stderr, SC1, True, 2) for ir0 in range(len(SC0)): oph0 = OPHS[SC0[ir0]] for ir1 in range(len(SC1)): oph1 = OPHS[SC1[ir1]] name = "%d.%d.%d" % (isc, ir0, ir1) lkini = make_raster_raster_link(oph0, oph1, mp_link, "L0." + name) if lkini != None: LKS.append(lkini) if debug: report(lkini, isc, ir0, isc+1, ir1, 0) lkfin = make_raster_raster_link(path.rev(oph0), path.rev(oph1), mp_link, "L1." + name) if lkfin != None: LKS.append(lkfin) if debug: report(lkfin, isc, ir0, isc+1, ir1, 1) return LKS # ---------------------------------------------------------------------- def create_all_raster_raster_contacts(OPHS, xdir, ydir): debug = False CTS = [] if len(OPHS) > 0: # Paranoia: mp_fill = move.parameters(path.elem(OPHS[0],0)) for oph in OPHS: assert path.nelems(oph) == 1 omv = path.elem(oph, 0) assert not move.is_jump(omv) assert move.parameters(omv) == mp_fill wdf = move_parms.width(mp_fill) ystep, yphase = get_spacing_and_phase(OPHS, xdir, ydir) OPHS = sort_by_scanline(OPHS, xdir, ydir, ystep, yphase) SCS = separate_by_scanline(OPHS, xdir, ydir, ystep, yphase) nsc = len(SCS) # Number of scanlines. def report(ct, isc0, ir0, isc1, ir1): # Reports creation of link. sys.stderr.write(" created contacr %s" % contact.get_name(ct)) sys.stderr.write(" from scanline %d raster %d" % (isc0, ir0)) sys.stderr.write(" to scanline %d raster %d" % (isc1, ir1)) sys.stderr.write("\n") return # ...................................................................... for isc in range(nsc-1): # Get the raster lists of two conseutive scanlines: SC0 = SCS[isc]; SC1 = SCS[isc+1]; if len(SC0) > 0 and len(SC1) > 0: if debug: path.show_list(sys.stderr, SC1, True, 2) for ir0 in range(len(SC0)): oph0 = OPHS[SC0[ir0]] omv0 = path.elem(oph0,0) for ir1 in range(len(SC1)): oph1 = OPHS[SC1[ir1]] omv1 = path.elem(oph1,0) name = "%d.%d.%d" % (isc, ir0, ir1) ct = contact.from_moves(omv0, omv1, 0.5*ystep, 0) if ct != None: CTS.append(ct) path_hp.add_contact(oph0, 1, ct) path_hp.add_contact(oph1, 0, ct) if debug: report(ct, isc, ir0, isc+1, ir1) return CTS # ---------------------------------------------------------------------- def endpoints_from_shape(S, B, xdir, ydir, ystep, yphase): # Compute approx range of X and of scan-line indices (possibly too large): Brot = rmxn.box_affine(B, (xdir,ydir), (0,0)) xlo = Brot[0][0] - 10; xhi = Brot[1][0] + 10; ylo = Brot[0][1] - 2*ystep; yhi = Brot[1][1] + 2*ystep; i_lo = int(floor((ylo - yphase)/ystep)) i_hi = int(ceil((yhi - yphase)/ystep)) nsc = i_hi - i_lo + 1 # Number of scan-lines in filling. # Clip the shape {S} to the box {B}: K = rootray_shape.box(B[0], B[1]) S = rootray_shape.intersection([S, K]) PTRS = [] sys.stderr.write("filling has %d scan-lines {%d..%d}\n" % (nsc, i_lo, i_hi)) for di in range(nsc): i = i_lo + di y = i*ystep + yphase # Ordinate of axis of scan-line. # Map to {xdir,ydir} system and compute scan-line ref points {p,q}: p = rn.mix(xlo, xdir, y, ydir) q = rn.mix(xhi, xdir, y, ydir) # sys.stderr.write("@@ scanline %d y = %d p = %s q = %s\n" % (i, y, str(p), str(q))) # Intersect scanl-line axis with shape {S}: sgn1, TS = rootray_shape.trace(p, q, S) assert sgn1 == +1 nt = len(TS) assert nt % 2 == 0, "odd ray-polygon intersection count" # Collect the pairs of endpoints: PTRSi = [] # List of endpoint pairs on scanline {ìsc} for j in range(nt//2): t0 = TS[2*j] t1 = TS[2*j + 1] # sys.stderr.write(" raster %d t = [%12.8f _ %12.8f]\n" % (j, t0, t1)) pr = rn.mix(1-t0, p, t0, q) qr = rn.mix(1-t1, p, t1, q) PTRSij = (pr, qr) PTRSi.append(PTRSij) PTRS.append(PTRSi) # Trim empty scan-lines from each end: while len(PTRS) > 0 and len(PTRS[0]) == 0: PTRS.pop(0) while len(PTRS) > 0 and len(PTRS[-1]) == 0: PTRS.pop() return PTRS # ---------------------------------------------------------------------- def from_endpoints(PTRS, mp): OPHS = [] for i in range(len(PTRS)): PTRSi = PTRS[i] # rasters on scan-line {i} for j in range(len(PTRSi)): PTSRij = PTRSi[j] # Endpoints of raster {j} on scan-line {i}. assert len(PTSRij) == 2 p = PTSRij[0] q = PTSRij[1] mv = move.make(p, q, mp) mname = "T%03d.%02d" % (i, j) move.set_name(mv, mname) ph = path.from_moves([mv,]) path.clear_links(ph) path_hp.clear_contacts(ph) OPHS.append(ph) return OPHS # ----------------------------------------------------------------------