DIRECTORY Atom, Basics, Convert, G3dBasic, G3dEdgeBlt, G3dRender, G3dScanConvert, ImagerPixel, ImagerSample, PrincOps, PrincOpsUtils, Real, RealFns, Rope, SF, Terminal; G3dScanConvertImpl: CEDAR MONITOR IMPORTS Atom, Basics, Convert, G3dEdgeBlt, G3dRender, ImagerPixel, ImagerSample, PrincOpsUtils, Real, RealFns EXPORTS G3dScanConvert ~ BEGIN LORA: TYPE ~ LIST OF REF ANY; RefSeq: TYPE ~ G3dRender.RefSeq; ROPE: TYPE ~ Rope.ROPE; LongNumber: TYPE ~ Basics.LongNumber; BytePair: TYPE ~ Basics.BytePair; SampleMap: TYPE ~ ImagerSample.SampleMap; EdgeDesc: TYPE ~ G3dEdgeBlt.EdgeDesc; EdgeSequence: TYPE ~ G3dEdgeBlt.EdgeSequence; EdgeBltTable: TYPE ~ G3dEdgeBlt.EdgeBltTable; Context: TYPE ~ G3dRender.Context; RGB: TYPE ~ G3dRender.RGB; -- RECORD[ R, G, B: REAL] RGBSequence: TYPE ~ G3dRender.RGBSequence; NatRGB: TYPE ~ G3dRender.NatRGB; NatRGBSequence: TYPE ~ G3dRender.NatRGBSequence; CtlPoint: TYPE ~ G3dRender.CtlPoint; CtlPtInfo: TYPE ~ G3dRender.CtlPtInfo; Pixel: TYPE ~ G3dRender.Pixel; PixelPart: TYPE ~ G3dRender.PixelPart; Box: TYPE ~ SF.Box; Pair: TYPE ~ G3dRender.Pair; -- RECORD[x, y: REAL] IntegerPair: TYPE ~ G3dRender.IntegerPair; -- RECORD[x, y: INTEGER] IntegerPairSequence: TYPE ~ G3dRender.IntegerPairSequence; IntSequence: TYPE ~ G3dRender.IntSequence; IntSequenceRep: TYPE ~ G3dBasic.IntSequenceRep; RealSequence: TYPE ~ G3dRender.RealSequence; RealSequenceRep: TYPE ~ G3dBasic.RealSequenceRep; Patch: TYPE ~ G3dRender.Patch; Shape: TYPE ~ G3dRender.Shape; ShadingClass: TYPE ~ G3dRender.ShadingClass; Spot: TYPE ~ G3dRender.Spot; TextureFunction: TYPE ~ G3dRender.TextureFunction; TextureMap: TYPE ~ G3dRender.TextureMap; PixelBuffer: TYPE ~ ImagerPixel.PixelBuffer; PixelBufferProc: TYPE ~ G3dScanConvert.PixelBufferProc; LinkedPoly: TYPE ~ RECORD [ lftVtces, rgtVtces: LIST OF CARD16 ]; Round: PROC [REAL] RETURNS [INT] ~ Real.Round; Fix: PROC [REAL] RETURNS [INT] ~ Real.Fix; GetProp: PROC [propList: Atom.PropList, prop: REF ANY] RETURNS [REF ANY] ~ Atom.GetPropFromList; constantBitBltTable: PrincOps.BitBltTable _ [ -- set up constant values in BitBlt tables dst: [word: NULL, bit: 0], dstBpl: 0, src: [word: NULL, 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] ]; longZero: LongNumber ~ [lc[0]]; longOne: LongNumber ~ [lc[1]]; 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]]; white: Pixel ~ [255, 255, 255, 0, 0]; numEdgeSequences: NAT ~ 6; edgeSequenceCachePtr: NAT _ numEdgeSequences; edgeSequenceCache: ARRAY [0..numEdgeSequences) OF REF EdgeSequence _ ALL[NIL]; statistics: BOOLEAN _ FALSE; polyCount, avePixelsPerPoly, aveScanSegLength, avePolyHeight: INT _ 0; pixelsPerPolyHist: IntSequence; scanSegLengthHist: IntSequence; polyHeightHist: IntSequence; useTextureCoords: BOOLEAN _ FALSE; InitHistograms: PROCEDURE [maxScanSeg, maxHeight: NAT] ~ { pixelsPerPolyHist _ NEW[ IntSequenceRep[maxScanSeg * maxHeight] ]; pixelsPerPolyHist.length _ maxScanSeg * maxHeight; scanSegLengthHist _ NEW[ IntSequenceRep[maxScanSeg] ]; scanSegLengthHist.length _ maxScanSeg; polyHeightHist _ NEW[ IntSequenceRep[maxHeight] ]; polyHeightHist.length _ maxHeight; polyCount _ 0; }; ClearHistograms: PROCEDURE [] ~ { FOR i: NAT IN [0..pixelsPerPolyHist.length) DO pixelsPerPolyHist[i] _ 0; ENDLOOP; FOR i: NAT IN [0..scanSegLengthHist.length) DO scanSegLengthHist[i] _ 0; ENDLOOP; FOR i: NAT IN [0..polyHeightHist.length) DO polyHeightHist[i] _ 0; ENDLOOP; polyCount _ 0; }; ShowHistograms: PROCEDURE [context: Context] ~ { ShowData: PROC[startWd, startHt: REAL, numbers: IntSequence, label: ROPE] ~ { maxVal: INT _ 0; lastJ: INT _ 0; wSize: REAL _ context.viewPort.w; FOR i: INT IN [0 .. numbers.length) DO IF maxVal < numbers[i] THEN maxVal _ numbers[i]; ENDLOOP; FOR i: INT IN [0 .. numbers.length) DO numbers[i] _ Round[2048 * numbers[i] / maxVal]; -- scale to known max ENDLOOP; context.class.draw2DRope[ context, "0", [startWd -.025, startHt -.025], white, .025*wSize ]; context.class.draw2DRope[ context, Convert.RopeFromInt[maxVal], [startWd - .06, startHt + .2], white, .025*wSize ]; context.class.draw2DRope[ context, Convert.RopeFromInt[numbers.length], [startWd + 0.762, startHt - .025], white, .025*wSize ]; context.class.draw2DRope[context, label, [startWd + 0.1, startHt - .037], white, .025*wSize]; FOR i: INT IN [ 0 .. Round[wSize * 0.75] ) DO s: REAL _ i / (wSize * 0.75); -- pctge. across histogram j: INT _ Fix[ numbers.length * s ]; -- index into histogram value: REAL _ numbers[j]; numVals: REAL _ 0; IF j - lastJ > 1 THEN { FOR k: INT IN (lastJ .. j) DO value _ value + numbers[k]; IF numbers[k] > 0 THEN numVals _ numVals + 1; ENDLOOP; value _ value / MAX[numVals, 1]; }; s _ 0.75 * s; -- distance normalized to 3/4 screen context.class.draw2DLine[ context, [startWd+s, startHt], [startWd+s, startHt + ( value / (4*2048) )], [255,255,255,0,0] ]; lastJ _ j; ENDLOOP; }; maxScanSeg, maxPolyHeight, maxPixelsPerPoly, total, count, largest: INT _ 0; FOR i: NAT IN [0..pixelsPerPolyHist.length) DO IF pixelsPerPolyHist[i] > maxPixelsPerPoly THEN maxPixelsPerPoly _ pixelsPerPolyHist[i]; total _ total + i * pixelsPerPolyHist[i]; IF pixelsPerPolyHist[i] > 0 THEN largest _ i ENDLOOP; pixelsPerPolyHist.length _ largest+1; avePixelsPerPoly _ total / polyCount; ShowData[0.125, 0.1, pixelsPerPolyHist, "Polygon size"]; total _ largest _ count _ 0; FOR i: NAT IN [0..scanSegLengthHist.length) DO IF scanSegLengthHist[i] > maxScanSeg THEN maxScanSeg _ scanSegLengthHist[i]; total _ total + i * scanSegLengthHist[i]; count _ count + scanSegLengthHist[i]; IF scanSegLengthHist[i] > 0 THEN largest _ i ENDLOOP; scanSegLengthHist.length _ largest+1; aveScanSegLength _ total / count; ShowData[0.125, 0.4, scanSegLengthHist, "Scan segment length"]; total _ largest _ count _ 0; FOR i: NAT IN [0..polyHeightHist.length) DO IF polyHeightHist[i] > maxPolyHeight THEN maxPolyHeight _ polyHeightHist[i]; total _ total + i * polyHeightHist[i]; count _ count + polyHeightHist[i]; IF polyHeightHist[i] > 0 THEN largest _ i ENDLOOP; polyHeightHist.length _ largest+1; avePolyHeight _ total / count; ShowData[0.125, 0.7, polyHeightHist, "Polygon height"]; }; Swap: PROCEDURE [first, second: INTEGER] RETURNS [INTEGER, INTEGER] = { RETURN [second, first]; }; SGN: PROCEDURE [number: INTEGER] RETURNS [INTEGER] = INLINE { IF number > 0 THEN RETURN[1] ELSE IF number < 0 THEN RETURN[-1] ELSE RETURN[0]; }; InlineIncr: PROC[edge: EdgeDesc, array: REF EdgeSequence _ NIL] RETURNS[EdgeDesc] ~ INLINE { edge.val _ edge.val + edge.lngthIncr; edge.bias _ edge.bias - 2*edge.hiccups; IF edge.bias <= 0 THEN { edge.val _ edge.val + edge.hicIncr; edge.bias _ edge.bias + 2*edge.length; }; edge.stepsLeft _ edge.stepsLeft - 1; IF edge.stepsLeft <= 0 -- Get next linked edge if this one exhausted THEN IF edge.nextEdge # 0 THEN edge _ array[edge.nextEdge]; RETURN[edge]; }; 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 ]]]; }; GetEdgeSeq: ENTRY PROC[length: NAT] RETURNS[REF EdgeSequence] ~ { ENABLE UNWIND => NULL; edges: REF EdgeSequence _ NIL; IF edgeSequenceCachePtr > 0 THEN { edgeSequenceCachePtr _ edgeSequenceCachePtr - 1; edges _ edgeSequenceCache[edgeSequenceCachePtr]; edgeSequenceCache[edgeSequenceCachePtr] _ NIL; }; IF edges = NIL OR length > edges.maxLength THEN edges _ NEW[ EdgeSequence[length] ]; edges.length _ length; RETURN[ edges ]; }; ReleaseEdgeSeq: ENTRY PROC[edges: REF EdgeSequence] ~ { ENABLE UNWIND => NULL; IF edgeSequenceCachePtr < numEdgeSequences THEN { edgeSequenceCache[edgeSequenceCachePtr] _ edges; FOR i: NAT IN [0..edgeSequenceCachePtr) DO IF edges = edgeSequenceCache[i] THEN SIGNAL G3dRender.Error[$Fatal, "Multiple release of EdgeSeq"]; ENDLOOP; edgeSequenceCachePtr _ edgeSequenceCachePtr + 1; }; }; Sqr: PROCEDURE [number: INT16] RETURNS [INT32] ~ INLINE { RETURN[ number * INT32[number] ]; }; Log2: PROC [n: INT] RETURNS [lg: NAT _ 0] ~ { -- finds log base 2 of input (from M. Plass) nn: CARD32 ~ n; k: CARD32 _ 1; UNTIL k=0 OR k>= nn DO lg _ lg + 1; k _ k + k; ENDLOOP; }; Power: PUBLIC PROC[ value: CARD16, power: NAT] RETURNS[ result: CARD16 ] ~ { binaryCount: NAT _ 2*power; -- makes highlights same size as those by ShadePt temp: CARD32 _ 65536; val: CARD32 _ value; IF power = 0 THEN RETURN[65535]; WHILE binaryCount > 0 DO -- compute power by repeated squares IF Basics.BITAND[binaryCount, 1] = 1 THEN temp _ Basics.HighHalf[temp * val]; val _ Basics.HighHalf[val * val]; binaryCount _ binaryCount/2; ENDLOOP; result _ temp; }; SampleMapBase: PROC[samples: SampleMap] RETURNS[CARD32, WORD] ~ { bytesPerLine: WORD; baseByteAddr: CARD32; WITH samples SELECT FROM raster: ImagerSample.RasterSampleMap => { base: PrincOps.BitAddress _ ImagerSample.GetBase[raster]; baseByteAddr _ CARD32[ Basics.DoubleShift[LOOPHOLE[base.word], 1].li + Basics.BITSHIFT[base.bit, -3] ]; bytesPerLine _ Basics.BITSHIFT[ImagerSample.GetBitsPerLine[raster], -3]; }; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "only RasterSampleMaps here"]; RETURN[ baseByteAddr, bytesPerLine]; }; DoForPixelColors: PROC[ context: Context, action: PROC[dstAddr: CARD32, xStep, yStep: WORD, element: PixelPart] ] ~ { base: CARD32; bpl: WORD; depth, alpha: REF NAT; SELECT context.class.displayType FROM $FullColor => { [base, bpl] _ SampleMapBase[context.pixels[0]]; action[base, 1, bpl, r]; [base, bpl] _ SampleMapBase[context.pixels[1]]; action[base, 1, bpl, g]; [base, bpl] _ SampleMapBase[context.pixels[2]]; action[base, 1, bpl, b]; }; $PseudoColor, $Gray => { [base, bpl] _ SampleMapBase[context.pixels[0]]; action[base, 1, bpl, r]; }; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Unexpected display type"]; alpha _ NARROW[ GetProp[context.displayProps, $Alpha] ]; IF alpha # NIL THEN { [base, bpl] _ SampleMapBase[context.pixels[alpha^]]; action[base, 2, bpl, a]; }; depth _ NARROW[ GetProp[context.displayProps, $Depth] ]; IF depth # NIL THEN { [base, bpl] _ SampleMapBase[context.pixels[depth^]]; action[base, 2, bpl, z]; }; }; ShowCoords: PROC[poly: REF Patch] RETURNS[list: LIST OF REF IntegerPair] ~ { FOR i: CARD16 DECREASING IN [0..poly.nVtces) DO p: REF IntegerPair _ NEW[IntegerPair _ [Round[poly[i].coord.sx], Round[poly[i].coord.sy]]]; list _ CONS[p, list]; ENDLOOP; RETURN[list]; }; MappedRGB: PUBLIC PROC[renderMode: ATOM, clr: RGB] RETURNS[NAT] ~ { SELECT renderMode FROM $Dithered => { mapVal: NAT _ Fix[clr.R*4.999]*24 + Fix[clr.G*5.999]*4 + Fix[clr.B*3.999]; IF mapVal >= 60 THEN mapVal _ mapVal + 135; -- move to top of map RETURN[ mapVal ]; }; $PseudoColor => RETURN[ Fix[clr.R*5.999]*42 + Fix[clr.G*6.999]*6 + Fix[clr.B*5.999] + 2 ]; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Bad Render Mode"]; 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 G3dRender.Error[$MisMatch, "Bad Render Mode"]; RETURN[[255, 255, 255]]; }; DoWithPixels: PUBLIC PROC [ context: Context, start: IntegerPair, length: NAT, proc: PixelBufferProc ] ~ { pixels: PixelBuffer _ ImagerPixel.ObtainScratchPixels[ context.pixels.samplesPerPixel, length ]; fMin: INTEGER _ context.pixels.box.min.f; sMin: INTEGER _ context.pixels.box.min.s; initIndex: SF.Vec _ [ f: MAX[fMin, start.x + fMin], s: start.y + sMin ]; delta: SF.Vec _ [ f: 1, s: 0 ]; ImagerPixel.GetPixels[ -- get pixels for segment self: context.pixels, pixels: pixels, initIndex: initIndex, delta: delta, count: length ]; proc[pixels]; -- call back with requested pixels ImagerPixel.PutPixels[ -- return modified pixels self: context.pixels, pixels: pixels, initIndex: initIndex, delta: delta, count: length ]; ImagerPixel.ReleaseScratchPixels[pixels]; }; Write: PROC [ dstVal, srcVal: INT32 ] ~ INLINE { dstAddr: LONG POINTER _ LOOPHOLE[Basics.DoubleShiftRight[LOOPHOLE[dstVal], 1]]; srcValue: BYTE _ LOOPHOLE[srcVal, LongNumber.bytes].ll; -- lo byte, lo half of long IF Basics.DoubleAnd[LOOPHOLE[dstVal], longOne] # longZero -- lo byte? (right pixel) THEN TRUSTED { LOOPHOLE[dstAddr^, BytePair].low _ srcValue; } -- right pixel ELSE TRUSTED { LOOPHOLE[dstAddr^, BytePair].high _ srcValue; }; -- left pixel }; Dither: PROC [ dst, r, g, b, scanline: INT32 ] ~ { dstAddr: LONG POINTER TO INT16 _ LOOPHOLE[Basics.DoubleShiftRight[LOOPHOLE[dst], 1]]; srcValue: BYTE; red: INT16 _ LOOPHOLE[r, LongNumber.bytes].ll; -- lowest byte grn: INT16 _ LOOPHOLE[g, LongNumber.bytes].ll; -- of longword blu: INT16 _ LOOPHOLE[b, LongNumber.bytes].ll; y: NAT _ Basics.BITAND[scanline, 3]; x: NAT _ Basics.BITAND[Basics.LowHalf[dst], 3]; threshold: NAT _ ditherTable[x][y]; -- calculate x and y table coordinates (4 x 4 table) 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, val2G, val2B: NAT; 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; srcValue _ 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 IF Basics.DoubleAnd[LOOPHOLE[dst], longOne] # longZero -- lo byte? (right pixel) THEN TRUSTED { LOOPHOLE[dstAddr^, BytePair].low _ srcValue; } -- right pixel ELSE TRUSTED { LOOPHOLE[dstAddr^, BytePair].high _ srcValue; }; -- left pixel }; Mat: PROC [ rDst, gDst, bDst, aDst, r, g, b, a: INT32 ] ~ { }; DepthLess: PROC [ zDst, zSrc: INT32 ] RETURNS [ BOOLEAN ] ~ TRUSTED { -- INLINE dstAddr: LONG POINTER TO CARD16 _ LOOPHOLE[ Basics.DoubleShiftRight[LOOPHOLE[zDst], 1]]; IF dstAddr^ < LOOPHOLE[zSrc, LongNumber.pair].lo THEN RETURN[FALSE] -- old value closer ELSE { dstAddr^ _ LOOPHOLE[zSrc, LongNumber.pair].lo; RETURN[TRUE]; }; }; PutLine: PUBLIC PROC [context: Context, p1, p2: IntegerPair, color1, color2: Pixel ] ~ { DoLine: PROC[dstAddr: CARD32, xStep, yStep: WORD, element: PixelPart] ~ { clrValue1: CARD16 _ SELECT element FROM r => color1[r], g => color1[g], b => color1[b], a => color1[a], z => color1[z], ENDCASE => ERROR; clrValue2: CARD16 _ SELECT element FROM r => color2[r], g => color2[g], b => color2[b], a => color2[a], z => color2[z], ENDCASE => ERROR; src, dst: G3dEdgeBlt.EdgeDesc; dstAddr _ dstAddr + p1.y * INT[yStep] + p1.x; -- from base to line endpt IF tall THEN dst _ [ val: dstAddr, length: ABS[height], hiccups: ABS[width], lngthIncr: yStep * SGN[height], hicIncr: xStep * SGN[width] ] ELSE dst _ [ val: dstAddr, length: ABS[width], hiccups: ABS[height], lngthIncr: xStep * SGN[width], hicIncr: yStep * SGN[height] ]; IF clrValue1 = clrValue2 OR dst.length = 0 THEN src _ [ val: clrValue1, length: 0, indirect: FALSE ] -- constant shade along line ELSE { clrDiff: INTEGER _ INTEGER[clrValue2] - INTEGER[clrValue1]; -- varying shade src.val _ clrValue1; src.length _ dst.length; [src.lngthIncr, src.hiccups] _ Basics.DivMod[ABS[clrDiff], dst.length]; IF clrDiff < 0 -- color moving negatively? THEN { src.lngthIncr _ -src.lngthIncr; src.hicIncr _ -1; } ELSE src.hicIncr _ 1; src.indirect _ FALSE; }; G3dEdgeBlt.Blt[[dst, src]]; -- draw line including endpoints }; box: Box _ context.pixels.box; checkLimits: NAT; tall: BOOLEAN _ FALSE; length, hiccups: NAT; width: INTEGER _ p2.x - p1.x; height: INTEGER _ p2.y - p1.y; IF ABS[width] > ABS[height] THEN { length _ ABS[width]; hiccups _ ABS[height]; tall _ FALSE; } ELSE { length _ ABS[height]; hiccups _ ABS[width]; tall _ TRUE; }; box _ [ [0, 0], [box.max.s - box.min.s - 1, box.max.f - box.min.f - 1] ]; -- normalize box checkLimits _ p1.x-box.min.f; checkLimits _ p2.x-box.min.f; -- raise bounds error if < 0 checkLimits _ p1.y-box.min.s; checkLimits _ p2.y-box.min.s; checkLimits _ box.max.f-p1.x; checkLimits _ box.max.f-p2.x; checkLimits _ box.max.s-p1.y; checkLimits _ box.max.s-p2.y; DoForPixelColors[context, DoLine]; }; MakeDstEdge: PROC[bot, top: CtlPoint, dstAddr: CARD32, xStep, yStep: WORD] RETURNS[edge: EdgeDesc] ~ { pLo: IntegerPair _ [ Round[bot.sx], Round[bot.sy] ]; pHi: IntegerPair _ [ Round[top.sx], Round[top.sy] ]; width: INTEGER _ pHi.x - pLo.x; length: INTEGER _ pHi.y - pLo.y; IF length <= 0 THEN { edge.length _ 0; RETURN[edge]; }; edge.val _ dstAddr + pLo.y * INT[yStep] + pLo.x * INT[xStep]; -- from base to edge bottom edge.length _ length; edge.hiccups _ ABS[width]; [edge.lngthIncr, edge.hiccups] _ Basics.DivMod[ABS[width], edge.length]; IF width < 0 -- x-position moving negatively? THEN { edge.lngthIncr _ -edge.lngthIncr; edge.hicIncr _ -xStep; } ELSE edge.hicIncr _ xStep; edge.lngthIncr _ edge.lngthIncr * xStep + yStep; -- add in scanline to scanline step size edge.bias _ edge.length; -- initial bias to center hiccups edge.stepsLeft _ edge.length; -- set stepsLeft for count down }; MakeSrcEdge: PROC[bot, top: CtlPtInfo, element: PixelPart] RETURNS[edge: EdgeDesc] ~ { yStart: CARD16 _ Round[bot.coord.sy]; yEnd: CARD16 _ Round[top.coord.sy]; length: INTEGER _ INTEGER[yEnd] - INTEGER[yStart]; srcStart, srcEnd: CARD16; width: INT16; IF length <= 0 THEN { edge.length _ 0; RETURN[edge]; }; SELECT element FROM r => { srcStart _ Fix[bot.shade.er * 255.0]; srcEnd _ Fix[top.shade.er * 255.0]; }; g => { srcStart _ Fix[bot.shade.eg * 255.0]; srcEnd _ Fix[top.shade.eg * 255.0]; }; b => { srcStart _ Fix[bot.shade.eb * 255.0]; srcEnd _ Fix[top.shade.eb * 255.0]; }; a => { srcStart _ Fix[bot.shade.et * 255.0]; srcEnd _ Fix[top.shade.et * 255.0]; }; z => { srcStart _ Round[bot.coord.sz]; srcEnd _ Round[top.coord.sz]; }; ENDCASE => ERROR; width _ srcEnd - INT32[srcStart]; edge.val _ srcStart; -- initial value edge.length _ length; edge.hiccups _ ABS[width]; [edge.lngthIncr, edge.hiccups] _ Basics.DivMod[ABS[width], edge.length]; IF width < 0 -- color moving negatively? THEN { edge.lngthIncr _ -edge.lngthIncr; edge.hicIncr _ -1; } ELSE edge.hicIncr _ 1; edge.bias _ edge.length; -- initial bias to center hiccups edge.stepsLeft _ edge.length; -- set stepsLeft for count down }; LinkEdges: PROC [ poly: REF Patch, box: Box] RETURNS [LinkedPoly] ~ { checkLimits: NAT; lft, rgt: LIST OF CARD16 _ NIL; least, most: REAL _ poly[0].coord.sy; topVtx, botVtx, vtx: CARD16 _ 0; box _ [ [0, 0], [box.max.s - box.min.s - 1, box.max.f - box.min.f - 1] ]; -- normalize box FOR i: CARD16 IN [1..poly.nVtces) DO -- find top and bottom vertex p: IntegerPair _ [ Round[poly[i].coord.sx], Round[poly[i].coord.sy] ]; checkLimits _ p.y-box.min.s; checkLimits _ p.x-box.min.f; -- check bounds checkLimits _ box.max.s-p.y; checkLimits _ box.max.f-p.x; IF poly[i].coord.sy < least THEN { least _ poly[i].coord.sy; botVtx _ i; } ELSE IF poly[i].coord.sy > most THEN { most _ poly[i].coord.sy; topVtx _ i; }; ENDLOOP; IF Fix[most] > Fix[least] THEN { vtx _ topVtx; lft _ CONS[topVtx, lft]; WHILE vtx # botVtx DO -- walk down left side vtx _ vtx + 1; IF vtx = poly.nVtces THEN vtx _ 0; lft _ CONS[ vtx, lft ]; ENDLOOP; vtx _ topVtx; rgt _ CONS[topVtx, rgt]; WHILE vtx # botVtx DO -- walk down right side vtx _ IF vtx = 0 THEN poly.nVtces - 1 ELSE vtx - 1; rgt _ CONS[vtx, rgt]; ENDLOOP; }; RETURN[ [lft, rgt] ]; }; LinkDst: PROC[ dst: REF EdgeSequence, links: LIST OF CARD16, plygn: REF Patch, ptr, size: NAT, dstAddr: CARD32, xStep, yStep: WORD] ~ { FOR list: LIST OF CARD16 _ links, list.rest UNTIL list.rest = NIL DO dst[ptr] _ MakeDstEdge[ plygn[list.first].coord, plygn[list.rest.first].coord, dstAddr, xStep, yStep ]; IF dst[ptr].length > 0 THEN { IF ptr - size >= 0 THEN dst[ptr-size].nextEdge _ ptr; -- link to last edge ptr _ ptr + size; }; ENDLOOP; }; LinkSrc: PROC[ src: REF EdgeSequence, links: LIST OF CARD16, plygn: REF Patch, ptr, size: NAT, element: PixelPart] ~ { FOR list: LIST OF CARD16 _ links, list.rest UNTIL list.rest = NIL DO src[ptr] _ MakeSrcEdge[ plygn[list.first], plygn[list.rest.first], element ]; IF src[ptr].length > 0 THEN { IF ptr - size >= 0 THEN src[ptr-size].nextEdge _ ptr; -- link to last edge ptr _ ptr + size; }; ENDLOOP; }; FastFlatTiler: PUBLIC PROC [context: Context, plygn: REF Patch, color: Pixel] ~ { DoPoly: PROC[dstAddr: CARD32, xStep, yStep: WORD, element: PixelPart] ~ { lft: REF EdgeSequence _ GetEdgeSeq[plygn.nVtces]; rgt: REF EdgeSequence _ GetEdgeSeq[plygn.nVtces]; clrValue: CARD16 _ SELECT element FROM r => color[r], g => color[g], b => color[b], z => color[z], ENDCASE => ERROR; IF element # z -- load high half of word to make brick for bitblt THEN LOOPHOLE[clrValue, Basics.ShortNumber].hi _ clrValue; LinkDst[lft, links.lftVtces, plygn, 0, 1, dstAddr, xStep, yStep]; -- chains for g3dEdgeBlt LinkDst[rgt, links.rgtVtces, plygn, 0, 1, dstAddr, xStep, yStep]; IF lft[0].length # 0 AND rgt[0].length # 0 THEN TRUSTED { -- not a zero-height poly bb^ _ constantBitBltTable; -- set up constant values in BitBlt table bb.dstBpl _ Basics.BITSHIFT[yStep, 3]; bb.src.word _ @clrValue; WHILE TRUE DO -- write scan segment with bitblt, then bump to next edge width: INT16 _ rgt[0].val - lft[0].val; IF width < 0 OR width > INT16[yStep] THEN width _ 0; -- twisted polygon bb.dst _ [word: LOOPHOLE[Basics.DoubleShiftRight[LOOPHOLE[lft[0].val], 1]], bit: Basics.BITSHIFT[ Basics.BITAND[ Basics.LowHalf[lft[0].val], 1], 3] ]; bb.width _ Basics.BITSHIFT[width + 1, 3]; -- convert from bytes to bits PrincOpsUtils.BITBLT[bb]; -- do bitblt lft[0] _ InlineIncr[lft[0], lft]; rgt[0] _ InlineIncr[rgt[0], rgt]; -- incr. edges IF lft[0].stepsLeft = 0 OR rgt[0].stepsLeft = 0 THEN EXIT ENDLOOP; }; ReleaseEdgeSeq[lft]; ReleaseEdgeSeq[rgt]; }; links: LinkedPoly; bbspace: PrincOps.BBTableSpace; -- get BitBlt table bb: PrincOps.BitBltTablePtr; TRUSTED { bb _ PrincOpsUtils.AlignedBBTable[@bbspace]; }; SELECT context.class.displayType FROM $PseudoColor => color[r] _ 42 * (color[r] * 6 / 256) + 6 * (color[g] * 7 / 256) + (color[b] * 6 / 256) +2; $Gray => color[r] _ (color[r] + color[g] + color[b]) / 3; ENDCASE; links _ LinkEdges[ plygn, context.pixels.box ]; -- link vertices into left and right side chains IF links.lftVtces # NIL THEN DoForPixelColors[context, DoPoly]; }; ScanPoly: PROC [context: Context, lftDst, lftSrc, rgtDst, rgtSrc: REF EdgeSequence, dstSize, srcSize, dstDpth, srcDpth, scanline: NAT, pixelProc: PROC[scanDst, scanSrc: REF EdgeSequence, scanline: NAT] _ NIL] ~ { polyHeight, pixelCount: NAT _ 0; dithering: BOOLEAN _ IF context.class.displayType = $PseudoColor THEN TRUE ELSE FALSE; clrsPerPixel: NAT _ IF context.class.displayType = $FullColor THEN 3 ELSE 1; scanSrc: REF EdgeSequence _ GetEdgeSeq[srcSize]; scanDst: REF EdgeSequence _ GetEdgeSeq[dstSize]; IF lftDst[0].length # 0 AND rgtDst[0].length # 0 THEN WHILE TRUE DO segLength: INT16 _ rgtDst[0].val - lftDst[0].val + 1; IF segLength <= 0 OR segLength > lftDst[0].lngthIncr THEN EXIT; -- twisted or backfacing FOR i: NAT IN [0..dstSize) DO -- build scan segment destinations scanDst[i] _ [ val: lftDst[i].val, length: segLength, lngthIncr: ABS[lftDst[i].hicIncr] ]; scanDst[i].bias _ scanDst[i].stepsLeft _ segLength; ENDLOOP; FOR i: NAT IN [0..srcSize) DO -- build scan segment sources width: INT16 _ rgtSrc[i].val - lftSrc[i].val; scanSrc[i] _ [val: lftSrc[i].val, length: segLength, indirect: FALSE ]; [scanSrc[i].lngthIncr, scanSrc[i].hiccups] _ Basics.DivMod[ABS[width], segLength]; IF width < 0 -- color/z moving negatively? THEN { scanSrc[i].lngthIncr _ -scanSrc[i].lngthIncr; scanSrc[i].hicIncr _ -1; } ELSE scanSrc[i].hicIncr _ 1; scanSrc[i].bias _ scanSrc[i].stepsLeft _ segLength; ENDLOOP; IF statistics THEN { -- gather statistics on polygon sizes polyHeight _ polyHeight + 1; pixelCount _ pixelCount + segLength; IF CARD16[segLength] >= scanSegLengthHist.length THEN segLength _ scanSegLengthHist.length - 1; -- catch overflowing values scanSegLengthHist[segLength] _ scanSegLengthHist[segLength] + 1; -- histogram }; WHILE TRUE DO -- Do scan segment, pixel by pixel IF NOT context.depthBuffering OR DepthLess[scanDst[dstDpth].val, scanSrc[srcDpth].val] THEN { -- test and put z IF pixelProc # NIL THEN { pixelProc[scanDst, scanSrc, scanline]; IF context.stopMe^ THEN RETURN; } ELSE IF dithering THEN Dither[ scanDst[0].val, scanSrc[0].val, scanSrc[1].val, scanSrc[2].val, scanline ] ELSE FOR i: NAT IN[0..clrsPerPixel) DO Write[scanDst[i].val, scanSrc[i].val]; ENDLOOP; }; FOR i: NAT IN [0..srcSize) DO scanSrc[i] _ InlineIncr[scanSrc[i]]; ENDLOOP; FOR i: NAT IN [0..dstSize) DO scanDst[i] _ InlineIncr[scanDst[i]]; ENDLOOP; IF scanDst[0].stepsLeft = 0 THEN EXIT ENDLOOP; FOR i: NAT IN [0..srcSize) DO lftSrc[i] _ InlineIncr[lftSrc[i], lftSrc]; rgtSrc[i] _ InlineIncr[rgtSrc[i], rgtSrc]; ENDLOOP; FOR i: NAT IN [0..dstSize) DO lftDst[i] _ InlineIncr[lftDst[i], lftDst]; rgtDst[i] _ InlineIncr[rgtDst[i], rgtDst]; ENDLOOP; IF lftDst[0].stepsLeft = 0 OR rgtDst[0].stepsLeft = 0 THEN EXIT; scanline _ scanline + 1; ENDLOOP; ReleaseEdgeSeq[scanSrc]; ReleaseEdgeSeq[scanDst]; IF statistics THEN { -- gather statistics on polygon sizes polyCount _ polyCount + 1; IF polyHeight >= polyHeightHist.length THEN polyHeight _ polyHeightHist.length - 1; polyHeightHist[polyHeight] _ polyHeightHist[polyHeight] + 1; IF pixelCount >= pixelsPerPolyHist.length THEN pixelCount _ pixelsPerPolyHist.length - 1; pixelsPerPolyHist[pixelCount] _ pixelsPerPolyHist[pixelCount] + 1; }; }; LerpTiler: PUBLIC PROC [context: Context, plygn: REF Patch] ~ { lftSrc, rgtSrc, lftDst, rgtDst: REF EdgeSequence _ NIL; scanline, srcSize, dstSize, dstDpth, srcDpth, srcPos: NAT _ 0; srcTmplate: Pixel _ IF context.class.displayType = $Gray THEN [1,0,0,0,0] ELSE [1,1,1,0,0]; dstTmplate: Pixel _ IF context.class.displayType = $FullColor THEN [1,1,1,0,0] ELSE [1,0,0,0,0]; dstBufPos: Pixel _ [0,1,2,0,0]; alpha: REF NAT _ NARROW[ GetProp[context.displayProps, $Alpha]]; depth: REF NAT _ NARROW[ GetProp[context.displayProps, $Depth]]; links: LinkedPoly; IF alpha # NIL THEN { dstBufPos[a] _ alpha^; srcTmplate[a] _ dstTmplate[a] _ 2; }; IF depth # NIL THEN { dstBufPos[z] _ depth^; srcTmplate[z] _ dstTmplate[z] _ 2; }; FOR i: PixelPart IN PixelPart DO IF srcTmplate[i] > 0 THEN srcSize _ srcSize + 1; IF dstTmplate[i] > 0 THEN dstSize _ dstSize + 1; ENDLOOP; IF depth # NIL THEN { dstDpth _ dstSize-1; srcDpth _ srcSize-1; }; links _ LinkEdges[ plygn, context.pixels.box ]; -- link vertices into left and right side chains IF links.lftVtces # NIL THEN { -- if non-zero height scanline _ Fix[plygn[links.lftVtces.first].coord.sy]; -- get first scanline lftSrc _ GetEdgeSeq[srcSize*plygn.nVtces]; rgtSrc _ GetEdgeSeq[srcSize*plygn.nVtces]; lftDst _ GetEdgeSeq[dstSize*plygn.nVtces]; rgtDst _ GetEdgeSeq[dstSize*plygn.nVtces]; FOR i: PixelPart IN PixelPart DO --Get Edge records for left and right sides IF dstTmplate[i] > 0 THEN { dstAddr, yStep: CARD32; [dstAddr, yStep] _ SampleMapBase[context.pixels[dstBufPos[i]]]; LinkDst[ lftDst, links.lftVtces, plygn, dstBufPos[i], dstSize, dstAddr, dstTmplate[i], yStep ]; LinkDst[ rgtDst, links.rgtVtces, plygn, dstBufPos[i], dstSize, dstAddr, dstTmplate[i], yStep ]; }; IF srcTmplate[i] > 0 THEN { LinkSrc[lftSrc, links.lftVtces, plygn, srcPos, srcSize, i ]; LinkSrc[rgtSrc, links.rgtVtces, plygn, srcPos, srcSize, i ]; srcPos _ srcPos + 1; }; ENDLOOP; ScanPoly[context, lftDst, lftSrc, rgtDst, rgtSrc, dstSize, srcSize, dstDpth, srcDpth, scanline]; ReleaseEdgeSeq[lftSrc]; ReleaseEdgeSeq[rgtSrc]; ReleaseEdgeSeq[lftDst]; ReleaseEdgeSeq[ rgtDst]; }; }; justNoticeable: NAT ~ Round[G3dScanConvert.justNoticeable * 65536]; spot: REF Spot _ NIL; HiliteTiler: PUBLIC PROC [context: Context, plygn: REF Patch, shininess: NAT] ~ { SimpleTexture: PROC[ red, grn, blu: CARD16, map: ImagerPixel.PixelMap, type: ATOM, ix, iy: INTEGER ] RETURNS [ newRed, newGrn, newBlu: CARD16 ] ~ { GetTxtrAddress: PROC[buf: ImagerPixel.PixelMap, ix, iy: INTEGER] RETURNS[ txtrX, txtrY: INTEGER ] ~ { x: REAL _ 1.0 * ix / (LAST[NAT]/32); y: REAL _ 1.0 * iy / (LAST[NAT]/32); bufWidth: NAT _ buf.box.max.f - buf.box.min.f; bufHeight: NAT _ buf.box.max.s - buf.box.min.s; txtrX _ Fix[x * bufWidth] MOD bufWidth; IF txtrX < 0 THEN txtrX _ txtrX + bufWidth; txtrY _ Fix[y * bufHeight] MOD bufHeight; IF txtrY < 0 THEN txtrY _ txtrY + bufHeight; }; pixel: ImagerPixel.PixelBuffer _ ImagerPixel.ObtainScratchPixels[map.samplesPerPixel, 1]; x, y: INTEGER; [x, y] _ GetTxtrAddress[map, ix, iy]; ImagerPixel.GetPixels[self: map, pixels: pixel, initIndex: [f: x, s: y+map.box.min.s], count: 1]; SELECT type FROM $Color => { red _ red * pixel[0][0] / 256; grn _ grn * pixel[1][0] / 256; blu _ blu * pixel[2][0] / 256; }; $Intensity => { red _ red * pixel[0][0] / 256; grn _ grn * pixel[0][0] / 256; blu _ blu * pixel[0][0] / 256; }; ENDCASE => SIGNAL G3dRender.Error[ $MisMatch, "Unknown texture type, or antialiasing needed" ]; ImagerPixel.ReleaseScratchPixels[pixel]; RETURN[red, grn, blu]; }; TextureFn: PROC[ red, grn, blu: CARD16, txtrFn: REF TextureFunction, x, y, z, dx, dy, dz: INT ] RETURNS [ newRed, newGrn, newBlu: CARD16 ] ~ { IF spot = NIL THEN { spot _ NEW[Spot]; spot.val _ NEW[RealSequenceRep[6]]; spot.val.length _ 6; spot.yIncr _ NEW[RealSequenceRep[6]]; spot.yIncr.length _ 6; spot.xIncr _ NEW[RealSequenceRep[6]]; spot.xIncr.length _ 6; spot.yIncr[3] _ spot.yIncr[4] _ spot.yIncr[5] _ 0.0; spot.xIncr[3] _ spot.xIncr[4] _ spot.xIncr[5] _ 0.0; }; spot.val[0] _ red/255.0; spot.val[1] _ grn/255.0; spot.val[2] _ blu/255.0; spot.val[3] _ 1.0 * x / (LAST[NAT]/32); spot.val[4] _ 1.0 * y / (LAST[NAT]/32); spot.val[5] _ 1.0 * z / (LAST[NAT]/32); spot.xIncr[3] _ (1.0 * dx / (LAST[NAT]/32)) / (LAST[NAT]/32); spot.xIncr[4] _ (1.0 * dy / (LAST[NAT]/32)) / (LAST[NAT]/32); spot.xIncr[5] _ (1.0 * dz / (LAST[NAT]/32)) / (LAST[NAT]/32); txtrFn.proc[context, plygn.renderData.shadingClass, spot, txtrFn.props]; RETURN[ Real.Round[spot.val[0]*255], Real.Round[spot.val[1]*255], Real.Round[spot.val[2]*255] ]; }; Hilight: PROC[ scanDst, scanSrc: REF EdgeSequence, scanline: NAT ] ~ { red, grn, blu: CARD16 _ 0; hltRed, hltGrn, hltBlu: CARD16 _ 0; reflStart: NAT _ srcSize - hltCnt * 2; hltStart: NAT _ 0; noticeableHilite: BOOLEAN _ FALSE; red _ scanSrc[0].val; IF NOT gray THEN { -- if gray, scanSrc[1].val, scanSrc[2].val are not grn & blu grn _ scanSrc[1].val; blu _ scanSrc[2].val; }; IF texture THEN { WITH plygn.renderData.shadingClass.texture.first SELECT FROM -- only one texture txtrMap: REF TextureMap => -- modify with mapped texture WITH txtrMap.pixels SELECT FROM buf: ImagerPixel.PixelMap => [red, grn, blu] _ SimpleTexture[ red, grn, blu, buf, txtrMap.type, scanSrc[reflStart-3].val, scanSrc[reflStart-2].val ]; ENDCASE => G3dRender.Error[$MisMatch, "Only simple mapped texture"]; txtrFn: REF TextureFunction => { -- solid or other function-based texture j: NAT _ reflStart-3; s: INT _ LAST[NAT]/32; [red, grn, blu] _ TextureFn[ red, grn, blu, txtrFn, scanSrc[j].val, scanSrc[j+1].val, scanSrc[j+2].val, s*scanSrc[j].lngthIncr + s*(scanSrc[j].hicIncr * scanSrc[j].hiccups)/scanSrc[j].length, s*scanSrc[j+1].lngthIncr + s*(scanSrc[j+1].hicIncr * scanSrc[j+1].hiccups)/scanSrc[j+1].length, s*scanSrc[j+2].lngthIncr + s*(scanSrc[j+2].hicIncr * scanSrc[j+2].hiccups)/scanSrc[j+2].length ]; }; ENDCASE => SIGNAL G3dRender.Error[$Unimplemented, "Unexpected texture type"]; }; FOR i: NAT IN [hltStart..hltCnt) DO j: NAT _ reflStart + i * 2; sqrX: INT32 _ Sqr[ scanSrc[j].val*2 ]; -- scale to full card range from half integer sqrY: INT32 _ Sqr[ scanSrc[j+1].val*2 ]; pctHilite: CARD32 _ Power[ 2 * Basics.HighHalf[LAST[INT32] - (sqrX+sqrY)], shininess ]; IF pctHilite > justNoticeable THEN { -- Scale light color by hilite strength hltRed _ MIN[ hltRed + CARD32[Basics.HighHalf[pctHilite * lightColor[i].r]], 65535 ]; hltGrn _ MIN[ hltGrn + CARD32[Basics.HighHalf[pctHilite * lightColor[i].g]], 65535 ]; hltBlu _ MIN[ hltBlu + CARD32[Basics.HighHalf[pctHilite * lightColor[i].b]], 65535 ]; noticeableHilite _ TRUE; }; ENDLOOP; IF noticeableHilite THEN { hltRed _ Basics.HighByte[hltRed]; hltGrn _ Basics.HighByte[hltGrn]; hltBlu _ Basics.HighByte[hltBlu]; IF gray THEN hltRed _ (hltRed + hltGrn + hltBlu) / 3; red _ MIN[ red + Basics.HighByte[(255 - red) * 2 * hltRed], 255 ]; IF NOT gray THEN { grn _ MIN[ grn + Basics.HighByte[(255 - grn) * 2 * hltGrn], 255 ]; blu _ MIN[ blu + Basics.HighByte[(255 - blu) * 2 * hltBlu], 255 ]; }; }; IF dithering THEN Dither[ scanDst[0].val, red, grn, blu, scanline ] ELSE IF gray THEN Write[scanDst[0].val, red] ELSE { Write[scanDst[0].val, red]; Write[scanDst[1].val, grn]; Write[scanDst[2].val, blu]; }; }; lftSrc, rgtSrc, lftDst, rgtDst: REF EdgeSequence _ NIL; scanline, srcSize, dstSize, dstDpth, srcDpth, srcPos: NAT _ 0; lx, ly, lz: REAL _ 0.0; -- for texture functions srcTmplate: Pixel _ IF context.class.displayType = $Gray THEN [1,0,0,0,0] ELSE [1,1,1,0,0]; dstTmplate: Pixel _ IF context.class.displayType = $FullColor THEN [1,1,1,0,0] ELSE [1,0,0,0,0]; dstBufPos: Pixel _ [0,1,2,0,0]; alpha: REF NAT _ NARROW[ GetProp[context.displayProps, $Alpha] ]; depth: REF NAT _ NARROW[ GetProp[context.displayProps, $Depth] ]; dithering: BOOLEAN _ IF context.class.displayType = $PseudoColor THEN TRUE ELSE FALSE; gray: BOOLEAN _ IF context.class.displayType = $Gray THEN TRUE ELSE FALSE; texture: BOOLEAN _ IF plygn.renderData.shadingClass.texture # NIL THEN TRUE ELSE FALSE; lightColor: NatRGBSequence _ NARROW[ GetProp[plygn.props, $LightColors] ]; hltCnt: NAT _ IF lightColor # NIL THEN lightColor.length ELSE 0; links: LinkedPoly; IF alpha # NIL THEN { dstBufPos[a] _ alpha^; srcTmplate[a] _ dstTmplate[a] _ 2; }; IF depth # NIL THEN { dstBufPos[z] _ depth^; srcTmplate[z] _ dstTmplate[z] _ 2; }; FOR i: PixelPart IN PixelPart DO IF srcTmplate[i] > 0 THEN srcSize _ srcSize + 1; IF dstTmplate[i] > 0 THEN dstSize _ dstSize + 1; ENDLOOP; IF depth # NIL THEN { dstDpth _ dstSize-1; srcDpth _ srcSize-1; }; IF texture THEN srcSize _ srcSize + 3; -- make room for texure coordinates srcSize _ srcSize + hltCnt * 2; -- reflection vector for each hilight-causing light source links _ LinkEdges[ plygn, context.pixels.box ]; -- link vertices into left and right side chains IF links.lftVtces # NIL THEN { -- if non-zero height scanline _ Fix[plygn[links.lftVtces.first].coord.sy]; -- get first scanline lftSrc _ GetEdgeSeq[srcSize*plygn.nVtces]; rgtSrc _ GetEdgeSeq[srcSize*plygn.nVtces]; lftDst _ GetEdgeSeq[dstSize*plygn.nVtces]; rgtDst _ GetEdgeSeq[dstSize*plygn.nVtces]; FOR i: PixelPart IN PixelPart DO --Get Edge records for left and right sides IF dstTmplate[i] > 0 THEN { dstAddr, yStep: CARD32; [dstAddr, yStep] _ SampleMapBase[context.pixels[dstBufPos[i]]]; LinkDst[ lftDst, links.lftVtces, plygn, dstBufPos[i], dstSize, dstAddr, dstTmplate[i], yStep ]; LinkDst[ rgtDst, links.rgtVtces, plygn, dstBufPos[i], dstSize, dstAddr, dstTmplate[i], yStep ]; }; IF srcTmplate[i] > 0 THEN { LinkSrc[lftSrc, links.lftVtces, plygn, srcPos, srcSize, i ]; LinkSrc[rgtSrc, links.rgtVtces, plygn, srcPos, srcSize, i ]; srcPos _ srcPos + 1; }; ENDLOOP; LinkAdditionalData[lftSrc, links.lftVtces, plygn, srcSize, hltCnt, texture ]; LinkAdditionalData[rgtSrc, links.rgtVtces, plygn, srcSize, hltCnt, texture ]; ScanPoly[ context, lftDst, lftSrc, rgtDst, rgtSrc, dstSize, srcSize, dstDpth, srcDpth, scanline, Hilight ]; ReleaseEdgeSeq[lftSrc]; ReleaseEdgeSeq[rgtSrc]; ReleaseEdgeSeq[lftDst]; ReleaseEdgeSeq[ rgtDst]; }; plygn.props _ Atom.RemPropFromList[plygn.props, $LightColors]; }; LinkAdditionalData: PROC[ src: REF EdgeSequence, links: LIST OF CARD16, plygn: REF Patch, srcSize, hltCnt: NAT, texture: BOOL] ~ { BuildEdgeDesc: PROC[length: INT16, start, end: INT32] RETURNS [edge: EdgeDesc] ~ { width: INT16 _ end - start; edge.length _ length; edge.val _ start; edge.hiccups _ ABS[width]; [edge.lngthIncr, edge.hiccups] _ Basics.DivMod[ABS[width], edge.length]; IF width < 0 -- color (etc.) moving negatively? THEN { edge.lngthIncr _ -edge.lngthIncr; edge.hicIncr _ -1; } ELSE edge.hicIncr _ 1; edge.bias _ edge.length; -- initial bias to center hiccups edge.stepsLeft _ edge.length; -- set stepsLeft for count down IF ptr-srcSize >= 0 THEN src[ptr-srcSize].nextEdge _ ptr; -- set ptr from last one }; ptr: NAT _ srcSize - hltCnt*2; FOR list: LIST OF CARD16 _ links, list.rest UNTIL list.rest = NIL DO bot: CtlPtInfo _ plygn[list.first]; top: CtlPtInfo _ plygn[list.rest.first]; srcStart: IntegerPairSequence _ IF hltCnt > 0 THEN NARROW[bot.data] ELSE NIL; srcEnd: IntegerPairSequence _ IF hltCnt > 0 THEN NARROW[top.data] ELSE NIL; yStart: CARD16 _ Round[bot.coord.sy]; yEnd: CARD16 _ Round[top.coord.sy]; length: INTEGER _ INTEGER[yEnd] - INTEGER[yStart]; IF length > 0 THEN { IF texture THEN { WITH plygn.renderData.shadingClass.texture.first SELECT FROM -- only one texture txtrMap: REF TextureMap => { -- modify with mapped texture bigC: INT16 _ LAST[NAT]/32; ptr _ ptr - 3; src[ptr] _ BuildEdgeDesc[ length, Round[bigC*bot.shade.txtrX], Round[bigC*top.shade.txtrX] ]; ptr _ ptr + 1; src[ptr] _ BuildEdgeDesc[ length, Round[bigC*bot.shade.txtrY], Round[bigC*top.shade.txtrY] ]; ptr _ ptr + 1; src[ptr] _ BuildEdgeDesc[ length, 0, 0 ]; -- filler to avoid bounds errors ptr _ ptr + 1; }; txtrFn: REF TextureFunction => { -- solid or other function-based texture bigC: INT16 _ LAST[NAT]/32; IF useTextureCoords THEN { ptr _ ptr - 3; src[ptr] _ BuildEdgeDesc[ length, Round[bigC*bot.shade.txtrX], Round[bigC*top.shade.txtrX] ]; ptr _ ptr + 1; src[ptr] _ BuildEdgeDesc[ length, Round[bigC*bot.shade.txtrY], Round[bigC*top.shade.txtrY] ]; ptr _ ptr + 1; src[ptr] _ BuildEdgeDesc[ length, 0, 0 ]; -- filler to avoid bounds errors ptr _ ptr + 1; } ELSE { ptr _ ptr - 3; src[ptr] _ BuildEdgeDesc[ length, Round[bigC*bot.coord.x], Round[bigC*top.coord.x] ]; ptr _ ptr + 1; src[ptr] _ BuildEdgeDesc[ length, Round[bigC*bot.coord.y], Round[bigC*top.coord.y] ]; ptr _ ptr + 1; src[ptr] _ BuildEdgeDesc[ length, Round[bigC*bot.coord.z], Round[bigC*top.coord.z] ]; ptr _ ptr + 1; } }; ENDCASE => SIGNAL G3dRender.Error[$Unimplemented, "Unexpected texture type"]; }; FOR i: NAT IN [0..hltCnt) DO -- add in reflection vector for each highlight src[ptr] _ BuildEdgeDesc[ length, srcStart[i].x, srcEnd[i].x ]; ptr _ ptr + 1; src[ptr] _ BuildEdgeDesc[ length, srcStart[i].y, srcEnd[i].y ]; ptr _ ptr + 1; ENDLOOP; }; IF length > 0 THEN ptr _ ptr + srcSize - hltCnt*2; -- skip over other data ENDLOOP; }; CheckLimits: PROC[] ~ { }; ImageTransform: PROC[dst, src: SampleMap, dstPos: IntegerPair, theta, scale: REAL] ~ { i: NAT _ 0; -- loop index dstEdge, srcEdge, dstStart, dstBias, srcStart: EdgeDesc; srcAddr, dstAddr: CARD16; srcBytesPerLine, dstBytesPerLine: WORD; srcBox: Box _ ImagerSample.GetBox[src]; ebt: EdgeBltTable; scaledWidth: NAT _ Round[scale * (srcBox.max.f - srcBox.min.f)]; scaledHeight: NAT _ Round[scale * (srcBox.max.s - srcBox.min.s)]; dstEdgeLength: CARD16 _ Round[RealFns.CosDeg[theta] * scaledWidth]; dstEdgeHiccups: CARD16 _ Round[RealFns.SinDeg[theta] * scaledWidth]; CheckLimits[]; -- ensure transformed image fits in target buffer, 0 1.0 THEN -srcBytesPerLine ELSE srcBytesPerLine, -- hiccup step bias: srcBox.max.s - srcBox.min.s -- supply bias since not using G3dEdgeBlt ]; dstEdge _ [ -- walks across destinatation image along slanted edge val: dstStart.val, length: dstEdgeLength, -- horizontal dimension of edge hiccups: dstEdgeHiccups, -- vertical dimension of edge lngthIncr: 1, -- horizontal step to next pixel hicIncr: -dstBytesPerLine, -- 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: srcBox.max.f - srcBox.min.f, -- width of source image hiccups: ABS[INTEGER[srcBox.max.f - srcBox.min.f] - scaledWidth], -- pixels to drop/add to scale down/up lngthIncr: 1, -- horizontal step to next pixel hicIncr: IF scale > 1.0 THEN -1 ELSE 1, -- hiccup step to next pixel bias: 0, -- will be supplied by G3dEdgeBlt indirect: TRUE -- incrementing addresses ]; ebt _ [dstEdge, srcEdge, [TRUE, FALSE] ]; WHILE i < dstStart.length DO G3dEdgeBlt.Blt[ebt]; IF dstBias.val <= - dstEdgeLength -- equals 0 after G3dEdgeBlt 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 _ InlineIncr[dstBias]; dstStart _ InlineIncr[dstStart]; ebt.dst.val _ dstStart.val; srcStart _ InlineIncr[srcStart]; ebt.src.val _ srcStart.val; ENDLOOP; }; END. ΤG3dScanConvertImpl.mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Frank Crow, November 3, 1989 4:17:06 pm PST Scan conversion operations on pixel buffers. Expects input to be properly clipped. Bloomenthal, February 21, 1989 11:32:14 pm PST Glassner, September 10, 1989 6:32:34 pm PDT Type Definitions RECORD [length: NAT _ 0, s: SEQUENCE maxLength: NAT OF G3dEdgeBlt.EdgeDesc ]; Renamed Procedures Constants Globals Statistical Procedures Utility Procedures Note! This returns zero if input is zero!! Pixel Operations Ordered dither for crude looks at full-color, $PseudoColor lookup table assumed Scan Conversion for Lines Scan Conversion for Convex Areas Build destination address edge description Build source value edge description Link vertices into left side and right side chains If not a zero-height poly then increment down edges and across scan segments dx, dy, dz: REAL _ 0; IF (lx # 0.0 OR ly # 0.0 OR lz # 0.0) THEN { dx _ ABS[spot.val[3] - lx]; dy _ ABS[spot.val[4] - ly]; dz _ ABS[spot.val[5] - lz]; }; lx _ spot.val[3]; ly _ spot.val[4]; lz _ spot.val[5]; Remove prop to prevent highlights recurring after lights/surfaces have moved Get reflection vectors Image Manipulation Κ)’˜™Jšœ Οmœ1™žœ˜FKšœ˜Kšœ˜Kšœ˜Jšœžœžœ˜"—šŸ™š’œž œžœ˜:Kšœžœ+˜BK˜2Kšœžœ˜6K˜&Kšœžœ˜2K˜"N˜N˜—š’œž œ˜!Nš žœžœžœžœžœ˜SNš žœžœžœžœžœ˜SNš žœžœžœžœžœ˜MN˜N˜—š’œž œ˜0š’œžœžœžœ˜MNšœžœ˜Nšœžœ˜Nšœžœ˜!šžœžœžœž˜&Nšžœžœ˜0Nšžœ˜—šžœžœžœž˜&Nšœ1 ˜FNšžœ˜—N˜]N˜}N˜‰N˜^šžœžœžœžœ˜.Nšœžœ ˜>Nšœžœ  ˜@Nšœžœ˜Nšœ žœ˜šžœžœ˜šžœžœžœžœ˜Nšœžœžœ˜LNšžœ˜—Nšœžœ ˜ N˜—Nšœ '˜>N˜‰N˜ Nšžœ˜—N˜—NšœDžœ˜Lšžœžœžœžœ˜0Nšžœ)žœ*˜YN˜)Nšžœžœ ˜,Nšžœ˜—N˜%N˜%N˜9N˜šžœžœžœžœ˜0Nšžœ#žœ$˜MN˜)N˜%Nšžœžœ ˜,Nšžœ˜—N˜%N˜!N˜@N˜šžœžœžœžœ˜-Nšžœ#žœ$˜MN˜&N˜"Nšžœžœ ˜)Nšžœ˜—N˜"N˜N˜7N˜——šŸ™š ’œž œžœžœžœ˜IJšžœ˜J˜J˜—š ’œž œ žœžœžœžœ˜?J™+Jšžœ žœžœžœžœ žœžœžœžœ˜QJšœ’˜—š ’ œžœžœžœžœ žœ˜aN˜%N˜'šžœžœ˜N˜#N˜&N˜—Nšœ#ž˜$šžœ -˜KNšžœžœžœ˜;—Nšžœ˜ N˜—š ’œžœžœ žœžœžœ  ˜LKš œžœžœžœžœžœ˜K—š ’œžœžœ žœžœžœ ˜OKš œžœžœžœžœžœžœ˜TK˜—š ’œžœ žœ žœžœžœ˜HJšžœžœžœ)˜EJ˜—š ’ œžœžœ žœžœžœ˜AJšžœžœžœ˜Nšœžœžœ˜šžœžœž˜"N˜0N˜0Nšœ*žœ˜.N˜—Nš žœ žœžœžœ žœ˜UN˜Nšžœ ˜N˜—š’œžœžœžœ˜7Jšžœžœžœ˜šžœ)žœ˜1N˜0šžœžœžœž˜*šžœžœ˜%Nšžœ8˜>—Nšžœ˜—N˜0N˜—N˜—š ’œž œ žœžœžœžœ˜:Ošžœ žœ’˜%—š ’œžœžœžœžœ  ,˜[Kšœžœ˜Kšœžœ˜šžœžœž˜K˜ K˜ Kšžœ˜—K˜K˜—š ’œž œ žœ žœžœ žœ˜LNšœ žœ 2˜PNšœžœ ˜Nšœžœ ˜Nšžœ žœžœ˜ šžœžœ $˜@Nšžœžœžœ$˜MN˜!N˜Nšžœ˜—J˜J˜—š ’ œžœžœžœžœ˜ANšœžœž˜+šžœ žœž˜˜+N˜9Nšœžœžœ'žœ˜rNšœžœ*˜HN˜—Nšžœžœ:˜K—Nšžœ˜$Nšœ’˜—š ’œžœ%žœ žœžœž œ˜†Nš œžœ žœžœžœ˜4šžœž˜%˜N˜KN˜KN˜KN˜—˜N˜KN˜—Nšžœžœ7˜HJšœžœ*˜8šžœ žœžœ˜J˜W—Jšœžœ*˜8šžœ žœžœ˜J˜W——N˜—š’ œžœžœžœžœžœžœ˜Lš žœžœž œžœž˜/JšœžœžœC˜[Jšœžœ ˜Jšžœ˜ —Jšžœ˜ J˜—šΠbn’œžœžœ žœžœžœžœ˜Cšžœ ž˜˜JšœžœM˜XJšžœžœ ˜CJšžœ ˜J˜—šœžœ˜Jšœ@˜@J˜—Jšžœžœ/˜A—Ošžœ˜O˜—š’ œžœžœ žœ žœžœžœ˜Fšžœ žœ˜˜Jšžœ žœ ˜BJšœžœ ˜šžœ˜ J˜Jšœ žœ˜Jšœ žœ ˜J˜—J˜—˜Jšœžœ˜šžœ˜ J˜Jšœ žœ˜Jšœ žœ ˜J˜—J˜—Jšžœžœ/˜A—Ošžœ˜O˜——šŸ™š’ œžœžœ1žœ*˜w˜6O˜&O˜—Ošœžœ˜)Ošœžœ˜)Ošœ žœ žœ.˜JOšœžœ˜!šœ# ˜šœ# ˜Nšœžœžœ ˜=Nšœžœžœ˜.Jšœžœ žœ˜$Jšœžœ žœ˜/Jšœ žœ 4˜YJšœžœ žœ žœ ˜SJš œžœ žœ žœžœ˜SJšœžœ žœ žœ ˜SJšœžœ˜Jšœžœ  ˜;Jšžœžœžœ ˜MJšœžœ˜%Jšžœžœžœ˜=Jšœžœ˜%Jšžœžœžœ˜=šœžœ˜Jšœžœžœžœžœžœ  œ Οi ˜»—šžœžœ ˜PNšž œžœžœ  ˜ONšžœžœžœ*  ˜O—N˜—š’œžœ'žœ˜;N˜—š ’ œžœžœžœžœžœž˜Oš œ žœžœžœžœžœ˜+Nšœžœ ˜,—šžœ žœ˜1Nšžœžœžœ ˜)Nšžœžœžœžœ˜J—N˜——šŸ™š’œžœžœD˜Xš’œžœ žœžœ˜Išœ žœžœ ž˜'N˜3Nšœ!žœžœ˜3—šœ žœžœ ž˜'N˜3Nšœ!žœžœ˜3—N˜Nšœžœ ˜Hšžœ˜Nš žœžœžœ$žœžœ ˜ŠNš žœ žœžœ%žœžœ ˜Œ—šžœžœ˜*Jšžœ.žœ ˜Všžœ˜Jšœ žœžœžœ  ˜LJ˜J˜Jšœ-žœ˜Gšžœ ˜1Jšžœ<˜@Jšžœ˜—Jšœžœ˜J˜——Nšœ!  ˜AN˜—N˜Nšœ žœ˜Nšœžœžœ˜Nšœžœ˜Nšœžœ˜Nšœžœ˜šžœžœ žœ ˜Nšžœ žœžœžœ˜HNšžœ žœžœžœ˜H—NšœJ ˜ZNšœ? ˜[N˜>N˜>N˜>N˜"N˜——šŸ ™ š ’ œžœžœžœžœ˜lJš +™+N˜4N˜4Nšœžœ˜ Nšœžœ˜!Jšžœ žœžœ ˜:Nšœžœžœ  ˜YNšœžœ˜Nšœžœ˜Jšœ/žœ˜Hšžœ  ˜4JšžœC˜GJšžœ˜—Jšœ1 (˜YNšœ !˜;Nšœ! ˜@J˜—š’ œžœ0žœ˜\Jš $™$Nšœžœ!žœ˜LNšœžœžœ žœ ˜3Nšœžœ˜Nšœžœ˜ Jšžœ žœžœ ˜:šžœ ž˜NšœV˜VNšœV˜VNšœV˜VNšœU˜UN˜ONšžœžœ˜—Nšœžœ ˜!Nšœ ˜-Nšœžœ˜Nšœžœ˜Jšœ/žœ˜Hšžœ ˜/Jšžœ?˜CJšžœ˜—Nšœ !˜;Nšœ! ˜@J˜—š’ œžœ žœžœ˜FN™2Ošœ žœ˜Oš œ žœžœžœžœ˜Jšœ žœ-žœ˜JNšœJ ˜Zš žœžœžœžœ ˜PJ˜FJšœ> ˜MJ˜Jš œžœžœ žœžœ˜>Jš œžœžœ žœžœ˜=JšœH˜Hšžœ˜JšœV˜VJšœ˜—N˜—š’œžœžœžœ˜FJšœžœ˜Nšœžœ˜#Nšœ žœ˜&Nšœ žœ˜Nšœžœžœ˜"N˜J˜šžœžœžœ <˜OJ˜J˜J˜—šžœžœ˜šžœ-žœžœ ˜Qšœ žœ ˜:šžœžœž˜˜˜!J˜"J˜2J˜——Jšžœ=˜D——šœžœ (˜MJš œžœžœžœžœ˜/šœ˜Jšœ˜Jšœ3˜3JšœZ˜ZJšœb˜bJšœa˜aJ˜—J˜—Jšžœžœ=˜N—N˜—šžœžœžœž˜#Jšœžœ˜Jšœžœ -˜UJšœžœ˜(Jšœ žœžœžœ˜Wšžœžœ '˜LJšœ žœ žœ8˜UJšœ žœ žœ8˜UJšœ žœ žœ8˜UJšœžœ˜J˜—Nšžœ˜ —šžœžœ˜J˜"J˜"J˜!Jšžœžœ)˜5Jšœžœ9˜Bšžœžœžœ˜Jšœžœ9˜BJšœžœ9˜BJ˜—J˜—šžœ ˜ Nšžœ2˜6šžœžœ˜ Nšžœ˜šžœ˜N˜UN˜———N˜—Nšœ žœžœ˜7Ošœ6žœ˜?Ošœ žœ ˜:Ošœžœ#žœ žœ ˜[Ošœžœ(žœ žœ ˜`O˜Ošœžœžœžœ*˜AOšœžœžœžœ*˜AOš œ žœžœ*žœžœžœžœ˜VOš œžœžœ#žœžœžœžœ˜JOšœ žœžœ)žœžœžœžœžœ˜WOšœžœ'˜JOš œžœžœžœžœžœ˜@O˜Nšžœ žœžœE˜XNšžœ žœžœD˜Wšžœžœ ž˜ Nšžœžœ˜0Nšžœžœ˜0Nšžœ˜—Nšžœ žœžœ4˜GNšžœ žœ #˜LNšœ  :˜ZNšœ/ 1˜`šžœžœžœ  ˜;Nšœ9 ˜NN˜VN˜Všžœžœ žœ +˜Ošžœžœ˜Nšœžœ˜N˜?N˜dN˜dN˜—šžœžœ˜N˜˜>N˜—š’œžœžœžœžœžœ žœ%žœ žœ˜š ’ œžœ žœžœžœ˜RNšœžœ˜Jšœ˜Jšœ˜Jšœžœ˜Jšœ/žœ˜Hšžœ "˜8Jšžœ?˜CJšžœ˜—Nšœ !˜?Nšœ! ˜@Jšžœžœ" ˜RJ˜—Jšœžœ˜š žœžœžœžœžœ žœž˜DšœO˜ONš ™—Nš œ žœ žœžœ žœžœ˜MNš œžœ žœžœ žœžœ˜KNšœžœ!žœ˜LNšœžœžœ žœ ˜3šžœ žœ˜šžœ žœ˜šžœ-žœžœ ˜Qšœ žœ ˜;Nšœžœžœžœ˜Jšœ˜šœ˜JšœC˜C—J˜šœ˜JšœC˜C—J˜Jšœ*  ˜JJ˜J˜—šœžœ (˜LNšœžœžœžœ˜šžœ˜šžœ˜Jšœ˜šœ˜JšœC˜C—J˜šœ˜JšœC˜C—J˜Jšœ*  ˜JJ˜J˜—šžœ˜Jšœ˜šœ˜Jšœ;˜;—J˜šœ˜Jšœ;˜;—J˜šœ˜Jšœ;˜;—J˜J˜—J˜—Jšœ˜—Jšžœžœ=˜N—J˜—š žœžœžœ žœ .˜NJšœP˜PJšœP˜PJšžœ˜—J˜—Jšžœ žœ" ˜KJšžœ˜—J˜——šŸ™š’ œžœ˜N˜—š’œžœ9žœ˜VNšœžœ  ˜N˜8Nšœžœ˜Nšœ"žœ˜'N˜'N˜Nšœ žœ0˜@Nšœžœ0˜ANšœž œ+˜CNšœž œ,˜ENšœ N˜]N˜0Nšœžœ˜?šœ B˜QNšœ '˜@Nšœ4 ˜SNšœ6 ˜MNšœ" !˜CNšœ  ˜7Nšœ6 !˜WN˜—šœ  O˜[Nšœ ˜+Nšœ! #˜DNšœ" ˜9Nšœ  %˜2Nšœ$ ˜8Nšœ.  ˜NN˜—N˜0šœ  M˜ZN˜ Nšœ) ˜BNšœ žœ!žœ &˜kNšœ (˜CNšœ žœ žœžœ ˜RNšœ& *˜PN˜—šœ  6˜CN˜Nšœ  ˜?Nšœ! ˜>Nšœ  ˜:Nšœ# ˜ANšœ ˜:Nšœ žœ  ˜3N˜—šœ  M˜YN˜Nšœ, ˜DNšœ žœžœ. &˜hNšœ  ˜9Nšœ žœ žœžœ ˜GNšœ "˜7Nšœ žœ  ˜2N˜—Nšœžœžœ˜)N˜šžœžœž˜N˜šžœ" .˜Ršžœ ˜&Nšœ1  ˜>Nšœ4 &˜ZN˜—Nšžœ ˜—Nšœ" +˜MN˜ N˜?N˜?Nšžœ˜—N˜——Jšžœ˜—…—­*Ϋ