<> <> <> <> DIRECTORY Atom USING [ PropList, GetPropFromList, PutPropOnList, RemPropFromList ], Basics USING [ BITAND, BITOR, BITSHIFT, BytePair, LowByte, HighByte ], Real USING [ Fix, Float, InlineRoundI, Round, LargestNumber ], RealFns USING [ SqRt, Power], Rope USING [ ROPE ], SF USING [ Displace, InlineSize, Vec ], Imager USING [ Context, Font, SetColor, SetFont, SetXY, ShowRope ], ImagerColor USING [ ColorFromRGB ], ImagerFont USING [ Find, Scale ], ImagerSmoothContext USING [ Create, SetOutputBuffer ], ImagerFullColorContext USING [ Create, SetPixelMap ], ImagerDitherContext USING [ Create, SetSampleMap ], ImagerGrayContext USING [ Create, SetSampleMap ], ImagerPixel USING [ NewPixelMap, GetPixels, NewPixels, ObtainScratchPixels, PixelBuffer, PixelProc, PutPixels, ReleaseScratchPixels ], ImagerSample USING [ BasicTransfer, Fill, Transfer ], G3dVector USING [ Normalize, Mul, Add ], AISAnimation USING [ GetAIS ], ScanConvert USING [ FastFlatTiler, HiliteTiler, justNoticeable, LerpTiler, PutLine], ThreeDBasics USING [ Box, BoxFromRectangle, ClipState, Context, ContextClass, ContextProc, Create, Error, GetDisplayType, IntegerPair, ImagerProc, IntegerPairSequence, NatRGB, NatRGBSequence, NoneOut, OutCode, Pair, PairSequence, Patch, PatchProc, Pixel, Quad, RealSequence, Rectangle, RegisterDisplayType, RGB, ShadingClass, ShadingValue, ShapeClass, ShapeInstance, Spot, SpotProc, TextureFunction, TextureMap, Triple, Vertex, VertexInfo, VertexInfoProc, VtxToRealSeqProc ], ShapeUtilities USING [ GetClipCodeForPt, XfmPtToDisplay ], SurfaceRender USING [ MakeFrame, ShowShapes, ValidateContext ], MappedAndSolidTexture USING [ AdjustTexture ], RenderWithPixels USING [ LerpVtx, LerpVtxSequence, FancyPatch, RopeDesc ]; RenderWithPixelsImpl: CEDAR MONITOR IMPORTS AISAnimation, Atom, Basics, Imager, ImagerColor, ImagerDitherContext, ImagerFont, ImagerFullColorContext, ImagerGrayContext, ImagerPixel, ImagerSample, ImagerSmoothContext, Real, RealFns, ScanConvert, SF, ShapeUtilities, SurfaceRender, MappedAndSolidTexture, ThreeDBasics, G3dVector EXPORTS RenderWithPixels = BEGIN <> LORA: TYPE ~ LIST OF REF ANY; PixelBuffer: TYPE ~ ImagerPixel.PixelBuffer; ImagerProc: TYPE ~ ThreeDBasics.ImagerProc; Context: TYPE ~ ThreeDBasics.Context; ContextProc: TYPE ~ ThreeDBasics.ContextProc; ContextClass: TYPE ~ ThreeDBasics.ContextClass; ShadingClass: TYPE ~ ThreeDBasics.ShadingClass; ShapeClass: TYPE ~ ThreeDBasics.ShapeClass; RGB: TYPE ~ ThreeDBasics.RGB; NatRGB: TYPE ~ ThreeDBasics.NatRGB; NatRGBSequence: TYPE ~ ThreeDBasics.NatRGBSequence; Pixel: TYPE ~ ThreeDBasics.Pixel; OutCode: TYPE ~ ThreeDBasics.OutCode; NoneOut: OutCode ~ ThreeDBasics.NoneOut; Pair: TYPE ~ ThreeDBasics.Pair; -- RECORD [ x, y: REAL]; PairSequence: TYPE ~ ThreeDBasics.PairSequence; IntegerPair: TYPE ~ ThreeDBasics.IntegerPair; IntegerPairSequence: TYPE ~ ThreeDBasics.IntegerPairSequence; Triple: TYPE ~ ThreeDBasics.Triple; -- RECORD [ x, y, z: REAL]; Quad: TYPE ~ ThreeDBasics.Quad; Box: TYPE ~ ThreeDBasics.Box; Patch: TYPE ~ ThreeDBasics.Patch; PatchProc: TYPE ~ ThreeDBasics.PatchProc; ColorPrimary: TYPE ~ { red, green, blue, gray }; BooleanSequence: TYPE ~ RECORD [ length: NAT, s: SEQUENCE maxLength: NAT OF BOOLEAN ]; RealSequence: TYPE ~ ThreeDBasics.RealSequence; TextureMap: TYPE ~ ThreeDBasics.TextureMap; TextureFunction: TYPE ~ ThreeDBasics.TextureFunction; Vertex: TYPE ~ ThreeDBasics.Vertex; VertexInfo: TYPE ~ ThreeDBasics.VertexInfo; ShapeInstance: TYPE ~ ThreeDBasics.ShapeInstance; Spot: TYPE ~ ThreeDBasics.Spot; <> LerpVtx: TYPE ~ RenderWithPixels.LerpVtx; <> LerpVtxSequence: TYPE ~ RenderWithPixels.LerpVtxSequence; <> FancyPatch: TYPE ~ RenderWithPixels.FancyPatch; <> RopeDesc: TYPE ~ RenderWithPixels.RopeDesc; <> <> EdgeBlock: TYPE ~ RECORD [ moreVertical: BOOLEAN, start, end: REAL, x, y, xIncr, yIncr: REAL, val, incr: REF RealSequence ]; ScanSegment: TYPE ~ RECORD [ start, end: REAL, coverage, cvrgIncr: REAL, lMask, rMask: CARDINAL, val, yIncr, xIncrVal, xIncrForY: REF RealSequence ]; ScanSegSequence: TYPE ~ RECORD [ length: NAT, segs: SEQUENCE maxLength: NAT OF REF ScanSegment ]; HilitSeqs: TYPE ~ RECORD [ -- reflection vectors and light source flags for hilites refls: REF IntegerPairSequence, flags: REF BooleanSequence ]; HilitSeqsSequence: TYPE ~ RECORD [ length: NAT, s: SEQUENCE maxLength: NAT OF REF HilitSeqs ]; <> tblLngth: NAT ~ 256; justNoticeable: REAL ~ ScanConvert.justNoticeable; -- 0.02 <> statistics: BOOLEAN _ FALSE; polyCount: INT _ 0; recurseLimit: NAT _ 16; -- limits recursion in fancy tiler depthSlop: INT16 _ 300; -- limit of indeterminate depth comparisons noHighLights: BOOLEAN _ FALSE; weight: ARRAY [0..tblLngth] OF REAL; -- filter table <> scanSegCache: REF ScanSegSequence _ NEW[ ScanSegSequence[2] ]; -- for ShowSteepTrap scanSegCacheLength: NAT _ 2; scanSegCachePtr: NAT _ 0; hilitSeqCache: REF HilitSeqsSequence _ NEW[ HilitSeqsSequence[2] ]; -- for hilites hilitSeqCacheLength: NAT _ 2; hilitSeqCachePtr: NAT _ 0; vertexCache: REF LerpVtxSequence _ NEW[ LerpVtxSequence[2] ]; -- for fancy tiler vertexCacheLength: NAT _ 2; vertexCachePtr: NAT _ 0; spotCacheSize: NAT ~ 4; spotCacheArray: ARRAY [0..spotCacheSize) OF REF Spot _ ALL[NIL]; <> GetScanSeg: ENTRY PROC[size: NAT] RETURNS[REF ScanSegment] ~ { ENABLE UNWIND => NULL; seg: REF ScanSegment; IF scanSegCachePtr = 0 THEN seg _ NEW[ ScanSegment ] ELSE { scanSegCachePtr _ scanSegCachePtr - 1; seg _ scanSegCache[scanSegCachePtr]; scanSegCache[scanSegCachePtr] _ NIL; }; IF seg.val = NIL OR seg.val.maxLength < size THEN { seg.val _ NEW[ RealSequence[size] ]; seg.yIncr _ NEW[ RealSequence[size] ]; seg.xIncrVal _ NEW[ RealSequence[size] ]; seg.xIncrForY _ NEW[ RealSequence[size] ]; }; RETURN[ seg ]; }; ReleaseScanSeg: ENTRY PROC[s: REF ScanSegment] ~ { ENABLE UNWIND => NULL; IF scanSegCachePtr = scanSegCacheLength THEN { scanSegCache _ NEW[ ScanSegSequence[scanSegCacheLength + 2] ]; scanSegCacheLength _ scanSegCacheLength + 2; scanSegCachePtr _ 0; }; scanSegCache[scanSegCachePtr] _ s; scanSegCachePtr _ scanSegCachePtr + 1; }; GetHilitSeqs: ENTRY PROC[reflSize, flagSize: NAT] RETURNS[REF HilitSeqs] ~ { ENABLE UNWIND => NULL; s: REF HilitSeqs; IF hilitSeqCachePtr = 0 THEN { s _ NEW[ HilitSeqs ]; s.refls _ NEW[ IntegerPairSequence[reflSize] ]; s.flags _ NEW[ BooleanSequence[flagSize] ]; } ELSE { hilitSeqCachePtr _ hilitSeqCachePtr - 1; s _ hilitSeqCache[hilitSeqCachePtr]; hilitSeqCache[hilitSeqCachePtr] _ NIL; IF s.refls.maxLength < reflSize THEN s.refls _ NEW[ IntegerPairSequence[reflSize] ]; IF s.flags.maxLength < flagSize THEN s.flags _ NEW[ BooleanSequence[flagSize] ]; }; FOR i: NAT IN [0..flagSize) DO s.flags[i] _ FALSE; ENDLOOP; RETURN[ s ]; }; ReleaseHilitSeqs: ENTRY PROC[s: REF HilitSeqs] ~ { ENABLE UNWIND => NULL; IF hilitSeqCachePtr = hilitSeqCacheLength THEN { hilitSeqCache _ NEW[ HilitSeqsSequence[hilitSeqCacheLength + 2] ]; hilitSeqCacheLength _ hilitSeqCacheLength + 2; hilitSeqCachePtr _ 0; }; hilitSeqCache[hilitSeqCachePtr] _ s; hilitSeqCachePtr _ hilitSeqCachePtr + 1; }; GetVertex: ENTRY PROC[size: NAT] RETURNS[REF LerpVtx] ~ { ENABLE UNWIND => NULL; vtx: REF LerpVtx; IF vertexCachePtr = 0 THEN vtx _ NEW[ LerpVtx ] ELSE { vertexCachePtr _ vertexCachePtr - 1; vtx _ vertexCache[vertexCachePtr]; vertexCache[vertexCachePtr] _ NIL; }; IF vtx.val = NIL OR vtx.val.maxLength < size THEN vtx.val _ NEW[ RealSequence[size] ]; RETURN[ vtx ]; }; ReleaseVertex: ENTRY PROC[vtx: REF LerpVtx] ~ { ENABLE UNWIND => NULL; IF vertexCachePtr = vertexCacheLength THEN { vertexCache _ NEW[ LerpVtxSequence[vertexCacheLength + 2] ]; vertexCacheLength _ vertexCacheLength + 2; vertexCachePtr _ 0; }; vertexCache[vertexCachePtr] _ vtx; vertexCachePtr _ vertexCachePtr + 1; }; GetSpot: ENTRY PROC[] RETURNS[REF Spot] ~ { ENABLE UNWIND => NULL; spot: REF Spot _ NIL; FOR i: NAT IN [0..spotCacheSize) DO IF spotCacheArray[i] # NIL THEN { spot _ spotCacheArray[i]; spotCacheArray[i] _ NIL; RETURN[spot]; }; ENDLOOP; RETURN[ NEW[Spot] ]; }; ReleaseSpot: ENTRY PROC[spot: REF Spot] ~ { ENABLE UNWIND => NULL; place: NAT _ 0; FOR i: NAT IN [0..spotCacheSize) DO IF spotCacheArray[i] = NIL THEN place _ i ELSE IF spotCacheArray[i] = spot THEN SIGNAL ThreeDBasics.Error[[$Fatal, "Multiple Spot release"]]; ENDLOOP; spotCacheArray[place] _ spot; }; <> HoldEverything: PROCEDURE [] ~ { ERROR ABORTED; }; Sqr: PROCEDURE [number: REAL] RETURNS [REAL] ~ INLINE { RETURN[number * number]; }; Sgn: PROCEDURE [number: REAL] RETURNS [REAL] ~ INLINE { IF number < 0. THEN RETURN[-1.] ELSE RETURN[1.]; }; Ceiling: PROC[ in: REAL ] RETURNS[ out: INTEGER ] ~ { out _ Real.Round[in]; IF Real.Float[out] < in THEN out _ out + 1; }; Floor: PROC[ in: REAL ] RETURNS[ out: INTEGER ] ~ { out _ Real.Round[in]; IF Real.Float[out] > in THEN out _ out - 1; }; DupLerpVtx: PROC[vtx: LerpVtx] RETURNS[newVtx: LerpVtx] ~ { newVtx.x _ vtx.x; newVtx.y _ vtx.y; newVtx.val _ NEW[RealSequence[vtx.val.length]]; FOR i: NAT IN [0..vtx.val.length) DO newVtx.val[i] _ vtx.val[i] ENDLOOP; newVtx.val.length _ vtx.val.length; }; DupEdgeBlock: PROC[srce: EdgeBlock] RETURNS[dest : EdgeBlock] ~ { dest.moreVertical _ srce.moreVertical; dest.start _ srce.start; dest.end _ srce.end; dest.x _ srce.x; dest.xIncr _ srce.xIncr; dest.y _ srce.y; dest.yIncr _ srce.yIncr; dest.val _ NEW[RealSequence[srce.val.length]]; dest.val.length _ srce.val.length; FOR i: NAT IN [0..srce.val.length) DO dest.val[i] _ srce.val[i] ENDLOOP; dest.incr _ NEW[RealSequence[srce.incr.length]]; dest.incr.length _ srce.incr.length; FOR i: NAT IN [0..srce.incr.length) DO dest.incr[i] _ srce.incr[i] ENDLOOP; }; GetLerpedVals: ThreeDBasics.VtxToRealSeqProc ~ { <> length: NAT _ IF data = $PixelShading THEN 11 ELSE 5; IF dest = NIL OR dest.maxLength < length THEN dest _ NEW[RealSequence[length]]; IF data = $PixelShading THEN { -- shade will be computed anew at each pixel dest[0] _ source.shade.r; dest[1] _ source.shade.g; dest[2] _ source.shade.b; dest[3] _ source.shade.t; dest[4] _ source.shade.exn; dest[5] _ source.shade.eyn; dest[6] _ source.shade.ezn; dest[7] _ source.coord.ex; dest[8] _ source.coord.ey; dest[9] _ source.coord.ez; dest[10] _ source.coord.sz; -- for depth buffering } ELSE { -- Shade will only be multiplied or interpolated dest[0] _ source.shade.er; dest[1] _ source.shade.eg; dest[2] _ source.shade.eb; dest[3] _ source.shade.et; dest[4] _ source.coord.sz; -- for depth buffering }; dest.length _ length; RETURN [dest]; }; ValueFromRGB: PROC[context: REF Context, red, green, blue: REAL] RETURNS[NAT] ~ { SELECT context.class.displayType FROM $PseudoColor => RETURN[ -- standard PseudoColor colormap Real.Fix[red*5.999]*42 + Real.Fix[green*6.999]*6 + Real.Fix[blue*5.999] + 2 ]; $Gray => RETURN[ Real.Round[255.0 * (red + green + blue) / 3.0] ]; ENDCASE => SIGNAL ThreeDBasics.Error[[$MisMatch, "Unknown display type"]]; RETURN[0]; -- error return }; PairToScreen: PROC[context: REF Context, p: Pair] RETURNS[ip: IntegerPair] ~ { ip.x _ Real.Round[ context.ndcToPixels.scaleX * p.x + context.ndcToPixels.addX ]; ip.y _ Real.Round[ context.ndcToPixels.scaleY * p.y + context.ndcToPixels.addY ]; }; GetImagerCtx: PROC[context: REF Context] RETURNS[imagerCtx: Imager.Context] ~ { IF context.antiAliasing THEN { imagerCtx _ ImagerSmoothContext.Create[SF.InlineSize[context.pixels.box]]; SELECT context.class.displayType FROM $FullColor => ImagerSmoothContext.SetOutputBuffer[ imagerCtx, context.pixels, LIST[$Red, $Green, $Blue, $Alpha] ]; $Gray => ImagerSmoothContext.SetOutputBuffer[ imagerCtx, context.pixels, LIST[$Intensity, $Alpha] ]; ENDCASE => SIGNAL ThreeDBasics.Error[[$Mismatch, "Bad display type"]]; } ELSE { SELECT context.class.displayType FROM $FullColor => { imagerCtx _ ImagerFullColorContext.Create[ deviceSpaceSize: SF.InlineSize[context.pixels.box], scanMode: [slow: down, fast: right], surfaceUnitsPerInch: [72, 72] ]; ImagerFullColorContext.SetPixelMap[ imagerCtx, context.pixels ]; }; $PseudoColor => { imagerCtx _ ImagerDitherContext.Create[ deviceSpaceSize: SF.InlineSize[context.pixels.box], scanMode: [slow: down, fast: right], surfaceUnitsPerInch: [72, 72] ]; ImagerDitherContext.SetSampleMap[ imagerCtx, context.pixels[0] ]; }; $Gray => { imagerCtx _ ImagerGrayContext.Create[ deviceSpaceSize: SF.InlineSize[context.pixels.box], scanMode: [slow: down, fast: right], surfaceUnitsPerInch: [72, 72] ]; ImagerGrayContext.SetSampleMap[ imagerCtx, context.pixels[0] ]; }; ENDCASE => SIGNAL ThreeDBasics.Error[[$Mismatch, "Bad display type"]]; }; context.props _ Atom.PutPropOnList[context.props, $ImagerCtx, imagerCtx]; }; <> Init: PROC[] ~ { -- For Pixels in VM, no display contextClass: ContextClass _ [ displayType: $FullColor, setUpDisplayType: AllocatePixelMemory, validateDisplay: DummyValidateDisplay, render: MakeFrame, loadBackground: FillInBackGround, draw2DLine: Draw2DLine, draw2DPolygon: Draw2DPoly, draw2DRope: DrawRope, displayPolygon: PolygonTiler ]; ThreeDBasics.RegisterDisplayType[contextClass, $FullColor]; ThreeDBasics.RegisterDisplayType[contextClass, $FullColorInVM]; contextClass.displayType _ $PseudoColor; -- set different fields for PseudoColor ThreeDBasics.RegisterDisplayType[contextClass, $PseudoColor]; ThreeDBasics.RegisterDisplayType[contextClass, $PseudoColorInVM]; contextClass.displayType _ $Gray; -- set different fields for Gray ThreeDBasics.RegisterDisplayType[contextClass, $Gray]; ThreeDBasics.RegisterDisplayType[contextClass, $GrayInVM]; <> FOR i: NAT IN [0..tblLngth/2] DO t: REAL _ i * 1.0 / (tblLngth/2); weight[i] _ Sqr[t] / 2.; weight[i + tblLngth/2] _ 1. - Sqr[1. - t] / 2.; ENDLOOP; <> FOR i: NAT IN [0..256) DO realFromByte[i] _ REAL[i] / 255.0; ENDLOOP; <> FOR i: NAT IN [0..256) DO total: NAT _ 0; FOR j: NAT IN [0..8) DO IF Basics.BITAND[ i, Basics.BITSHIFT[1, j] ] # 0 THEN total _ total + 1; ENDLOOP; populationCount[i] _ total; ENDLOOP; }; AllocatePixelMemory: PUBLIC ThreeDBasics.ContextProc ~ { PixelCount: ImagerPixel.PixelProc ~ { <> IF i < samplesPerColor THEN RETURN[255] -- red, green, and blue have 8 bits ELSE RETURN[LAST[WORD]]; -- alpha and depth have 16 bits }; samplesPerColor, samplesPerPixel: NAT _ IF context.class.displayType = $FullColor THEN 3 ELSE 1; box: Box _ IF context.viewPort # NIL THEN ThreeDBasics.BoxFromRectangle[context.viewPort^] -- taken from viewport ELSE [[0, 0], [1023, 767]]; -- default to 1024 x 768 display context.displayProps _ NIL; -- remove any lingering props IF context.antiAliasing THEN { -- set up alpha buffer (before depth) context.displayProps _ Atom.PutPropOnList[ context.displayProps, $Alpha, NEW[NAT _ samplesPerPixel] ]; samplesPerPixel _ samplesPerPixel + 1; }; IF context.depthBuffering THEN { -- set up depth buffer context.displayProps _ Atom.PutPropOnList[ context.displayProps, $Depth, NEW[NAT _ samplesPerPixel] ]; samplesPerPixel _ samplesPerPixel + 1; }; IF Atom.GetPropFromList[context.props, $NormalBuffer] # NIL THEN { context.displayProps _ Atom.PutPropOnList[ context.displayProps, $NormalBuffer, NEW[NAT _ samplesPerPixel] ]; samplesPerPixel _ samplesPerPixel + 3; }; <> box _ [ min: [f: 0, s: 0], max: [f: box.max.f - box.min.f, s: box.max.s - box.min.s] ]; <<>> context.pixels _ NIL; -- No pixel memory needed if viewer can be used directly IF samplesPerPixel > 1 OR context.doVisibly OR context.viewer = NIL THEN { context.pixels _ ImagerPixel.NewPixelMap[samplesPerPixel, box, PixelCount]; context.displayProps _ Atom.PutPropOnList[ context.displayProps, $FullDisplayMemory, context.pixels ]; -- for clipping etc. }; }; GetContext: PUBLIC PROC [type: ATOM, width, height: NAT] RETURNS[ctx: REF Context] ~ { <> vmType: ATOM _ SELECT type FROM $FullColor => $FullColorInVM, $PseudoColor => $PseudoColorInVM, $Gray => $GrayInVM, ENDCASE => NIL; class: ContextClass _ ThreeDBasics.GetDisplayType[vmType]; ctx _ ThreeDBasics.Create[]; ctx.class _ NEW[ ContextClass _ class ]; ctx.viewPort _ NEW[ ThreeDBasics.Rectangle _ [ x: 0.0, y: 0.0, w: Real.Float[width], h: Real.Float[height] ] ]; AllocatePixelMemory[ctx]; -- get display memory }; DummyValidateDisplay: ContextProc ~{ -- update viewPort, etc., (placeholder) }; DepthBuffering: PUBLIC PROC[ context: REF Context, on: BOOLEAN _ TRUE ] ~ { <> IF on # context.depthBuffering THEN { context.depthBuffering _ on; context.class.setUpDisplayType[context]; }; }; AntiAliasing: PUBLIC PROC[ context: REF Context, on: BOOLEAN _ TRUE ] ~ { <> IF on # context.antiAliasing THEN { context.antiAliasing _ on; context.class.setUpDisplayType[context]; }; }; NormalBuffering: PUBLIC PROC[ context: REF Context, on: BOOLEAN _ TRUE ] ~ { <> IF on THEN { IF Atom.GetPropFromList[context.props, $NormalBuffer] = NIL THEN { context.props _ Atom.PutPropOnList[context.props, $NormalBuffer, $On ]; context.class.setUpDisplayType[context]; }; } ELSE IF Atom.GetPropFromList[context.props, $NormalBuffer] # NIL THEN { context.props _ Atom.RemPropFromList[context.props, $NormalBuffer ]; context.displayProps _ Atom.RemPropFromList[context.displayProps, $NormalBuffer ]; }; }; BufferRendering: PUBLIC PROC[ context: REF Context, on: BOOLEAN _ TRUE ] ~ { <> IF on = context.doVisibly THEN { context.doVisibly _ NOT on; context.class.setUpDisplayType[context]; }; }; FillInBackGround: PUBLIC ContextProc ~ { <> IF context.stopMe^ THEN RETURN; WITH Atom.GetPropFromList[context.props, $BackGround] SELECT FROM bkgrdClr: REF RGB => FillInConstantBackGround[context, bkgrdClr^]; -- constant color bkgrdCtx: REF Context => FillInBackGroundImage[context, bkgrdCtx]; -- background ctx ENDCASE => IF NOT context.antiAliasing -- leave alpha channel alone if no background THEN FillInConstantBackGround[context, [0.0,0.0,0.0] ]; -- NIL, clear to black }; FillInConstantBackGround: PUBLIC PROC[context: REF Context, bkgrdClr: RGB, cvrge: BYTE _ 255 ] ~ { <> FillPixels: PROC[box: Box] ~ { box _ SF.Displace[box, context.pixels.box.min]; -- offset box to address base of samplemap FOR i: NAT IN [0..context.pixels.samplesPerPixel) DO ImagerSample.Fill[ context.pixels[i], box, value[i] ]; ENDLOOP; }; value: ARRAY[0..5) OF WORD; alpha: REF NAT _ NARROW[ Atom.GetPropFromList[context.displayProps, $Alpha] ]; depth: REF NAT _ NARROW[ Atom.GetPropFromList[context.displayProps, $Depth] ]; normals: REF NAT _ NARROW[ Atom.GetPropFromList[context.displayProps, $NormalBuffer] ]; samplesPerColor: NAT; IF context.class.displayType = $FullColor THEN { value[0] _ Real.Fix[255.0 * bkgrdClr.R]; value[1] _ Real.Fix[255.0 * bkgrdClr.G]; value[2] _ Real.Fix[255.0 * bkgrdClr.B]; samplesPerColor _ 3; } ELSE { value[0] _ ValueFromRGB[context, bkgrdClr.R, bkgrdClr.G, bkgrdClr.B]; samplesPerColor _ 1; }; IF alpha # NIL THEN value[alpha^] _ cvrge; IF depth # NIL THEN value[depth^] _ LAST[WORD]; IF normals # NIL THEN value[normals^] _ value[normals^+1] _ value[normals^+2] _ 0; IF context.viewPort = NIL THEN SurfaceRender.ValidateContext[context]; IF context.extentCovered.min.s >= context.extentCovered.max.s OR alpha = NIL THEN FillPixels[[ -- whole viewport, nothing covered min: [ f: 0, s: 0 ], max: [ f: Ceiling[context.viewPort.w], s: Ceiling[context.viewPort.h] ] ]] ELSE { screenExtent: Box _ SF.Displace[context.extentCovered, context.pixels.box.min]; scanLength: NAT _ context.extentCovered.max.f - context.extentCovered.min.f; srcBuf: PixelBuffer _ ImagerPixel.NewPixels[context.pixels.samplesPerPixel, scanLength]; FillPixels[[ -- below previously affected area min: [f: 0, s: 0 ], max: [f: Ceiling[context.viewPort.w], s: context.extentCovered.min.s] ]]; IF Ceiling[context.viewPort.h] > context.extentCovered.max.s THEN FillPixels[[ -- above min: [f: 0, s: context.extentCovered.max.s], max: [f: Ceiling[context.viewPort.w], s: Ceiling[context.viewPort.h] ] ]]; FillPixels[[ -- left of previously affected area min: [f: 0, s: context.extentCovered.min.s], max: [f: context.extentCovered.min.f, s: context.extentCovered.max.s] ]]; IF Ceiling[context.viewPort.w] > context.extentCovered.max.f THEN FillPixels[[ -- right of previously affected area min: [f: context.extentCovered.max.f, s: context.extentCovered.min.s], max: [f: Ceiling[context.viewPort.w], s: context.extentCovered.max.s] ]]; <> FOR i: NAT IN [screenExtent.min.s..screenExtent.max.s) DO ImagerPixel.GetPixels[ -- get foreground pixels self: context.pixels, pixels: srcBuf, initIndex: [f: screenExtent.min.f, s: i], count: scanLength ]; FOR j: NAT IN [0..scanLength) DO -- blend background behind foreground uncvrd: NAT _ 255 - Basics.LowByte[ srcBuf[alpha^][j] ]; -- top byte may be mask IF uncvrd > 0 THEN IF uncvrd < 255 THEN FOR k: NAT IN [0..samplesPerColor) DO -- partially covered newPart: CARDINAL _ Basics.HighByte[ uncvrd * value[k] + 128 ]; srcBuf[k][j] _ srcBuf[k][j] + newPart; -- ( + 128 for rounding ) ENDLOOP ELSE FOR k: NAT IN [0..samplesPerColor) DO -- no previous coverage srcBuf[k][j] _ value[k]; ENDLOOP; ENDLOOP; ImagerPixel.PutPixels[ -- store result in foreground self: context.pixels, pixels: srcBuf, initIndex: [f: screenExtent.min.f, s: i], count: scanLength ]; ENDLOOP; }; <> context.extentCovered _ [[0,0], [0,0]]; }; FillInBackGroundImage: PROC[context, bkgrdCtx: REF Context] ~ { IF NOT bkgrdCtx.imageReady THEN IF Atom.GetPropFromList[bkgrdCtx.props, $BackGrdImage] # NIL THEN { aisFile: Rope.ROPE _ NARROW[Atom.GetPropFromList[bkgrdCtx.props, $BackGrdImage]]; bkgrdCtx.viewPort _ context.viewPort; -- assumes we want consistency of viewports AllocatePixelMemory[ bkgrdCtx ]; FillInConstantBackGround[bkgrdCtx, [0,0,0] ]; -- ensure alpha, depth, etc. clean [] _ AISAnimation.GetAIS[bkgrdCtx, aisFile]; bkgrdCtx.imageReady _ TRUE; } ELSE SIGNAL ThreeDBasics.Error[[$MisMatch, "Background image not ready"]]; IF context.antiAliasing THEN CopyUnder[context, bkgrdCtx] ELSE FOR i: NAT IN[0..bkgrdCtx.pixels.samplesPerPixel) DO ImagerSample.Transfer[ dst: context.pixels[i], src: bkgrdCtx.pixels[i] ]; ENDLOOP; IF context.antiAliasing -- try to add yet another background behind this one THEN WITH Atom.GetPropFromList[bkgrdCtx.props, $BackGround] SELECT FROM bkgrdClr: REF RGB => FillInConstantBackGround[context, bkgrdClr^]; -- constant color bhndCtx: REF Context => FillInBackGroundImage[context, bhndCtx]; -- background ctx ENDCASE => {}; -- NIL or garbage, all done <> context.extentCovered _ [[0,0], [0,0]]; }; CopyUnder: PROC[context, bkgrdCtx: REF Context] ~ { Transfer: PROC[box: Box] ~ { FOR i: NAT IN [0..bkgrdCtx.pixels.samplesPerPixel) DO ImagerSample.BasicTransfer[ dst: context.pixels[i], src: bkgrdCtx.pixels[i], dstMin: box.min, srcMin: box.min, size: [f: box.max.f - box.min.f, s: box.max.s - box.min.s] ]; ENDLOOP; }; samplesPerColor: NAT _ IF context.class.displayType = $FullColor THEN 3 ELSE 1; alpha: REF NAT _ NARROW[ Atom.GetPropFromList[context.displayProps, $Alpha] ]; scanLength: NAT _ context.extentCovered.max.f - context.extentCovered.min.f; srcBuf: PixelBuffer _ ImagerPixel.NewPixels[bkgrdCtx.pixels.samplesPerPixel, scanLength]; dstBuf: PixelBuffer _ ImagerPixel.NewPixels[context.pixels.samplesPerPixel, scanLength]; IF alpha = NIL THEN SIGNAL ThreeDBasics.Error[[$MisMatch, "No A buffer"]]; <> Transfer[[ -- below previously affected area min: [0, 0], max: [f: Ceiling[context.viewPort.w], s: context.extentCovered.min.s] ]]; IF Ceiling[context.viewPort.h] > context.extentCovered.max.s THEN Transfer[[ min: [f: 0, s: context.extentCovered.max.s], -- above previously affected area max: [f: Ceiling[context.viewPort.w], s: Ceiling[context.viewPort.h]] ]]; Transfer[[ -- left of previously affected area min: [f: 0, s: context.extentCovered.min.s], max: [f: context.extentCovered.min.f, s: context.extentCovered.max.s] ]]; IF Ceiling[context.viewPort.w] > context.extentCovered.max.f THEN Transfer[[ -- right of previously affected area min: [f: context.extentCovered.max.f, s: context.extentCovered.min.s], max: [f: Ceiling[context.viewPort.w], s: context.extentCovered.max.s] ]]; <> FOR i: NAT IN [context.extentCovered.min.s .. context.extentCovered.max.s) DO ImagerPixel.GetPixels[ -- get foreground pixels self: bkgrdCtx.pixels, pixels: srcBuf, initIndex: [f: context.extentCovered.min.f, s: i], count: scanLength ]; ImagerPixel.GetPixels[ -- get background pixels self: context.pixels, pixels: dstBuf, initIndex: [f: context.extentCovered.min.f, s: i], count: scanLength ]; FOR j: NAT IN [0..scanLength) DO -- blend background behind foreground uncvrd: NAT _ 255 - Basics.LowByte[ dstBuf[alpha^][j] ]; -- top byte may be mask IF uncvrd > 0 THEN FOR k: NAT IN [0..samplesPerColor) DO <> dstBuf[k][j] _ dstBuf[k][j] + Basics.HighByte[ uncvrd * srcBuf[k][j] + 128 ] ENDLOOP; -- ( + 128 for rounding ) dstBuf[alpha^][j] _ 255; -- set to full coverage LOOPHOLE[dstBuf[alpha^][j], Basics.BytePair].high _ 255; -- mask now meaningless ENDLOOP; ImagerPixel.PutPixels[ -- store result in foreground self: context.pixels, pixels: dstBuf, initIndex: [f: context.extentCovered.min.f, s: i], count: scanLength ]; ENDLOOP; }; <> MakeFrame: PUBLIC ContextProc ~ { <> IF context.antiAliasing THEN { SurfaceRender.ValidateContext[context]; -- ensure viewPort etc. updated FillInConstantBackGround[context, [0.0,0.0,0.0], 0 ]; -- clear screen, alpha buffer SurfaceRender.ShowShapes[context]; -- render image context.class.loadBackground[context]; -- load background } ELSE SurfaceRender.MakeFrame[context]; }; <> ShadeSpot: PUBLIC ThreeDBasics.SpotProc ~ { <> pt: VertexInfo; pt.coord.ex _ spot.val[7]; pt.coord.ey _ spot.val[8]; pt.coord.ez _ spot.val[9]; pt.shade.exn _ spot.val[4]; pt.shade.eyn _ spot.val[5]; pt.shade.ezn _ spot.val[6]; pt.shade.r _ spot.val[0]; pt.shade.g _ spot.val[1]; pt.shade.b _ spot.val[2]; pt.shade.t _ spot.val[3]; IF spot.partShiny < 1.0 THEN pt.props _ Atom.PutPropOnList[pt.props, $PartShiny, NEW[REAL _ spot.partShiny] ]; pt _ shading.shadeVtx[ context, pt, shading ]; spot.val[0] _ pt.shade.er; spot.val[1] _ pt.shade.eg; spot.val[2] _ pt.shade.eb; spot.val[3] _ pt.shade.et; }; Draw2DLine: PUBLIC PROC[context: REF Context, p1, p2: Pair, color: Pixel] ~ { ip1, ip2: IntegerPair; ip1 _ PairToScreen[context, p1]; ip2 _ PairToScreen[context, p2]; ScanConvert.PutLine[ context, ip1, ip2, color, color ] }; Draw2DPoly: PUBLIC PROC[context: REF Context, poly: REF PairSequence, color: Pixel] ~ { plygn: REF Patch _ NEW[Patch[poly.length]]; FOR i: NAT IN [0..poly.length) DO p: IntegerPair _ PairToScreen[ context, poly[i] ]; plygn[i].coord.sx _ p.x; plygn[i].coord.sy _ p.y; ENDLOOP; plygn.nVtces _ poly.length; ScanConvert.FastFlatTiler[ context, plygn, color ] }; DrawRope: PUBLIC PROC[context: REF Context, rope: Rope.ROPE, position: Pair, color: Pixel _ [255,255,255,0,0] , size: REAL _ 20, font: Rope.ROPE _ NIL] ~ { imagerCtx: Imager.Context _ NARROW[ Atom.GetPropFromList[context.props, $ImagerCtx] ]; ropeData: REF RopeDesc _ NEW[ RopeDesc _ [rope, position, color, size, font] ]; IF imagerCtx = NIL THEN imagerCtx _ GetImagerCtx[context]; DoRope[ context, imagerCtx, ropeData ]; }; DoRope: PUBLIC ThreeDBasics.ImagerProc ~ { <> ropeData: REF RopeDesc _ NARROW[data]; p: Pair _ ropeData.position; color: Pixel _ ropeData.color; theFont: Imager.Font; Imager.SetColor[ -- ok but how do you set transparency?? imagerCtx, ImagerColor.ColorFromRGB[[color[r]/255.0, color[g]/255.0, color[b]/255.0]] ]; IF ropeData.font = NIL THEN ropeData.font _ "Xerox/Pressfonts/TimesRoman-MRR"; theFont _ ImagerFont.Find[ropeData.font]; theFont _ ImagerFont.Scale[theFont, ropeData.size]; Imager.SetFont[imagerCtx, theFont]; Imager.SetXY[ imagerCtx, [context.ndcToPixels.scaleX * p.x, ABS[context.ndcToPixels.scaleY] * p.y] ]; Imager.ShowRope[imagerCtx, ropeData.rope]; }; <> PolygonTiler: PUBLIC PatchProc ~ { <> shape: REF ShapeInstance _ NARROW[ Atom.GetPropFromList[patch.props, $Shape] ]; IF patch.type = $PolyLine THEN OutLineTiler[context, patch] ELSE IF context.antiAliasing THEN FancyTiler[context, patch] -- standard anti-aliasing tiler ELSE SELECT shape.shadingClass.shadingType FROM $Lines => OutLineTiler[context, patch]; -- lines with constant shading $Faceted => IF NOT context.depthBuffering -- filled polygons with flat shading THEN FastFlatTiler[context, patch] ELSE LerpTiler[context, patch]; $ShadedLines => OutLineTiler[context, patch]; -- lines with varying shading $HiddenLines, $NormaledLines => { -- white faceted polygon with black outlines IF shape.shadingClass.shadingType = $NormaledLines AND patch.dir = back THEN DrawNormals[context, patch]; FOR i: NAT IN [0..patch.nVtces) DO -- force patch white for opaque polygon patch[i].shade.er _ patch[i].shade.eg _ patch[i].shade.eb _ 1.0; ENDLOOP; IF NOT context.depthBuffering THEN FastFlatTiler[context, patch] ELSE LerpTiler[context, patch]; IF shape.shadingClass.shadingType = $HiddenLines THEN FOR i: NAT IN [0..patch.nVtces) DO -- use shape color for outlining patch[i].shade.er _ shape.shadingClass.color.R; patch[i].shade.eg _ shape.shadingClass.color.G; patch[i].shade.eb _ shape.shadingClass.color.B; ENDLOOP ELSE FOR i: NAT IN [0..patch.nVtces) DO -- force vertices black for outlining patch[i].shade.er _ patch[i].shade.eg _ patch[i].shade.eb _ 0.0; ENDLOOP; OutLineTiler[context, patch]; IF shape.shadingClass.shadingType = $NormaledLines AND patch.dir # back THEN DrawNormals[context, patch]; }; ENDCASE => IF shape.shadingClass.shininess > 0.0 OR shape.shadingClass.texture # NIL THEN ShinyTiler[context, patch] -- highlight tiler ELSE LerpTiler[context, patch]; -- default is smooth shading RETURN[NIL]; }; OutLineTiler: PROC[ context: REF Context, poly: REF Patch ] ~ { last: NAT _ IF poly.type = $PolyLine THEN 0 ELSE poly.nVtces - 1; p1: IntegerPair _ [ Real.Round[poly[last].coord.sx], Real.Round[poly[last].coord.sy] ]; clr1: Pixel _ [ Real.Fix[poly[last].shade.er*255.0], Real.Fix[poly[last].shade.eg*255.0], Real.Fix[poly[last].shade.eb*255.0], Real.Fix[poly[last].shade.et*255.0], Real.Round[poly[last].coord.sz] ]; SELECT context.class.displayType FROM $PseudoColor => clr1[r] _ 42 * (clr1[r] * 6 / 256) + 6 * (clr1[g] * 7 / 256) + (clr1[b] * 6 / 256) +2; $Gray => clr1[r] _ (clr1[r] + clr1[g] + clr1[b]) / 3; ENDCASE; FOR i: NAT IN [0..poly.nVtces) DO p2: IntegerPair _ [ Real.Round[poly[i].coord.sx], Real.Round[poly[i].coord.sy] ]; clr2: Pixel _ [ Real.Fix[poly[i].shade.er*255.0], Real.Fix[poly[i].shade.eg*255.0], Real.Fix[poly[i].shade.eb*255.0], Real.Fix[poly[i].shade.et*255.0], Real.Round[poly[i].coord.sz] ]; SELECT context.class.displayType FROM $PseudoColor => clr2[r] _ 42 * (clr2[r] * 6 / 256) + 6 * (clr2[g] * 7 / 256) + (clr2[b] * 6 / 256) +2; $Gray => clr2[r] _ (clr2[r] + clr2[g] + clr2[b]) / 3; ENDCASE; ScanConvert.PutLine[context, p1, p2, clr1, clr2]; p1 _ p2; clr1 _ clr2; ENDLOOP; }; DrawNormals: PROC[ context: REF Context, poly: REF Patch ] ~ { pixel: Pixel _ [0, 0, 0, 0, 0]; FOR i: NAT IN [0..poly.nVtces) DO p1: IntegerPair _ [ Real.Round[poly[i].coord.sx], Real.Round[poly[i].coord.sy] ]; ep2: Triple _ G3dVector.Add[ [poly[i].coord.ex, poly[i].coord.ey, poly[i].coord.ez], [poly[i].shade.exn/4, poly[i].shade.eyn/4, poly[i].shade.ezn/4] ]; clip: OutCode _ ShapeUtilities.GetClipCodeForPt[ context, ep2 ]; IF clip = NoneOut THEN { sp2: Triple _ ShapeUtilities.XfmPtToDisplay[ context, ep2 ]; p2: IntegerPair _ [ Real.Round[sp2.x], Real.Round[sp2.y] ]; ScanConvert.PutLine[context, p1, p2, pixel, pixel]; }; ENDLOOP; }; FastFlatTiler: PROC[ context: REF Context, poly: REF Patch ] ~ { pixel: Pixel _ [ Real.Fix[poly.vtx[0].shade.er * 255.0], -- red Real.Fix[poly.vtx[0].shade.eg * 255.0], -- green Real.Fix[poly.vtx[0].shade.eb * 255.0], -- blu 255, -- full coverage LAST[CARD16] - 1 -- depth ]; ScanConvert.FastFlatTiler[context, poly, pixel]; }; LerpTiler: PROC[ context: REF Context, poly: REF Patch] ~ { IF context.class.displayType = $Gray THEN FOR i: NAT IN [0..poly.nVtces) DO OPEN poly.vtx[i].shade; er _ (er + eg + eb) * (1.0/3.0); ENDLOOP; IF context.depthBuffering THEN { zScale: REAL _ REAL[LAST[INT16]] / context.depthResolution; FOR i: NAT IN [0..poly.nVtces) DO OPEN poly.vtx[i].coord; sz _ sz * zScale; -- scale for max depth range ENDLOOP; }; ScanConvert.LerpTiler[context, poly]; }; <> ShinyTiler: PROC[context: REF Context, poly: REF Patch] ~ { shape: REF ShapeInstance _ NARROW[ Atom.GetPropFromList[poly.props, $Shape] ]; zScale: REAL _ REAL[LAST[INT16]] / context.depthResolution; hltCnt: NAT _ 0; lightColor: REF NatRGBSequence; hiliteInfo: REF HilitSeqs _ IF shape.shadingClass.shininess > 0.0 THEN GotAHilite[context, poly, shape.shadingClass.shininess] -- hilight? ELSE NIL; IF (hiliteInfo = NIL OR noHighLights) AND shape.shadingClass.texture = NIL THEN { <> IF shape.shadingClass.shadingType = $Faceted AND NOT context.depthBuffering THEN FastFlatTiler[context, poly] ELSE LerpTiler[context, poly]; RETURN[]; }; <> IF hiliteInfo # NIL THEN FOR j: NAT IN [0..context.lightSources.length) DO IF hiliteInfo.flags[j] THEN hltCnt _ hltCnt + 1; -- figure storage for extra normal info ENDLOOP; IF shape.shadingClass.texture # NIL THEN { txtrRange: REF Pair _ NARROW[ Atom.GetPropFromList[shape.shadingProps, $TxtrCoordRange] ]; IF txtrRange # NIL -- fix texture seams THEN MappedAndSolidTexture.AdjustTexture[ poly, shape.shadingClass.texture, txtrRange.x, txtrRange.y ] ELSE MappedAndSolidTexture.AdjustTexture[poly, shape.shadingClass.texture]; hltCnt _ hltCnt + 1; }; FOR i: NAT IN [0..poly.nVtces) DO -- get extra storage for each vertex txtrX, txtrY: REAL; intPairs: REF IntegerPairSequence; IF shape.shadingClass.texture # NIL THEN [txtrX, txtrY] _ NARROW[poly.vtx[i].aux, REF Pair]^; poly.vtx[i].aux _ intPairs _ NEW[IntegerPairSequence[hltCnt]]; IF shape.shadingClass.texture # NIL THEN { intPairs[0] _ [ Real.Round[LAST[NAT]/32*txtrX], Real.Round[LAST[NAT]/32*txtrY] ]; }; ENDLOOP; IF hiliteInfo # NIL THEN { lightColor _ NEW[NatRGBSequence[hltCnt]]; lightColor.length _ hltCnt; -- light colors hltCnt _ IF shape.shadingClass.texture # NIL THEN 1 ELSE 0; FOR j: NAT IN [0..context.lightSources.length) DO IF hiliteInfo.flags[j] THEN { -- pick up info for each highlight-causing light clr: RGB _ context.lightSources[j].shadingClass.color; lightColor[hltCnt] _ [ Real.Round[LAST[NAT]*clr.R], Real.Round[LAST[NAT]*clr.G], Real.Round[LAST[NAT]*clr.B] ]; FOR i: NAT IN [0..poly.nVtces) DO vtxAux: REF IntegerPairSequence _ NARROW[poly.vtx[i].aux]; vtxAux[hltCnt] _ hiliteInfo.refls[i + j*poly.nVtces]; -- store with vertex ENDLOOP; hltCnt _ hltCnt + 1; }; ENDLOOP; poly.props _ Atom.PutPropOnList[poly.props, $LightColors, lightColor]; -- store light colors }; IF context.class.displayType = $Gray THEN FOR i: NAT IN [0..poly.nVtces) DO OPEN poly.vtx[i].shade; er _ (er + eg + eb) / 3.0; ENDLOOP; IF context.depthBuffering THEN FOR i: NAT IN [0..poly.nVtces) DO poly.vtx[i].coord.sz _ poly.vtx[i].coord.sz * zScale; -- scale for max depth range ENDLOOP; ScanConvert.HiliteTiler[ context, poly, Real.Round[shape.shadingClass.shininess] ]; IF hiliteInfo # NIL THEN ReleaseHilitSeqs[hiliteInfo]; }; GotAHilite: PROC[context: REF Context, poly: REF Patch, shininess: REAL] RETURNS[REF HilitSeqs ] ~ { XfmNormal: PROC[light: Vertex, vtx: VertexInfo] RETURNS[ Triple ] ~ { <> toLightSrc: Triple _ G3dVector.Normalize[ -- normalized direction to light [ light.ex - vtx.coord.ex, light.ey - vtx.coord.ey, light.ez - vtx.coord.ez ] ]; toEye: Triple _ G3dVector.Normalize[ -- normalized direction to eye [ -vtx.coord.ex, -vtx.coord.ey, -vtx.coord.ez ] ]; idealRefl: Triple _ G3dVector.Mul[ G3dVector.Add[toLightSrc, toEye], 0.5 ]; hypotA: REAL _ RealFns.SqRt[ Sqr[idealRefl.x] + Sqr[idealRefl.z] ]; -- rotate about y cosA: REAL _ idealRefl.z / hypotA; sinA: REAL _ idealRefl.x / hypotA; hypotB: REAL _ RealFns.SqRt[ Sqr[idealRefl.y] + Sqr[hypotA] ]; -- rotate about x cosB: REAL _ hypotA / hypotB; sinB: REAL _ idealRefl.y / hypotB; tx: REAL _ cosA*vtx.shade.exn - sinA*vtx.shade.ezn; ty: REAL _ vtx.shade.eyn; tz: REAL _ sinA*vtx.shade.exn + cosA*vtx.shade.ezn; IF tx = 0.0 AND ty = 0.0 AND tz = 0.0 THEN RETURN [[0.0, 0.0, 0.0]]; -- null normal RETURN[ G3dVector.Normalize[ [x: tx, y: cosB*ty - sinB*tz, z: sinB*ty + cosB*tz] ] ]; }; gotAHilite: BOOLEAN _ FALSE; hilitInfo: REF HilitSeqs _ GetHilitSeqs[ reflSize: poly.nVtces * context.lightSources.length, flagSize: context.lightSources.length ]; idealReflSeq: REF IntegerPairSequence _ hilitInfo.refls; lightFlags: REF BooleanSequence _ hilitInfo.flags; IF shininess <= 0.0 THEN { ThreeDBasics.Error[[$Warning, "Shininess not positive"]]; RETURN[NIL]; -- illegitimate input }; FOR j: NAT IN [0..context.lightSources.length) DO minX, minY: REAL _ Real.LargestNumber; maxX, maxY: REAL _ -Real.LargestNumber; FOR i: NAT IN [0..poly.nVtces) DO -- get bounding box on highlight reflection vectors reflVec: Triple _ XfmNormal[ context.lightSources[j].centroid, poly[i] ]; k: NAT _ (j * poly.nVtces) + i; minX _ MIN[minX, reflVec.x]; maxX _ MAX[maxX, reflVec.x]; minY _ MIN[minY, reflVec.y]; maxY _ MAX[maxY, reflVec.y]; idealReflSeq[k] _ [ Real.Round[reflVec.x*LAST[NAT]/2], -- scale to half of integer range (to allow sums) Real.Round[reflVec.y*LAST[NAT]/2] ]; ENDLOOP; <> minX _ IF Sgn[minX] # Sgn[maxX] THEN 0.0 ELSE MIN[ABS[minX], ABS[maxX]]; minY _ IF Sgn[minY] # Sgn[maxY] THEN 0.0 ELSE MIN[ABS[minY], ABS[maxY]]; IF RealFns.Power[ MAX[ 0.0, 1.0 - Sqr[minX] - Sqr[minY] ], shininess ] > .25*justNoticeable THEN { gotAHilite _ TRUE; lightFlags[j] _ TRUE; } ELSE lightFlags[j] _ FALSE; ENDLOOP; idealReflSeq.length _ poly.nVtces * context.lightSources.length; IF NOT gotAHilite THEN { ReleaseHilitSeqs[hilitInfo]; hilitInfo _ NIL; }; RETURN[hilitInfo]; }; <> FancyTiler: PUBLIC PROC[context: REF Context, poly: REF Patch] ~ { shape: REF ShapeInstance _ NARROW[ Atom.GetPropFromList[poly.props, $Shape] ]; shadingClass: REF ShadingClass _ shape.shadingClass; zScale: REAL _ REAL[LAST[INT16]] / context.depthResolution; pixelShading: ATOM _ context.class.displayType; -- flags shading per pixel textureMapping, solidTexture, yIncrements: BOOLEAN _ FALSE; fancyPoly: REF FancyPatch _ NEW[ FancyPatch[poly.nVtces] ]; IF NOT context.antiAliasing THEN ThreeDBasics.Error[[$MisMatch, "Not AntiAliased - wrong tiler"]]; <<>> IF context.depthBuffering THEN FOR i: NAT IN [0..poly.nVtces) DO poly.vtx[i].coord.sz _ poly.vtx[i].coord.sz * zScale; -- scale for max depth range ENDLOOP; IF shadingClass.cnvrtVtx = NIL THEN shadingClass.cnvrtVtx _ GetLerpedVals; IF shadingClass.getColor = NIL THEN shadingClass.getColor _ ShadeSpot; <> IF shadingClass.shininess > 0.0 THEN { hilitInfo: REF HilitSeqs _ GotAHilite[context, poly, shadingClass.shininess]; IF hilitInfo # NIL THEN { pixelShading _ $PixelShading; ReleaseHilitSeqs[hilitInfo]; }; }; <> IF shadingClass.texture # NIL THEN { txtrRange: REF Pair _ NARROW[ Atom.GetPropFromList[shape.shadingProps, $TxtrCoordRange] ]; IF txtrRange # NIL -- fix texture seams THEN MappedAndSolidTexture.AdjustTexture[ poly, shadingClass.texture, txtrRange.x, txtrRange.y ] ELSE MappedAndSolidTexture.AdjustTexture[poly, shadingClass.texture]; pixelShading _ $PixelShading; -- shading at each pixel (could be avoided for intensity) }; <> IF Atom.GetPropFromList[context.displayProps, $NormalBuffer] # NIL THEN pixelShading _ $PixelShading; FOR i: NAT IN [0..poly.nVtces) DO -- convert vertices to lerp fancyPoly[i] _ [ x: poly[i].coord.sx, y: poly[i].coord.sy, val: shadingClass.cnvrtVtx[ dest: fancyPoly[i].val, source: poly[i], data: pixelShading ] ]; ENDLOOP; fancyPoly.shadingClass _ IF pixelShading = $PixelShading OR shadingClass.texture # NIL THEN shadingClass ELSE NIL; RealFancyTiler[context, fancyPoly]; -- now go tile it IF statistics THEN polyCount _ polyCount + 1; }; RealFancyTiler: PUBLIC PROC[context: REF Context, poly: REF FancyPatch] ~ { least, top, bottom: REAL; lEdge, rEdge: EdgeBlock; vtxCount, lVtx, rVtx, nxtlVtx, nxtrVtx: NAT; lftStep: NAT _ poly.length - 1; rgtStep: NAT _ 1; -- increments to next vtx in order leftVtxNeeded, rightVtxNeeded: BOOLEAN; least _ poly.vtx[0].y; nxtlVtx _ 0; FOR i: CARDINAL IN [1..poly.length) DO -- find bottom vertex IF poly.vtx[i].y < least THEN { least _ poly[i].y; nxtlVtx _ i; }; ENDLOOP; nxtrVtx _ nxtlVtx; -- set pointers to bottom vertex leftVtxNeeded _ rightVtxNeeded _ TRUE; vtxCount _ 1; WHILE vtxCount < poly.length DO -- Do until all vertices reached IF leftVtxNeeded THEN { -- work around left side lVtx _ nxtlVtx; nxtlVtx _ (nxtlVtx + lftStep) MOD poly.length; lEdge _ MakeEdge[poly[lVtx], poly[nxtlVtx]]; leftVtxNeeded _ FALSE; }; IF rightVtxNeeded THEN { -- work around right side rVtx _ nxtrVtx; nxtrVtx _ (nxtrVtx + rgtStep) MOD poly.length; rEdge _ MakeEdge[poly[rVtx], poly[nxtrVtx]]; rightVtxNeeded _ FALSE; }; IF poly[nxtlVtx].y < poly[nxtrVtx].y -- get trapezoid given by next higher vertex THEN { top _ poly[nxtlVtx].y; -- next left vertex reached leftVtxNeeded _ TRUE; vtxCount _ vtxCount + 1; } ELSE { top _ poly[nxtrVtx].y; -- next right vertex reached rightVtxNeeded _ TRUE; vtxCount _ vtxCount + 1; }; bottom _ MAX[poly[lVtx].y, poly[rVtx].y]; ShowFancyTrap[ context, poly, bottom, top, lEdge, rEdge ! ThreeDBasics.Error => IF reason.code = $DeepRecursionInTiler THEN CONTINUE ]; ENDLOOP; }; MakeEdge: PROC[vtx1, vtx2: LerpVtx] RETURNS[edge: EdgeBlock] ~ { length: REAL; edge.val _ NEW[ RealSequence[vtx1.val.length] ]; edge.incr _ NEW[ RealSequence[vtx1.val.length] ]; IF ABS[vtx2.y - vtx1.y] >= ABS[vtx2.x - vtx1.x] THEN { length _ vtx2.y - vtx1.y; edge.start _ MIN[vtx1.y, vtx2.y]; edge.end _ MAX[vtx1.y, vtx2.y]; edge.moreVertical _ TRUE; } ELSE { length _ vtx2.x - vtx1.x; edge.start _ MIN[vtx1.x, vtx2.x]; edge.end _ MAX[vtx1.x, vtx2.x]; edge.moreVertical _ FALSE; }; IF ABS[length] < justNoticeable THEN length _ 1.; -- prevent divide errors edge.x _ vtx1.x; edge.xIncr _ (vtx2.x - vtx1.x) / length; edge.y _ vtx1.y; edge.yIncr _ (vtx2.y - vtx1.y) / length; FOR i: NAT IN [0..vtx1.val.length) DO edge.val[i] _ vtx1.val[i]; edge.incr[i] _ (vtx2.val[i] - vtx1.val[i]) / length; ENDLOOP; edge.val.length _ edge.incr.length _ vtx1.val.length; RETURN[edge]; }; EvalEdgeAt: PROC[vtx: LerpVtx, edge: EdgeBlock, position: REAL] RETURNS[LerpVtx] ~ { pos, dist: REAL; IF vtx.val = NIL OR vtx.val.maxLength < edge.val.length THEN vtx.val _ NEW[ RealSequence[edge.val.length] ]; IF position > edge.end THEN pos _ edge.end -- keep values between vertex values ELSE IF position < edge.start THEN pos _ edge.start ELSE pos _ position; dist _ IF edge.moreVertical THEN pos - edge.y ELSE pos - edge.x; vtx.x _ edge.x + edge.xIncr * dist; vtx.y _ edge.y + edge.yIncr * dist; FOR i: NAT IN [0..edge.val.length) DO vtx.val[i] _ edge.val[i] + edge.incr[i] * dist; ENDLOOP; vtx.val.length _ edge.val.length; RETURN[vtx]; }; ShowFancyTrap: PROC[ context: REF Context, inPoly: REF FancyPatch, bottom, top: REAL, lEdge, rEdge: EdgeBlock] ~ { GetXcoordAt: PROC[edge: EdgeBlock, yPos: REAL] RETURNS [REAL] ~ { dist: REAL _ yPos - edge.y; RETURN [ edge.x + dist / edge.yIncr ]; }; tEdge, bEdge, midlEdge, midrEdge: EdgeBlock; leftTopVtx, leftBotVtx, rightTopVtx, rightBotVtx, vtx0, vtx1, vtx2, vtx3, vertex: LerpVtx; sideways, midSection: BOOLEAN _ TRUE; toughCase: BOOLEAN _ FALSE; a, b: REAL; -- parameters for defining 45 degree lines IF bottom + justNoticeable >= top THEN RETURN[]; -- too thin to affect image midlEdge _ DupEdgeBlock[lEdge]; -- copy sides for possible later use midrEdge _ DupEdgeBlock[rEdge]; FOR i: NAT IN [0..midlEdge.val.length) DO midlEdge.val[i] _ lEdge.val[i]; midlEdge.incr[i] _ lEdge.incr[i]; midrEdge.val[i] _ rEdge.val[i]; midrEdge.incr[i] _ rEdge.incr[i]; ENDLOOP; IF NOT (lEdge.moreVertical AND rEdge.moreVertical) THEN { -- get corners IF lEdge.moreVertical THEN { leftTopVtx _ EvalEdgeAt[leftTopVtx, lEdge, top]; leftBotVtx _ EvalEdgeAt[leftBotVtx, lEdge, bottom]; } ELSE { topX: REAL _ GetXcoordAt[lEdge, top]; botX: REAL _ GetXcoordAt[lEdge, bottom]; leftTopVtx _ EvalEdgeAt[leftTopVtx, lEdge, topX]; leftBotVtx _ EvalEdgeAt[leftBotVtx, lEdge, botX]; }; IF rEdge.moreVertical THEN { rightTopVtx _ EvalEdgeAt[rightTopVtx, rEdge, top]; rightBotVtx _ EvalEdgeAt[rightBotVtx, rEdge, bottom]; } ELSE { topX: REAL _ GetXcoordAt[rEdge, top]; botX: REAL _ GetXcoordAt[rEdge, bottom]; rightTopVtx _ EvalEdgeAt[rightTopVtx, rEdge, topX]; rightBotVtx _ EvalEdgeAt[rightBotVtx, rEdge, botX]; }; IF rightTopVtx.x + justNoticeable < leftTopVtx.x OR rightBotVtx.x + justNoticeable < leftBotVtx.x THEN RETURN[]; -- twisted or backfacing }; << if left side more horizontal, check for slope, make top or bottom edge,>> <> IF NOT lEdge.moreVertical THEN IF lEdge.yIncr < 0. THEN { -- left edge is more horizontal, top vertex is leftmost tEdge _ MakeEdge[leftTopVtx, rightTopVtx]; bEdge _ DupEdgeBlock[lEdge]; IF leftBotVtx.x <= rightTopVtx.x THEN { -- easy case: right triangle containing whole left edge ShowSteepTrap[context, inPoly.shadingClass, leftTopVtx.x, leftBotVtx.x, bEdge, tEdge, sideways]; midlEdge _ MakeEdge[leftBotVtx, EvalEdgeAt[vertex, tEdge, leftBotVtx.x]]; } ELSE { -- right top is left of left bottom IF rEdge.moreVertical THEN { -- difficult case bot. more horz. top more vert. toughCase _ TRUE; ShowSteepTrap[context, inPoly.shadingClass, leftTopVtx.x, rightTopVtx.x, bEdge, tEdge, sideways]; vtx0 _ EvalEdgeAt[vtx0, bEdge, rightTopVtx.x]; --build new polygon vtx1 _ EvalEdgeAt[vtx1, rEdge, rightTopVtx.y]; vtx2 _ EvalEdgeAt[vtx2, rEdge, rightBotVtx.y]; vtx3 _ EvalEdgeAt[vtx3, bEdge, leftBotVtx.x]; a _ .707; b _ .707; -- test against negative 45 degree slope } ELSE { -- both more horz. do triangle then trapezoid ShowSteepTrap[context, inPoly.shadingClass, leftTopVtx.x, rightTopVtx.x, bEdge, tEdge, sideways]; ShowSteepTrap[context, inPoly.shadingClass, rightTopVtx.x, leftBotVtx.x, bEdge, rEdge, sideways]; midSection _ FALSE; }; }; } ELSE { -- left edge is more horizontal, bottom vertex is leftmost bEdge _ MakeEdge[leftBotVtx, rightBotVtx]; tEdge _ DupEdgeBlock[lEdge]; IF leftTopVtx.x <= rightBotVtx.x THEN { -- easy case: right triangle containing whole left edge ShowSteepTrap[context, inPoly.shadingClass, leftBotVtx.x, leftTopVtx.x, bEdge, tEdge, sideways]; midlEdge _ MakeEdge[leftTopVtx, EvalEdgeAt[vertex, bEdge, leftTopVtx.x]]; } ELSE { -- right bottom is left of left top IF rEdge.moreVertical THEN { -- difficult case bot. more vert. top more horz. toughCase _ TRUE; ShowSteepTrap[context, inPoly.shadingClass, leftBotVtx.x, rightBotVtx.x, bEdge, tEdge, sideways]; vtx0 _ EvalEdgeAt[vtx0, rEdge, rightBotVtx.y]; --build new polygon vtx1 _ EvalEdgeAt[vtx1, tEdge, rightBotVtx.x]; vtx2 _ EvalEdgeAt[vtx2, tEdge, leftTopVtx.x]; vtx3 _ EvalEdgeAt[vtx3, rEdge, rightTopVtx.y]; a _ -.707; b _ .707; -- test against positive 45 degree slope } ELSE { -- both more horz. do triangle then trapezoid ShowSteepTrap[context, inPoly.shadingClass, leftBotVtx.x, rightBotVtx.x, bEdge, tEdge, sideways]; ShowSteepTrap[context, inPoly.shadingClass, rightBotVtx.x, leftTopVtx.x, rEdge, tEdge, sideways]; midSection _ FALSE; }; }; }; <> IF NOT rEdge.moreVertical THEN IF rEdge.yIncr < 0. THEN { -- right edge is more horizontal, top vertex is leftmost bEdge _ MakeEdge[leftBotVtx, rightBotVtx]; tEdge _ DupEdgeBlock[rEdge]; IF leftBotVtx.x <= rightTopVtx.x THEN { -- easy case: right triangle containing whole right edge ShowSteepTrap[context, inPoly.shadingClass, rightTopVtx.x, rightBotVtx.x, bEdge, tEdge, sideways]; midrEdge _ MakeEdge[EvalEdgeAt[vertex, bEdge, rightTopVtx.x], rightTopVtx]; } ELSE { -- left bottom is right of right top IF lEdge.moreVertical THEN { -- difficult case bot. more vert. top more horz. toughCase _ TRUE; vtx0 _ EvalEdgeAt[vtx0, lEdge, rightTopVtx.y]; --build new polygon vtx1 _ EvalEdgeAt[vtx1, tEdge, rightTopVtx.x]; vtx2 _ EvalEdgeAt[vtx2, tEdge, leftBotVtx.x]; vtx3 _ EvalEdgeAt[vtx3, lEdge, leftBotVtx.y]; a _ .707; b _ .707; -- test against negative 45 degree slope }; <> ShowSteepTrap[context, inPoly.shadingClass, leftBotVtx.x, rightBotVtx.x, bEdge, tEdge, sideways]; }; } ELSE { -- right edge is more horizontal, bottom vertex is leftmost tEdge _ MakeEdge[leftTopVtx, rightTopVtx]; bEdge _ DupEdgeBlock[rEdge]; IF leftTopVtx.x <= rightBotVtx.x THEN { -- easy case: right triangle containing whole right edge ShowSteepTrap[context, inPoly.shadingClass, rightBotVtx.x, rightTopVtx.x, bEdge, tEdge, sideways]; midrEdge _ MakeEdge[rightBotVtx, EvalEdgeAt[vertex, tEdge, rightBotVtx.x]]; } ELSE { -- left top is right of right bottom IF lEdge.moreVertical THEN { -- difficult case bot. more vert. top more horz. toughCase _ TRUE; vtx0 _ EvalEdgeAt[vtx0, bEdge, rightBotVtx.x]; --build new polygon vtx1 _ EvalEdgeAt[vtx1, lEdge, rightBotVtx.y]; vtx2 _ EvalEdgeAt[vtx2, lEdge, leftTopVtx.y]; vtx3 _ EvalEdgeAt[vtx3, bEdge, leftTopVtx.x]; a _ -.707; b _ .707; -- test against positive 45 degree slope }; <> ShowSteepTrap[context, inPoly.shadingClass, leftTopVtx.x, rightTopVtx.x, bEdge, tEdge, sideways]; }; }; <<>> <> IF toughCase THEN { -- quadrilateral with top and bottom slopes on both sides of 45 deg. c, d1, d2, g3d: REAL; -- evaluate area based on distance of vertices from 45 degree line c _ -(a * vtx0.x + b * vtx0.y); -- equation for line through vtx0 d1 _ a * vtx1.x + b * vtx1.y + c; -- distances of other vertices from line d2 _ a * vtx2.x + b * vtx2.y + c; g3d _ a * vtx3.x + b * vtx3.y + c; IF (top - bottom) * ( MAX[d1, d2, g3d, 0.0] - MIN[d1, d2, g3d, 0.0] ) / 2.0 < justNoticeable THEN RETURN[] -- estimated area too small to matter ELSE { poly: REF FancyPatch _ NEW[FancyPatch[4]]; poly.vtx[0] _ DupLerpVtx[vtx3]; poly.vtx[1] _ DupLerpVtx[vtx2]; poly.vtx[2] _ DupLerpVtx[vtx1]; poly.vtx[3] _ DupLerpVtx[vtx0]; poly.shadingClass _ inPoly.shadingClass; poly.recurseLevel _ inPoly.recurseLevel + 1; IF poly.recurseLevel > recurseLimit THEN SIGNAL ThreeDBasics.Error[[$DeepRecursionInTiler, "Needs a new tiler"]]; RealFancyTiler[context, poly]; -- go draw it (recursively) }; } ELSE IF midSection THEN ShowSteepTrap[ context, inPoly.shadingClass, bottom, top, midlEdge, midrEdge ]; }; ShowSteepTrap: PROC[ context: REF Context, shadingClass: REF ShadingClass, bottom, top: REAL, lEdge, rEdge: EdgeBlock, sideways: BOOLEAN _ FALSE ] ~ { scanSeg: REF ScanSegment _ GetScanSeg[lEdge.val.length]; yIncrements: BOOLEAN _ FALSE; lStartSave: REAL _ lEdge.start; lEndSave: REAL _ lEdge.end; -- save limits to restore later rStartSave: REAL _ rEdge.start; rEndSave: REAL _ rEdge.end; yLimit: INTEGER _ IF sideways THEN Ceiling[context.viewPort.w-1] ELSE Ceiling[context.viewPort.h-1]; xLimit: INTEGER _ IF sideways THEN Ceiling[context.viewPort.h-1] ELSE Ceiling[context.viewPort.w-1]; IF context.stopMe^ THEN RETURN; IF bottom + justNoticeable >= top THEN RETURN[]; -- too vertically thin to affect image IF shadingClass # NIL THEN yIncrements _TRUE; -- indicates texture or highlights lEdge.start _ rEdge.start _ bottom; -- set edge limits for coverage calcs. lEdge.end _ rEdge.end _ top; FOR y: INTEGER IN [Floor[bottom]..Ceiling[top]] DO IF context.stopMe^ THEN RETURN; IF y < 0 OR y >= yLimit THEN LOOP; -- scissor off if out-of-bounds scanSeg _ MakeScanSeg[scanSeg, lEdge, rEdge, Real.Float[y], yIncrements]; IF scanSeg.end - scanSeg.start > justNoticeable THEN -- if wide enough to affect image ShowScanSeg[context, y, scanSeg, xLimit, shadingClass, sideways]; ENDLOOP; lEdge.start _ lStartSave; lEdge.end _ lEndSave; -- restore limits rEdge.start _ rStartSave; rEdge.end _ rEndSave; ReleaseScanSeg[scanSeg]; }; populationCount: ARRAY [0..256) OF NAT; realFromByte: ARRAY [0..256) OF REAL; MakeScanSeg: PROC[seg: REF ScanSegment, lEdge, rEdge: EdgeBlock, position: REAL, increments: BOOLEAN] RETURNS[REF ScanSegment] ~ { length, lCvrge, rCvrge: REAL; size: NAT _ lEdge.val.length; lVtx: REF LerpVtx _ GetVertex[size]; rVtx: REF LerpVtx _ GetVertex[size]; lVtx^ _ EvalEdgeAt[lVtx^, lEdge, position]; rVtx^ _ EvalEdgeAt[rVtx^, rEdge, position]; IF lEdge.moreVertical -- horizontal scan segment THEN { length _ rVtx.x - lVtx.x; seg.start _ lVtx.x; seg.end _ rVtx.x; } ELSE { -- vertical scan segment length _ rVtx.y - lVtx.y; seg.start _ lVtx.y; seg.end _ rVtx.y; }; IF ABS[length] > justNoticeable THEN { -- long enough to show up [lCvrge, seg.lMask] _ EvalCvrgeAt[lEdge.start, lEdge.end, position]; [rCvrge, seg.rMask] _ EvalCvrgeAt[rEdge.start, rEdge.end, position]; seg.coverage _ lCvrge; seg.cvrgIncr _ (rCvrge - lCvrge) / length; FOR i: NAT IN [0..lEdge.val.length) DO seg.val[i] _ lVtx.val[i]; seg.xIncrVal[i] _ (rVtx.val[i] - lVtx.val[i]) / length; -- x-increments ENDLOOP; seg.val.length _ seg.xIncrVal.length _ lEdge.val.length; IF increments THEN { FOR i: NAT IN [0..lEdge.val.length) DO seg.yIncr[i] _ lEdge.incr[i]; -- yincrements seg.xIncrForY[i] _ (rEdge.incr[i] - lEdge.incr[i]) / length; -- x-incrs for yincrs ENDLOOP; seg.yIncr.length _ seg.xIncrForY.length _ lEdge.val.length; }; FOR i: NAT IN [0..3) DO IF seg.val[i] < 0.0 THEN IF seg.val[i] < -(justNoticeable * justNoticeable) THEN SIGNAL ThreeDBasics.Error[[$MisMatch, "Negative Color"]] ELSE seg.val[i] _ 0.0; ENDLOOP; }; ReleaseVertex[lVtx]; ReleaseVertex[rVtx]; RETURN[seg]; }; maskFromReal: ARRAY [0..8] OF BYTE _ [0, 1, 3, 7, 15, 31, 63, 127, 255]; EvalCvrgeAt: PROC[ start, end, position: REAL] RETURNS[ cvrge: REAL, mask: CARDINAL] ~ { <> lCoverage, rCoverage, rUnCoverage: REAL; rMask, lMask: BYTE; lCoverage _ position - start; IF lCoverage >= 1. THEN lCoverage _ 2.0 -- fully covered ELSE IF lCoverage > -1.0 THEN lCoverage _ 1.0 + lCoverage -- partially covered ELSE lCoverage _ 0.; lMask _ maskFromReal[Real.Fix[lCoverage * 4.49]]; <> lCoverage _ weight[Real.Fix[ tblLngth/2 * lCoverage ]]; rCoverage _ end - position; IF rCoverage >= 1. THEN rCoverage _ 2.0 -- fully covered ELSE IF rCoverage > -1.0 THEN rCoverage _ 1.0 + rCoverage -- partially covered ELSE rCoverage _ 0.; rMask _ 255 - maskFromReal[Real.Fix[(2.0 - rCoverage) * 4.49]]; -- inverted mask <> rUnCoverage _ weight[Real.Fix[ tblLngth/2 * (2.0 - rCoverage) ]]; -- weight uncovered part <<>> cvrge _ lCoverage - rUnCoverage; -- l - r is total coverage mask _ Basics.BITAND[lMask, rMask]; }; EvalScanSegAt: PROC[spot: REF Spot, seg: REF ScanSegment, position: REAL] RETURNS[REF Spot] ~ { pos, dist: REAL; IF spot.val = NIL OR spot.val.maxLength < seg.val.length THEN { spot.val _ NEW[ RealSequence[seg.val.length] ]; spot.yIncr _ NEW[ RealSequence[seg.val.length] ]; spot.xIncr _ NEW[ RealSequence[seg.val.length] ]; }; IF position > seg.end THEN pos _ seg.end -- keep values between vertex values ELSE IF position < seg.start THEN pos _ seg.start ELSE pos _ position; dist _ pos - seg.start; FOR i: NAT IN [0..seg.val.length) DO -- load up spot values spot.val[i] _ seg.val[i] + seg.xIncrVal[i] * dist; spot.yIncr[i] _ seg.yIncr[i] + seg.xIncrForY[i] * dist; ENDLOOP; spot.xIncr _ seg.xIncrVal; -- use x increments as is spot.val.length _ spot.yIncr.length _ spot.xIncr.length _ seg.val.length; IF position - seg.start > 1.0 AND seg.end - position > 1.0 THEN { spot.coverage _ 1.0; spot.mask _ 255; } -- not near segment end ELSE [spot.coverage, spot.mask] _ EvalCvrgeAt[seg.start, seg.end, position]; spot.coverage _ spot.coverage * (seg.coverage + seg.cvrgIncr * dist); spot.partShiny _ 1.0; -- initialize RETURN[spot]; }; ShowScanSeg: PROC[ context: REF Context, y: NAT, scanSeg: REF ScanSegment, xLimit: INTEGER, shadingClass: REF ShadingClass, sideways: BOOLEAN ] ~ { Swap: PROC[ref1, ref2: REF RealSequence] RETURNS [outRef1, outRef2: REF RealSequence] ~{ RETURN[ outRef1: ref2, outRef2: ref1 ]; }; a, d, v, numClrs: NAT _ IF context.class.displayType = $FullColor THEN 3 ELSE 1; refA: REF NAT _ NARROW[ Atom.GetPropFromList[ context.displayProps, $Alpha] ]; refD: REF NAT _ NARROW[ Atom.GetPropFromList[ context.displayProps, $Depth] ]; refV: REF NAT _ NARROW[ Atom.GetPropFromList[ context.displayProps, $NormalBuffer] ]; trns: NAT _ 3; spot: REF Spot _ GetSpot[]; segStart: INTEGER _ Floor[scanSeg.start]; segEnd: INTEGER _ Ceiling[scanSeg.end]; segLength: NAT _ MIN[xLimit, segEnd] - MAX[0, segStart] + 1; pixels: PixelBuffer _ ImagerPixel.ObtainScratchPixels[ context.pixels.samplesPerPixel, segLength ]; initIndex, delta: SF.Vec; fMin: INTEGER _ context.pixels.box.min.f; sMin: INTEGER _ context.pixels.box.min.s; IF refA # NIL THEN a _ refA^; IF refD # NIL THEN d _ refD^; IF refV # NIL THEN v _ refV^; IF sideways THEN { initIndex _ [ f: y+fMin, s: MAX[sMin, segStart+sMin] ]; delta _ [ f: 0, s: 1 ]; } ELSE { initIndex _ [ f: MAX[fMin, segStart+fMin], s: y+sMin ]; delta _ [ f: 1, s: 0 ]; }; ImagerPixel.GetPixels[ -- get pixels for segment self: context.pixels, pixels: pixels, initIndex: initIndex, delta: delta, count: segLength ]; FOR x: INTEGER IN [ segStart .. segEnd ] DO writeBehind: BOOLEAN _ TRUE; cvrge: REAL; i: NAT; IF x < 0 OR x >= xLimit THEN LOOP; -- scissor off if out-of-bounds i _ x - MAX[segStart, 0]; IF Basics.LowByte[pixels[a][i]] > 254 AND NOT context.depthBuffering THEN LOOP; -- pixel already covered and not using depth buffer spot _ EvalScanSegAt[ spot, scanSeg, Real.Float[x] ]; IF spot.coverage < justNoticeable THEN LOOP; -- surface covers tiny area in pixel IF shadingClass # NIL THEN { -- evaluate texture and/or highlights IF sideways THEN spot.xySwapped _ TRUE ELSE spot.xySwapped _ FALSE; shadingClass.getColor[ context, shadingClass, spot ]; }; IF numClrs = 1 THEN { -- average colors for gray image spot.val[0] _ ( spot.val[0] + spot.val[1] + spot.val[2] ) / 3.0; spot.val[1] _ spot.val[3]; }; cvrge _ spot.coverage * (1.0 - spot.val[trns]); IF context.depthBuffering THEN { depth: REAL _ IF spot.val.length > 10 THEN spot.val[10] ELSE spot.val[4]; IF pixels[d][i] > Real.Round[depth] THEN { -- new surface is in front writeBehind _ FALSE; pixels[d][i] _ Real.Round[depth]; }; }; <> IF pixels[a][i] # 0 THEN { -- previous coverage on this pixel, prepare to blend oldAlpha: BYTE _ Basics.LowByte[pixels[a][i]]; oldMask: BYTE _ Basics.HighByte[pixels[a][i]]; IF writeBehind THEN { -- previous coverage blend under IF oldAlpha < 255 AND oldMask < 255 THEN { -- check for partial coverage <> intersection: NAT _ populationCount[Basics.BITAND[oldMask, spot.mask]]; oldCover: NAT _ populationCount[oldMask]; newCover: NAT _ populationCount[spot.mask]; IF oldCover = intersection AND newCover = intersection THEN newCover _ intersection; -- debug test point }; cvrge _ MIN[ cvrge, 1.0 - realFromByte[oldAlpha] ]; -- up to uncovered part FOR j: NAT IN [0..numClrs) DO pixels[j][i] _ pixels[j][i] + Real.Round[ spot.val[j] * cvrge * 255.0 ] ENDLOOP; } ELSE { -- previous coverage blend over oldCvrge: REAL _ oldAlpha / 255.0; IF oldCvrge + cvrge > 1.0 -- is pixel completely covered? THEN oldCvrge _ (1.0 - cvrge) / oldCvrge -- scale back prev. cvrge. ELSE oldCvrge _ 1.0; -- assume non-overlapping (already scaled by cvrge.) FOR j: NAT IN [0..numClrs) DO pixels[j][i] _ MIN[ Real.Round[oldCvrge * pixels[j][i] + spot.val[j] * cvrge * 255.0], 255 ]; ENDLOOP; }; spot.mask _ Basics.BITOR[oldMask, spot.mask]; } ELSE { FOR j: NAT IN [0..numClrs) DO -- first surface, weight by coverage and transp. pixels[j][i] _ Real.Round[ spot.val[j] * cvrge * 255.0 ] ENDLOOP; IF refV # NIL THEN { -- store normals pixels[v][i] _ LOOPHOLE[Real.InlineRoundI[LAST[NAT] * spot.val[4]]]; pixels[v+1][i] _ LOOPHOLE[Real.InlineRoundI[LAST[NAT] * spot.val[5]]]; pixels[v+2][i] _ LOOPHOLE[Real.InlineRoundI[LAST[NAT] * spot.val[6]]]; }; }; pixels[a][i] _ MIN[ 255, -- coverage is sum of previous and current coverage INTEGER[Real.Round[ cvrge * 255.0 ]] + Basics.LowByte[pixels[a][i]] ]; LOOPHOLE[pixels[a][i], Basics.BytePair].high _ spot.mask; ENDLOOP; ImagerPixel.PutPixels[ -- return modified pixels self: context.pixels, pixels: pixels, initIndex: initIndex, delta: delta, count: segLength ]; ImagerPixel.ReleaseScratchPixels[pixels]; ReleaseSpot[spot]; }; Init[]; END.