DIRECTORY Atom, Basics, G3dBasic, G3dColorDisplaySupport, G3dMappedAndSolidTexture, G3dMatrix, G3dRender, G3dRenderWithPixels, G3dScanConvert, G3dClipXfmShade, G3dSortandDisplay, G3dVector, Imager, ImagerColor, ImagerDitherContext, ImagerFont, ImagerFullColorContext, ImagerGrayContext, ImagerPixel, ImagerSample, ImagerSmoothContext, Real, RealFns, Rope, SF; G3dRenderWithPixelsImpl: CEDAR MONITOR IMPORTS Atom, Basics, G3dColorDisplaySupport, G3dMappedAndSolidTexture, G3dRender, G3dScanConvert, G3dClipXfmShade, G3dSortandDisplay, G3dVector, Imager, ImagerColor, ImagerDitherContext, ImagerFont, ImagerFullColorContext, ImagerGrayContext, ImagerPixel, ImagerSample, ImagerSmoothContext, Real, RealFns, SF EXPORTS G3dRenderWithPixels = BEGIN LORA: TYPE ~ LIST OF REF ANY; RefSeq: TYPE ~ G3dRender.RefSeq; RefSeqRep: TYPE ~ G3dRender.RefSeqRep; PixelBuffer: TYPE ~ ImagerPixel.PixelBuffer; ImagerProc: TYPE ~ G3dRender.ImagerProc; Context: TYPE ~ G3dRender.Context; ContextProc: TYPE ~ G3dRender.ContextProc; ContextClass: TYPE ~ G3dRender.ContextClass; ShadingClass: TYPE ~ G3dRender.ShadingClass; RenderStyle: TYPE ~ G3dRender.RenderStyle; ShapeClass: TYPE ~ G3dRender.ShapeClass; RGB: TYPE ~ G3dRender.RGB; NatRGB: TYPE ~ G3dRender.NatRGB; NatRGBSequence: TYPE ~ G3dRender.NatRGBSequence; NatRGBSequenceRep: TYPE ~ G3dRender.NatRGBSequenceRep; Pixel: TYPE ~ G3dRender.Pixel; OutCode: TYPE ~ G3dRender.OutCode; Pair: TYPE ~ G3dRender.Pair; -- RECORD [ x, y: REAL]; PairSequence: TYPE ~ G3dRender.PairSequence; IntegerPair: TYPE ~ G3dRender.IntegerPair; IntegerPairSequence: TYPE ~ G3dRender.IntegerPairSequence; IntegerPairSequenceRep: TYPE ~ G3dBasic.IntegerPairSequenceRep; Triple: TYPE ~ G3dRender.Triple; -- RECORD [ x, y, z: REAL]; Quad: TYPE ~ G3dRender.Quad; Matrix: TYPE ~ G3dRender.Matrix; Box: TYPE ~ ImagerSample.Box; Rectangle: TYPE ~ G3dRender.Rectangle; Patch: TYPE ~ G3dRender.Patch; PatchProc: TYPE ~ G3dRender.PatchProc; ColorPrimary: TYPE ~ {red, green, blue, gray}; BOOLSequence: TYPE ~ REF BOOLSequenceRep; BOOLSequenceRep: TYPE ~ RECORD [length: NAT, s: SEQUENCE maxLength: NAT OF BOOL]; RealSequence: TYPE ~ G3dRender.RealSequence; RealSequenceRep: TYPE ~ G3dBasic.RealSequenceRep; TextureMap: TYPE ~ G3dRender.TextureMap; TextureFunction: TYPE ~ G3dRender.TextureFunction; CtlPoint: TYPE ~ G3dRender.CtlPoint; CtlPtInfo: TYPE ~ G3dRender.CtlPtInfo; RenderData: TYPE ~ G3dRender.RenderData; Spot: TYPE ~ G3dRender.Spot; LerpVtx: TYPE ~ G3dRenderWithPixels.LerpVtx; LerpVtxSequence: TYPE ~ G3dRenderWithPixels.LerpVtxSequence; FancyPatch: TYPE ~ G3dRenderWithPixels.FancyPatch; RopeDesc: TYPE ~ G3dRenderWithPixels.RopeDesc; NoneOut: OutCode ~ G3dRender.NoneOut; EdgeBlock: TYPE ~ RECORD [ moreVertical: BOOL, start, end: REAL, x, y, xIncr, yIncr: REAL, val, incr: RealSequence ]; ScanSegment: TYPE ~ RECORD [ start, end: REAL, coverage, cvrgIncr: REAL, lMask, rMask: CARDINAL, val, yIncr, xIncrVal, xIncrForY: 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: IntegerPairSequence, flags: BOOLSequence ]; HilitSeqsSequence: TYPE ~ RECORD [ length: NAT, s: SEQUENCE maxLength: NAT OF REF HilitSeqs ]; tblLngth: NAT ~ 256; justNoticeable: REAL _ G3dScanConvert.justNoticeable; -- 0.02 statistics: BOOL _ FALSE; polyCount: INT _ 0; recurseLimit: NAT _ 16; -- limits recursion in fancy tiler depthSlop: INT16 _ 300; -- limit of indeterminate depth comparisons noHighLights: BOOL _ 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]; Round: PROC [REAL] RETURNS [INT] ~ Real.Round; Fix: PROC [REAL] RETURNS [INT] ~ Real.Fix; GetProp: PROC [propList: Atom.PropList, prop: REF ANY] RETURNS [REF ANY] ~ Atom.GetPropFromList; PutProp: PROC [propList: Atom.PropList, prop: REF ANY, val: REF ANY] RETURNS [Atom.PropList] ~ Atom.PutPropOnList; 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[ RealSequenceRep[size] ]; seg.yIncr _ NEW[ RealSequenceRep[size] ]; seg.xIncrVal _ NEW[ RealSequenceRep[size] ]; seg.xIncrForY _ NEW[ RealSequenceRep[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[ IntegerPairSequenceRep[reflSize] ]; s.flags _ NEW[ BOOLSequenceRep[flagSize] ]; } ELSE { hilitSeqCachePtr _ hilitSeqCachePtr - 1; s _ hilitSeqCache[hilitSeqCachePtr]; hilitSeqCache[hilitSeqCachePtr] _ NIL; IF s.refls.maxLength < reflSize THEN s.refls _ NEW[ IntegerPairSequenceRep[reflSize] ]; IF s.flags.maxLength < flagSize THEN s.flags _ NEW[ BOOLSequenceRep[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; }; GetCtlPoint: 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[ RealSequenceRep[size] ]; RETURN[ vtx ]; }; ReleaseCtlPoint: 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 G3dRender.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 _ Round[in]; IF Real.Float[out] < in THEN out _ out + 1; }; Floor: PROC[ in: REAL ] RETURNS[ out: INTEGER ] ~ { out _ Round[in]; IF Real.Float[out] > in THEN out _ out - 1; }; BoxFromRectangle: PUBLIC PROC[ rect: Rectangle ] RETURNS[ ImagerSample.Box ] ~ { RETURN [[ min: [ f: Fix[rect.x], s: Fix[rect.y] ], max: [ f: Fix[rect.x + rect.w], s: Fix[rect.y + rect.h] ] ]]; }; DupLerpVtx: PROC[vtx: LerpVtx] RETURNS[newVtx: LerpVtx] ~ { newVtx.x _ vtx.x; newVtx.y _ vtx.y; newVtx.val _ NEW[RealSequenceRep[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[RealSequenceRep[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[RealSequenceRep[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: G3dRender.CtlPtToRealSeqProc ~ { length: NAT _ IF data = $PixelShading THEN 11 ELSE 5; IF dest = NIL OR dest.maxLength < length THEN dest _ NEW[RealSequenceRep[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: Context, red, green, blue: REAL] RETURNS[NAT] ~ { SELECT context.class.displayType FROM $PseudoColor => RETURN[ -- standard PseudoColor colormap Fix[red*5.999]*42 + Fix[green*6.999]*6 + Fix[blue*5.999] + 2 ]; $Gray => RETURN[ Round[255.0 * (red + green + blue) / 3.0] ]; ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Unknown display type"]; RETURN[0]; -- error return }; PairToScreen: PROC[context: Context, p: Pair] RETURNS[ip: IntegerPair] ~ { ip.x _ Round[ context.ndcToPixels.scaleX * p.x + context.ndcToPixels.addX ]; ip.y _ Round[ context.ndcToPixels.scaleY * p.y + context.ndcToPixels.addY ]; }; GetImagerCtx: PROC[context: 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 G3dRender.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 G3dRender.Error[$Mismatch, "Bad display type"]; }; context.props _ PutProp[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 ]; G3dRender.RegisterDisplayClass[contextClass, $FullColor]; G3dRender.RegisterDisplayClass[contextClass, $FullColorInVM]; contextClass.displayType _ $PseudoColor; -- set different fields for PseudoColor G3dRender.RegisterDisplayClass[contextClass, $PseudoColor]; G3dRender.RegisterDisplayClass[contextClass, $PseudoColorInVM]; contextClass.displayType _ $Gray; -- set different fields for Gray G3dRender.RegisterDisplayClass[contextClass, $Gray]; G3dRender.RegisterDisplayClass[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 G3dRender.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 BoxFromRectangle[context.viewPort^] -- taken from viewport ELSE [[f: 0, s: 0], [f: 1023, s: 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 _ PutProp[ context.displayProps, $Alpha, NEW[NAT _ samplesPerPixel] ]; samplesPerPixel _ samplesPerPixel + 1; }; IF context.depthBuffering THEN { -- set up depth buffer context.displayProps _ PutProp[ context.displayProps, $Depth, NEW[NAT _ samplesPerPixel] ]; samplesPerPixel _ samplesPerPixel + 1; }; IF GetProp[context.props, $NormalBuffer] # NIL THEN { context.displayProps _ PutProp[ 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 NOT context.doVisibly OR context.viewer = NIL THEN { context.pixels _ ImagerPixel.NewPixelMap[samplesPerPixel, box, PixelCount]; context.displayProps _ PutProp[ context.displayProps, $FullDisplayMemory, context.pixels ]; -- for clipping etc. }; }; DummyValidateDisplay: ContextProc ~{ -- update viewPort, etc., (placeholder) }; DepthBuffering: PUBLIC PROC[ context: Context, on: BOOL _ TRUE ] ~ { IF on # context.depthBuffering THEN { context.depthBuffering _ on; context.class.setUpDisplayType[context]; }; }; AntiAliasing: PUBLIC PROC[ context: Context, on: BOOL _ TRUE ] ~ { IF on # context.antiAliasing THEN { context.antiAliasing _ on; context.class.setUpDisplayType[context]; }; }; NormalBuffering: PUBLIC PROC[ context: Context, on: BOOL _ TRUE ] ~ { IF on THEN { IF GetProp[context.props, $NormalBuffer] = NIL THEN { context.props _ PutProp[context.props, $NormalBuffer, $On ]; context.class.setUpDisplayType[context]; }; } ELSE IF GetProp[context.props, $NormalBuffer] # NIL THEN { context.props _ Atom.RemPropFromList[context.props, $NormalBuffer ]; context.displayProps _ Atom.RemPropFromList[context.displayProps, $NormalBuffer ]; }; }; BufferRendering: PUBLIC PROC[ context: Context, on: BOOL _ TRUE ] ~ { IF on = context.doVisibly THEN { context.doVisibly _ NOT on; context.class.setUpDisplayType[context]; }; }; FillInBackGround: PUBLIC ContextProc ~ { IF context.stopMe^ THEN RETURN; WITH GetProp[context.props, $BackGround] SELECT FROM bkgrdClr: REF RGB => FillInConstantBackGround[context, bkgrdClr^]; -- constant color bkgrdCtx: 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: 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[ GetProp[context.displayProps, $Alpha] ]; depth: REF NAT _ NARROW[ GetProp[context.displayProps, $Depth] ]; normals: REF NAT _ NARROW[ GetProp[context.displayProps, $NormalBuffer] ]; samplesPerColor: NAT; IF context.class.displayType = $FullColor THEN { value[0] _ Fix[255.0 * bkgrdClr.R]; value[1] _ Fix[255.0 * bkgrdClr.G]; value[2] _ 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 G3dSortandDisplay.ValidateContext[context]; IF context.screenExtent.min.y >= context.screenExtent.max.y 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 { screenBox: Box _ [ min: [ f: Round[context.screenExtent.min.x] + context.pixels.box.min.f, s: Round[context.screenExtent.min.y] + context.pixels.box.min.s ], max: [ f: Round[context.screenExtent.max.x] + context.pixels.box.min.f, s: Round[context.screenExtent.max.y] + context.pixels.box.min.s ] ]; scanLength: NAT _ Round[context.screenExtent.max.x - context.screenExtent.min.x]; 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: Round[context.screenExtent.min.y] ] ]]; IF Ceiling[context.viewPort.h] > context.screenExtent.max.y THEN FillPixels[[ -- above min: [ f: 0, s: Round[context.screenExtent.max.y] ], max: [ f: Ceiling[context.viewPort.w], s: Ceiling[context.viewPort.h] ] ]]; FillPixels[[ -- left of previously affected area min: [ f: 0, s: Round[context.screenExtent.min.y] ], max: [ f: Round[context.screenExtent.min.x], s: Round[context.screenExtent.max.y] ] ]]; IF Ceiling[context.viewPort.w] > Round[context.screenExtent.max.x] THEN FillPixels[[ -- right of previously affected area min: [ f: Round[context.screenExtent.max.x], s: Round[context.screenExtent.min.y] ], max: [ f: Ceiling[context.viewPort.w], s: Round[context.screenExtent.max.y] ] ]]; FOR i: NAT IN [screenBox.min.s .. screenBox.max.s) DO ImagerPixel.GetPixels[ -- get foreground pixels self: context.pixels, pixels: srcBuf, initIndex: [f: screenBox.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: screenBox.min.f, s: i], count: scanLength ]; ENDLOOP; }; context.screenExtent _ [[0.0,0.0], [0.0,0.0]]; }; FillInBackGroundImage: PROC[context, bkgrdCtx: Context] ~ { IF NOT bkgrdCtx.imageReady THEN IF GetProp[bkgrdCtx.props, $BackGrdImage] # NIL THEN { aisFile: Rope.ROPE _ NARROW[GetProp[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 [] _ G3dColorDisplaySupport.GetAIS[bkgrdCtx, aisFile]; bkgrdCtx.imageReady _ TRUE; } ELSE SIGNAL G3dRender.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 GetProp[bkgrdCtx.props, $BackGround] SELECT FROM bkgrdClr: REF RGB => FillInConstantBackGround[context, bkgrdClr^]; -- constant color bhndCtx: Context => FillInBackGroundImage[context, bhndCtx]; -- background ctx ENDCASE => {}; -- NIL or garbage, all done context.screenExtent _ [[0.0,0.0], [0.0,0.0]]; }; CopyUnder: PROC[context, bkgrdCtx: 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[ GetProp[context.displayProps, $Alpha] ]; scanLength: NAT _ Round[context.screenExtent.max.x - context.screenExtent.min.x]; srcBuf: PixelBuffer _ ImagerPixel.NewPixels[bkgrdCtx.pixels.samplesPerPixel, scanLength]; dstBuf: PixelBuffer _ ImagerPixel.NewPixels[context.pixels.samplesPerPixel, scanLength]; IF alpha = NIL THEN SIGNAL G3dRender.Error[$MisMatch, "No A buffer"]; Transfer[[ -- below previously affected area min: [0, 0], max: [f: Ceiling[context.viewPort.w], s: Round[context.screenExtent.min.y]] ]]; IF Ceiling[context.viewPort.h] > Round[context.screenExtent.max.y] THEN Transfer[[ min: [f: 0, s: Round[context.screenExtent.max.y]], -- 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: Round[context.screenExtent.min.y]], max: [f: Round[context.screenExtent.min.x], s: Round[context.screenExtent.max.y]] ]]; IF Ceiling[context.viewPort.w] > Round[context.screenExtent.max.x] THEN Transfer[[ min: [f: Round[context.screenExtent.max.x], s: Round[context.screenExtent.min.y]], max: [f: Ceiling[context.viewPort.w], s: Round[context.screenExtent.max.y]] ]]; FOR i: INT IN [ Round[context.screenExtent.min.y] .. Round[context.screenExtent.max.y] ) DO ImagerPixel.GetPixels[ -- get foreground pixels self: bkgrdCtx.pixels, pixels: srcBuf, initIndex: [f: Round[context.screenExtent.min.x], s: i], count: scanLength ]; ImagerPixel.GetPixels[ -- get background pixels self: context.pixels, pixels: dstBuf, initIndex: [f: Round[context.screenExtent.min.x], 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: Round[context.screenExtent.min.x], s: i], count: scanLength ]; ENDLOOP; }; MakeFrame: PUBLIC ContextProc ~ { IF context.antiAliasing THEN { G3dSortandDisplay.ValidateContext[context]; -- ensure viewPort etc. updated FillInConstantBackGround[context, [0.0,0.0,0.0], 0 ]; -- clear screen, alpha buffer G3dSortandDisplay.ShowShapes[context]; -- render image context.class.loadBackground[context]; -- load background } ELSE G3dSortandDisplay.MakeFrame[context]; }; ShadeSpot: PUBLIC G3dRender.SpotProc ~ { pt: CtlPtInfo; 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.data _ NEW[REAL _ spot.partShiny]; pt _ shading.shadeVtx[ context, pt, shading ]; pt.data _ NIL; 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: Context, p1, p2: Pair, color: Pixel] ~ { ip1, ip2: IntegerPair; ip1 _ PairToScreen[context, p1]; ip2 _ PairToScreen[context, p2]; G3dScanConvert.PutLine[ context, ip1, ip2, color, color ] }; Draw2DPoly: PUBLIC PROC[context: Context, poly: 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; G3dScanConvert.FastFlatTiler[ context, plygn, color ] }; DrawRope: PUBLIC G3dRender.RopeProc ~ { imagerCtx: Imager.Context _ NARROW[ GetProp[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 G3dRender.ImagerProc ~ { ropeData: REF RopeDesc _ NARROW[data]; p: Pair _ ropeData.pos; 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 ~ { IF patch.type = $PolyLine THEN OutLineTiler[context, patch] ELSE IF context.antiAliasing THEN FancyTiler[context, patch] -- standard anti-aliasing tiler ELSE WITH patch.renderData.shadingClass.renderMethod SELECT FROM style: REF RenderStyle => { renderStyle: RenderStyle _ style^; SELECT renderStyle 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, linesWnormals => { -- white faceted polygon with black outlines IF renderStyle = linesWnormals 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 renderStyle = hiddenLines THEN { -- use shape color or black for outlining OPEN patch.renderData.shadingClass; useClr: RGB; IF (color.R = 1.0 AND color.G = 1.0 AND color.B = 1.0) THEN useClr _ [0.0, 0.0, 0.0] ELSE useClr _ color; FOR i: NAT IN [0..patch.nVtces) DO patch[i].shade.er _ useClr.R; patch[i].shade.eg _ useClr.G; patch[i].shade.eb _ useClr.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 renderStyle = linesWnormals AND patch.dir # back THEN DrawNormals[context, patch]; }; ENDCASE => IF patch.renderData.shadingClass.shininess > 0.0 OR patch.renderData.shadingClass.texture # NIL THEN ShinyTiler[context, patch] -- highlight tiler ELSE LerpTiler[context, patch]; -- default is smooth shading }; ENDCASE => SIGNAL G3dRender.Error[$Unimplemented, "Only REF RenderStyle works here"]; RETURN[patch]; }; OutLineTiler: PROC[ context: Context, poly: REF Patch ] ~ { last: NAT _ IF poly.type = $PolyLine THEN 0 ELSE poly.nVtces - 1; p1: IntegerPair _ [ Round[poly[last].coord.sx], Round[poly[last].coord.sy] ]; clr1: Pixel _ [ Fix[poly[last].shade.er*255.0], Fix[poly[last].shade.eg*255.0], Fix[poly[last].shade.eb*255.0], Fix[poly[last].shade.et*255.0], 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 _ [ Round[poly[i].coord.sx], Round[poly[i].coord.sy] ]; clr2: Pixel _ [ Fix[poly[i].shade.er*255.0], Fix[poly[i].shade.eg*255.0], Fix[poly[i].shade.eb*255.0], Fix[poly[i].shade.et*255.0], 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; G3dScanConvert.PutLine[context, p1, p2, clr1, clr2]; p1 _ p2; clr1 _ clr2; ENDLOOP; }; DrawNormals: PROC[ context: Context, poly: REF Patch ] ~ { pixel: Pixel _ [0, 0, 0, 0, 0]; FOR i: NAT IN [0..poly.nVtces) DO p1: IntegerPair _ [ Round[poly[i].coord.sx], 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 _ G3dClipXfmShade.GetClipCodeForPt[ context, ep2 ]; IF clip = NoneOut THEN { sp2: Triple _ G3dClipXfmShade.XfmPtToDisplay[ context, ep2 ]; p2: IntegerPair _ [ Round[sp2.x], Round[sp2.y] ]; G3dScanConvert.PutLine[context, p1, p2, pixel, pixel]; }; ENDLOOP; }; FastFlatTiler: PROC[ context: Context, poly: REF Patch ] ~ { pixel: Pixel _ [ Fix[poly.ctlPt[0].shade.er * 255.0], -- red Fix[poly.ctlPt[0].shade.eg * 255.0], -- green Fix[poly.ctlPt[0].shade.eb * 255.0], -- blu 255, -- full coverage LAST[CARD16] - 1 -- depth ]; G3dScanConvert.FastFlatTiler[context, poly, pixel]; }; LerpTiler: PROC[ context: Context, poly: REF Patch] ~ { IF context.class.displayType = $Gray THEN FOR i: NAT IN [0..poly.nVtces) DO OPEN poly.ctlPt[i].shade; er _ (er + eg + eb) * (1.0/3.0); ENDLOOP; G3dScanConvert.LerpTiler[context, poly]; }; ShinyTiler: PROC[context: Context, poly: REF Patch] ~ { zScale: REAL _ REAL[LAST[INT16]] / context.depthResolution; hltCnt: NAT _ 0; lightColor: NatRGBSequence; hilitRefls: IntegerPairSequence; hiliteInfo: REF HilitSeqs _ IF poly.renderData.shadingClass.shininess > 0.0 THEN GotAHilite[context, poly, poly.renderData.shadingClass.shininess] -- hilight? ELSE NIL; IF (hiliteInfo = NIL OR noHighLights) AND poly.renderData.shadingClass.texture = NIL THEN{ IF NARROW[poly.renderData.shadingClass.renderMethod, REF RenderStyle]^ = 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 poly.renderData.shadingClass.texture # NIL THEN { WITH poly.renderData.shadingClass.texture.first SELECT FROM -- only one texture txtrMap: REF TextureMap => { -- modify with mapped texture txtrRange: REF Pair _ NARROW[ GetProp[poly.renderData.shadingProps, $TxtrCoordRange] ]; IF txtrRange # NIL -- fix texture seams if texture range is specified THEN G3dMappedAndSolidTexture.AdjustTexture[ poly, poly.renderData.shadingClass.texture, txtrRange^ ]; }; txtrFn: REF TextureFunction => {}; -- solid or other function-based texture ENDCASE => SIGNAL G3dRender.Error[$Unimplemented, "Unexpected texture type"]; }; IF hiliteInfo # NIL THEN { lightColor _ NEW[NatRGBSequenceRep[hltCnt]]; lightColor.length _ hltCnt; hilitRefls _ NEW[IntegerPairSequenceRep[hltCnt]]; hilitRefls.length _ hltCnt; FOR i: NAT IN [0..poly.nVtces) DO -- get reflection vector storage for each vertex poly.ctlPt[i].data _ NEW[IntegerPairSequenceRep[hltCnt]]; ENDLOOP; hltCnt _ 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].color; lightColor[hltCnt] _ [ Round[LAST[NAT]*clr.R], Round[LAST[NAT]*clr.G], Round[LAST[NAT]*clr.B] ]; FOR i: NAT IN [0..poly.nVtces) DO vtxData: IntegerPairSequence _ NARROW[poly.ctlPt[i].data]; vtxData[hltCnt] _ hiliteInfo.refls[i + j*poly.nVtces]; -- store with vertex ENDLOOP; hltCnt _ hltCnt + 1; }; ENDLOOP; poly.props _ PutProp[poly.props, $LightColors, lightColor]; -- store light colors }; IF context.class.displayType = $Gray THEN FOR i: NAT IN [0..poly.nVtces) DO OPEN poly.ctlPt[i].shade; er _ (er + eg + eb) / 3.0; ENDLOOP; IF context.depthBuffering THEN FOR i: NAT IN [0..poly.nVtces) DO poly.ctlPt[i].coord.sz _ poly.ctlPt[i].coord.sz * zScale; -- scale for max depth range ENDLOOP; G3dScanConvert.HiliteTiler[ context, poly, Round[poly.renderData.shadingClass.shininess] ]; IF hiliteInfo # NIL THEN ReleaseHilitSeqs[hiliteInfo]; }; minHilite: REAL _ 0.25 * justNoticeable; GotAHilite: PROC[context: Context, poly: REF Patch, shininess: REAL] RETURNS[REF HilitSeqs ] ~ { XfmNormal: PROC[light: Triple, vtx: CtlPtInfo] RETURNS[ Triple ] ~ { toLightSrc: Triple _ G3dVector.Unit[ -- unitd direction to light [ light.x - vtx.coord.ex, light.y - vtx.coord.ey, light.z - vtx.coord.ez ] ]; toEye: Triple _ G3dVector.Unit[ -- unitd 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.Unit[ [x: tx, y: cosB*ty - sinB*tz, z: sinB*ty + cosB*tz] ] ]; }; gotAHilite: BOOL _ FALSE; hilitInfo: REF HilitSeqs _ GetHilitSeqs[ reflSize: poly.nVtces * context.lightSources.length, flagSize: context.lightSources.length ]; idealReflSeq: IntegerPairSequence _ hilitInfo.refls; lightFlags: BOOLSequence _ hilitInfo.flags; IF shininess <= 0.0 THEN { G3dRender.Error[$Warning, "Shininess not positive"]; RETURN[NIL]; -- illegitimate input }; FOR j: NAT IN [0..context.lightSources.length) DO lightPos: Triple _ context.lightSources[j].eyePosition; 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[ lightPos, 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] _ [ Round[reflVec.x*LAST[NAT]/2], -- scale to half of integer range (to allow sums) 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 ] > minHilite 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: Context, poly: REF Patch] ~ { zScale: REAL _ REAL[LAST[INT16]] / context.depthResolution; pixelShading: ATOM _ context.class.displayType; -- flags shading per pixel textureMapping, solidTexture, yIncrements: BOOL _ FALSE; fancyPoly: REF FancyPatch _ NEW[ FancyPatch[poly.nVtces] ]; IF NOT context.antiAliasing THEN G3dRender.Error[$MisMatch, "Not AntiAliased - wrong tiler"]; IF context.depthBuffering THEN FOR i: NAT IN [0..poly.nVtces) DO poly.ctlPt[i].coord.sz _ poly.ctlPt[i].coord.sz * zScale; -- scale for max depth range ENDLOOP; IF poly.renderData.shadingClass.cnvrtVtx = NIL THEN poly.renderData.shadingClass.cnvrtVtx _ GetLerpedVals; IF poly.renderData.shadingClass.getColor = NIL THEN poly.renderData.shadingClass.getColor _ ShadeSpot; IF poly.renderData.shadingClass.shininess > 0.0 THEN { hilitInfo: REF HilitSeqs _ GotAHilite[context, poly, poly.renderData.shadingClass.shininess]; IF hilitInfo # NIL THEN { pixelShading _ $PixelShading; ReleaseHilitSeqs[hilitInfo]; }; }; IF poly.renderData.shadingClass.texture # NIL THEN { txtrRange: REF Pair _ NARROW[ GetProp[poly.renderData.shadingProps, $TxtrCoordRange] ]; IF txtrRange # NIL -- fix texture seams THEN G3dMappedAndSolidTexture.AdjustTexture[ poly, poly.renderData.shadingClass.texture, txtrRange^ ]; pixelShading _ $PixelShading; -- shading at each pixel (could be avoided for intensity) }; IF GetProp[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: poly.renderData.shadingClass.cnvrtVtx[ dest: fancyPoly[i].val, source: poly[i], data: pixelShading ] ]; ENDLOOP; fancyPoly.renderData _ IF pixelShading = $PixelShading OR poly.renderData.shadingClass.texture # NIL THEN poly.renderData ELSE NIL; RealFancyTiler[context, fancyPoly]; -- now go tile it IF statistics THEN polyCount _ polyCount + 1; }; RealFancyTiler: PUBLIC PROC[context: 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: BOOL; least _ poly.ctlPt[0].y; nxtlVtx _ 0; FOR i: CARDINAL IN [1..poly.length) DO -- find bottom vertex IF poly.ctlPt[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 ! G3dRender.Error => IF code = $DeepRecursionInTiler THEN CONTINUE ]; ENDLOOP; }; MakeEdge: PROC[vtx1, vtx2: LerpVtx] RETURNS[edge: EdgeBlock] ~ { length: REAL; edge.val _ NEW[ RealSequenceRep[vtx1.val.length] ]; edge.incr _ NEW[ RealSequenceRep[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[ RealSequenceRep[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: 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: BOOL _ TRUE; toughCase: BOOL _ 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.renderData, 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.renderData, 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.renderData, leftTopVtx.x, rightTopVtx.x, bEdge, tEdge, sideways]; ShowSteepTrap[context, inPoly.renderData, 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.renderData, 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.renderData, 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.renderData, leftBotVtx.x, rightBotVtx.x, bEdge, tEdge, sideways]; ShowSteepTrap[context, inPoly.renderData, 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.renderData, 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.renderData, 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.renderData, 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.renderData, 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.ctlPt[0] _ DupLerpVtx[vtx3]; poly.ctlPt[1] _ DupLerpVtx[vtx2]; poly.ctlPt[2] _ DupLerpVtx[vtx1]; poly.ctlPt[3] _ DupLerpVtx[vtx0]; poly.renderData _ inPoly.renderData; poly.recurseLevel _ inPoly.recurseLevel + 1; IF poly.recurseLevel > recurseLimit THEN SIGNAL G3dRender.Error[$DeepRecursionInTiler, "Needs a new tiler"]; RealFancyTiler[context, poly]; -- go draw it (recursively) }; } ELSE IF midSection THEN ShowSteepTrap[ context, inPoly.renderData, bottom, top, midlEdge, midrEdge ]; }; ShowSteepTrap: PROC[ context: Context, renderData: REF RenderData, bottom, top: REAL, lEdge, rEdge: EdgeBlock, sideways: BOOL _ FALSE ] ~ { scanSeg: REF ScanSegment _ GetScanSeg[lEdge.val.length]; yIncrements: BOOL _ 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 renderData # 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, renderData, 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: BOOL] RETURNS[REF ScanSegment] ~ { length, lCvrge, rCvrge: REAL; size: NAT _ lEdge.val.length; lVtx: REF LerpVtx _ GetCtlPoint[size]; rVtx: REF LerpVtx _ GetCtlPoint[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 G3dRender.Error[$MisMatch, "Negative Color"] ELSE seg.val[i] _ 0.0; ENDLOOP; }; ReleaseCtlPoint[lVtx]; ReleaseCtlPoint[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[Fix[lCoverage * 4.49]]; lCoverage _ weight[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[Fix[(2.0 - rCoverage) * 4.49]]; -- inverted mask rUnCoverage _ weight[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[ RealSequenceRep[seg.val.length] ]; spot.yIncr _ NEW[ RealSequenceRep[seg.val.length] ]; spot.xIncr _ NEW[ RealSequenceRep[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); RETURN[spot]; }; ShowScanSeg: PROC[ context: Context, y: NAT, scanSeg: REF ScanSegment, xLimit: INTEGER, renderData: REF RenderData, sideways: BOOL ] ~ { Swap: PROC[ref1, ref2: RealSequence] RETURNS [outRef1, outRef2: RealSequence] ~{ RETURN[ outRef1: ref2, outRef2: ref1 ]; }; a, d, v, numClrs: NAT _ IF context.class.displayType = $FullColor THEN 3 ELSE 1; refA: REF NAT _ NARROW[ GetProp[ context.displayProps, $Alpha] ]; refD: REF NAT _ NARROW[ GetProp[ context.displayProps, $Depth] ]; refV: REF NAT _ NARROW[ GetProp[ 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: BOOL _ 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 renderData # NIL THEN { -- evaluate texture and/or highlights IF sideways THEN spot.xySwapped _ TRUE ELSE spot.xySwapped _ FALSE; renderData.shadingClass.getColor[ context, renderData.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] > Round[depth] THEN { -- new surface is in front writeBehind _ FALSE; pixels[d][i] _ 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] + 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[ 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] _ 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[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. ΨG3dRenderWithPixelsImpl.mesa Last Edited by: Crow, November 6, 1989 6:36:33 pm PST Glassner, March 14, 1989 2:18:34 pm PST Bloomenthal, June 19, 1989 12:15:40 pm PDT Types RECORD [ coverage: REAL, mask: WORD, txtrTransmittance: REAL, class: REF ShadingClass, val, yIncr, xIncr: RealSequence, props: PropList] RECORD [x, y: REAL, val: RealSequence]; RECORD [length: NAT, SEQ maxLength: NAT OF REF LerpVtx]; RECORD [ recurseLevel: NAT _ 0, renderData: REF RenderData, element: SEQUENCE maxLength: NAT OF LerpVtx]; RECORD [rope:ROPE, pos:Pair, color:Pixel, size:REAL, font:ROPE]; Data Structure for trapezoid edges Global Constants Global Variables allocation avoidance structures - caches of peculiar data types Renamed Procedures Caching Procedures Utility procedures PROC[dest: RealSequence, source: CtlPtInfo, data: REF ANY] RETURNS[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] 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 right of previously affected area 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: Context, shading: REF ShadingClass, spot: REF Spot] PROC[ context: Context, imagerCtx: Imager.Context, data: REF ANY ] Simple, Fast Tilers PROC[ context: Context, patch: REF Patch, data: REF ANY _ NIL ] RETURNS[REF Patch] ShinyTiler: Phong Shading, Simple Texture, and Highlight Utilities no highlight or texture Got highlight(s)! Got Texture Allocate light color and highlight reflection vector sequences 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‚˜™Jšœ5™5Icodešœ$Οk™'Kšœ*™*J˜Jš œΪœ˜ηJ˜—head2šΠlnΟlΠklŸ ˜&Iašœ«˜΄Mšœ˜J˜Jšœœ˜—headšŸ™Jš œœœœœœ˜"Jšœ œ˜$Jšœœ˜)Jšœœ˜/Jšœœ˜+Jšœ œ˜&Jšœœ˜-Jšœœ˜/Jšœœ˜.Jšœœ˜-Jšœœ˜+Jšœœ œ˜Jšœ œ˜$Jšœœ˜1Jšœœ˜6Jšœ œ˜#Jšœ œ˜&Jšœ œΟc˜>Jšœœ˜.Jšœœ˜-Jšœœ!˜:Jšœœ#˜?Jšœ œ‘˜DJšœ œ˜!Jšœ œ˜$Jšœ œ˜"Jšœœ˜)Jšœ œ˜#Jšœœ˜)Jšœœ˜1Jšœœœ˜+Jšœœœ œœ œœœ˜RJšœœ˜.Jšœœ˜2Jšœœ˜+Jšœœ˜3Jšœœ˜(Jšœœ˜*Jšœœ˜+J˜šœ œ˜!šœ œ™Jšœœ™Jšœœ™Jšœ™Jšœ œ™Jšœœ™!Jšœ)™)Jšœ™Jšœ™——šœ œ˜0Jšœ œœ™0J™—šœœ'˜=Jš œ œ œœ œœœ ™AJ™—šœœ"˜5šœ œ™Jšœœ™Jšœœ ™$Jšœœ œœ ™6J™——defaultšœœ ˜2š œ ΠksΟsœ’œ’œ’œ™IO™——Jšœ)˜)˜JšΟb"™"—šœœœ˜Jšœœœ˜+Jšœœ˜Jšœ˜J˜J˜—šœœœ˜Jšœœ˜Jšœœ˜Jšœœ˜Jšœ-˜-J˜J˜—šœœœ˜!Jšœœ˜Jš œœ œœœ ˜0J˜J˜—šœœœ‘8˜WJšœ#˜#Jšœ˜J˜J˜—šœœœ˜#Jšœœ˜Jš œœ œœœ ˜,J˜——šŸ™Jšœ œ˜Jšœœ'‘˜C—šŸ™Jšœœœ˜Jšœ œ˜J˜Jšœœ ‘"œ˜AJšœ œ ‘+œ˜JJšœœœ˜Jš œ œœœ‘œ˜:Jš€@™@Jšœœœ‘˜RJšœœ˜Jšœœ˜Jšœœœ‘˜SJšœœ˜Jšœœ˜Jšœœœ‘˜RJšœœ˜Jšœœ˜Jšœœ˜Jš œœœœœœ˜A—šŸ™Jš Οnœ£œœœœ˜.Jš ₯œ£œœœœ ˜*Jš₯œœ!œœœœœ.˜uJš₯œœ!œœœœœ&˜x—šŸ™š ₯ œœœœœœ˜?Jšœœœ˜Ošœœ ˜šœ˜Ošœ œ˜šœ˜O˜&O˜$Ošœ œ˜$O˜——šœ œœœ˜3Jšœ œ˜'Jšœ œ˜)Jšœœ˜,Jšœœ˜-J˜—Ošœ˜O˜—š₯œœœœ˜2Jšœœœ˜šœ&œ˜.Ošœœ,˜>O˜,O˜O˜—O˜"O˜&O˜—O˜š ₯ œœœœ œœ˜SJšœœœ˜Ošœœ ˜šœ˜šœ˜Ošœœ˜Ošœ œ%˜2Ošœ œ˜+O˜—šœ˜O˜(O˜$Ošœ"œ˜&Ošœœ œ%˜YOšœœ œ˜RO˜——Oš œœœœœœ˜;Ošœ˜ O˜—š₯œœœœ˜2Jšœœœ˜šœ(œ˜0Ošœœ/˜BO˜.O˜O˜—O˜$O˜(O˜—š ₯ œœœœœœ ˜˜>J˜——šŸ$™$š₯œœ ‘˜6˜J˜O˜&O˜&O˜O˜!O˜O˜O˜J˜J˜—Jšœ9˜9Jšœ>˜>Jšœ+‘'˜RJšœ;˜;Jšœ@˜@Jšœ(‘ ˜HJšœ4˜4Jšœ9˜9J™‰šœœœœ˜"Jšœœ˜!J˜J˜/Jšœ˜—J™,Jš œœœ œœœ˜GJ™1šœœœ ˜Jšœœ˜šœœœœ˜Jšœœ œ œ˜HJšœ˜—J˜Jšœ˜—J˜—š₯œœ˜5š₯ œ˜%Jšœœœ ™šœ˜Jšœœ ‘#˜9Jš œœœœ‘˜:—J˜—šœ"œœ˜+Jšœ'œœ˜5—šœ œœ˜%Jšœ'‘˜AJšœ(‘ ˜L—Jšœœ‘˜>šœœ ‘%˜KJšœMœœ˜jJ˜&J˜—šœœ‘˜˜SMšœœœ,˜Jšœ;œ ˜Kšœ‘"˜;M˜M˜HM˜—šœ˜šœ˜MšœG˜GMšœD˜DMšœG˜GMšœC˜CMšœ˜—Mšœ œB˜QM˜Xšœ‘!˜:M˜MšœN˜NM˜—šœ:œ‘˜WMšœ4˜4M˜GM˜—šœ‘#˜——O˜—šœ˜ JšœD˜J—O˜———Jšœ˜J˜J˜—š₯ œœœ ˜;Jš œœœœœ˜AJšœM˜MJšœΆ˜Άšœ˜%˜O˜W—O˜5Ošœ˜—šœœœ˜!JšœG˜GJšœ§˜§šœ˜%˜O˜W—O˜5Ošœ˜—J˜5J˜Jšœ˜—J˜J˜—š₯ œœœ ˜:J˜šœœœ˜!JšœG˜G˜J˜8J˜@J˜—JšœA˜Ašœœ˜Jšœ=˜=Jšœ1˜1J˜7J˜—Jšœ˜—J˜J˜—š₯ œœœ ˜<˜Jšœ'‘˜-Jšœ'‘˜/Jšœ'‘˜-Jšœ‘˜"Jšœœ ‘˜ J˜—J˜4J˜J˜—š₯ œœœ ˜7š œ#œœœœœ˜LJšœ9˜=Jšœ˜—J˜(J˜J˜——šž Ÿ8™Bš₯ œœœ ˜7Jš œœœœœ˜;J˜Jšœœ˜Jšœ˜Jšœ ˜ šœ œ œ-˜KJšœC‘ ˜RJšœœ˜ —š œœœœ(œœ˜ZJšœ™š œœ,œœœ˜pJšœ ˜$Jšœ˜—Jšœ˜ J˜Jšœ™—šœœœœœœ"œ˜KJšœœ‘'˜YJšœ˜Jšœ ™ —šœ(œœ˜4šœ,œœ‘˜Sšœ œ‘˜;šœ œœ˜Jšœ7˜7J˜—šœ œ‘2˜Jšœ(˜,Jšœ6˜6Jšœ˜——J˜—Jšœœ‘(˜NJšœœ=˜N—J˜—šœœœ˜O™>Ošœ œ;˜KOšœ œ>˜Nš œœœœ‘0˜UOšœœ!˜9Jšœ˜—Ošœ ˜ šœœœ"œ˜2šœœ‘0˜PJšœœ!˜)Jš œœœœœœœ ˜gšœœœœ˜"Jšœœ˜:Jšœ8‘˜LJšœ˜—J˜J˜—Jšœ˜—Jšœ<‘˜QJ˜—š œ#œœœœœ˜LJšœ3˜7Jšœ˜—š œœœœœœ˜AJšœ<‘˜XJšœ˜ —Jšœ[˜[Jšœœœ˜6J˜J˜—Jšœ œ˜)š ₯ œœœœœœ˜fš₯ œœ œ˜DJ™wšœ)‘˜DO˜M—šœ%‘˜>O˜2—J˜KJšœœ8‘˜UJšœœ"œ˜HJšœœ4‘˜QJšœœœ˜CJšœœ,˜4Jšœœ˜Jšœœ,˜4Jš œ œ œ œœ‘˜UJšœL˜RJ˜—Jšœ œœ˜šœ œ˜(J˜[J˜—Jšœ4˜4Jšœ+˜+J˜šœœ˜J˜4Jšœœ ‘˜(J˜—šœœœ"œ˜2Mšœ7˜7Jšœ œ˜&Jšœ œ˜'š œœœœ‘3˜UO˜1Jšœœ˜Jšœœœ˜;Jšœœœ˜:˜Jšœœœ‘1˜OJšœœœ˜J˜—Jšœ˜J™1—Jš œœœœœœœ˜HJš œœœœœœœ˜Hšœœ>˜SJšœœœ˜6Jšœœ˜—Jšœ˜—J˜@Jšœœ œ/œ˜PJšœ ˜J˜——šŸ-™-š₯ œœœœ ˜>Jš œœœœœ˜;Jšœœ!‘˜MJšœ+œœ˜8Jšœ œœ˜;J˜šœœ˜Jšœ=˜AJ™—š œœœœœœ˜AJšœ<‘˜XJšœ˜ —šœ)œ˜/Jšœ7˜;—šœ)œ˜/Jšœ4˜8J™—šœ.œ˜7Jšœ œP˜^šœ œœ˜J˜J˜J˜—J˜Jšœ™—šœ(œœ˜4šœ œœ˜Jšœ7˜7J˜—šœ œ‘˜6šœ(˜,Jšœ6˜6J˜——Jšœ‘9˜XJ˜Jšœ!™!—šœ0œ˜6Jšœ˜"J˜—š œœœœ‘˜K˜J˜J˜šœ,˜,JšœE˜E—J˜—Jšœ˜—šœœ œ(˜mJšœœœœ˜—Jšœ&‘˜7Jšœ œ˜-˜J˜——š₯œœœœ˜HJšœœ˜J˜Jšœ(œ˜-Jšœ œ!œ‘"˜XJšœœ˜$Iunit˜)š œœœœ‘˜JJšœ˜Jšœ*˜.Jšœ˜ —Jšœ)‘!˜JJšœ!œ˜'J˜ šœœ ‘!˜Jšœœ‘˜FJšœ/œ ˜?J˜,Jšœœ˜J˜—šœœ‘˜GJšœ1œ ˜AJ˜,Jšœœ˜J˜—šœ$‘,˜Ršœ˜Jšœ!‘˜