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 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. RRenderWithPixelsImpl.mesa Last Edited by: Crow, April 4, 1989 9:56:23 am PDT Glassner, March 14, 1989 2:18:34 pm PST Bloomenthal, October 28, 1988 5:05:48 pm PDT Types RECORD[ coverage: REAL, mask: WORD, txtrTransmittance: REAL, class: REF ShadingClass, val, yIncr, xIncr: REF RealSequence, props: Atom.PropList ] RECORD [x, y: REAL, val: REF RealSequence]; RECORD [ length: NAT, s: SEQUENCE maxLength: NAT OF REF LerpVtx ]; RECORD [ recurseLevel: NAT _ 0, shadingClass: REF ShadingClass, vtx: SEQUENCE length: NAT OF LerpVtx ]; RECORD[rope: ROPE, position: Pair, color: Pixel, size: REAL, font: ROPE]; Data Structure for trapezoid edges Global Constants Global Variables allocation avoidance structures - caches of peculiar data types Caching Procedures Utility procedures PROC[dest: REF RealSequence, source: VertexInfo, data: REF ANY] RTRNS[REF RealSequence]; Initializing and Updating Pixel Maps Calculates the integral over the left half of a pyramid function, equal to the left half of a parabolic window or B-spline basis function Calculate a table mapping 0-255 into 0.0-1.0 Calculate a table for population count over 0-255 PROC [i: NAT] RETURNS [Sample] Pixel maps all start at [0,0] gets a bare context for making images independent of color display Sets up or destroys depth buffer in VM for context.pixels Sets up or destroys alpha buffer in VM for context.pixels (used for antialiasing) Sets up or destroys normals buffer in VM for context.pixels Renders through buffer and blits to screen when on, directly to screen (slowly) when off Loads background behind current image Clears frame, use with alpha buffer to load background color behind shapes Fill in behind previously affected area, using alpha values to blend Set uncovered: indicates no alpha information Set uncovered (negative width and height) indicates no alpha information Fill in around edges Now, do previously affected area dstBuf already weighted by its coverage (dst _ a * dst), so dst _ dst + (1-a) * src Frame Generation Makes image, clears frame, adds background, etc. Low-level drawing PROC[context: REF Context, shading: REF ShadingClass, spot: REF Spot] PROC[ context: REF Context, imagerCtx: Imager.Context, data: REF ANY ] Simple, Fast Tilers PROC[ context: REF Context, patch: REF Patch, data: REF ANY _ NIL ] RETURNS[REF Patch] ShinyTiler for Phong shading and Simple Texture, and Highlight Utilities no highlight or texture Got highlight(s)! Transform the normal to a space in which a normal aligned with the z-axis would reflect the light straight into the eye Get closest point to zero spanned in bounding box Anti-Aliasing Tiler for Fancier shading, etc. Do we have a highlight? Do we have texture? Do we have normal vectors stored? if left side more horizontal, check for slope, make top or bottom edge, do right triangle, do new left edge if right side more horizontal do likewise get triangle at right, if both horz. then left edge got middle trapezoid get triangle at right, if both horz. then left edge got middle trapezoid Do middle section Get Pixel area coverage weighted by function stored in "weight" This fills bits in mask from right to left as converage increases from 0.0-2.0 This fills bits in mask from left to right as converage increases from 0.0-2.0 Blend new pixel value with old Earlier surfaces did not fully cover pixel, check for overlap, skip out if found Κ8˜head™J™2Icode™'L™,J˜šΟk ˜ Jšœ œI˜XJš œ œœœœ*˜UJšœ œ4˜CJšœ œ˜!Jšœ œœ˜Jšœœ˜-Jšœ œ7˜GJšœœ˜&Jšœœ˜$Jšœœ˜6Jšœœ˜5Jšœœ˜3Jšœœ˜2Jšœœ€˜”Jšœœ#˜7Jšœ œ˜+Jšœœ ˜ JšœœC˜WJšœœΥœœΜ˜ΚJšœœ&˜P˜,P˜P˜—P˜"P˜&P˜—P˜š   œœœœ œœ˜SJšœœœ˜Pšœœ ˜šœ˜šœ˜Pšœœ˜Pšœ œ"˜/Pšœ œ˜+P˜—šœ˜P˜(P˜$Pšœ"œ˜&Pšœœ œ"˜VPšœœ œ˜RP˜——Pš œœœœœœ˜;Pšœ˜ P˜—š œœœœ˜2Jšœœœ˜šœ(œ˜0Pšœœ/˜BP˜.P˜P˜—P˜$P˜(P˜—š   œœœœœœ ˜:Jšœœœ˜Pšœœ ˜šœ˜Pšœœ ˜šœ˜P˜$P˜"Pšœœ˜"P˜——Pš œ œœœ œ˜XPšœ˜P˜—š  œœœœ ˜/Jšœœœ˜šœ$œ˜,Pšœœ+˜šœœ Ÿ%˜KJšœXœœ˜uJšœ&˜&Jšœ˜—šœœŸ˜˜WNšœœ˜šœ'˜)šœ˜ N˜(N˜(N˜(N˜N˜—šœ˜N˜EN˜N˜——Nšœ œœ˜*Nš œ œœœœ˜/Nšœ œœ>˜SNšœœœ(˜Fšœ=œ ˜MšœŸ"˜;N˜N˜HN˜—šœ˜Nšœœ9˜ONšœ œ=˜LN˜XšœŸ!˜:N˜N˜FN˜—šœ;œŸ˜XN˜,N˜FN˜—šœŸ#˜—————Jšœœ˜ J˜J˜—š  œœ œœ ˜?Jš œœœœœ˜AJ˜WJ˜Οšœ˜%˜P˜W—P˜5Pšœ˜—šœœœ˜!J˜QJ˜ΐšœ˜%˜P˜W—P˜5Pšœ˜—J˜2J˜Jšœ˜—J˜J˜—š  œœ œœ ˜>J˜šœœœ˜!J˜Q˜J˜8J˜@J˜—J˜@šœœ˜J˜šœœœ˜*Jš œœœœœ˜RJ˜—Jšœ˜—šœœœ˜Pšœ œ9Ÿ˜XPš œ œœœœ˜;šœœœ"œ˜2šœœŸ0˜PJšœœ.˜6Jš œ"œœœœœœ ˜všœœœœ˜"Jšœœœ˜:Jšœ7Ÿ˜KJšœ˜—J˜J˜—Jšœ˜—JšœFŸ˜\J˜—š œ#œœœœœ˜LJšœ1˜5Jšœ˜—š œœœœœœ˜AJšœ8Ÿ˜TJšœ˜ —J˜SJšœœœ˜6J˜J˜—š  œœ œœœœœ˜jš  œœ!œ˜EJ™wšœ.Ÿ ˜NP˜P—šœ*Ÿ˜HP˜2—J˜KJšœœ8Ÿ˜UJšœœ"œ˜HJšœœ4Ÿ˜QJšœœœ˜CJšœœ+Ÿ˜4Jšœœ˜Jšœœ,˜4Jš œ œ œ œœŸ˜UJšœQ˜WJ˜—Jšœ œœ˜šœ œ˜(J˜[J˜—Jšœœ'˜8Jšœ œ#˜2J˜šœœ˜Jšœ9˜9Jšœœ Ÿ˜(J˜—šœœœ"œ˜2Jšœ œ˜&Jšœ œ˜'š œœœœŸ3˜UP˜IJšœœ˜Jšœœœ˜;Jšœœœ˜:˜JšœœœŸ1˜TJšœœœ˜!J˜—Jšœ˜J™1—Jš œœœœœœœ˜HJš œœœœœœœ˜HšœœG˜\Jšœœœ˜6Jšœœ˜—Jšœ˜—J˜@Jšœœ œ/œ˜PJšœ ˜J˜——šž-™-š   œœœ œœ ˜BIunitšœœœ-˜NJšœœ#˜4Jšœœ œœ˜;Jšœœ!Ÿ˜MJšœ+œœ˜;Jšœ œœ˜;J˜šœœ˜JšœB˜FJ™—š œœœœœœ˜AJšœ8Ÿ˜TJšœ˜ —Jšœœœ'˜Jšœœœ$˜GJ™—šœœ˜'Jšœ œ@˜Nšœ œœ˜J˜J˜J˜—J˜JšŸ™—šœœœ˜$šœ œœ˜J˜:J˜—šœ œŸ˜6šœ%˜)J˜4J˜—JšœA˜E—JšœŸ9˜XJ˜—JšŸ!™!—šœ=œ˜CJšœ˜"J˜š œœœœŸ˜K˜J˜J˜J˜YJ˜—Jšœ˜—šœœœ˜VJšœœœ˜—Jšœ&Ÿ˜7Jšœ œ˜-J˜J˜—š  œœœ œœ˜LJšœœ˜J˜Jšœ(œ˜-Jšœ œ!œŸ"˜YJšœœ˜'Q˜'š œœœœŸ˜JJšœ˜Jšœ*˜.Jšœ˜ —Jšœ)Ÿ!˜JJšœ!œ˜'J˜ šœœ Ÿ!˜JšœœŸ˜FJšœ/œ ˜?J˜,Jšœœ˜J˜—šœœŸ˜GJšœ1œ ˜AJ˜,Jšœœ˜J˜—šœ$Ÿ,˜Ršœ˜Jšœ!Ÿ˜