<> <> DIRECTORY Basics USING [BITSHIFT], Real USING [Fix, FixI, FixC, RoundC, RoundI, Float], RealFns USING [Log, SqRt], PolygonPackage USING [Polygon, VertexInfo], Imager USING [Context], ImagerDisplay USING [DisplayData], ImagerPixelMaps USING [GetPixel, PixelMap, Create, Transfer], ImagerPixelMapsExtras USING [SetPixel], TilerPackage; TilerPackageImpl: CEDAR PROGRAM IMPORTS Basics, RealFns, Real, ImagerPixelMaps, ImagerPixelMapsExtras EXPORTS TilerPackage = BEGIN OPEN TilerPackage; minSize: REAL ~ .004; tblLngth: NAT ~ 256; weightsCalculated: BOOLEAN _ FALSE; weight: ARRAY [0..tblLngth] OF REAL; blendPixels: BOOLEAN _ FALSE; <> cachedContext: Imager.Context; displayData, textureData: ImagerDisplay.DisplayData _ NIL; <> mipTexture: BOOLEAN _ FALSE; mipTextureRange, sumTextureRange, interpThreshold: REAL; mipTable: ARRAY [0..10) OF ImagerPixelMaps.PixelMap; maxScale: REAL; -- largest meaningful ratio of texture to image maxTable: NAT; -- NAT version of the above <> IntSequence: TYPE ~ RECORD[SEQUENCE length: NAT OF INT]; ScanSequence: TYPE ~ RECORD[SEQUENCE length: NAT OF REF IntSequence]; txtrSum: REF ScanSequence _ NIL; maxTxtrX, maxTxtrY: NAT; <> EdgeBlock: TYPE = RECORD [ moreVertical: BOOLEAN, start, end: REAL, x, y, xn, yn, zn, r, g, b, t, txtrX, txtrY: REAL, xIncr, yIncr, xnIncr, ynIncr, znIncr, rIncr, gIncr, bIncr, tIncr, txtrXIncr, txtrYIncr: REAL ]; 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: REAL ] ~ { out _ Real.Float[Real.RoundI[in]]; IF out < in THEN out _ out + 1.; }; Floor: PROC[ in: REAL ] RETURNS[ out: REAL ] ~ { out _ Real.Float[Real.RoundI[in]]; IF out > in THEN out _ out - 1.; }; CalculateWeights: PROC[] ~ { <> 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; weightsCalculated _ TRUE; }; FancyTiler: PUBLIC PROC[context: Imager.Context, poly: REF PolygonPackage.Polygon] ~ { least, top, bottom: REAL; vtxCount, lVtx, rVtx, nxtlVtx, nxtrVtx, nVtcesMinusOne: NAT; leftVtxNeeded, rightVtxNeeded: BOOLEAN; rEdge, lEdge: REF EdgeBlock; vtx: REF PolygonPackage.VertexInfo _ NEW[PolygonPackage.VertexInfo]; nxtVtx: REF PolygonPackage.VertexInfo _ NEW[PolygonPackage.VertexInfo]; IF context # cachedContext THEN { -- cache context as global displayData _ NARROW[context.data, ImagerDisplay.DisplayData]; cachedContext _ context; }; IF NOT weightsCalculated THEN CalculateWeights[]; least _ poly.vtx[0].coord.y; nxtlVtx _ 0; FOR i: CARDINAL IN [1..poly.nVtces) DO -- find bottom vertex IF poly.vtx[i].coord.y < least THEN { least _ poly.vtx[i].coord.y; nxtlVtx _ i; }; ENDLOOP; nxtrVtx _ nxtlVtx; -- set pointers to bottom vertex leftVtxNeeded _ rightVtxNeeded _ TRUE; nVtcesMinusOne _ poly.nVtces - 1; vtxCount _ 1; WHILE vtxCount < poly.nVtces DO -- Do until all vertices reached IF leftVtxNeeded THEN { -- work around left side lVtx _ nxtlVtx; nxtlVtx _ (nxtlVtx + nVtcesMinusOne) MOD poly.nVtces; vtx^ _ poly.vtx[lVtx]; nxtVtx^ _ poly.vtx[nxtlVtx]; lEdge _ MakeEdge[vtx, nxtVtx]; leftVtxNeeded _ FALSE; }; IF rightVtxNeeded THEN { -- work around right side rVtx _ nxtrVtx; nxtrVtx _ (nxtrVtx + 1) MOD poly.nVtces; vtx^ _ poly.vtx[rVtx]; nxtVtx^ _ poly.vtx[nxtrVtx]; rEdge _ MakeEdge[vtx, nxtVtx]; rightVtxNeeded _ FALSE; }; <> IF poly.vtx[nxtlVtx].coord.y < poly.vtx[nxtrVtx].coord.y THEN { top _ poly.vtx[nxtlVtx].coord.y; -- next left vertex reached leftVtxNeeded _ TRUE; vtxCount _ vtxCount + 1; } ELSE { top _ poly.vtx[nxtrVtx].coord.y; -- next right vertex reached rightVtxNeeded _ TRUE; vtxCount _ vtxCount + 1; }; bottom _ MAX[poly.vtx[lVtx].coord.y, poly.vtx[rVtx].coord.y]; ShowFancyTrap[ bottom, top, lEdge, rEdge]; ENDLOOP; }; MakeEdge: PROC[vtx1, vtx2: REF PolygonPackage.VertexInfo] RETURNS [REF EdgeBlock] = { length: REAL; edge: REF EdgeBlock _ NEW [ EdgeBlock ]; IF ABS[vtx2.coord.y - vtx1.coord.y] >= ABS[vtx2.coord.x - vtx1.coord.x] THEN { length _ vtx2.coord.y - vtx1.coord.y; edge.start _ MIN[vtx1.coord.y, vtx2.coord.y]; edge.end _ MAX[vtx1.coord.y, vtx2.coord.y]; edge.moreVertical _ TRUE; } ELSE { length _ vtx2.coord.x - vtx1.coord.x; edge.start _ MIN[vtx1.coord.x, vtx2.coord.x]; edge.end _ MAX[vtx1.coord.x, vtx2.coord.x]; edge.moreVertical _ FALSE; }; IF ABS[length] < minSize THEN length _ 1.; -- prevent divide errors { OPEN edge; -- get scanline increment, set edge values on scanline x _ vtx1.coord.x; xIncr _ (vtx2.coord.x - vtx1.coord.x) / length; y _ vtx1.coord.y; yIncr _ (vtx2.coord.y - vtx1.coord.y) / length; xn _ vtx1.shade.xn; xnIncr _ (vtx2.shade.xn - vtx1.shade.xn) / length; yn _ vtx1.shade.yn; ynIncr _ (vtx2.shade.yn - vtx1.shade.yn) / length; zn _ vtx1.shade.zn; znIncr _ (vtx2.shade.zn - vtx1.shade.zn) / length; r _ vtx1.shade.r; rIncr _ (vtx2.shade.r - vtx1.shade.r) / length; g _ vtx1.shade.g; gIncr _ (vtx2.shade.g - vtx1.shade.g) / length; b _ vtx1.shade.b; bIncr _ (vtx2.shade.b - vtx1.shade.b) / length; t _ vtx1.shade.t; tIncr _ (vtx2.shade.t - vtx1.shade.t) / length; txtrX _ vtx1.shade.txtrX; txtrXIncr _ (vtx2.shade.txtrX - vtx1.shade.txtrX) / length; txtrY _ vtx1.shade.txtrY; txtrYIncr _ (vtx2.shade.txtrY - vtx1.shade.txtrY) / length; }; RETURN[ edge ]; }; EvalEdgeAt: PROC[ edge: REF EdgeBlock, position: REAL, cvrgeNeeded: BOOLEAN _ FALSE] RETURNS[ vtx: REF PolygonPackage.VertexInfo] ~ { pos, dist: REAL; vtx _ NEW[PolygonPackage.VertexInfo]; 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.coord.x _ edge.x + edge.xIncr * dist; vtx.coord.y _ edge.y + edge.yIncr * dist; vtx.shade.xn _ edge.xn + edge.xnIncr * dist; vtx.shade.yn _ edge.yn + edge.ynIncr * dist; vtx.shade.zn _ edge.zn + edge.znIncr * dist; vtx.shade.r _ edge.r + edge.rIncr * dist; vtx.shade.g _ edge.g + edge.gIncr * dist; vtx.shade.b _ edge.b + edge.bIncr * dist; vtx.shade.t _ edge.t + edge.tIncr * dist; vtx.shade.txtrX _ edge.txtrX + edge.txtrXIncr * dist; vtx.shade.txtrY _ edge.txtrY + edge.txtrYIncr * dist; IF cvrgeNeeded THEN { <> rCoverage, rUnCoverage: REAL; lCoverage: REAL _ position - edge.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.; lCoverage _ weight[Real.FixI[ tblLngth/2 * lCoverage ]]; rCoverage _ edge.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.; rUnCoverage _ weight[Real.FixI[ tblLngth/2 * (2.0 - rCoverage) ]]; -- weight uncovered part <<>> <> vtx.shade.t _ 1.0 - (1.0 - vtx.shade.t) * (lCoverage - rUnCoverage); -- l - r is total coverage }; }; ShowFancyTrap: PROC[ bottom, top: REAL, lEdge, rEdge: REF EdgeBlock] ~ { GetXcoordAt: PROC[edge: REF EdgeBlock, yPos: REAL] RETURNS [REAL] ~ { dist: REAL _ yPos - edge.y; RETURN [ edge.x + dist / edge.yIncr ]; }; sideways: BOOLEAN _ TRUE; tEdge, bEdge: REF EdgeBlock; leftTopVtx, leftBotVtx, rightTopVtx, rightBotVtx: REF PolygonPackage.VertexInfo; IF bottom + minSize >= top THEN RETURN[]; -- too thin to affect image IF NOT (lEdge.moreVertical AND rEdge.moreVertical) THEN IF lEdge.moreVertical THEN { leftTopVtx _ EvalEdgeAt[lEdge, top]; leftBotVtx _ EvalEdgeAt[lEdge, bottom]; } ELSE { topX: REAL _ GetXcoordAt[lEdge, top]; botX: REAL _ GetXcoordAt[lEdge, bottom]; leftTopVtx _ EvalEdgeAt[lEdge, topX]; leftBotVtx _ EvalEdgeAt[lEdge, botX]; }; IF rEdge.moreVertical THEN { rightTopVtx _ EvalEdgeAt[rEdge, top]; rightBotVtx _ EvalEdgeAt[rEdge, bottom]; } ELSE { topX: REAL _ GetXcoordAt[rEdge, top]; botX: REAL _ GetXcoordAt[rEdge, bottom]; rightTopVtx _ EvalEdgeAt[rEdge, topX]; rightBotVtx _ EvalEdgeAt[rEdge, botX]; }; << if left side more horizontal, check for slope, make top or bottom edge,>> <> IF NOT lEdge.moreVertical THEN IF lEdge.yIncr < 0. THEN { -- left edge is more horizontal, top vertex is leftmost tEdge: REF EdgeBlock _ MakeEdge[leftTopVtx, rightTopVtx]; bEdge _ lEdge; IF leftBotVtx.coord.x < rightTopVtx.coord.x THEN { -- easy case: right triangle containing whole left edge ShowSteepTrap[leftTopVtx.coord.x, leftBotVtx.coord.x, bEdge, tEdge, sideways]; lEdge _ MakeEdge[leftBotVtx, EvalEdgeAt[tEdge, leftBotVtx.coord.x]]; bEdge _ NIL; -- all drawn, won't be needed below } ELSE { -- right top is left of left bottom ShowSteepTrap[leftTopVtx.coord.x, rightTopVtx.coord.x, bEdge, tEdge, sideways]; lEdge _ MakeEdge[EvalEdgeAt[bEdge, rightTopVtx.coord.x], rightTopVtx]; }; } ELSE { -- left edge is more horizontal, bottom vertex is leftmost bEdge: REF EdgeBlock _ MakeEdge[leftBotVtx, rightBotVtx]; tEdge _ lEdge; IF leftTopVtx.coord.x < rightBotVtx.coord.x THEN { -- easy case: right triangle containing whole left edge ShowSteepTrap[leftBotVtx.coord.x, leftTopVtx.coord.x, bEdge, tEdge, sideways]; lEdge _ MakeEdge[leftTopVtx, EvalEdgeAt[bEdge, leftTopVtx.coord.x]]; tEdge _ NIL; -- all drawn, won't be needed below } ELSE { -- right bottom is left of left top ShowSteepTrap[leftBotVtx.coord.x, rightBotVtx.coord.x, bEdge, tEdge, sideways]; lEdge _ MakeEdge[rightBotVtx, EvalEdgeAt[tEdge, rightBotVtx.coord.x]]; }; }; <> IF NOT rEdge.moreVertical THEN IF rEdge.yIncr < 0. THEN { -- right edge is more horizontal, top vertex is leftmost bEdge: REF EdgeBlock _ MakeEdge[leftBotVtx, rightBotVtx]; tEdge _ rEdge; IF leftBotVtx.coord.x < rightTopVtx.coord.x THEN { -- easy case: right triangle containing whole right edge ShowSteepTrap[rightTopVtx.coord.x, rightBotVtx.coord.x, bEdge, tEdge, sideways]; rEdge _ MakeEdge[EvalEdgeAt[bEdge, rightTopVtx.coord.x], rightTopVtx]; tEdge _ NIL; -- all drawn, won't be needed below } ELSE { -- left bottom is right of right top ShowSteepTrap[leftBotVtx.coord.x, rightBotVtx.coord.x, bEdge, tEdge, sideways]; rEdge _ MakeEdge[EvalEdgeAt[bEdge, leftBotVtx.coord.x], leftBotVtx]; }; } ELSE { -- right edge is more horizontal, bottom vertex is leftmost tEdge: REF EdgeBlock _ MakeEdge[leftTopVtx, rightTopVtx]; bEdge _ rEdge; IF leftTopVtx.coord.x < rightBotVtx.coord.x THEN { -- easy case: right triangle containing whole right edge ShowSteepTrap[rightBotVtx.coord.x, rightTopVtx.coord.x, bEdge, tEdge, sideways]; rEdge _ MakeEdge[rightBotVtx, EvalEdgeAt[tEdge, rightBotVtx.coord.x]]; bEdge _ NIL; -- all drawn, won't be needed below } ELSE { -- left top is right of right bottom ShowSteepTrap[leftTopVtx.coord.x, rightTopVtx.coord.x, bEdge, tEdge, sideways]; rEdge _ MakeEdge[EvalEdgeAt[bEdge, leftTopVtx.coord.x], leftTopVtx]; }; }; <<>> <> IF (lEdge.xIncr # rEdge.xIncr) OR ((lEdge.x + minSize) < rEdge.x) THEN -- non-parallepiped or thick enough IF tEdge = NIL AND bEdge = NIL -- middle rectangle or unmodified THEN ShowSteepTrap[bottom, top, lEdge, rEdge] ELSE IF NOT (tEdge = NIL OR bEdge = NIL) -- both more horizontal THEN ShowSteepTrap[lEdge.x, rEdge.x, bEdge, tEdge, sideways] ELSE { -- hard case: make new polygon and recurse a, b, c: REAL; -- parameters for defining 45 degree lines poly: REF PolygonPackage.Polygon _ NEW[PolygonPackage.Polygon[4]]; poly.nVtces _ 4; IF tEdge = NIL THEN IF bEdge.yIncr > 0.0 THEN tEdge _ lEdge ELSE tEdge _ rEdge ELSE IF bEdge = NIL THEN IF tEdge.yIncr > 0.0 THEN bEdge _ rEdge ELSE bEdge _ lEdge; IF tEdge.moreVertical THEN -- upper edge runs top to bottom IF tEdge.xIncr < 0.0 THEN { -- originally right edge poly.vtx[0] _ EvalEdgeAt[tEdge, bottom]^; poly.vtx[1] _ EvalEdgeAt[tEdge, top]^; poly.vtx[2] _ EvalEdgeAt[bEdge, lEdge.x]^; poly.vtx[3] _ EvalEdgeAt[bEdge, GetXcoordAt[bEdge, bottom]]^; a _ .707; b _ .707; } ELSE { -- originally left edge poly.vtx[0] _ EvalEdgeAt[tEdge, top]^; poly.vtx[1] _ EvalEdgeAt[tEdge, bottom]^; poly.vtx[2] _ EvalEdgeAt[bEdge, GetXcoordAt[bEdge, bottom]]^; poly.vtx[3] _ EvalEdgeAt[bEdge, rEdge.x]^; a _ -.707; b _ .707; } ELSE -- lower edge runs top to bottom IF bEdge.xIncr > 0.0 THEN { -- originally right edge poly.vtx[0] _ EvalEdgeAt[bEdge, bottom]^; poly.vtx[1] _ EvalEdgeAt[bEdge, top]^; poly.vtx[2] _ EvalEdgeAt[tEdge, GetXcoordAt[tEdge, top]]^; poly.vtx[3] _ EvalEdgeAt[tEdge, lEdge.x]^; a _ -.707; b _ .707; } ELSE { -- originally left edge poly.vtx[0] _ EvalEdgeAt[bEdge, top]^; poly.vtx[1] _ EvalEdgeAt[bEdge, bottom]^; poly.vtx[2] _ EvalEdgeAt[tEdge, rEdge.x]^; poly.vtx[3] _ EvalEdgeAt[tEdge, GetXcoordAt[tEdge, top]]^; a _ .707; b _ .707; }; -- evaluate area based on distance of vertices from 45 degree line c _ -(a * poly.vtx[0].coord.x + b * poly.vtx[0].coord.y); IF (top - bottom) * ( MAX[a * poly.vtx[1].coord.x + b * poly.vtx[1].coord.y + c, a * poly.vtx[2].coord.x + b * poly.vtx[2].coord.y + c, a * poly.vtx[3].coord.x + b * poly.vtx[3].coord.y + c] - MIN[a * poly.vtx[1].coord.x + b * poly.vtx[1].coord.y + c, a * poly.vtx[2].coord.x + b * poly.vtx[2].coord.y + c, a * poly.vtx[3].coord.x + b * poly.vtx[3].coord.y + c] ) < minSize THEN RETURN[] -- too small to matter ELSE FancyTiler[cachedContext, poly]; -- go draw it (recursively) }; }; ShowSteepTrap: PROC[ bottom, top: REAL, lEdge, rEdge: REF EdgeBlock, sideways: BOOLEAN _ FALSE ] ~ { getCvrge: BOOLEAN ~ TRUE; lVtx, rVtx, vtx: REF PolygonPackage.VertexInfo; scanSeg: REF EdgeBlock; scanTxtrXIncr, scanTxtrYIncr: REAL; lStartSave: REAL _ lEdge.start; lEndSave: REAL _ lEdge.end; -- save limits to restore later rStartSave: REAL _ rEdge.start; rEndSave: REAL _ rEdge.end; botNat, topNat, lftNat, rgtNat: INTEGER; IF bottom + minSize >= top THEN RETURN[]; -- too thin to affect image lEdge.start _ rEdge.start _ bottom; -- set edge limits for coverage calcs. lEdge.end _ rEdge.end _ top; botNat _ Real.FixI[Floor[bottom]]; topNat _ Real.FixI[Ceiling[top]]; -- get integer range IF sideways THEN { -- correct for possible floating point error botNat _ MAX[displayData[0].fMin, MIN[botNat, displayData[0].fMin + displayData[0].fSize - 1]]; topNat _ MAX[displayData[0].fMin, MIN[topNat, displayData[0].fMin + displayData[0].fSize - 1]]; } ELSE { botNat _ MAX[displayData[0].sMin, MIN[botNat, displayData[0].sMin + displayData[0].sSize - 1]]; topNat _ MAX[displayData[0].sMin, MIN[topNat, displayData[0].sMin + displayData[0].sSize - 1]]; }; FOR y: NAT IN [botNat..topNat] DO lVtx _ EvalEdgeAt[lEdge, Real.Float[y], getCvrge]; rVtx _ EvalEdgeAt[rEdge, Real.Float[y], getCvrge]; IF sideways THEN { -- on side, scan up and down IF lVtx.coord.y + minSize < rVtx.coord.y THEN { -- do only if thick enough vertically scanSeg _ MakeEdge[lVtx, rVtx]; IF txtrSum # NIL OR mipTexture = TRUE THEN { divisor: REAL _ rVtx.coord.y - lVtx.coord.y; IF ABS[divisor] < 1.0 THEN divisor _ 1.0 * Sgn[divisor]; scanTxtrXIncr _ (rEdge.txtrXIncr - lEdge.txtrXIncr) / divisor; scanTxtrYIncr _ (rEdge.txtrYIncr - lEdge.txtrYIncr) / divisor; }; lftNat _ Real.FixI[Floor[lVtx.coord.y]]; -- get integer range lftNat _ MAX[displayData[0].sMin, -- correct for possible floating point error MIN[lftNat, displayData[0].sMin + displayData[0].sSize - 1]]; rgtNat _ Real.FixI[Ceiling[rVtx.coord.y]]; rgtNat _ MAX[displayData[0].sMin, MIN[rgtNat,displayData[0].sMin + displayData[0].sSize - 1]]; FOR x: NAT IN [lftNat..rgtNat] DO vtx _ EvalEdgeAt[scanSeg, Real.Float[x], getCvrge]; IF textureData = NIL -- no texture THEN IF blendPixels THEN BlendPixelAt[vtx^, y, x] ELSE AddPixelAt[vtx^, y, x] ELSE IF txtrSum = NIL AND mipTexture = FALSE -- nearest texture pixel algorithm THEN WriteTxtrAt[vtx^, y, x] ELSE { -- antialiased texture txtrXIncr: REAL _ MAX[ABS[(x - lVtx.coord.y) * scanTxtrXIncr + lEdge.txtrXIncr], ABS[scanSeg.txtrXIncr] ] / 2.0; txtrYIncr: REAL _ MAX[ABS[(x - lVtx.coord.y) * scanTxtrYIncr + lEdge.txtrYIncr], ABS[scanSeg.txtrYIncr] ] / 2.0; IF txtrSum # NIL THEN WriteSumTxtrAt[vtx^, y, x, txtrXIncr, txtrYIncr] ELSE WriteMipTxtrAt[vtx^, y, x, txtrXIncr, txtrYIncr]; }; ENDLOOP; }; } ELSE -- upright, scan across IF lVtx.coord.x + minSize < rVtx.coord.x THEN { -- do only if wide enough scanSeg _ MakeEdge[lVtx, rVtx]; IF txtrSum # NIL OR mipTexture = TRUE THEN { divisor: REAL _ rVtx.coord.x - lVtx.coord.x; IF ABS[divisor] < 1.0 THEN divisor _ 1.0 * Sgn[divisor]; scanTxtrXIncr _ (rEdge.txtrXIncr - lEdge.txtrXIncr) / divisor; scanTxtrYIncr _ (rEdge.txtrYIncr - lEdge.txtrYIncr) / divisor; }; lftNat _ Real.FixI[Floor[lVtx.coord.x]]; -- get integer range lftNat _ MAX[displayData[0].fMin, -- correct for possible floating point error MIN[lftNat, displayData[0].fMin + displayData[0].fSize - 1]]; rgtNat _ Real.FixI[Ceiling[rVtx.coord.x]]; rgtNat _ MAX[displayData[0].fMin, MIN[rgtNat,displayData[0].fMin + displayData[0].fSize - 1]]; FOR x: NAT IN [lftNat..rgtNat] DO vtx _ EvalEdgeAt[scanSeg, Real.Float[x], getCvrge]; IF textureData = NIL -- no texture THEN IF blendPixels THEN BlendPixelAt[vtx^, x, y] ELSE AddPixelAt[vtx^, x, y] ELSE IF txtrSum = NIL AND mipTexture = FALSE -- nearest texture pixel algorithm THEN WriteTxtrAt[vtx^, x, y] ELSE { -- antialiased texture txtrXIncr: REAL _ MAX[ABS[(x - lVtx.coord.x) * scanTxtrXIncr + lEdge.txtrXIncr], ABS[scanSeg.txtrXIncr] ] / 2.0; txtrYIncr: REAL _ MAX[ABS[(x - lVtx.coord.x) * scanTxtrYIncr + lEdge.txtrYIncr], ABS[scanSeg.txtrYIncr] ] / 2.0; IF txtrSum # NIL THEN WriteSumTxtrAt[vtx^, x, y, txtrXIncr, txtrYIncr] ELSE WriteMipTxtrAt[vtx^, x, y, txtrXIncr, txtrYIncr]; }; ENDLOOP; }; ENDLOOP; lEdge.start _ lStartSave; lEdge.end _ lEndSave; -- restore limits rEdge.start _ rStartSave; rEdge.end _ rEndSave; }; AddPixelAt: PROC[vtx: PolygonPackage.VertexInfo, x, y: NAT] ~ { pxlValue: LONG CARDINAL _ Real.FixC[vtx.shade.g * 255]; IF vtx.shade.t > minSize -- if t < minsize then opaque, just write pixel THEN { oldValue: LONG CARDINAL _ ImagerPixelMaps.GetPixel[displayData[0], y, x]; pxlValue _ Real.RoundC[pxlValue * (1.0 - vtx.shade.t)]; pxlValue _ MAX[ 1, MIN[pxlValue + oldValue, 255]]; }; ImagerPixelMapsExtras.SetPixel[displayData[0], y, x, pxlValue]; }; BlendPixelAt: PROC[vtx: PolygonPackage.VertexInfo, x, y: NAT] ~ { pxlValue: LONG CARDINAL _ Real.FixC[vtx.shade.g * 255]; IF vtx.shade.t > minSize -- if t < minsize then opaque, just write pixel THEN { oldValue: LONG CARDINAL _ ImagerPixelMaps.GetPixel[displayData[0], y, x]; pxlValue _ Real.RoundC[pxlValue + vtx.shade.t * (oldValue - pxlValue)]; }; ImagerPixelMapsExtras.SetPixel[displayData[0], y, x, pxlValue]; }; <> WriteTxtrAt: PROC[vtx: PolygonPackage.VertexInfo, x, y: NAT] ~ { txtrValue, pxlValue: LONG CARDINAL; txtrX, txtrY: INTEGER; txtrX _ Real.Fix[vtx.shade.txtrX * textureData[0].fSize] MOD textureData[0].fSize; IF txtrX < 0 THEN txtrX _ txtrX + textureData[0].fSize; txtrY _ Real.Fix[vtx.shade.txtrY * textureData[0].sSize] MOD textureData[0].sSize; IF txtrY < 0 THEN txtrY _ txtrY + textureData[0].sSize; txtrY _ textureData[0].sSize - 1 - txtrY; -- compensate for inverted pixel map txtrValue _ ImagerPixelMaps.GetPixel[ textureData[0], txtrY, txtrX]; pxlValue _ Real.FixC[vtx.shade.g * txtrValue]; IF vtx.shade.t > minSize -- if transmittance is significant, add to overwritten pixel THEN { oldValue: LONG CARDINAL _ ImagerPixelMaps.GetPixel[displayData[0], y, x]; pxlValue _ Real.RoundC[pxlValue * (1.0 - vtx.shade.t)]; pxlValue _ MAX[ 1, MIN[pxlValue + oldValue, 255]]; }; ImagerPixelMapsExtras.SetPixel[displayData[0], y, x, pxlValue]; }; WriteSumTxtrAt: PROC[vtx: PolygonPackage.VertexInfo, x, y: NAT, txtrXIncr, txtrYIncr: REAL] ~{ GetLerpedValue: PROC[ llVal, ulVal, urVal, lrVal: INT, xPos, yPos: REAL ] RETURNS[ REAL ] ~ { lowerValue: REAL _ llVal + xPos * (lrVal - llVal); upperValue: REAL _ ulVal + xPos * (urVal - ulVal); RETURN [ lowerValue + yPos * (upperValue - lowerValue) ]; }; CorrectSum: PROC[x, y: NAT] RETURNS[INT] ~ { IF x < txtrSum[0].length AND y < txtrSum.length THEN { RETURN[ txtrSum[y][x] ] } ELSE IF x >= txtrSum[0].length AND y >= txtrSum.length THEN { x _ x - txtrSum[0].length; y _ y - txtrSum.length; RETURN[ CorrectSum[x, y] + txtrSum[maxTxtrY][maxTxtrX] + CorrectSum[x, maxTxtrY] + CorrectSum[maxTxtrX, y] ]; } ELSE IF x >= txtrSum[0].length THEN { x _ x - txtrSum[0].length; RETURN[ CorrectSum[x, y] + txtrSum[y][maxTxtrX] ]; } ELSE { -- IF y >= txtrSum.length y _ y - txtrSum.length; RETURN[ CorrectSum[x, y] + txtrSum[maxTxtrY][x] ]; }; }; GetValueAt: PROC[ x, y: REAL] RETURNS[ REAL ] ~ { xPos, yPos: REAL; lX, lY, rX, uY: NAT; xPos _ x * txtrSum[0].length; yPos _ y * txtrSum.length; lX _ Real.Fix[xPos]; rX _ lX + 1; lY _ Real.Fix[yPos]; uY _ lY + 1; xPos _ xPos - Real.Fix[xPos]; -- get fractional part yPos _ yPos - Real.Fix[yPos]; RETURN [ GetLerpedValue[ CorrectSum[lX, lY], CorrectSum[lX, uY], CorrectSum[rX, uY], CorrectSum[rX, lY], xPos, yPos ] ]; }; pxlValue: INTEGER; txtrValue: REAL; area: REAL; interp: BOOLEAN _ TRUE; txtrXIncr _ txtrXIncr * sumTextureRange; txtrYIncr _ txtrYIncr * sumTextureRange; area _ 4 * txtrXIncr * txtrYIncr * txtrSum.length * txtrSum[0].length; IF area > interpThreshold THEN interp _ FALSE; WHILE vtx.shade.txtrX - txtrXIncr < 0.0 DO vtx.shade.txtrX _ vtx.shade.txtrX + 1.0; ENDLOOP; WHILE vtx.shade.txtrY - txtrYIncr < 0.0 DO vtx.shade.txtrY _ vtx.shade.txtrY + 1.0; ENDLOOP; IF interp THEN { txtrValue _ GetValueAt[ vtx.shade.txtrX + txtrXIncr, vtx.shade.txtrY + txtrYIncr ] + GetValueAt[ vtx.shade.txtrX - txtrXIncr, vtx.shade.txtrY - txtrYIncr ] - GetValueAt[ vtx.shade.txtrX + txtrXIncr, vtx.shade.txtrY - txtrYIncr ] - GetValueAt[ vtx.shade.txtrX - txtrXIncr, vtx.shade.txtrY + txtrYIncr ]; txtrValue _ txtrValue / area; } ELSE { lX, lY, rX, uY: NAT; lX _ Real.RoundI[(vtx.shade.txtrX - txtrXIncr) * txtrSum[0].length]; rX _ Real.RoundI[(vtx.shade.txtrX + txtrXIncr) * txtrSum[0].length]; IF lX >= rX THEN rX _ lX + 1; lY _ Real.RoundI[(vtx.shade.txtrY - txtrYIncr) * txtrSum.length]; uY _ Real.RoundI[(vtx.shade.txtrY + txtrYIncr) * txtrSum.length]; IF lY >= uY THEN uY _ lY + 1; txtrValue _ ( CorrectSum[rX, uY] + CorrectSum[lX, lY] - CorrectSum[lX, uY] - CorrectSum[rX, lY] ) / Real.FixI[ (rX - lX) * (uY - lY) ]; }; pxlValue _ Real.RoundI[vtx.shade.g * txtrValue]; IF pxlValue > 255 THEN pxlValue _ 255 ELSE IF pxlValue < 0 THEN pxlValue _ 0; IF vtx.shade.t > minSize -- if transmittance is significant, add to overwritten pixel THEN { oldValue: INTEGER _ ImagerPixelMaps.GetPixel[displayData[0], y, x]; pxlValue _ Real.RoundI[pxlValue * (1.0 - vtx.shade.t)]; pxlValue _ MAX[ 1, MIN[pxlValue + oldValue, 255]]; }; ImagerPixelMapsExtras.SetPixel[displayData[0], y, x, pxlValue]; }; WriteMipTxtrAt: PROC[vtx: PolygonPackage.VertexInfo, x, y: NAT, txtrXincr, txtrYincr: REAL] ~{ GetValueAt: PROC[mipTable: ImagerPixelMaps.PixelMap, txtrX, txtrY: REAL] RETURNS[value: REAL] ~ { GetVal: PROC[x, y: NAT] RETURNS[val: NAT] ~ { x _ x MOD mipTable.fSize; y _ y MOD mipTable.sSize; val _ ImagerPixelMaps.GetPixel[mipTable, mipTable.sSize - 1 - y, x]; -- correct inversion }; xAddr, yAddr: NAT; xAlpha, yAlpha, leftVal, rightVal, upperVal, lowerVal: REAL; txtrX _ txtrX * mipTable.fSize; txtrY _ txtrY * mipTable.sSize; xAddr _ Real.FixC[txtrX]; yAddr _ Real.FixC[txtrY]; -- get fractional part xAlpha _ txtrX - xAddr; yAlpha _ txtrY - yAddr; leftVal _ GetVal[xAddr, yAddr+1]; rightVal _ GetVal[xAddr+1, yAddr+1]; upperVal _ leftVal + xAlpha * (rightVal - leftVal); leftVal _ GetVal[xAddr, yAddr]; rightVal _ GetVal[xAddr+1, yAddr]; lowerVal _ leftVal + xAlpha * (rightVal - leftVal); value _ lowerVal + yAlpha * (upperVal - lowerVal); }; alpha, txtrValue, bigTxtrValue, smallTxtrValue: REAL; pxlValue: INTEGER; txtrScale: REAL _ mipTextureRange * RealFns.SqRt[Sqr[txtrXincr * mipTable[0].fSize] * Sqr[txtrYincr * mipTable[0].sSize] ]; logTxtrScale: REAL _ RealFns.Log[2.0, txtrScale]; -- Log base 2 SELECT TRUE FROM logTxtrScale < 0.0 => { bigTxtrValue _ GetValueAt[mipTable[0], vtx.shade.txtrX, vtx.shade.txtrY]; alpha _ 0.0; }; logTxtrScale > maxScale => { bigTxtrValue _ GetValueAt[mipTable[maxTable], vtx.shade.txtrX, vtx.shade.txtrY]; alpha _ 0.0; }; ENDCASE => { tblAddr: NAT _ Real.FixC[logTxtrScale]; bigTxtrValue _ GetValueAt[mipTable[tblAddr], vtx.shade.txtrX, vtx.shade.txtrY]; smallTxtrValue _ GetValueAt[mipTable[tblAddr + 1], vtx.shade.txtrX, vtx.shade.txtrY]; alpha _ ( txtrScale - Basics.BITSHIFT[1, tblAddr] ) / Basics.BITSHIFT[1, tblAddr]; }; txtrValue _ bigTxtrValue + alpha * (smallTxtrValue - bigTxtrValue); pxlValue _ Real.RoundI[vtx.shade.g * txtrValue]; pxlValue _ MAX[0, MIN[255, pxlValue]]; IF vtx.shade.t > minSize -- if coverage significant, add to pixel THEN { oldValue: INTEGER _ ImagerPixelMaps.GetPixel[displayData[0], y, x]; pxlValue _ Real.RoundI[pxlValue * (1.0 - vtx.shade.t)]; pxlValue _ MAX[ 1, MIN[pxlValue + oldValue, 255]]; }; ImagerPixelMapsExtras.SetPixel[displayData[0], y, x, pxlValue]; }; <<>> <> BlendPixels: PUBLIC PROC[onNotOff: BOOLEAN] ~ { blendPixels _ onNotOff; }; <> SetTexture: PUBLIC PROC[context: Imager.Context] ~ { IF context = NIL THEN { txtrSum _ NIL; mipTexture _ FALSE; textureData _ NIL } ELSE textureData _ NARROW[context.data, ImagerDisplay.DisplayData]; }; SumTexture: PUBLIC PROC[range, interp: REAL] ~ { IF textureData = NIL THEN RETURN[]; { sumTextureRange _ range; -- store texture footprint width as global interpThreshold _ interp; -- store area above which no interpolation maxTxtrX _ textureData[0].fSize - 1; -- set global coordinate limits maxTxtrY _ textureData[0].sSize - 1; txtrSum _ NEW[ ScanSequence[maxTxtrY+1] ]; FOR y: NAT IN [0 .. maxTxtrY] DO invY: NAT _ maxTxtrY - y; -- inverted Y to invert texture txtrSum[y] _ NEW[ IntSequence[maxTxtrX+1] ]; FOR x: NAT IN [0 .. maxTxtrX] DO txtrSum[y][x] _ ImagerPixelMaps.GetPixel[textureData[0], invY + textureData[0].sMin + textureData[0].sOrigin, x + textureData[0].fMin + textureData[0].fOrigin]; IF x > 0 THEN { txtrSum[y][x] _ txtrSum[y][x] + txtrSum[y][x - 1]; -- add area to left IF y > 0 THEN -- x > 0 and y > 0: add area below minus area below and to left txtrSum[y][x] _ txtrSum[y][x] + (txtrSum[y - 1][x] - txtrSum[y - 1][x - 1]); } ELSE IF y > 0 THEN txtrSum[y][x] _ txtrSum[y][x] + txtrSum[y - 1][x]; -- add area below ENDLOOP; ENDLOOP; }; }; MipMapTexture: PUBLIC PROC[range: REAL, showMaps: BOOLEAN _ FALSE] ~ { ShowMip: PROC[mipTable: ImagerPixelMaps.PixelMap] ~ { sOrigin, fOrigin: INTEGER; sOrigin _ displayData[0].sOrigin; displayData[0].sOrigin _ sOrigin + mipTable.sSize; fOrigin _ displayData[0].fOrigin; displayData[0].fOrigin _ fOrigin + mipTable.fSize; ImagerPixelMaps.Transfer[displayData[0], mipTable, [NULL, NULL]]; displayData[0].sOrigin _ sOrigin; displayData[0].fOrigin _ fOrigin; }; IF textureData = NIL THEN RETURN[]; { sDim: NAT _ textureData[0].sSize; fDim: NAT _ textureData[0].fSize; sMin: NAT _ textureData[0].sMin + textureData[0].sOrigin; fMin: NAT _ textureData[0].fMin + textureData[0].fOrigin; minDimension: NAT _ MIN[fDim, sDim]; mipTextureRange _ range; -- save in global for later use mipTable[0] _ ImagerPixelMaps.Create[3, [sMin, fMin, sDim, fDim]]; mipTable[0].sOrigin _ mipTable[0].fOrigin _ 0; mipTable[0].refRep _ textureData[0].refRep; --point at bits in original texture IF showMaps THEN ShowMip[mipTable[0]]; minDimension _ minDimension / 2; sDim _ sDim / 2; fDim _ fDim / 2; maxTable _ 0; WHILE minDimension >= 1 DO maxTable _ maxTable + 1; mipTable[maxTable] _ ImagerPixelMaps.Create[3, [sMin, fMin, sDim, fDim]]; mipTable[maxTable].sOrigin _ mipTable[maxTable].fOrigin _ 0; FOR j: NAT IN [sMin..sDim+sMin) DO FOR i: NAT IN [fMin..fDim+fMin) DO tbl: NAT _ maxTable - 1; value: NAT _ ( ImagerPixelMaps.GetPixel[mipTable[tbl], i*2, j*2] + ImagerPixelMaps.GetPixel[mipTable[tbl], i*2 +1, j*2] + ImagerPixelMaps.GetPixel[mipTable[tbl], i*2, j*2 +1] + ImagerPixelMaps.GetPixel[mipTable[tbl], i*2 +1, j*2 +1] ) / 4; ImagerPixelMapsExtras.SetPixel[mipTable[maxTable], i, j, value]; ENDLOOP; ENDLOOP; minDimension _ minDimension / 2; sDim _ sDim / 2; fDim _ fDim / 2; IF showMaps THEN ShowMip[mipTable[maxTable]]; ENDLOOP; maxScale _ maxTable; mipTexture _ TRUE; }; }; END.