TextureMapsImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Last Edited by: Crow, October 14, 1986 10:01:19 am PDT
DIRECTORY
Atom     USING [GetPropFromList, PutPropOnList, RemPropFromList],
Real     USING [LargestNumber, Float, Fix],
RealFns    USING [SqRt, ArcTanDeg],
Rope     USING [ROPE],
Pixels     USING [PixelBuffer, SampleSet, GetSampleSet, Create, Copy,
        SampleSetSequence, GetScanSeg, GetValue],
ThreeDBasics  USING [Context, Pair, ShapeInstance, Triple, TripleSequence,
        VertexInfoProc],
ThreeDScenes  USING [Create, DisplayFromVM, Error, GetShading, PutShading,
        ShadeVtx],
ThreeDSurfaces  USING [PtrPatchSequence],
ScanConvert   USING [Spot],
Vector3d    USING [Normalize, Mul],
AISAnimation  USING [GetAIS],
TextureMaps   USING [TextureMap, IntSequence, ScanSequence, SummedTexture];
TextureMapsImpl: CEDAR PROGRAM
IMPORTS Atom, Real, RealFns, ThreeDScenes, AISAnimation, Pixels, Vector3d
EXPORTS TextureMaps
= BEGIN
Internal Declarations
Pair: TYPE ~ ThreeDBasics.Pair;           -- [ x, y: REAL];
Triple: TYPE ~ ThreeDBasics.Triple;
TripleSequence: TYPE ~ ThreeDBasics.TripleSequence;
TextureMap: TYPE ~ TextureMaps.TextureMap;
IntSequence: TYPE ~ TextureMaps.IntSequence;
ScanSequence: TYPE ~ TextureMaps.ScanSequence;
SummedTexture: TYPE ~ TextureMaps.SummedTexture;
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.];
};
Procedures for Auxiliary Clipping and Shading
LoadVtxAux: PUBLIC ThreeDBasics.VertexInfoProc ~ {  -- load aux field in vtx
PROC[ context: REF Context, vtx: REF VertexInfo, data: REF ANYNIL ] RETURNS[REF VertexInfo];
input: LIST OF REF ANYNARROW[ data ];
auxInfo: REF TripleSequence ← NARROW[ input.first ];
index: INTEGERNARROW[ input.rest.first, REF INTEGER ]^;
vtx.aux ← NEW[Triple ← auxInfo[index]];    -- load with triple
RETURN[ vtx ];
};
LerpVtxAux: PUBLIC ThreeDBasics.VertexInfoProc ~ { -- linear interpolation for texture coords
PROC[ context: REF Context, vtx: REF VertexInfo, data: REF ANYNIL ] RETURNS[REF VertexInfo];
input: LIST OF REF ANYNARROW[data];
vtxAux: REF Triple ← NARROW[ vtx.aux ];
vtxa: REF Triple ← NARROW[ input.first ];
vtxb: REF Triple ← NARROW[ input.rest.first ];
a: REALNARROW[input.rest.rest.first, REF REAL]^;
b: REALNARROW[input.rest.rest.rest.first, REF REAL]^;
vtxAux.x ← vtxa.x*a + vtxb.x*b;
vtxAux.y ← vtxa.y*a + vtxb.y*b;
RETURN[ vtx ];
};
ShadeVtx: PUBLIC ThreeDBasics.VertexInfoProc ~ {   -- special shading function
PROC[ context: REF Context, vtx: REF VertexInfo, data: REF ANYNIL ] RETURNS[REF VertexInfo];
[ [vtx.shade.er, vtx.shade.eg, vtx.shade.eb], vtx.shade.et ]
            ← ThreeDScenes.ShadeVtx[ context, vtx^, 0.0 ];
RETURN[ vtx ];
};
Procedures for Computing Texture Coordinates
MakeTxtrCoordsFromVtxNos: PUBLIC PROC[ shape: REF ThreeDBasics.ShapeInstance,
              vtcesInRow, numberOfRows: NAT,
        row0col0, rowNcol0, rowNcolM, row0ColM: Pair] ~ {
auxInfo: REF TripleSequence ← NEW[ TripleSequence[shape.shade.length] ];
texture: REF TextureMap;
args: LIST OF REAL;
IF ThreeDScenes.GetShading[ shape, $VertexTextureInFile] # NIL THEN {
SIGNAL ThreeDScenes.Error[[$MisMatch, "Overwriting original texture coords, OK?"]];
shape.props ← Atom.RemPropFromList[shape.props, $VertexTextureInFile];
};
IF row0ColM.x - row0col0.x > .3 * vtcesInRow
OR
rowNcolM.x - rowNcol0.x > .3 * vtcesInRow
OR rowNcolM.y - row0ColM.y > .3 * numberOfRows
OR
rowNcol0.y - row0col0.y > .3 * numberOfRows
THEN SIGNAL ThreeDScenes.Error[[$MisMatch, "Texture mapping dangerously dense"]];
FOR i: NAT IN [0..shape.shade.length) DO
lPosX, lPosY, rPosX, rPosY: REAL;
auxInfo.element[i].x ← Real.Float[i MOD vtcesInRow] / vtcesInRow; -- pct along row
auxInfo.element[i].y ← Real.Float[i / vtcesInRow] / numberOfRows; -- pct across rows
Stretch as indicated by corner coordinates
lPosX ← row0col0.x + auxInfo.element[i].y * (rowNcol0.x - row0col0.x);-- interp across rows
lPosY ← row0col0.y + auxInfo.element[i].y * (rowNcol0.y - row0col0.y);
rPosX ← row0ColM.x + auxInfo.element[i].y * (rowNcolM.x - row0ColM.x);
rPosY ← row0ColM.y + auxInfo.element[i].y * (rowNcolM.y - row0ColM.y);
auxInfo.element[i].x ← lPosX + auxInfo.element[i].x * (rPosX - lPosX); -- interp along row
auxInfo.element[i].y ← lPosY + auxInfo.element[i].x * (rPosY - lPosY);
auxInfo.element[i].z ← 0.0;
ENDLOOP;
auxInfo.length ← shape.shade.length;
shape.auxInfo ← auxInfo;
texture ← NARROW[ThreeDScenes.GetShading[shape, $TextureMap], REF TextureMap];
IF texture = NIL THEN texture ← NEW[TextureMap];
texture.props ← Atom.PutPropOnList[texture.props, $CoordType, $FromVtxNos];
args ← CONS[row0ColM.y, NIL]; args ← CONS[row0ColM.x, args];
args ← CONS[rowNcolM.y, args]; args ← CONS[rowNcolM.x, args];
args ← CONS[rowNcol0.y, args]; args ← CONS[rowNcol0.x, args];
args ← CONS[row0col0.y, args]; args ← CONS[row0col0.x, args];
args ← CONS[Real.Float[numberOfRows], args]; args ← CONS[Real.Float[vtcesInRow], args];
texture.props ← Atom.PutPropOnList[texture.props, $Coords, args];
ThreeDScenes.PutShading[shape, $TextureMap, texture];
};
MakeTxtrCoordsFromNormals: PUBLIC PROC[ shape: REF ThreeDBasics.ShapeInstance,
        botLeft: Pair ← [0.0, 0.0], topLeft: Pair ← [0.0, 1.0],
        topRight: Pair ← [1.0, 1.0], botRight: Pair ← [1.0, 0.0],
           sw: Pair ← [-180.0, -90.0], nw: Pair ← [-180.0, 90.0],
           ne: Pair ← [180.0, 90.0], se: Pair ← [180.0, -90.0] ] ~ {
auxInfo: REF TripleSequence ← NEW[ TripleSequence[shape.shade.length] ];
texture: REF TextureMap;
args: LIST OF REAL;
poly: REF ThreeDSurfaces.PtrPatchSequence ← NARROW[shape.surface];
polyTags: ARRAY [0..8) OF BOOLEAN;
IF ThreeDScenes.GetShading[ shape, $VertexTextureInFile] # NIL THEN {
SIGNAL ThreeDScenes.Error[[$MisMatch, "Overwriting original texture coords, OK?"]];
shape.props ← Atom.RemPropFromList[shape.props, $VertexTextureInFile];
};
IF ABS[nw.y - sw.y] < 0.001 THEN nw.y ← sw.y + Sgn[nw.y - sw.y] * 0.001; -- stop div errs
IF ABS[ne.y - se.y] < 0.001 THEN ne.y ← se.y + Sgn[ne.y - se.y] * 0.001;
IF ABS[sw.x - se.x] < 0.001 THEN sw.x ← se.x + Sgn[sw.x - se.x] * 0.001;
IF ABS[nw.x - ne.x] < 0.001 THEN nw.x ← ne.x + Sgn[nw.x - ne.x] * 0.001;
FOR polyNumber: NAT IN [0..poly.length) DO
minTxtrX, minTxtrY: REAL ← Real.LargestNumber;
maxTxtrX: REAL ← 0.;
FOR i: NAT IN [0..poly[polyNumber].nVtces) DO
Map from sphere to Cartesian coordinates 1st quadrant 0 - 1 range.
vtx: NAT ← poly[polyNumber].vtxPtr[i];
hypotenuse: REAL ← RealFns.SqRt[Sqr[shape.shade[vtx].yn] + Sqr[shape.shade[vtx].xn]];
longitude: REAL ← RealFns.ArcTanDeg[shape.shade[vtx].yn, shape.shade[vtx].xn];
latitude: REAL ← RealFns.ArcTanDeg[shape.shade[vtx].zn, hypotenuse];
Map polar coordinates into quadrilateral given by sw, nw, ne, se
lPosY: REAL ← (latitude - sw.y) / (nw.y - sw.y); -- percentage of distance on left edge
rPosY: REAL ← (latitude - se.y) / (ne.y - se.y);
lPosX: REAL ← sw.x + lPosY * (nw.x - sw.x);   -- weighted average of positions
rPosX: REAL ← se.x + rPosY * (ne.x - se.x);
auxInfo.element[vtx].x ← (longitude - lPosX) / (rPosX - lPosX);  -- percentage across
auxInfo.element[vtx].y ← lPosY + auxInfo.element[vtx].x * (rPosY - lPosY); -- wtd av. %
auxInfo.element[vtx].x ← MIN[ 1.0, MAX[0.0, auxInfo.element[vtx].x]];
auxInfo.element[vtx].y ← MIN[ 1.0, MAX[0.0, auxInfo.element[vtx].y]];
IF hypotenuse < 0.00001 THEN polyTags[i] ← TRUE   -- catch unstable arithmetic
ELSE {
polyTags[i] ← FALSE;
IF auxInfo.element[vtx].x < minTxtrX THEN minTxtrX ← auxInfo.element[vtx].x;
IF auxInfo.element[vtx].x > maxTxtrX THEN maxTxtrX ← auxInfo.element[vtx].x;
};
auxInfo.element[vtx].z ← 0.0;
ENDLOOP;
auxInfo.length ← shape.shade.length;
shape.auxInfo ← auxInfo;
IF maxTxtrX - minTxtrX > .5    -- wrapping around seam, fix up coords
THEN FOR i: NAT IN [0..poly[polyNumber].nVtces) DO
vtx: NAT ← poly[polyNumber].vtxPtr[i];
IF maxTxtrX - auxInfo[vtx].x > .5
THEN auxInfo.element[vtx].x ← auxInfo.element[vtx].x + 1.;
ENDLOOP;
minTxtrX ← Real.LargestNumber;
maxTxtrX ← 0.;
FOR i: NAT IN [0..poly[polyNumber].nVtces) DO   -- get corrected max and min
IF polyTags[i] = FALSE THEN {
vtx: NAT ← poly[polyNumber].vtxPtr[i];
IF auxInfo.element[vtx].x < minTxtrX THEN minTxtrX ← auxInfo.element[vtx].x;
IF auxInfo.element[vtx].x > maxTxtrX THEN maxTxtrX ← auxInfo.element[vtx].x;
};
ENDLOOP;
FOR i: NAT IN [0..poly[polyNumber].nVtces) DO-- fix up unstable vertical normals
vtx: NAT ← poly[polyNumber].vtxPtr[i];
IF polyTags[i] = TRUE THEN auxInfo.element[vtx].x ← (maxTxtrX + minTxtrX) / 2.;
ENDLOOP;
minTxtrX ← minTxtrY ← Real.LargestNumber;
FOR i: NAT IN [0..poly[polyNumber].nVtces) DO  -- slew according to corner coords
vtx: NAT ← poly[polyNumber].vtxPtr[i];
lPosX: REAL ← botLeft.x + auxInfo.element[vtx].y * (topLeft.x - botLeft.x);
lPosY: REAL ← botLeft.y + auxInfo.element[vtx].y * (topLeft.y - botLeft.y);
rPosX: REAL ← botRight.x + auxInfo.element[vtx].y * (topRight.x - botRight.x);
rPosY: REAL ← botRight.y + auxInfo.element[vtx].y * (topRight.y - botRight.y);
auxInfo.element[vtx].x ← lPosX + auxInfo.element[vtx].x * (rPosX - lPosX);
auxInfo.element[vtx].y ← lPosY + auxInfo.element[vtx].x * (rPosY - lPosY);
IF auxInfo.element[vtx].x < minTxtrX THEN minTxtrX ← auxInfo.element[vtx].x;
IF auxInfo.element[vtx].y < minTxtrY THEN minTxtrY ← auxInfo.element[vtx].y;
ENDLOOP;
minTxtrX ← Real.Float[Real.Fix[minTxtrX]];
minTxtrY ← Real.Float[Real.Fix[minTxtrY]];
FOR i: NAT IN [0..poly[polyNumber].nVtces) DO   -- translate to origin
vtx: NAT ← poly[polyNumber].vtxPtr[i];
auxInfo[vtx].x ← auxInfo[vtx].x - minTxtrX;
auxInfo[vtx].y ← auxInfo[vtx].y - minTxtrY;
ENDLOOP;
ENDLOOP;
texture ← NARROW[ThreeDScenes.GetShading[shape, $TextureMap], REF TextureMap];
IF texture = NIL THEN texture ← NEW[TextureMap];
texture.props ← Atom.PutPropOnList[texture.props, $CoordType, $FromNormals];
args ← CONS[botRight.y, NIL]; args ← CONS[botRight.x, args];
args ← CONS[topRight.y, args]; args ← CONS[topRight.x, args];
args ← CONS[topLeft.y, args]; args ← CONS[topLeft.x, args];
args ← CONS[botLeft.y, args]; args ← CONS[botLeft.x, args];
texture.props ← Atom.PutPropOnList[texture.props, $Coords, args];
ThreeDScenes.PutShading[shape, $TextureMap, texture];
};
Procedures for Reading and Preparing Texture Files
TextureFromAIS: PUBLIC PROC[context: REF ThreeDBasics.Context, fileName: Rope.ROPE,
          type: ATOM ← $Intensity]
      RETURNS[texture: REF TextureMap] ~ {
Create context and load texture into it
width, height: NAT ← 1024;
buf: REF Pixels.PixelBuffer ← NEW[ Pixels.PixelBuffer ];
pixelSizes: Pixels.SampleSet;
renderMode: ATOMIF type = $Color THEN $FullColor ELSE $Grey;
firstBuf: NATIF context.alphaBuffer THEN 1 ELSE 0;
bufContext: REF ThreeDBasics.Context ← ThreeDScenes.Create[];
ThreeDScenes.DisplayFromVM[bufContext, width, height, renderMode];
bufContext.props ← context.props;      -- bring along working directory, etc.
[width, height] ← AISAnimation.GetAIS[     -- load pixelbuffer with AIS bits
    context: bufContext, fileRoot: fileName, center: FALSE];
IF width > bufContext.display.width OR height > bufContext.display.height THEN {
ThreeDScenes.DisplayFromVM[bufContext, width, height, renderMode]; -- get bigger buf
[width, height] ← AISAnimation.GetAIS[           -- load it up
          context: bufContext, fileRoot: fileName, center: FALSE];
};
SELECT bufContext.renderMode FROM
$Dithered, $PseudoColor, $Grey =>
{ pixelSizes ← Pixels.GetSampleSet[1]; pixelSizes[0] ← 8; };
$FullColor, $Dorado24  => {
pixelSizes ← Pixels.GetSampleSet[3];
pixelSizes[0] ← pixelSizes[1] ← pixelSizes[2] ← 8;
};
ENDCASE;
texture ← NEW[TextureMap];
buf^ ← Pixels.Create[width, height, pixelSizes];  -- get properly sized buffer
texture.pixels ← buf;
Pixels.Copy[buf^, bufContext.display,        -- copy texture pixels to it
[0, 0, width, height],
[0, 0, width, height]
];
texture.pixels ← buf;
texture.type ← type;
texture.props ← Atom.PutPropOnList[texture.props, $FileName, fileName];
};
SetTexture: PUBLIC PROC[shape: REF ThreeDBasics.ShapeInstance, texture: REF TextureMap] ~ {
oldTexture: REF TextureMap ← NARROW[ ThreeDScenes.GetShading[shape, $TextureMap] ];
IF oldTexture # NIL AND texture # NIL THEN {
oldTexture.pixels ← texture.pixels;
oldTexture.type ← texture.type;
oldTexture.props ← Atom.PutPropOnList[ oldTexture.props, $FileName,
            Atom.GetPropFromList[texture.props, $FileName]
           ];
texture ← oldTexture;
};
ThreeDScenes.PutShading[shape, $TextureMap, texture];
ThreeDScenes.PutShading[shape, $AuxLoad,
        NEW
[ThreeDBasics.VertexInfoProc ← LoadVtxAux]];
ThreeDScenes.PutShading[shape, $AuxLerp,
        NEW
[ThreeDBasics.VertexInfoProc ← LerpVtxAux]];
ThreeDScenes.PutShading[shape, $ShadeVtx, NEW[ThreeDBasics.VertexInfoProc ← ShadeVtx]];
};
SumTexture: PUBLIC PROC[shape: REF ThreeDBasics.ShapeInstance] ~ {
SumOneLine: PROC[ line: Pixels.SampleSet, sumMap: REF ScanSequence, y: NAT ]
     RETURNS[REF ScanSequence] ~ {
IF sumMap = NIL THEN sumMap ← NEW[ ScanSequence[buf.height] ];
IF sumMap[y] = NIL THEN sumMap[y] ← NEW[ IntSequence[buf.width] ];
FOR x: NAT IN [0..line.length) DO
sumMap[y][x] ← line[x];
IF x > 0 THEN {
sumMap[y][x] ← sumMap[y][x] + sumMap[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
sumMap[y][x] ← sumMap[y][x] + (sumMap[y - 1][x] - sumMap[y - 1][x - 1]);
}
ELSE IF y > 0 THEN
sumMap[y][x] ← sumMap[y][x] + sumMap[y - 1][x];  --add area below
ENDLOOP;
RETURN[sumMap];
};
buf: Pixels.PixelBuffer;
scanLine: REF Pixels.SampleSetSequence;
summedTexture: REF SummedTexture;
texture: REF TextureMap ← NARROW[ ThreeDScenes.GetShading[shape, $TextureMap] ];
IF texture = NIL THEN RETURN[];
buf ← NARROW[ texture.pixels, REF Pixels.PixelBuffer ]^;
summedTexture ← NEW[ SummedTexture[buf.samplesPerPixel] ];
FOR y: NAT IN [0..buf.height) DO
scanLine ← Pixels.GetScanSeg[buf, 0, y, buf.width, scanLine];
FOR i: NAT IN [0..buf.samplesPerPixel) DO
summedTexture[i] ← SumOneLine[ scanLine[i], summedTexture[i], y ];
ENDLOOP;
ENDLOOP;
texture.pixels ← summedTexture;
ThreeDScenes.PutShading[shape, $TextureMap, texture]
};
Procedures for Evaluating Texture at a Spot
GetTxtrAt: PUBLIC PROC[spot: ScanConvert.Spot] RETURNS[ScanConvert.Spot] ~ {
texture: REF TextureMap ← NARROW[ Atom.GetPropFromList[spot.props, $TextureMap] ];
WITH texture.pixels SELECT FROM
buf: REF Pixels.PixelBuffer => IF texture.type = $Bump
THEN SIGNAL ThreeDScenes.Error[[$MisMatch, "Need summed area table"]]
ELSE spot ← SimpleTexture[spot, buf^];
sumMap: REF SummedTexture => IF texture.type = $Bump
THEN spot ← BumpTexture[spot, sumMap]
ELSE spot ← SumMapTexture[spot, sumMap];
ENDCASE => ERROR;
RETURN[spot];
};
SimpleTexture: PROC[spot: ScanConvert.Spot, map: Pixels.PixelBuffer]
     RETURNS[ScanConvert.Spot] ~ {
GetTxtrAddress: PROC[buf: Pixels.PixelBuffer, x, y: REAL]
RETURNS[ txtrX, txtrY: INTEGER ] ~ {
txtrX ← Real.Fix[x * buf.width] MOD buf.width;
IF txtrX < 0 THEN txtrX ← txtrX + buf.width;
txtrY ← Real.Fix[y * buf.height] MOD buf.height;
IF txtrY < 0 THEN txtrY ← txtrY + buf.height;
};
x, y: INTEGER;
txtrX: NAT ~ NARROW[ Atom.GetPropFromList[spot.props, $MapVals], REF NAT ]^;
txtrY: NAT ~ txtrX+1;
[x, y] ← GetTxtrAddress[map, spot.val[txtrX], spot.val[txtrY]];
SELECT map.samplesPerPixel FROM
1,2 => {
txtrValue: REAL ← Pixels.GetValue[map, x, y, 0] / 256.0 ;
FOR i: NAT IN [0..3) DO spot.val[i] ← spot.val[i] * txtrValue; ENDLOOP;
};
3,4 => FOR i: NAT IN [0..3) DO
spot.val[i] ← spot.val[i] * Pixels.GetValue[map, x, y, i] / 256.0;
ENDLOOP;
ENDCASE  => SIGNAL ThreeDScenes.Error[[$MisMatch,
             "Wrong no. of samples in texture"]];
RETURN[spot];
};
SumMapTexture: PROC[spot: ScanConvert.Spot, txtrSum: REF SummedTexture]
       RETURNS[ScanConvert.Spot] ~ {
maxTxtrX: REAL ← txtrSum[0][0].length;    -- width (thus max x address) of texture
maxTxtrY: REAL ← txtrSum[0].length;     -- height (thus max y address) of texture
txtrX: NAT ~ NARROW[ Atom.GetPropFromList[spot.props, $MapVals], REF NAT ]^;
txtrY: NAT ~ txtrX+1;
txtrXIncr: REALMAX[ .001, ABS[spot.yIncr[txtrX]], ABS[spot.xIncr[txtrX]] ] / 2.0; -- incmnts in x&y
txtrYIncr: REALMAX[ .001, ABS[spot.yIncr[txtrY]], ABS[spot.xIncr[txtrY]] ] / 2.0;
area: REAL ← 4 * txtrXIncr * maxTxtrX * txtrYIncr * maxTxtrY;
WHILE spot.val[txtrX] - txtrXIncr < 0.0 DO spot.val[txtrX] ← spot.val[txtrX] + 1.0; ENDLOOP;
WHILE spot.val[txtrY] - txtrYIncr < 0.0 DO spot.val[txtrY] ← spot.val[txtrY] + 1.0; ENDLOOP;
SELECT txtrSum.length FROM
1 => {
txtrValue: REAL
   SumValues[spot, txtrSum[0], txtrX, txtrY, txtrXIncr, txtrYIncr, area] / 256.0;
IF txtrValue < 0.0 THEN txtrValue ← 0.0;
FOR i: NAT IN [0..3) DO spot.val[i] ← spot.val[i] * txtrValue; ENDLOOP;
};
3 => FOR i: NAT IN [0..3) DO
txtrValue: REAL
   SumValues[spot, txtrSum[i], txtrX, txtrY, txtrXIncr, txtrYIncr, area] / 256.0;
spot.val[i] ← MAX[ 0.0, spot.val[i] * txtrValue];
ENDLOOP;
ENDCASE  => SIGNAL ThreeDScenes.Error[[$MisMatch,
             "Wrong no. of samples in texture"]];
RETURN[spot];
};
BumpTexture: PROC[spot: ScanConvert.Spot, pixels: REF] RETURNS[ScanConvert.Spot] ~ {
txtrX: NAT ~ NARROW[ Atom.GetPropFromList[spot.props, $MapVals], REF NAT ]^;
txtrY: NAT ~ txtrX+1;
Positivize: PROC[] ~ {
WHILE spot.val[txtrX] - txtrXIncr < 0.0 DO spot.val[txtrX] ← spot.val[txtrX] + 1.0; ENDLOOP;
WHILE spot.val[txtrY] - txtrYIncr < 0.0 DO spot.val[txtrY] ← spot.val[txtrY] + 1.0; ENDLOOP;
};
txtrSum: REF SummedTexture ← NARROW[pixels, REF SummedTexture];
maxTxtrX: REAL ← txtrSum[0][0].length;    -- width (thus max x address) of texture
maxTxtrY: REAL ← txtrSum[0].length;     -- height (thus max y address) of texture
txtrXIncr: REALMAX[ .001, ABS[spot.yIncr[txtrX]], ABS[spot.xIncr[txtrX]] ] / 2.0;
txtrYIncr: REALMAX[ .001, ABS[spot.yIncr[txtrY]], ABS[spot.xIncr[txtrY]] ] / 2.0;
area: REAL ← 4 * txtrXIncr * maxTxtrX * txtrYIncr * maxTxtrY;
nmlX: NAT ~ 4; nmlY: NAT ~ nmlX + 1; nmlZ: NAT ~ nmlX + 2;
nmlXIncr: Triple ← Vector3d.Normalize[
          [ spot.xIncr[nmlX], spot.xIncr[nmlY], spot.xIncr[nmlZ] ] ];
nmlYIncr: Triple ← Vector3d.Normalize[
          [ spot.yIncr[nmlX], spot.yIncr[nmlY], spot.yIncr[nmlZ] ] ];
SELECT txtrSum.length FROM
1 => {
txtrValue, txtrValueX, txtrValueY: REAL;
Positivize[];         -- ensure positive texture coordinate values
txtrValue ← SumValues[spot, txtrSum[0], txtrX, txtrY, txtrXIncr, txtrYIncr, area] / 256.0;
spot.val[txtrX] ← spot.val[txtrX] + spot.xIncr[txtrX]; -- get txtr offset one pixel to right
spot.val[txtrY] ← spot.val[txtrY] + spot.xIncr[txtrY];
Positivize[];
txtrValueX ← SumValues[spot, txtrSum[0], txtrX, txtrY, txtrXIncr, txtrYIncr, area] / 256.0;
spot.val[txtrX] ← spot.val[txtrX] - spot.xIncr[txtrX]; -- get original texture value back
spot.val[txtrY] ← spot.val[txtrY] - spot.xIncr[txtrY];
spot.val[txtrX] ← spot.val[txtrX] + spot.yIncr[txtrX]; -- get txtr offset one pixel above
spot.val[txtrY] ← spot.val[txtrY] + spot.yIncr[txtrY];
Positivize[];
txtrValueY ← SumValues[spot, txtrSum[0], txtrX, txtrY, txtrXIncr, txtrYIncr, area] / 256.0;
nmlXIncr ← Vector3d.Mul[ nmlXIncr, txtrValueX - txtrValue]; -- perturb in x
nmlYIncr ← Vector3d.Mul[ nmlYIncr, txtrValueY - txtrValue]; -- perturb in y
spot.val[nmlX] ← spot.val[nmlX] + nmlXIncr.x + nmlYIncr.x;
spot.val[nmlY] ← spot.val[nmlY] + nmlXIncr.y + nmlYIncr.y;
spot.val[nmlZ] ← spot.val[nmlZ] + nmlXIncr.z + nmlYIncr.z;
};
ENDCASE  => SIGNAL ThreeDScenes.Error[[$MisMatch,
             "Wrong no. of samples in texture"]];
RETURN[spot];
};
Support Procedures for Summed Textures
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[txtrSum: REF ScanSequence, x, y: NAT] RETURNS[INT] ~ {
maxTxtrX: NAT ← txtrSum[0].length-1;
maxTxtrY: NAT ← txtrSum.length-1;
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[txtrSum, x, y]    + txtrSum[maxTxtrY][maxTxtrX]
   + CorrectSum[txtrSum, x, maxTxtrY]  + CorrectSum[txtrSum, maxTxtrX, y] ];
}
ELSE IF x >= txtrSum[0].length THEN {
x ← x - txtrSum[0].length;  
RETURN[ CorrectSum[txtrSum, x, y] + txtrSum[y][maxTxtrX] ];
}
ELSE {            -- IF y >= txtrSum.length
y ← y - txtrSum.length;
RETURN[ CorrectSum[txtrSum, x, y] + txtrSum[maxTxtrY][x] ];
};
};
GetValueAt: PROC[txtrSum: REF ScanSequence, x, y: REAL] RETURNS[ REAL ] ~ {
xPos, yPos: REAL;  lX, lY, rX, uY: NAT;
xPos ← x * txtrSum[0].length;
lX ← Real.Fix[xPos]; rX ← lX + 1;
yPos ← y * txtrSum.length;
lY ← Real.Fix[yPos]; uY ← lY + 1;
xPos ← xPos - Real.Fix[xPos];         -- get fractional part
yPos ← yPos - Real.Fix[yPos];
RETURN [ GetLerpedValue[ CorrectSum[txtrSum, lX, lY], CorrectSum[txtrSum, lX, uY],
         CorrectSum[txtrSum, rX, uY], CorrectSum[txtrSum, rX, lY],
        xPos, yPos
       ]
  ];
};
SumValues: PROC[ spot: ScanConvert.Spot, txtrSum: REF ScanSequence, txtrX, txtrY: NAT,
      txtrXIncr, txtrYIncr, area: REAL ]
    RETURNS[ txtrValue: REAL ] ~ {
txtrValue ← GetValueAt[ txtrSum,  spot.val[txtrX] + txtrXIncr, spot.val[txtrY] + txtrYIncr ]
  + GetValueAt[ txtrSum, spot.val[txtrX] - txtrXIncr, spot.val[txtrY] - txtrYIncr ]
  - GetValueAt[ txtrSum, spot.val[txtrX] + txtrXIncr, spot.val[txtrY] - txtrYIncr ]
  - GetValueAt[ txtrSum, spot.val[txtrX] - txtrXIncr, spot.val[txtrY] + txtrYIncr ];
txtrValue ← txtrValue / area;
};
END.