<> <> <> <> <<>> DIRECTORY Atom USING [GetPropFromList], Basics USING [LongMult, BITSHIFT, BITAND, logBitsPerWord, bitsPerWord, BITOR, BITNOT, BytePair], PrincOps USING [DstFunc, SrcFunc, BitAddress, BBTableSpace, BBptr], PrincOpsUtils USING [AlignedBBTable, BITBLT], Real USING [RoundC, FixI, FixC, Float], Terminal USING [ModifyColorFrame, Virtual], ImagerColor USING [RGB], SampleMapOps USING [SampleMap, Function], Vector2 USING [VEC], Pixels USING [Extent, GetPixel, GetSampleSet, GetTerminalYOffset, PixelBuffer, PixelOp, PutPixel, SampleSet, SampleSetSequence, SubMap, TerminalFromBuffer, XfmMapPlace], EdgeBlt USING [ EdgeBltError, EdgeBltTable, EdgeDesc ], ScanConvert USING [IntPair, IntPairSequence, RealSequence, Spot, SpotSequence, justNoticeable]; EdgeBltAppliedImpl: CEDAR MONITOR IMPORTS Atom, Basics, PrincOpsUtils, Terminal, Real, Pixels EXPORTS ScanConvert ~ BEGIN <> EdgeDesc: TYPE ~ EdgeBlt.EdgeDesc; EdgeBltTable: TYPE ~ EdgeBlt.EdgeBltTable; PixelBuffer: TYPE ~ Pixels.PixelBuffer; RGB: TYPE ~ ImagerColor.RGB; -- RECORD[ R, G, B: REAL] IntRGB: TYPE ~ RECORD[ r, g, b: CARDINAL]; IntRGBZ: TYPE ~ RECORD[ r, g, b, z: CARDINAL]; Pair: TYPE ~ Vector2.VEC; -- RECORD[x, y: REAL] IntPair: TYPE ~ ScanConvert.IntPair; -- RECORD[x, y: INTEGER] IntPairSequence: TYPE ~ ScanConvert.IntPairSequence; RealSequence: TYPE ~ ScanConvert.RealSequence; SampleSet: TYPE ~ Pixels.SampleSet; SampleSetSequence: TYPE ~ Pixels.SampleSetSequence; Spot: TYPE ~ ScanConvert.Spot; SpotSequence: TYPE ~ ScanConvert.SpotSequence; BytePair: TYPE ~ Basics.BytePair; Function: TYPE ~ SampleMapOps.Function; IncrementalDesc: TYPE ~ RECORD [ val, xSgn, incrmnt, range, position: INTEGER, steep: BOOL ]; IncDescSeq: TYPE ~ RECORD [length: NAT _ 0, s: SEQUENCE maxLength: NAT OF IncrementalDesc]; ScanConvertError: PUBLIC SIGNAL [reason: ATOM] = CODE; <> lgBitsPerWord: NAT ~ Basics.logBitsPerWord; bitsPerWord: NAT ~ Basics.bitsPerWord; <> bltValue: ARRAY [0..4] OF CARDINAL; bbspace: ARRAY[0..4) OF PrincOps.BBTableSpace; bb: ARRAY[0..4) OF PrincOps.BBptr; bbWdsPerLine: ARRAY[0..4) OF NAT; ShiftL: PROC [val: INTEGER, log2Scale: NAT] RETURNS [INTEGER] -- shift left = INLINE { RETURN[INTEGER[Basics.BITSHIFT[ LOOPHOLE[val], log2Scale ]]] }; ShiftR: PROC [val: INTEGER, log2Scale: NAT] RETURNS [INTEGER] -- shift right = INLINE { RETURN[INTEGER[Basics.BITSHIFT[ LOOPHOLE[val], -INTEGER[log2Scale] ]]] }; SubPixel: PROC [position: INTEGER, log2Scale: NAT] RETURNS [INTEGER] ~ { RETURN[INTEGER[Basics.BITAND[ position, ShiftL[1, log2Scale] - 1 ]]]; }; Extend: PUBLIC PROC[seq: REF RealSequence, newLength: NAT] RETURNS[REF RealSequence]~{ newSeq: REF RealSequence _ NEW[ RealSequence[newLength] ]; FOR i: NAT IN [0..seq.length) DO newSeq[i] _ seq[i]; ENDLOOP; newSeq.length _ seq.length; seq _ newSeq; RETURN[seq]; }; Sqr: PROCEDURE [number: INTEGER] RETURNS [INTEGER] ~ INLINE { RETURN[number * number]; }; Log2: PROC [n: INT] RETURNS [lg: NAT _ 0] ~ { -- finds log base 2 of input (from M. Plass) nn: LONG CARDINAL ~ n; k: LONG CARDINAL _ 1; UNTIL k=0 OR k>= nn DO lg _ lg + 1; k _ k + k; ENDLOOP; }; Power: PUBLIC PROC[ value: INTEGER, power: NAT] RETURNS[ result: INTEGER ] ~ { binaryCount: NAT _ power; -- makes highlights same size as those by ShadePt temp: REAL _ 1.0; val: REAL _ Real.Float[value] / 256.0; WHILE binaryCount > 0 DO IF Basics.BITAND[binaryCount, 1] = 1 THEN temp _ temp * val; val _ val * val; binaryCount _ binaryCount/2; ENDLOOP; result _ Real.RoundC[temp*256.0]; }; SetUpConstBlt: PROC[buf: PixelBuffer, y: INTEGER, color: SampleSet] ~ TRUSTED { <> j: NAT _ 0; FOR i: NAT IN [0..buf.pixels.length) DO dest: SampleMapOps.SampleMap _ buf.pixels[i].subMap.sampleMap; bbWdsPerLine[i] _ Basics.BITSHIFT[dest.bitsPerLine, -4]; IF Basics.BITAND[dest.bitsPerLine, 15] # 0 THEN bbWdsPerLine[i] _ bbWdsPerLine[i] + 1; IF buf.pixels[i].df = 2 THEN IF dest.base.bit = 0 THEN { bltValue[i] _ 256*color[j] + color[j+1]; j _ j + 2; } -- interleaved RG ELSE bltValue[i] _ 0 -- ignore interleaved map off word boundary ELSE { bltValue[i] _ color[j] * 00101H; j _ j + 1; }; -- replicate value bb[i] _ PrincOpsUtils.AlignedBBTable[@bbspace[i]]; bb[i]^ _ [ -- set up to point to beginning of scan line at polygon bottom dst: [word: dest.base.word + Basics.LongMult[y, bbWdsPerLine[i]], bit: 0], dstBpl: dest.bitsPerLine, src: [word: @bltValue[i], bit: 0], srcDesc: [gray[[yOffset: 0, widthMinusOne: 0, heightMinusOne: 0]]], height: 1, width: 1, flags: [direction: forward, disjoint: TRUE, disjointItems: TRUE, gray: TRUE, srcFunc: null, dstFunc: null] ]; ENDLOOP; }; DoConstBlt: PROC[buf: PixelBuffer, left, right: INTEGER] ~ TRUSTED { IF left > right THEN [left, right] _ Swap[left, right]; -- allow screwed up scan segments FOR i: NAT IN [0..buf.samplesPerPixel) DO scanLinePtr: LONG POINTER _ bb[i].dst.word; IF buf.pixels[i].df = 2 -- interleaved maps, write the one which is word-aligned THEN IF buf.pixels[i].subMap.sampleMap.base.bit = 0 THEN { bb[i].dst _ [word: scanLinePtr + left, bit: 0]; bb[i].width _ (right - left + 1) * 16; } ELSE LOOP -- not word-aligned, ignore ELSE { bb[i].dst _ [ word: scanLinePtr + Basics.BITSHIFT[left,-1], bit: Basics.BITSHIFT[Basics.BITAND[left,1], 3] ]; bb[i].width _ (right - left + 1) * 8; }; PrincOpsUtils.BITBLT[bb[i]]; bb[i].dst.word _ scanLinePtr + bbWdsPerLine[i]; -- increment scan pointer for next call ENDLOOP; }; GetSlopeIncr: PROC[pBeg, pEnd: IntPair, log2Scale: NAT] RETURNS[incr: IncrementalDesc] ~ { <> xDiff: INTEGER _ pEnd.x - pBeg.x; yDiff: INTEGER _ pEnd.y - pBeg.y; -- assumed positive IF yDiff <= 0 THEN yDiff _ LAST[NAT]; -- zero height edge, let increments => 0 IF yDiff > ABS[xDiff] THEN { -- steep edge (more vertical than horizontal) incr.incrmnt _ 2 * ABS[xDiff]; -- fractional increment incr.range _ 2 * yDiff; -- range to increment over incr.steep _ TRUE; } ELSE { -- shallow edge (more horizontal than vertical) incr.incrmnt _ 2 * yDiff; -- fractional increment incr.range _ 2 * ABS[xDiff]; -- range to increment over incr.steep _ FALSE; }; incr.xSgn _ SGN[xDiff]; -- get sign of horizontal increment IF log2Scale > 0 -- scaled for subpixel precision THEN { incr.val _ ShiftR[pBeg.x, log2Scale]; -- scale initial pixel value back to screen address IF NOT incr.steep THEN{ -- more horizontal line, fractional position inversely related to height <> partWay: INTEGER _ ShiftL[1, log2Scale-1] - SubPixel[pBeg.y, log2Scale]; incr.position _ - ShiftR[ partWay * incr.range, log2Scale ]; WHILE incr.position < 0 DO -- increment to end of scan segment incr.val _ incr.val + incr.xSgn; incr.position _ incr.position + incr.incrmnt; ENDLOOP; incr.position _ incr.position - incr.range; -- reset fractional part } ELSE { -- more vertical line, fractional position (difference from halfway) partWay: INTEGER _ SubPixel[pBeg.x, log2Scale] - ShiftL[1, log2Scale-1]; IF incr.xSgn < 0 THEN partWay _ - partWay; -- reverse for positive slope incr.position _ - ShiftR[ partWay * incr.range, log2Scale ] - incr.range/2; }; } ELSE { incr.val _ pBeg.x; -- initial value incr.position _ - incr.range/2; -- start at halfway position (midpixel) }; }; UpdateIncr: PROC[incr: IncrementalDesc] RETURNS [IncrementalDesc] ~ { <> WHILE TRUE DO IF NOT incr.steep THEN incr.val _ incr.val + incr.xSgn; -- always increment if shallow incr.position _ incr.position + incr.incrmnt; -- increment fractional position IF incr.position > 0 THEN { -- fractional position overflowed incr.position _ incr.position - incr.range; -- reset fractional part IF incr.steep THEN incr.val _ incr.val + incr.xSgn -- bump integer part ELSE EXIT; -- exit and render scan line if shallow }; IF incr.steep THEN EXIT; -- always exit if steep (one pixel per scanline) ENDLOOP; RETURN[ incr ]; }; ditherTable: ARRAY [0..4) OF ARRAY [0..4) OF NAT _ [[0,12,3,15], [8,4,11,7], [2,14,1,13], [10,6,9,5]]; DitheredRGB: PUBLIC PROC[renderMode: ATOM, x, y, red, grn, blu: INTEGER] RETURNS[INTEGER] ~ { val2R, val2G, val2B, pixelValue: NAT; SELECT renderMode FROM $PseudoColor => { <> threshold: NAT _ ditherTable[ Basics.BITAND[x,3] ][ Basics.BITAND[y,3] ]; valR: NAT _ Basics.BITSHIFT[ Basics.BITSHIFT[red,2] + red, -4 ]; -- (red * 5) / 16 valG: NAT _ Basics.BITSHIFT[ Basics.BITSHIFT[grn,2] + Basics.BITSHIFT[grn,1], -4 ]; valB: NAT _ Basics.BITSHIFT[ Basics.BITSHIFT[blu,2] + blu, -4 ]; -- (blu * 5) / 16 val2R _ Basics.BITSHIFT[valR,-4]; -- valR / 16 IF Basics.BITAND[valR,15] > threshold THEN val2R _ val2R + 1; -- valr MOD 16 val2G _ Basics.BITSHIFT[valG,-4]; IF Basics.BITAND[valG,15] > threshold THEN val2G _ val2G + 1; val2B _ Basics.BITSHIFT[valB,-4]; IF Basics.BITAND[valB,15] > threshold THEN val2B _ val2B + 1; RETURN[ MIN[ 255, Basics.BITSHIFT[val2R,5] + Basics.BITSHIFT[val2R,3] + Basics.BITSHIFT[val2R,1] + Basics.BITSHIFT[val2G,2] + Basics.BITSHIFT[val2G,1] + val2B + 2 ] ]; --val2R*42 + val2G*6 + val2B + 2 }; $Dithered => { <> threshold: NAT _ ditherTable[x MOD 4][y MOD 4]; valR: NAT _ 4* red / 16; valG: NAT _ 5* grn / 16; valB: NAT _ 3* blu / 16; val2R _ valR/16; IF valR MOD 16 > threshold THEN val2R _ val2R + 1; val2G _ valG/16; IF valG MOD 16 > threshold THEN val2G _ val2G + 1; val2B _ valB/16; IF valB MOD 16 > threshold THEN val2B _ val2B + 1; pixelValue _ val2R*24 + val2G*4 + val2B; IF pixelValue >= 60 THEN pixelValue _ pixelValue + 135; -- move to top of map RETURN[ MIN[255, pixelValue] ]; }; ENDCASE => SIGNAL ScanConvertError[$BadRenderMode]; RETURN[ 255 ]; }; MappedRGB: PUBLIC PROC[renderMode: ATOM, clr: RGB] RETURNS[NAT] ~ { SELECT renderMode FROM $Dithered => { mapVal: NAT _ Real.FixC[clr.R*4.999]*24 + Real.FixC[clr.G*5.999]*4 + Real.FixC[clr.B*3.999]; IF mapVal >= 60 THEN mapVal _ mapVal + 135; -- move to top of map RETURN[ mapVal ]; }; $PseudoColor => RETURN[ Real.FixC[clr.R*5.999]*42 + Real.FixC[clr.G*6.999]*6 + Real.FixC[clr.B*5.999] + 2 ]; ENDCASE => SIGNAL ScanConvertError[$BadRenderMode]; RETURN[ 255 ]; }; RGBFromMap: PUBLIC PROC[renderMode: ATOM, value: NAT] RETURNS[RGB] ~ { SELECT renderMode FROM $Dithered => { IF value >= 60 THEN value _ value - 135; -- move from top of map value _ MIN[119, value]; RETURN[ [ R: (value / 24) / 5.0, G: (value MOD 24) / 4 / 6.0, B: (value MOD 4) / 4.0 ] ]; }; $PseudoColor => { value _ MIN[253, value] - 2; RETURN[ [ R: (value / 42) / 6.0, G: (value MOD 42) / 6 / 7.0, B: (value MOD 6) / 6.0 ] ]; }; ENDCASE => SIGNAL ScanConvertError[$BadRenderMode]; RETURN[[255, 255, 255]]; }; Swap: PROCEDURE [first, second: INTEGER] RETURNS [INTEGER, INTEGER] = { RETURN [second, first]; }; BitAddr: UNSAFE PROC [lineStart: LONG POINTER, pixel: CARDINAL, lgBitsPerPixel: INTEGER] RETURNS [PrincOps.BitAddress] ~ UNCHECKED { RETURN [[ word: lineStart + Basics.BITSHIFT[pixel, lgBitsPerPixel-lgBitsPerWord], bit: Basics.BITAND[Basics.BITSHIFT[pixel, lgBitsPerPixel], bitsPerWord-1] ]] }; SGN: PROCEDURE [number: INTEGER] RETURNS [INTEGER] = INLINE { IF number >= 0 THEN RETURN[1] ELSE RETURN[-1]; }; <> PutLine: PUBLIC PROC [ buf: PixelBuffer, p1, p2: IntPair, color: SampleSet ] ~ { DoLine: PROC[] ~ { FOR i: NAT IN [0..buf.pixels.length) DO -- do one spot for each 8 bits or less IF buf.pixels[i].df = 1 THEN DrawLine[ buf.pixels[i], p1, p2, color[i] ] ELSE IF buf.pixels[i].subMap.sampleMap.base.bit = 0 -- do only for red map <> THEN DrawLine[ buf.pixels[i], p1, p2, color[i] * 256 + color[i+1] ]; ENDLOOP; }; vt : Terminal.Virtual _ Pixels.TerminalFromBuffer[buf]; [p1.x, p1.y] _ Pixels.XfmMapPlace[buf.pixels[buf.pixels.length-1], p1.x, p1.y ]; -- df = 1!! [p2.x, p2.y] _ Pixels.XfmMapPlace[buf.pixels[buf.pixels.length-1], p2.x, p2.y ]; IF vt # NIL THEN { -- Lock out cursor yOffset: NAT _ Pixels.GetTerminalYOffset[buf]; Terminal.ModifyColorFrame[ vt, DoLine, MIN[p1.x, p2.x], MIN[p1.y, p2.y] + yOffset, MAX[p1.x, p2.x], MAX[p1.y, p2.y] + yOffset ]; } ELSE DoLine[]; }; <> SetUpPoly: PROC [buf: PixelBuffer, plygn: REF SpotSequence, log2Scale: NAT] RETURNS [firstVtx, highest, lowest, leftmost, rightmost: NAT] ~ { <> firstVtx _ 0; <> FOR i: CARDINAL IN [0..plygn.length) DO [ plygn[i].x, plygn[i].y ] _ Pixels.XfmMapPlace[ buf.pixels[buf.pixels.length-1], -- df = 1! plygn[i].x, plygn[i].y, log2Scale ]; ENDLOOP; highest _ lowest _ plygn[0].y; leftmost _ rightmost _ plygn[0].x; FOR i: CARDINAL IN [1..plygn.length) DO yCoord: NAT _ plygn[i].y; xCoord: NAT _ plygn[i].x; IF yCoord < lowest THEN { lowest _ yCoord; firstVtx _ i; } ELSE IF yCoord > highest THEN highest _ yCoord; IF xCoord < leftmost THEN leftmost _ xCoord ELSE IF xCoord > rightmost THEN rightmost _ xCoord; ENDLOOP; IF log2Scale > 0 THEN { highest _ ShiftR[highest, log2Scale]; lowest _ ShiftR[lowest, log2Scale]; leftmost _ ShiftR[leftmost, log2Scale]; rightmost _ ShiftR[rightmost, log2Scale]; }; }; ConstantPoly: PUBLIC ENTRY PROC [buf: PixelBuffer, color: SampleSet, plygn: REF SpotSequence, log2Scale: NAT _ 0] ~ { <> ENABLE UNWIND => NULL; vt : Terminal.Virtual _ Pixels.TerminalFromBuffer[buf]; highest, lowest, leftmost, rightmost, firstVtx: NAT _ 0; DoItConstant: PROC[] ~ { yPosn: NAT; -- current scan line nxtLVtx, nxtRVtx, rVtx, lVtx, nxtRHeight, nxtLHeight: NAT _ 0; left, right: IncrementalDesc; -- incremental descriptions for left and right edges <> nxtLVtx _ nxtRVtx _ rVtx _ lVtx _ firstVtx; -- set pointers to bottom vertex yPosn _ nxtRHeight _ nxtLHeight _ lowest; SetUpConstBlt[buf, yPosn, color]; -- set up scan segment blt WHILE yPosn < highest DO -- work up through vertices WHILE yPosn >= nxtLHeight DO -- next left vertex reached? lVtx _ nxtLVtx; nxtLVtx _ (lVtx + plygn.length - 1) MOD plygn.length; nxtLHeight _ ShiftR[plygn[nxtLVtx].y, log2Scale]; left _ GetSlopeIncr[ [plygn[lVtx].x, plygn[lVtx].y], [plygn[nxtLVtx].x, plygn[nxtLVtx].y], log2Scale ]; ENDLOOP; WHILE yPosn >= nxtRHeight DO -- next right vertex reached? rVtx _ nxtRVtx; nxtRVtx _ (rVtx + 1) MOD plygn.length; nxtRHeight _ ShiftR[plygn[nxtRVtx].y, log2Scale]; right _ GetSlopeIncr[ [plygn[rVtx].x, plygn[rVtx].y], [plygn[nxtRVtx].x, plygn[nxtRVtx].y], log2Scale ]; ENDLOOP; DoConstBlt[buf, left.val, right.val]; -- write segment left _ UpdateIncr[left]; right _ UpdateIncr[right]; yPosn _ yPosn + 1; -- update scan line ENDLOOP; IF yPosn > lowest THEN DoConstBlt[buf, left.val, right.val]; -- write top scan segment }; <> [firstVtx, highest, lowest, leftmost, rightmost] _ SetUpPoly[buf, plygn, log2Scale]; IF vt # NIL THEN { -- Lock out cursor yOffset: NAT _ Pixels.GetTerminalYOffset[ buf ]; Terminal.ModifyColorFrame[ vt, DoItConstant, leftmost, lowest+yOffset, rightmost, highest+yOffset ]; } ELSE DoItConstant[]; }; SmoothPoly: PUBLIC PROC [buf: PixelBuffer, plygn: REF SpotSequence, renderMode: ATOM, log2Scale: NAT _ 0] ~ { vt : Terminal.Virtual _ Pixels.TerminalFromBuffer[buf]; alpha: REF NAT _ NARROW[ Atom.GetPropFromList[buf.props, $Alpha] ]; depth: REF NAT _ NARROW[ Atom.GetPropFromList[buf.props, $Depth] ]; highest, lowest, leftmost, rightmost, firstVtx: NAT _ 0; DoItSmooth: PROC[] ~ { segPtr: ARRAY [0..5) OF LONG POINTER; wdsPerLine: ARRAY [0..5) OF CARDINAL; wrdPtr: ARRAY [0..5) OF LONG POINTER; SetUpScanSeg: PROC[yPosn: NAT] ~ TRUSTED { addOn: NAT _ IF alpha # NIL THEN 1 ELSE 0; -- make room for alpha buffer FOR i: NAT IN [0 .. buf.pixels.length - addOn) DO wdsPerLine[i] _ buf.pixels[i].subMap.sampleMap.bitsPerLine / bitsPerWord; segPtr[i] _ buf.pixels[i].subMap.sampleMap.base.word + Basics.LongMult[yPosn, wdsPerLine[i]]; ENDLOOP; }; PutScanSeg: PROC[ left, lftR, lftG, lftB, lftZ, right, rgtR, rgtG, rgtB, rgtZ: INTEGER ] ~ TRUSTED { doIt: BOOLEAN _ TRUE; red, grn, blu, z: IncrementalDesc; EvalDepth: PROC[] ~ TRUSTED { IF LOOPHOLE[wrdPtr[depth^]^, CARDINAL] > CARDINAL[z.val] THEN { LOOPHOLE[wrdPtr[depth^]^, CARDINAL] _ z.val; doIt _ TRUE; } ELSE doIt _ FALSE; z _ UpdateIncr[z]; wrdPtr[depth^] _ wrdPtr[depth^] + 1; }; IF left > right THEN [left, right] _ Swap[left, right]; red _ GetSlopeIncr[ [lftR, left], [rgtR, right], 0 ]; grn _ GetSlopeIncr[ [lftG, left], [rgtG, right], 0 ]; blu _ GetSlopeIncr[ [lftB, left], [rgtB, right], 0 ]; IF depth # NIL THEN { z _ GetSlopeIncr[ [lftZ, left], [rgtZ, right], 0 ]; wrdPtr[depth^] _ segPtr[depth^] + left; }; SELECT renderMode FROM $Dithered, $PseudoColor => { wrdPtr[0] _ segPtr[0] + Basics.BITSHIFT[left,-1]; FOR x: INTEGER IN [left..right] DO value: INTEGER _ DitheredRGB[renderMode, x, yPosn, red.val, grn.val, blu.val]; IF depth # NIL THEN EvalDepth[]; -- sets/clears doIt based on depth IF Basics.BITAND[x,1] = 0 THEN { IF doIt THEN LOOPHOLE[wrdPtr[0]^, BytePair].high _ value; } ELSE { IF doIt THEN LOOPHOLE[wrdPtr[0]^, BytePair].low _ value; wrdPtr[0] _ wrdPtr[0] + 1; }; -- odd, low byte, incr. to next red _ UpdateIncr[red]; grn _ UpdateIncr[grn]; blu _ UpdateIncr[blu]; ENDLOOP; }; $Grey => { wrdPtr[0] _ segPtr[0] + Basics.BITSHIFT[left,-1]; FOR x: INTEGER IN [left..right] DO IF depth # NIL THEN EvalDepth[]; -- sets/clears doIt based on depth IF Basics.BITAND[x,1] = 0 THEN { IF doIt THEN LOOPHOLE[wrdPtr[0]^, BytePair].high _ red.val; } ELSE { IF doIt THEN LOOPHOLE[wrdPtr[0]^, BytePair].low _ red.val; wrdPtr[0] _ wrdPtr[0] + 1; }; -- odd, low byte, incr. to next red _ UpdateIncr[red]; ENDLOOP; }; $FullColor, $Dorado24 => { wrdPtr[0] _ segPtr[0] + left; wrdPtr[2] _ segPtr[2] + Basics.BITSHIFT[left,-1]; FOR x: INTEGER IN [left..right] DO IF depth # NIL THEN EvalDepth[]; -- sets/clears doIt based on depth IF doIt THEN wrdPtr[0]^ _ Basics.BITSHIFT[red.val, 8] + grn.val; wrdPtr[0] _ wrdPtr[0] + 1; IF Basics.BITAND[x,1] = 0 THEN { IF doIt THEN LOOPHOLE[wrdPtr[2]^, BytePair].high _ blu.val; } ELSE { IF doIt THEN LOOPHOLE[wrdPtr[2]^, BytePair].low _ blu.val; wrdPtr[2] _ wrdPtr[2] + 1; }; -- odd, low byte, incr. to next red _ UpdateIncr[red]; grn _ UpdateIncr[grn]; blu _ UpdateIncr[blu]; ENDLOOP; segPtr[2] _ segPtr[2] + wdsPerLine[2]; }; ENDCASE => SIGNAL ScanConvertError[$BadRenderMode]; IF depth # NIL THEN segPtr[depth^] _ segPtr[depth^] + wdsPerLine[depth^]; segPtr[0] _ segPtr[0] + wdsPerLine[0]; }; GetColor: PROC[color: REF RealSequence] RETURNS[outClr: IntRGBZ] ~ { SELECT color.length FROM 1 => outClr _ [Real.RoundC[color[0]*255.0], 0, 0, 0 ]; 2 => outClr _ [Real.RoundC[color[0]*255.0], 0, 0, Real.RoundC[color[1]*255.0] ]; 3 => outClr _ [Real.RoundC[color[0]*255.0], Real.RoundC[color[1]*255.0], Real.RoundC[color[2]*255.0], 0]; 4 => outClr _ [Real.RoundC[color[0]*255.0], Real.RoundC[color[1]*255.0], Real.RoundC[color[2]*255.0], Real.RoundC[color[3]] ]; ENDCASE => SIGNAL ScanConvertError[$BadLength]; }; yPosn: NAT; -- current scan line left, lftR, lftG, lftB, lftZ, right, rgtR, rgtG, rgtB, rgtZ: IncrementalDesc; -- edge description nxtLVtx, nxtRVtx, rVtx, lVtx, nxtRHeight, nxtLHeight: NAT _ 0; clr, nxtClr: IntRGBZ; <> <> nxtLVtx _ nxtRVtx _ rVtx _ lVtx _ firstVtx; -- set pointers to bottom vertex yPosn _ nxtRHeight _ nxtLHeight _ lowest; SetUpScanSeg[yPosn]; WHILE yPosn < highest DO -- work up through vertices lastLHeight: NAT _ nxtLHeight; lastRHeight: NAT _ nxtRHeight; WHILE yPosn >= nxtLHeight DO -- next left vertex reached? lVtx _ nxtLVtx; nxtLVtx _ (lVtx + plygn.length - 1) MOD plygn.length; nxtLHeight _ ShiftR[plygn[nxtLVtx].y, log2Scale]; left _ GetSlopeIncr[ [plygn[lVtx].x, plygn[lVtx].y], [plygn[nxtLVtx].x, plygn[nxtLVtx].y], log2Scale ]; clr _ GetColor[plygn[lVtx].val]; nxtClr _ GetColor[plygn[nxtLVtx].val]; lftR _ GetSlopeIncr[ [clr.r, lastLHeight], [nxtClr.r, nxtLHeight], 0 ]; lftG _ GetSlopeIncr[ [clr.g, lastLHeight], [nxtClr.g, nxtLHeight], 0 ]; lftB _ GetSlopeIncr[ [clr.b, lastLHeight], [nxtClr.b, nxtLHeight], 0 ]; IF depth # NIL THEN lftZ _ GetSlopeIncr[ [clr.z, lastLHeight], [nxtClr.z, nxtLHeight], 0]; ENDLOOP; WHILE yPosn >= nxtRHeight DO -- next right vertex reached? rVtx _ nxtRVtx; nxtRVtx _ (rVtx + 1) MOD plygn.length; nxtRHeight _ ShiftR[plygn[nxtRVtx].y, log2Scale]; right _ GetSlopeIncr[ [plygn[rVtx].x, plygn[rVtx].y], [plygn[nxtRVtx].x, plygn[nxtRVtx].y], log2Scale ]; clr _ GetColor[plygn[rVtx].val]; nxtClr _ GetColor[plygn[nxtRVtx].val]; rgtR _ GetSlopeIncr[ [clr.r, lastRHeight], [nxtClr.r, nxtRHeight], 0 ]; rgtG _ GetSlopeIncr[ [clr.g, lastRHeight], [nxtClr.g, nxtRHeight], 0 ]; rgtB _ GetSlopeIncr[ [clr.b, lastRHeight], [nxtClr.b, nxtRHeight], 0 ]; IF depth # NIL THEN rgtZ _ GetSlopeIncr[[clr.z, lastRHeight], [nxtClr.z, nxtRHeight], 0]; ENDLOOP; PutScanSeg[ left.val, lftR.val, lftG.val, lftB.val, lftZ.val, right.val, rgtR.val, rgtG.val, rgtB.val, rgtZ.val ]; left _ UpdateIncr[left]; lftR _ UpdateIncr[lftR]; lftG _ UpdateIncr[lftG]; lftB _ UpdateIncr[lftB]; right _ UpdateIncr[right]; rgtR _ UpdateIncr[rgtR]; rgtG _ UpdateIncr[rgtG]; rgtB _ UpdateIncr[rgtB]; IF depth # NIL THEN { lftZ _ UpdateIncr[lftZ]; rgtZ _ UpdateIncr[rgtZ]; }; yPosn _ yPosn + 1; -- update scan line ENDLOOP; <> IF yPosn > lowest THEN PutScanSeg[ left.val, lftR.val, lftG.val, lftB.val, lftZ.val, right.val, rgtR.val, rgtG.val, rgtB.val, rgtZ.val ]; }; << Proc Body Starts Here!!!!>> <> [firstVtx, highest, lowest, leftmost, rightmost] _ SetUpPoly[buf, plygn, log2Scale]; IF vt # NIL THEN { -- Lock out cursor yOffset: NAT _ Pixels.GetTerminalYOffset[ buf ]; Terminal.ModifyColorFrame[ vt, DoItSmooth, leftmost, lowest+yOffset, rightmost, highest+yOffset ]; } ELSE DoItSmooth[]; }; lftSeq: REF IncDescSeq _ NEW[IncDescSeq[9]]; -- 9 = rgb(3) + nml x-y(2) + light rgb(3) + z rgtSeq: REF IncDescSeq _ NEW[IncDescSeq[9]]; segSeq: REF IncDescSeq _ NEW[IncDescSeq[9]]; ShinyPoly: PUBLIC PROC [buf: PixelBuffer, plygn: REF SpotSequence, shininess: NAT, renderMode: ATOM, log2Scale: NAT _ 0] ~ { vt : Terminal.Virtual _ Pixels.TerminalFromBuffer[buf]; alpha: REF NAT _ NARROW[ Atom.GetPropFromList[buf.props, $Alpha] ]; depth: REF NAT _ NARROW[ Atom.GetPropFromList[buf.props, $Depth] ]; highest, lowest, leftmost, rightmost, firstVtx: NAT _ 0; DoItShiny: PROC[] ~ { segPtr: ARRAY [0..5) OF LONG POINTER; wdsPerLine: ARRAY [0..5) OF CARDINAL; wrdPtr: ARRAY [0..5) OF LONG POINTER; SetUpShinySeg: PROC[yPosn: NAT] ~ TRUSTED { -- set up scan segment pointers addOn: NAT _ IF alpha # NIL THEN 1 ELSE 0; -- make room for alpha buffer FOR i: NAT IN [0 .. buf.pixels.length - addOn) DO wdsPerLine[i] _ buf.pixels[i].subMap.sampleMap.bitsPerLine / bitsPerWord; segPtr[i] _ buf.pixels[i].subMap.sampleMap.base.word + Basics.LongMult[yPosn, wdsPerLine[i]]; ENDLOOP; }; GetShinyColor: PROC[ segSeq: REF IncDescSeq ] RETURNS[clr: IntRGB] ~ { <> red, grn, blu: CARDINAL _ 0; noticeableHilite: BOOLEAN _ FALSE; FOR i: NAT IN [ 0 .. (segSeq.length-3)/5 ) DO j: NAT _ i*5 + 3; sqr1: INTEGER _ Sqr[ segSeq[j].val ]; sqr2: INTEGER _ Sqr[ segSeq[j+1].val ]; pctHilite: CARDINAL _ Power[ Basics.BITSHIFT[65535 - (sqr1 + sqr2), -8], shininess ]; IF pctHilite > justNoticeable THEN { -- Scale light color by hilite strength red _ red + Basics.BITSHIFT[pctHilite * segSeq[j+2].val, -8]; grn _ grn + Basics.BITSHIFT[pctHilite * segSeq[j+3].val, -8]; blu _ blu + Basics.BITSHIFT[pctHilite * segSeq[j+4].val, -8]; noticeableHilite _ TRUE; }; ENDLOOP; IF noticeableHilite THEN { clr.r _ MIN[255, segSeq[0].val + Basics.BITSHIFT[(255 - segSeq[0].val) * red, -8] ]; clr.g _ MIN[255, segSeq[1].val + Basics.BITSHIFT[(255 - segSeq[1].val) * grn, -8] ]; clr.b _ MIN[255, segSeq[2].val + Basics.BITSHIFT[(255 - segSeq[2].val) * blu, -8] ]; } ELSE { clr.r _ segSeq[0].val; clr.g _ segSeq[1].val; clr.b _ segSeq[2].val; }; FOR j: NAT IN [0..segSeq.length) DO -- increment for next pixel segSeq[j] _ UpdateIncr[ segSeq[j] ]; ENDLOOP; }; PutShinySeg: PROC[ left: INTEGER, lftSeq: REF IncDescSeq, right: INTEGER, rgtSeq: REF IncDescSeq ] ~ TRUSTED { doIt: BOOLEAN _ TRUE; EvalDepth: PROC[] ~ TRUSTED { IF LOOPHOLE[wrdPtr[depth^]^, CARDINAL] > CARDINAL[segSeq[segSeq.length-1].val] THEN { LOOPHOLE[wrdPtr[depth^]^, CARDINAL] _ segSeq[segSeq.length-1].val; doIt _ TRUE; } ELSE doIt _ FALSE; wrdPtr[depth^] _ wrdPtr[depth^] + 1; }; IF left > right THEN [left, right] _ Swap[left, right]; FOR j: NAT IN [0..lftSeq.length) DO segSeq[j] _ GetSlopeIncr[ [lftSeq[j].val, left], [rgtSeq[j].val, right], 0 ]; ENDLOOP; IF depth # NIL THEN wrdPtr[depth^] _ segPtr[depth^] + left; SELECT renderMode FROM $Dithered, $PseudoColor, $Grey => { wrdPtr[0] _ segPtr[0] + Basics.BITSHIFT[left,-1]; FOR x: INTEGER IN [left..right] DO clr: IntRGB; value: INTEGER; IF depth # NIL THEN EvalDepth[]; -- sets/clears doIt based on depth clr _ GetShinyColor[segSeq]; value _ IF renderMode = $Grey THEN (clr.r+clr.g+clr.b)/3 ELSE DitheredRGB[renderMode, x, yPosn, clr.r, clr.g, clr.b]; IF Basics.BITAND[x,1] = 0 THEN { IF doIt THEN LOOPHOLE[wrdPtr[0]^, BytePair].high _ value; } ELSE { IF doIt THEN LOOPHOLE[wrdPtr[0]^, BytePair].low _ value; wrdPtr[0] _ wrdPtr[0] + 1; }; -- odd, low byte, incr. to next ENDLOOP; }; $FullColor, $Dorado24 => { wrdPtr[0] _ segPtr[0] + left; wrdPtr[2] _ segPtr[2] + Basics.BITSHIFT[left,-1]; FOR x: INTEGER IN [left..right] DO clr: IntRGB; IF depth # NIL THEN EvalDepth[]; -- sets/clears doIt based on depth clr _ GetShinyColor[segSeq]; IF doIt THEN wrdPtr[0]^ _ Basics.BITSHIFT[clr.r, 8] + clr.g; wrdPtr[0] _ wrdPtr[0] + 1; IF Basics.BITAND[x,1] = 0 THEN { IF doIt THEN LOOPHOLE[wrdPtr[2]^, BytePair].high _ clr.b; } ELSE { IF doIt THEN LOOPHOLE[wrdPtr[2]^, BytePair].low _ clr.b; wrdPtr[2] _ wrdPtr[2] + 1; }; -- odd, low byte, incr. to next ENDLOOP; segPtr[2] _ segPtr[2] + wdsPerLine[2]; }; ENDCASE => SIGNAL ScanConvertError[$BadRenderMode]; IF depth # NIL THEN segPtr[depth^] _ segPtr[depth^] + wdsPerLine[depth^]; segPtr[0] _ segPtr[0] + wdsPerLine[0]; }; yPosn: NAT; -- current scan line left, right: IncrementalDesc; -- edge descriptions nxtLVtx, nxtRVtx, rVtx, lVtx, nxtRHeight, nxtLHeight: NAT _ 0; nxtLVtx _ nxtRVtx _ rVtx _ lVtx _ firstVtx; -- set pointers to bottom vertex yPosn _ nxtRHeight _ nxtLHeight _ lowest; SetUpShinySeg[yPosn]; -- prepare scan segment structure IF lftSeq.maxLength < plygn[lVtx].val.length THEN { length: NAT_ plygn[lVtx].val.length; -- get more space lftSeq _ NEW[IncDescSeq[length]]; -- length = rgb(3) rgtSeq _ NEW[IncDescSeq[length]]; -- + (nml x-y(2) + light rgb(3)) * # of lights segSeq _ NEW[IncDescSeq[length]]; }; lftSeq.length _ rgtSeq.length _ segSeq.length _ plygn[lVtx].val.length; WHILE yPosn < highest DO -- work up through vertices this, next: REF RealSequence; lastLHeight: NAT _ nxtLHeight; lastRHeight: NAT _ nxtRHeight; WHILE yPosn >= nxtLHeight DO -- next left vertex reached? lVtx _ nxtLVtx; nxtLVtx _ (lVtx + plygn.length - 1) MOD plygn.length; nxtLHeight _ ShiftR[plygn[nxtLVtx].y, log2Scale]; left _ GetSlopeIncr[ [plygn[lVtx].x, plygn[lVtx].y], [plygn[nxtLVtx].x, plygn[nxtLVtx].y], log2Scale ]; this _ plygn[lVtx].val; next _ plygn[nxtLVtx].val; FOR j: NAT IN [0..plygn[lVtx].val.length) DO lftSeq[j] _ GetSlopeIncr[ [Real.FixI[this[j]], lastLHeight], [Real.FixI[next[j]], nxtLHeight], 0 ]; ENDLOOP; ENDLOOP; WHILE yPosn >= nxtRHeight DO -- next right vertex reached? rVtx _ nxtRVtx; nxtRVtx _ (rVtx + 1) MOD plygn.length; nxtRHeight _ ShiftR[plygn[nxtRVtx].y, log2Scale]; right _ GetSlopeIncr[ [plygn[rVtx].x, plygn[rVtx].y], [plygn[nxtRVtx].x, plygn[nxtRVtx].y], log2Scale ]; this _ plygn[rVtx].val; next _ plygn[nxtRVtx].val; FOR j: NAT IN [0..plygn[lVtx].val.length) DO rgtSeq[j] _ GetSlopeIncr[ [Real.FixI[this[j]], lastRHeight], [Real.FixI[next[j]], nxtRHeight], 0 ]; ENDLOOP; ENDLOOP; PutShinySeg[ left.val, lftSeq, right.val, rgtSeq ]; -- put out scan segment left _ UpdateIncr[left]; right _ UpdateIncr[right]; FOR j: NAT IN [0..lftSeq.length) DO lftSeq[j] _ UpdateIncr[ lftSeq[j] ]; rgtSeq[j] _ UpdateIncr[ rgtSeq[j] ]; ENDLOOP; yPosn _ yPosn + 1; -- update scan line ENDLOOP; <> IF yPosn > lowest THEN PutShinySeg[ left.val, lftSeq, right.val, rgtSeq ]; }; << Proc Body Starts Here!!!!>> <> [firstVtx, highest, lowest, leftmost, rightmost] _ SetUpPoly[buf, plygn, log2Scale]; IF vt # NIL THEN { -- Lock out cursor yOffset: NAT _ Pixels.GetTerminalYOffset[ buf ]; Terminal.ModifyColorFrame[ vt, DoItShiny, leftmost, lowest+yOffset, rightmost, highest+yOffset ]; } ELSE DoItShiny[]; }; <> <> DrawLine: PROC [destination: Pixels.SubMap, p1, p2: IntPair, -- fast line, constant color pxlValue: CARDINAL, function: Function _ [null, null]] ~ { increment, bias, error, sBump, t, shiftDist: INTEGER; wrdPtr: LONG POINTER TO WORD; p1f: INTEGER _ p1.x; p1s: INTEGER _ p1.y; p2f: INTEGER _ p2.x; p2s: INTEGER _ p2.y; dest: SampleMapOps.SampleMap _ destination.subMap.sampleMap; <> bitsPerPixel: NAT ~ dest.bitsPerSample * destination.df; lgBitsPerPixel: NAT ~ Log2[bitsPerPixel]; logPxlsPerWd: NAT ~ Basics.logBitsPerWord - lgBitsPerPixel; wordsPerLine: NAT _ dest.bitsPerLine / bitsPerWord; maxShift: NAT ~ Basics.bitsPerWord - bitsPerPixel; maxValue: CARDINAL ~ Basics.BITSHIFT[1, bitsPerPixel] - 1; value: CARDINAL ~ MIN[pxlValue, maxValue]; <> IF p1f > p2f THEN { t _ p1f; p1f _ p2f; p2f _ t; t _ p1s; p1s _ p2s; p2s _ t; }; <> TRUSTED { wrdPtr _ LOOPHOLE[dest.base.word + Basics.LongMult[wordsPerLine, p1s] + Basics.BITSHIFT[p1f, -logPxlsPerWd] ]; }; shiftDist _ maxShift - Basics.BITSHIFT[ Basics.BITAND[ p1f, Basics.BITSHIFT[1, logPxlsPerWd] - 1], -- p1f MOD pixelsPerWord lgBitsPerPixel ]; IF (p2f - p1f) > ABS[p2s - p1s] <> THEN { increment _ LOOPHOLE[Basics.BITSHIFT[LOOPHOLE[p2s - p1s], 1], INTEGER]; bias _ LOOPHOLE[Basics.BITSHIFT[LOOPHOLE[p2f - p1f], 1], INTEGER]; sBump _ SGN[increment] * wordsPerLine; increment _ ABS[increment]; error _ increment - Basics.BITSHIFT[bias, -1]; IF lgBitsPerPixel = 3 -- speedup for 8 bits per pixel THEN FOR i: NAT IN [0..(p2f-p1f)] DO TRUSTED { IF shiftDist = 0 THEN { LOOPHOLE[wrdPtr^, BytePair].low _ value; wrdPtr _ wrdPtr + 1; shiftDist _ 8; } ELSE { LOOPHOLE[wrdPtr^, BytePair].high _ value; shiftDist _ 0; }; IF error > 0 THEN TRUSTED { wrdPtr _ wrdPtr + sBump; error _ error - bias; }; error _ error + increment; }; ENDLOOP ELSE FOR i: NAT IN [0..(p2f-p1f)] DO TRUSTED { wrdPtr^ _ Basics.BITOR[ -- deposit pixel bits in word Basics.BITAND[wrdPtr^, Basics.BITNOT[Basics.BITSHIFT[maxValue, shiftDist]]], Basics.BITSHIFT[value, shiftDist] ]; }; IF shiftDist = 0 THEN TRUSTED { wrdPtr _ wrdPtr + 1; shiftDist _ maxShift; } ELSE shiftDist _ shiftDist - bitsPerPixel; IF error > 0 THEN TRUSTED { wrdPtr _ wrdPtr + sBump; error _ error - bias; }; error _ error + increment; ENDLOOP; } <> ELSE { j: NAT _ Basics.BITSHIFT[shiftDist, -lgBitsPerPixel]; pixelsPerWd: NAT ~ Basics.BITSHIFT[1, logPxlsPerWd]; mask, values: ARRAY [0..16) OF CARDINAL; FOR i: NAT IN [0..pixelsPerWd) DO mask[i] _ Basics.BITNOT[Basics.BITSHIFT[maxValue, Basics.BITSHIFT[i, lgBitsPerPixel]]]; values[i] _ Basics.BITSHIFT[value, Basics.BITSHIFT[i, lgBitsPerPixel]]; ENDLOOP; increment _ LOOPHOLE[Basics.BITSHIFT[LOOPHOLE[p2f - p1f], 1], INTEGER]; bias _ LOOPHOLE[Basics.BITSHIFT[LOOPHOLE[p2s - p1s], 1], INTEGER]; sBump _ SGN[bias] * wordsPerLine; bias _ ABS[bias]; error _ increment - Basics.BITSHIFT[bias, -1]; IF lgBitsPerPixel = 3 -- speedup for 8 bits per pixel THEN FOR i: NAT IN [0..ABS[p2s - p1s]] DO TRUSTED { IF shiftDist = 0 THEN { LOOPHOLE[wrdPtr^, BytePair].low _ value; IF error > 0 THEN { wrdPtr _ wrdPtr + 1; shiftDist _ 8; error _ error - bias; }; } ELSE { LOOPHOLE[wrdPtr^, BytePair].high _ value; IF error > 0 THEN { shiftDist _ 0; error _ error - bias; }; }; wrdPtr _ wrdPtr + sBump; error _ error + increment; }; ENDLOOP ELSE FOR i: NAT IN [0..ABS[p2s - p1s]] DO TRUSTED { wrdPtr^ _ Basics.BITOR[ Basics.BITAND[wrdPtr^, mask[j]] , values[j]]; }; TRUSTED { wrdPtr _ wrdPtr + sBump; }; IF error > 0 THEN { error _ error - bias; IF j = 0 THEN TRUSTED { wrdPtr _ wrdPtr + 1; j _ pixelsPerWd - 1; } ELSE j _ j - 1; }; error _ error + increment; ENDLOOP; }; }; <> CheckLimits: PROC[] ~ { }; ImageTransform: PROC[dst, src: Pixels.SubMap, dstPos: IntPair, theta, scale: REAL] ~ { i: NAT _ 0; -- loop index dstEdge, srcEdge, dstStart, dstBias, srcStart: EdgeDesc; srcBytesPerLine, dstBytesPerLine: CARDINAL; ebt: EdgeBltTable; scaledWidth: NAT _ Real.RoundC[scale * src.subMap.size.f]; scaledHeight: NAT _ Real.RoundC[scale * src.subMap.size.s]; srcAddr: INT _ LOOPHOLE[src.subMap.sampleMap.base.word, INT]*2 + src.subMap.sampleMap.base.bit/8; dstAddr: INT _ LOOPHOLE[dst.subMap.sampleMap.base.word, INT]*2 + dst.subMap.sampleMap.base.bit/8; dstEdgeLength: CARDINAL _ Real.RoundC[RealFns.CosDeg[theta] * scaledWidth]; dstEdgeHiccups: CARDINAL _ Real.RoundC[RealFns.SinDeg[theta] * scaledWidth]; CheckLimits[]; -- ensure transformed image fits in target buffer, 0 1.0 THEN -srcBytesPerLine ELSE srcBytesPerLine, -- hiccup step bias: src.subMap.size.s -- supply bias since not using EdgeBlt ]; dstEdge _ [ -- walks across destinatation image along slanted edge val: dstStart.val, length: dstEdgeLength, -- horizontal dimension of edge hiccups: dstEdgeHiccups, -- vertical dimension of edge lngthIncr: dst.df, -- horizontal step to next pixel hicIncr: -dst.subMap.sampleMap.bitsPerLine / 8, -- vertical step to next pixel bias: dstBias.val, -- bias computed by dstBias indirect: TRUE -- incrementing addresses ]; srcEdge _ [ -- walks along scanline of source image, skipping or copying pixels as needed val: srcStart.val, length: src.subMap.size.f, -- width of source image hiccups: ABS[INTEGER[src.subMap.size.f] - scaledWidth], -- pixels to drop/add to scale down/up lngthIncr: src.df, -- horizontal step to next pixel hicIncr: IF scale > 1.0 THEN -src.df ELSE src.df, -- hiccup step to next pixel bias: 0, -- will be supplied by EdgeBlt indirect: TRUE -- incrementing addresses ]; ebt _ [dstEdge, srcEdge, [TRUE, FALSE] ]; WHILE i < dstStart.length DO EdgeBlt[ebt]; IF dstBias.val <= - dstEdgeLength -- equals 0 after EdgeBlt adds initial bias THEN { -- should be integrated better dstBias.val _ dstBias.val + 2 * dstEdgeLength; -- reset bias dstStart.val _ dstStart.val - dstStart.lngthIncr; -- move start position up one line } ELSE i _ i+1; ebt.dst.bias _ dstBias.val; -- set up bias for distance to first hiccup dstBias _ EdgeIncr[dstBias]; dstStart _ EdgeIncr[dstStart]; ebt.dst.val _ dstStart.val; srcStart _ EdgeIncr[srcStart]; ebt.src.val _ srcStart.val; ENDLOOP; }; END.