G3dMappedAndSolidTextureImpl.mesa
Copyright © 1984, 1986 by Xerox Corporation. All rights reserved.
Last Edited by: Crow, August 9, 1989 6:37:39 pm PDT
Bloomenthal, June 19, 1989 11:59:36 am PDT
DIRECTORY Atom, Checksum, G3dBasic, G3dClipXfmShade, G3dColorDisplaySupport, G3dMappedAndSolidTexture, G3dRender, G3dRenderWithPixels, G3dScanConvert, G3dShade, G3dShape, G3dSortandDisplay, G3dVector, ImagerPixel, ImagerSample, IO, List, Real, RealFns, Rope, Vector2;
G3dMappedAndSolidTextureImpl: CEDAR PROGRAM
IMPORTS Atom, Checksum, G3dClipXfmShade, G3dColorDisplaySupport, G3dRender, G3dRenderWithPixels, G3dShade, G3dSortandDisplay, G3dVector, ImagerPixel, IO, List, Real, RealFns, Rope, Vector2
EXPORTS G3dMappedAndSolidTexture
= BEGIN
Internal Declarations
ROPE:     TYPE ~ Rope.ROPE;
Context:    TYPE ~ G3dRender.Context;
RGB:     TYPE ~ G3dRender.RGB;
Box:     TYPE ~ ImagerSample.Box;
Rectangle:   TYPE ~ G3dRender.Rectangle;
Pair:     TYPE ~ G3dRender.Pair;       -- [ x, y: REAL];
PairSequence:  TYPE ~ G3dRender.PairSequence;
Triple:    TYPE ~ G3dRender.Triple;      -- [ x, y, z: REAL]
TripleSequence:  TYPE ~ G3dRender.TripleSequence;
Vertex:    TYPE ~ G3dShape.Vertex;
CtlPoint:    TYPE ~ G3dRender.CtlPoint;
CtlPtInfo:    TYPE ~ G3dRender.CtlPtInfo;
CtlPtInfoSequence: TYPE ~ G3dRender.CtlPtInfoSequence;
IntSequence:   TYPE ~ G3dRender.IntSequence;
IntSequenceRep:  TYPE ~ G3dBasic.IntSequenceRep;
RealSequence:  TYPE ~ G3dRender.RealSequence;
RealSequenceRep: TYPE ~ G3dBasic.RealSequenceRep;
TextureFunction: TYPE ~ G3dRender.TextureFunction;
TextureMap:   TYPE ~ G3dRender.TextureMap;
SumSequence:  TYPE ~ G3dRender.SumSequence;
SummedTexture:  TYPE ~ G3dRender.SummedTexture;
Patch:     TYPE ~ G3dRender.Patch;
PatchSequence:  TYPE ~ G3dRender.PatchSequence;
Spot:     TYPE ~ G3dRender.Spot;
SpotProc:    TYPE ~ G3dRender.SpotProc;
RenderData:   TYPE ~ G3dRender.RenderData;
ShadingClass:  TYPE ~ G3dRender.ShadingClass;
RenderStyle:   TYPE ~ G3dRender.RenderStyle;
Shape:    TYPE ~ G3dRender.Shape;
ShapeProc:   TYPE ~ G3dRender.ShapeProc;
DisplayMode:  TYPE ~ G3dRender.DisplayMode;
LORA: TYPE = LIST OF REF ANY;
Swap: PROCEDURE [p: Pair] RETURNS [Pair] ~ INLINE { RETURN[ [p.y, p.x] ]; };
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.];
};
Renamed Procedures
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;
Global Variables
justNoticeable: REAL ~ G3dScanConvert.justNoticeable;         -- 0.02
registeredTextureFunctions: Atom.PropList ← NIL; -- keeps active solid texture functions
maxTxtrRange: REAL ← 32.0;    -- texture coordinate range, small numbers expected
Initialization
Init: PROC[] ~ {
txtrShadingClass: ShadingClass ← [
type: $G3dMappedAndSolidTexture,
cnvrtVtx: GetLerpedVals,
getColor: RecoverColor,
shadeVtx: G3dShade.ShadeVtx
];
G3dShade.RegisterShadingClass[txtrShadingClass, $G3dMappedAndSolidTexture];
Register solid texture procs
RegisterTextureFunction[ $Spots, Spots ];
RegisterTextureFunction[ $Wurlitzer, Wurlitzer ];
RegisterTextureFunction[ $TwistedStripes, TwistedStripes ];
RegisterTextureFunction[ $BurlWood, BurlWood ];
RegisterTextureFunction[ $ZebraBurl, ZebraBurl ];
RegisterTextureFunction[ $Marble, Marble ];
};
Procedures for Controlling Texture
CheckAndAddProcs: PUBLIC PROC[shape: Shape ] ~ {
Update the shadingClass with texture procs
renderData: REF RenderData ← G3dRender.RenderDataFrom[shape];
IF renderData.shadingClass = NIL
THEN G3dShade.LoadShadingClass[shape, $G3dMappedAndSolidTexture]
ELSE IF renderData.shadingClass.type # $G3dMappedAndSolidTexture
THEN {
renderData.shadingClass.type ← $G3dMappedAndSolidTexture;
renderData.shadingClass.cnvrtVtx ← GetLerpedVals;
renderData.shadingClass.getColor ← RecoverColor;
renderData.shadingClass.shadeVtx ← G3dShade.ShadeVtx;
};
};
AddSolidTexture: PUBLIC PROC[context: Context, shapeName: Rope.ROPE, name: ATOM ] ~{
shape: Shape ← G3dRender.FindShape[ context, shapeName ];
renderData: REF RenderData ← G3dRender.RenderDataFrom[shape];
txtrFn: TextureFunction ← GetRegisteredTextureFunction[name];
txtrFn.props ← PutProp[txtrFn.props, $Shape, shape];
CheckAndAddProcs[shape];  -- make sure texture Procs loaded in shadingClass
renderData.shadingClass.texture ← List.Nconc1[  -- append to existing list
renderData.shadingClass.texture, NEW[TextureFunction ← txtrFn]
];
};
AddMappedTexture: PUBLIC PROC[context: Context, shapeName: Rope.ROPE,
           texture: REF TextureMap] ~ {
Append a mapped texture to the shape's texture list
shape: Shape ← G3dRender.FindShape[ context, shapeName ];
renderData: REF RenderData ← G3dRender.RenderDataFrom[shape];
CheckAndAddProcs[shape];
renderData.shadingClass.texture ← List.Nconc1[ renderData.shadingClass.texture, texture ];
};
SumAllMappedTextures: PUBLIC PROC[context: Context, shapeName: Rope.ROPE] ~ {
shape: Shape ← G3dRender.FindShape[ context, shapeName ];
renderData: REF RenderData ← G3dRender.RenderDataFrom[shape];
texture: LIST OF REF ANY ← renderData.shadingClass.texture;
FOR txtrList: LIST OF REF ANY ← texture, txtrList.rest UNTIL txtrList = NIL DO
WITH txtrList.first SELECT FROM
textureMap: REF TextureMap => SumMappedTexture[textureMap];
ENDCASE;
ENDLOOP;
};
RemoveAllTexture: PUBLIC PROC[context: Context, shapeName: Rope.ROPE] ~ {
shape: Shape ← G3dRender.FindShape[ context, shapeName ];
renderData: REF RenderData ← G3dRender.RenderDataFrom[shape];
renderData.shadingClass.texture ← NIL;
};
Procedures for VtxToRealSeq and computing color at a spot
GetLerpedVals: G3dRender.CtlPtToRealSeqProc ~ {
PROC[dest: RealSequence, source: CtlPtInfo, data: REF ANY] RTRNS[REF RealSequence];
maxLength: NATIF data = $PixelShading THEN 16 ELSE 10;
IF dest = NIL OR dest.maxLength < maxLength
THEN dest ← NEW[RealSequenceRep[maxLength]];
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
dest[11] ← source.shade.txtrX;
dest[12] ← source.shade.txtrY;
dest[13] ← source.coord.x;
dest[14] ← source.coord.y;
dest[15] ← source.coord.z;
}
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[5] ← source.shade.txtrX;
dest[6] ← source.shade.txtrY;
dest[7] ← source.coord.x;
dest[8] ← source.coord.y;
dest[9] ← source.coord.z;
};
dest.length ← maxLength;
RETURN [dest];
};
RecoverColor: SpotProc ~ {
PROC[context: Context, shading: REF ShadingClass, spot: REF Spot]
IF shading.texture # NIL THEN GetTxtrAt[context, shading, spot];
IF shading.texture # NIL OR spot.val.length > 15
THEN G3dRenderWithPixels.ShadeSpot[context, shading, spot];
};
Support Procedures for setting up texture for tiler
AdjustTexture: PUBLIC PROC[poly: REF Patch, texture: LORA, txtrRange: Pair] ~ {
This tries to eliminate problems around seams in texture mapped so that many polygons/patches tile one texture image. A seam is characterized by an unusually large range of texture coordinate values in one polygon/patch. If the range is larger than the indicated by "txtrRange" then those coordinates closer to the minimum value are incremented by one until they match or exceed the maximum value. This is not gauranteed to work for irregular mappings or situations I haven't thought of.
mappedTexture: BOOLEANFALSE;
halfXRange: REAL ← txtrRange.x / 2.0;
halfYRange: REAL ← txtrRange.y / 2.0;
FOR txtrList: LORANARROW[texture], txtrList.rest UNTIL txtrList = NIL DO
WITH txtrList.first SELECT FROM         -- look for mapped texture
texture: REF G3dRender.TextureMap => mappedTexture ← TRUE;
ENDCASE;
ENDLOOP;
IF mappedTexture THEN {
maxXtxtr, maxYtxtr, minXtxtr, minYtxtr: REAL;
FOR i: CARDINAL IN [0..poly.nVtces) DO  -- find maxima and minima
IF i = 0
THEN {
maxXtxtr ← minXtxtr ← poly[i].shade.txtrX;
maxYtxtr ← minYtxtr ← poly[i].shade.txtrY;
}
ELSE {
IF maxXtxtr < poly[i].shade.txtrX THEN maxXtxtr ← poly[i].shade.txtrX;
IF maxYtxtr < poly[i].shade.txtrY THEN maxYtxtr ← poly[i].shade.txtrY;
IF minXtxtr > poly[i].shade.txtrX THEN minXtxtr ← poly[i].shade.txtrX;
IF minYtxtr > poly[i].shade.txtrY THEN minYtxtr ← poly[i].shade.txtrY;
};
ENDLOOP;
IF maxXtxtr - minXtxtr > halfXRange THEN {   -- seam in x
FOR i: CARDINAL IN [0..poly.nVtces) DO-- push small ones beyond maximum
IF maxXtxtr - poly[i].shade.txtrX > poly[i].shade.txtrX - minXtxtr
THEN WHILE maxXtxtr > poly[i].shade.txtrX DO -- closer to minTxtr, so boost
poly[i].shade.txtrX ← poly[i].shade.txtrX + 1.0;
ENDLOOP;
ENDLOOP;
minXtxtr ← maxXtxtr;
FOR i: CARDINAL IN [0..poly.nVtces) DO  -- find minimum for normalization
IF minXtxtr > poly[i].shade.txtrX THEN minXtxtr ← poly[i].shade.txtrX;
ENDLOOP;
};
IF maxYtxtr - minYtxtr > halfYRange THEN {   -- seam in y
FOR i: CARDINAL IN [0..poly.nVtces) DO-- push small ones beyond maximum
IF maxYtxtr - poly[i].shade.txtrY > poly[i].shade.txtrY - minYtxtr
THEN WHILE maxYtxtr > poly[i].shade.txtrY DO
poly[i].shade.txtrY ← poly[i].shade.txtrY + 1.0;
ENDLOOP;
ENDLOOP;
minYtxtr ← maxYtxtr;
FOR i: CARDINAL IN [0..poly.nVtces) DO  -- find minimum for normalization
IF minYtxtr > poly[i].shade.txtrY THEN minYtxtr ← poly[i].shade.txtrY;
ENDLOOP;
};
minXtxtr ← Real.Float[Real.Fix[minXtxtr]]; minYtxtr ← Real.Float[Real.Fix[minYtxtr]];
FOR i: CARDINAL IN [0..poly.nVtces) DO  -- unit to minima under 1.0
poly[i].shade.txtrX ← poly[i].shade.txtrX - minXtxtr;
poly[i].shade.txtrY ← poly[i].shade.txtrY - minYtxtr;
ENDLOOP;
};
};
Procedures for Evaluating Texture at a Spot
GetTxtrAt: PUBLIC SpotProc ~ {
PROC[context: Context, shading: REF ShadingClass, spot: REF Spot]
texture: LORA ← shading.texture;
spot.partShiny ← 1.0;    -- initialize to full shiny, texture procs can only reduce it
FOR txtrList: LORA ← texture, txtrList.rest UNTIL txtrList = NIL DO
WITH txtrList.first SELECT FROM  -- textures evaluated top to bottom
texture: REF TextureMap =>   -- modify with mapped texture
WITH texture.pixels SELECT FROM
buf: ImagerPixel.PixelMap => IF texture.type = $Bump
THEN SIGNAL G3dRender.Error[$MisMatch, "Need summed area table"]
ELSE SimpleTexture[spot, buf, texture.type];
sumMap: REF SummedTexture => IF texture.type = $Bump
THEN {
ref: REF ANY ← Atom.GetPropFromList[texture.props, $BumpScale];
bumpScale: REALIF ref # NIL THEN NARROW[ref, REF REAL]^ ELSE 1.0;
BumpTexture[spot, sumMap, bumpScale];
}
ELSE SumMapTexture[spot, sumMap, texture.type];
ENDCASE => ERROR;
txtrFn: REF TextureFunction =>    -- solid or other function-based texture
txtrFn.proc[context, shading, spot, txtrFn.props];
ENDCASE => SIGNAL G3dRender.Error[$Unimplemented, "Unknown texture type"];
ENDLOOP;
};
SimpleTexture: PROC[spot: REF Spot, map: ImagerPixel.PixelMap, type: ATOM] ~ {
GetTxtrAddress: PROC[buf: ImagerPixel.PixelMap, x, y: REAL]
RETURNS[ txtrX, txtrY: INTEGER ] ~ {
bufWidth: NAT ← buf.box.max.f - buf.box.min.f;
bufHeight: NAT ← buf.box.max.s - buf.box.min.s;
txtrX ← Real.Fix[x * bufWidth] MOD bufWidth;
IF txtrX < 0 THEN txtrX ← txtrX + bufWidth;
txtrY ← Real.Fix[y * bufHeight] MOD bufHeight;
IF txtrY < 0 THEN txtrY ← txtrY + bufHeight;
};
pixel: ImagerPixel.PixelBuffer ← ImagerPixel.ObtainScratchPixels[map.samplesPerPixel, 1];
x, y: INTEGER;
txtrX: NAT ~ spot.val.length - 5;
txtrY: NAT ~ txtrX+1;
[x, y] ← GetTxtrAddress[map, spot.val[txtrX], spot.val[txtrY]];
ImagerPixel.GetPixels[self: map, pixels: pixel, initIndex: [f: x, s: y+map.box.min.s], count: 1];
SELECT type FROM
$Color => FOR i: NAT IN [0..3) DO
txtrValue: REAL ← pixel[i][0] / 256.0 ;
spot.val[i] ← spot.val[i] * txtrValue;
ENDLOOP;
$ColorAndTransmittance => {
surfTrans: REAL ← 1.0 - spot.val[3];
txtrTrans: REAL ← 1.0 - pixel[3][0] /256.0;
FOR i: NAT IN [0..3) DO
txtrVal: REAL ← pixel[i][0] / 256.0 ;
spot.val[i] ← txtrTrans * surfTrans * spot.val[i] -- lerp spot reduced by transmittance
    + (1.0 - txtrTrans) * spot.val[i] * txtrVal; -- with spot times texture
ENDLOOP;
spot.val[3] ← spot.val[3] * txtrTrans;    -- transmittances multiply
};
$IntensityandTransmittance => { -- alpha channels provide coverage = 1.0 - transmtnce
surfTrans: REAL ← 1.0 - spot.val[3];
txtrVal: REAL ← 1.0 - pixel[0][0] / 256.0 ;
txtrTrans: REAL ← 1.0 - pixel[1][0] / 256.0 ;
FOR i: NAT IN [0..3) DO
spot.val[i] ← txtrTrans * surfTrans * spot.val[i] -- lerp spot reduced by transmittance
    + (1.0 - txtrTrans) * spot.val[i] * txtrVal; -- with spot times texture
ENDLOOP;
spot.val[3] ← spot.val[3] * txtrTrans;    -- transmittances multiply
};
$Intensity => {
txtrValue: REAL ← pixel[0][0] / 256.0 ;
FOR i: NAT IN [0..3) DO
spot.val[i] ← spot.val[i] * txtrValue;
ENDLOOP;
};
ENDCASE  => SIGNAL G3dRender.Error[$MisMatch, "Unknown texture type"];
ImagerPixel.ReleaseScratchPixels[pixel];
};
SumMapTexture: PROC[spot: REF Spot, txtrSum: REF SummedTexture, type: ATOM] ~ {
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 ~ spot.val.length - 5;
txtrY: NAT ~ txtrX+1;
Increments in x and y
txtrXIncr: REAL ← 0.5 * MAX[ ABS[spot.yIncr[txtrX]], ABS[spot.xIncr[txtrX]] ];
txtrYIncr: REAL ← 0.5 * MAX[ ABS[spot.yIncr[txtrY]], ABS[spot.xIncr[txtrY]] ];
area: REAL ← 4 * txtrXIncr * maxTxtrX * txtrYIncr * maxTxtrY;
SELECT type FROM
$Color => 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;
$ColorAndTransmittance => {
surfTrans: REAL ← 1.0 - spot.val[3];
txtrTrans: REAL ← SumValues[
spot, txtrSum[3], txtrX, txtrY, txtrXIncr, txtrYIncr, area
] /256.0;
txtrTrans ← 1.0 - MAX[ 0.0, txtrTrans]; -- from alpha to transmittance
FOR i: NAT IN [0..3) DO
txtrVal: REAL ← SumValues[
spot, txtrSum[i], txtrX, txtrY, txtrXIncr, txtrYIncr, area
] /256.0;
spot.val[i] ←   txtrTrans * surfTrans * spot.val[i] -- lerp spot reduced by trans.
    + (1.0 - txtrTrans) * spot.val[i] * txtrVal;  -- with spot times texture
spot.val[i] ← MAX[ 0.0, spot.val[i]];
ENDLOOP;
spot.val[3] ← spot.val[3] * txtrTrans;    -- transmittances multiply
};
$IntensityandTransmittance => {
txtrVal: REAL
   SumValues[spot, txtrSum[0], txtrX, txtrY, txtrXIncr, txtrYIncr, area] / 256.0;
surfTrans: REAL ← 1.0 - spot.val[3];
txtrTrans: REAL
   SumValues[spot, txtrSum[1], txtrX, txtrY, txtrXIncr, txtrYIncr, area] / 256.0;
txtrVal ← MAX[ 0.0, txtrVal];
txtrTrans ← 1.0 - MAX[ 0.0, txtrTrans]; -- from alpha to transmittance
FOR i: NAT IN [0..3) DO
spot.val[i] ← txtrTrans * surfTrans * spot.val[i] -- lerp spot reduced by trans.
    + (1.0 - txtrTrans) * spot.val[i] * txtrVal;  -- with spot times texture
spot.val[i] ← MAX[ 0.0, spot.val[i]];
ENDLOOP;
spot.val[3] ← spot.val[3] * txtrTrans;    -- transmittances multiply
};
$Intensity => {
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] ← MAX[ 0.0, spot.val[i] * txtrValue ];
ENDLOOP;
};
ENDCASE  => SIGNAL G3dRender.Error[$MisMatch, "Unknown texture type"];
};
BumpTexture: PROC[spot: REF Spot, txtrSum: REF SummedTexture, bumpScale: REAL] ~ {
txtrX: NAT ~ spot.val.length - 5;
txtrY: NAT ~ txtrX+1;
nmlX: NAT ~ 4; nmlY: NAT ~ nmlX + 1; nmlZ: NAT ~ nmlX + 2;
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
This section could be executed once per scan segment
maxXIncr, maxYIncr, area, length: REAL;
txtrXIncr is vector for texture change to next pixel on scanline
txtrXIncr: Pair ← [spot.xIncr[txtrX], spot.xIncr[txtrY]];
txtrYIncr is vector for texture change to corresponding pixel on scanline above
txtrYIncr: Pair ← [-txtrXIncr.y, txtrXIncr.x];     -- rotate txtrXIncr 90 deg.
scale txtrYIncr to projected length of texture increment along leading polygon edge
cosA: REAL ← Vector2.Dot[
Vector2.Unit[txtrYIncr], Vector2.Unit[[spot.yIncr[txtrX], spot.yIncr[txtrY]]]
];
txtrYIncr ← Vector2.Mul[txtrYIncr, ABS[cosA]];
IF spot.xySwapped THEN {    -- compensate for sideways scanning in tiler
txtrYIncr.x ← -txtrYIncr.x; txtrYIncr.y ← -txtrYIncr.y;
[[txtrXIncr.x, txtrYIncr.x]] ← Swap[[txtrXIncr.x, txtrYIncr.x]];
[[txtrXIncr.y, txtrYIncr.y]] ← Swap[[txtrXIncr.y, txtrYIncr.y]];
};
Find max size of texture offsets to adjacent pixels to estimate texture spot spread
maxXIncr ← 0.5 * MAX[ ABS[txtrXIncr.x], ABS[txtrYIncr.x] ];
maxYIncr ← 0.5 * MAX[ ABS[txtrXIncr.y], ABS[txtrYIncr.y] ];
area ← 4 * maxXIncr * maxTxtrX * maxYIncr * maxTxtrY;
Make sure area considered for surface orientation is large enough to avoid artifacts
(This is independent of texture orientation, extreme aspect ratios in texture images should be avoided)
length ← Vector2.Length[ txtrXIncr ];
IF length < 2.0/maxTxtrX THEN txtrXIncr ← Vector2.Mul[ txtrXIncr, 2.0 / (length*maxTxtrX) ];
length ← Vector2.Length[ txtrYIncr ];
IF length < 2.0/maxTxtrX THEN txtrYIncr ← Vector2.Mul[ txtrYIncr, 2.0 / (length*maxTxtrX) ];
SELECT txtrSum.length FROM
1 => {
txtrValue, txtrValueX, txtrValueY, cosA, sinA, length: REAL;
perturbDir, tnml, nml: Triple;
txtrValue ← SumValues[spot, txtrSum[0], txtrX, txtrY, maxXIncr, maxYIncr, area] / 256.0;
spot.val[txtrX] ← spot.val[txtrX] + txtrXIncr.x; -- get txtr offset one pixel to right
spot.val[txtrY] ← spot.val[txtrY] + txtrXIncr.y;
txtrValueX ← SumValues[spot, txtrSum[0], txtrX, txtrY, maxXIncr, maxYIncr, area] /256.0;
spot.val[txtrX] ← spot.val[txtrX] - txtrXIncr.x; -- get original texture value back
spot.val[txtrY] ← spot.val[txtrY] - txtrXIncr.y;
spot.val[txtrX] ← spot.val[txtrX] + txtrYIncr.x; -- get txtr offset one pixel above
spot.val[txtrY] ← spot.val[txtrY] + txtrYIncr.y;
txtrValueY ← SumValues[spot, txtrSum[0], txtrX, txtrY, maxXIncr, maxYIncr, area] /256.0;
perturbDir ← G3dVector.Cross[
[maxTxtrX*txtrXIncr.x, maxTxtrY*txtrXIncr.y, bumpScale*(txtrValueX - txtrValue)],
[maxTxtrX*txtrYIncr.x, maxTxtrY*txtrYIncr.y, bumpScale*(txtrValueY - txtrValue)]
];
perturbDir.z ← ABS[perturbDir.z];
perturbDir ← G3dVector.Unit[perturbDir];
perturb in x (rotate about y)
length ← RealFns.SqRt[ Sqr[perturbDir.x] + Sqr[perturbDir.z] ];
nml ← [spot.val[nmlX], spot.val[nmlY], spot.val[nmlZ]];
cosA ← perturbDir.z / length;
sinA ← perturbDir.x / length;
tnml.x ← nml.x * cosA + nml.z * sinA;
tnml.y ← nml.y;
tnml.z ← - nml.x * sinA + nml.z * cosA;
nml ← G3dVector.Unit[tnml];
perturb in y (rotate about x)
length ← RealFns.SqRt[ Sqr[perturbDir.z] + Sqr[perturbDir.y] ];
cosA ← perturbDir.z / length;
sinA ← perturbDir.y / length;
tnml.x ← nml.x;
tnml.y ← nml.y * cosA + nml.z * sinA;
tnml.z ← - nml.y * sinA + nml.z * cosA;
nml ← G3dVector.Unit[tnml];
spot.val[nmlX] ← nml.x;
spot.val[nmlY] ← nml.y;
spot.val[nmlZ] ← nml.z;
};
ENDCASE  => SIGNAL G3dRender.Error[$MisMatch, "Wrong no. of samples in texture"];
};
Support Procedures for Summed Textures
GetLerpedValue: PROC[ llVal, ulVal, urVal, lrVal: INT, xPos, yPos: REAL ]
RETURNS[ intPart: INT, fracPart: REAL ] ~ {
lowerValue: REAL ← llVal + xPos * (lrVal - llVal);  -- if we only had double precision!!
upperValue: REAL ← ulVal + xPos * (urVal - ulVal);
RETURN [ lowerValue + yPos * (upperValue - lowerValue) ];
lowerFrac, upperFrac: REAL; lowerInt, upperInt: INT;
lowerFrac ← xPos * (lrVal - llVal);     -- lerp upper values
lowerInt ← Real.Fix[ lowerFrac ];
lowerFrac ← lowerFrac - lowerInt;
lowerInt ← llVal + lowerInt;
upperFrac ← xPos * (urVal - ulVal);     -- lerp lower values
upperInt ← Real.Fix[ upperFrac ];
upperFrac ← upperFrac - upperInt;
upperInt ← ulVal + upperInt;
fracPart ← yPos * (upperInt - lowerInt);   -- lerp upper and lower lerps
intPart ← Real.Fix[ fracPart ];
fracPart ← fracPart - intPart;
   
intPart ← intPart + lowerInt;
fracPart ← fracPart + lowerFrac + yPos * (upperFrac - lowerFrac);
};
CorrectSum: PROC[txtrSum: REF SumSequence, 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 SumSequence, x, y: REAL]
    RETURNS[ intPart: INT, fracPart: 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];
[ intPart, fracPart ] ← GetLerpedValue[
CorrectSum[txtrSum, lX, lY], CorrectSum[txtrSum, lX, uY],
CorrectSum[txtrSum, rX, uY], CorrectSum[txtrSum, rX, lY],
xPos, yPos
];
};
SumValues: PROC[ spot: REF Spot, txtrSum: REF SumSequence, txtrX, txtrY: NAT,
      txtrXIncr, txtrYIncr, area: REAL ]
    RETURNS[ txtrValue: REAL ] ~ {
i1, i2, i3, i4: INT; f1, f2, f3, f4: REAL;
IF area < 4.0 THEN { -- summed area tables don't work if all samples lie in the same pixel
area ← 4.0;     -- filtering works best if done over two pixel widths
txtrXIncr ← 1.0 / txtrSum[0].length; txtrYIncr ← 1.0 / txtrSum.length;
};
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;
[ i1, f1 ] ← GetValueAt[ txtrSum, spot.val[txtrX] + txtrXIncr, spot.val[txtrY] + txtrYIncr ];
[ i2, f2 ] ← GetValueAt[ txtrSum, spot.val[txtrX] - txtrXIncr, spot.val[txtrY] - txtrYIncr ];
[ i3, f3 ] ← GetValueAt[ txtrSum, spot.val[txtrX] + txtrXIncr, spot.val[txtrY] - txtrYIncr ];
[ i4, f4 ] ← GetValueAt[ txtrSum, spot.val[txtrX] - txtrXIncr, spot.val[txtrY] + txtrYIncr ];
txtrValue ← (i1 + i2 - i3 - i4 ) / area;
txtrValue ← txtrValue + (f1 + f2 - f3 - f4 ) / area;
};
Procedures for solid texture calculation
RegisterTextureFunction: PUBLIC PROC[ name: ATOM, proc: SpotProc,
             props: Atom.PropList ← NIL ] ~ {
registeredTextureFunctions ← Atom.PutPropOnList[
registeredTextureFunctions, name, NEW[ TextureFunction ← [name, proc, props] ]
]; 
};
GetRegisteredTextureFunction: PUBLIC PROC[name: ATOM]
            RETURNS[ txtrFn: TextureFunction ] ~ {
ref: REF TextureFunction ← NARROW[
Atom.GetPropFromList[registeredTextureFunctions, name]
];
IF ref # NIL
THEN txtrFn ← ref^
ELSE SIGNAL G3dRender.Error[$MisMatch, "Unregistered procedure"]
};
Spots: SpotProc ~ {
Regular array of dark spots
x: NAT ← spot.val.length-3; y: NAT ← x+1; z: NAT ← x+2; -- object space coordinate
r: NAT ~ 0; g: NAT ~ 1; b: NAT ~ 2; t: NAT ~ 3;
intensity: REAL ← RealFns.Sin[10.0 * spot.val[x] ]
     * RealFns.Sin[14.0 * spot.val[y] ]
     * RealFns.Sin[20.0 * spot.val[z] ];
intensity ← (intensity + 1.0) / 2.0;
spot.val[r] ← spot.val[r] * intensity;
spot.val[g] ← spot.val[g] * intensity;
spot.val[b] ← spot.val[b] * intensity;
spot.val[t] ← spot.val[t] * intensity;
};
Wurlitzer: SpotProc ~ {  
Wurlitzer colors, stripes in 3-d
x: NAT ← spot.val.length-3; y: NAT ← x+1; z: NAT ← x+2; -- object space coordinate
r: NAT ~ 0; g: NAT ~ 1; b: NAT ~ 2; t: NAT ~ 3;
intensity: REAL ← RealFns.Sin[10.0 * spot.val[x] ]
     * RealFns.Sin[14.0 * spot.val[y] ]
     * RealFns.Sin[20.0 * spot.val[z] ];
intensity ← (intensity + 1.0) / 2.0;
spot.val[r] ← spot.val[r] * (RealFns.Sin[10.0*spot.val[x]] +1.0) / 2.0;
spot.val[g] ← spot.val[g] * (RealFns.Sin[14.0*spot.val[y]] +1.0) / 2.0;
spot.val[b] ← spot.val[b] * (RealFns.Sin[20.0*spot.val[z]] +1.0) / 2.0;
spot.val[t] ← spot.val[t] * intensity;
};
TwistedStripes: SpotProc ~ {
Rotating stripes (barber pole)
x: NAT ← spot.val.length-3; y: NAT ← x+1; z: NAT ← x+2; -- object space coordinate
r: NAT ~ 0; g: NAT ~ 1; b: NAT ~ 2; t: NAT ~ 3;
angle: REAL ← 3.1416 * spot.val[z];    -- rotation varies with z
cosAngle: REAL ← RealFns.Cos[angle];
sinAngle: REAL ← RealFns.Sin[angle];
intensity: REAL ← RealFns.Sin[40.0 *     -- x component of rotated x-y vector
         (cosAngle * spot.val[x] + sinAngle * spot.val[y]) ];
transmittance: REAL;
intensity ← (intensity + 1.0) / 2.0;
transmittance ← (1.0 - intensity);
spot.val[t] ← spot.val[t] * transmittance;
spot.val[r] ← spot.val[r] * intensity;
spot.val[g] ← spot.val[g] * (1.0 - intensity);
spot.val[b] ← spot.val[b] * intensity;
};
BurlWood: SpotProc ~ {
x: NAT ← spot.val.length-3; y: NAT ← x+1; z: NAT ← x+2; -- object space coordinate
r: NAT ~ 0; g: NAT ~ 1; b: NAT ~ 2; t: NAT ~ 3;
red, grn, blu: REAL;
chaos: REAL ← Chaos[ spot.val[x], spot.val[y], spot.val[z] ];
midBrown: REAL ← RealFns.Sin[ chaos*8 + 7*spot.val[x] + 3* spot.val[y] ];
brownLayer: REALABS[ RealFns.Sin[midBrown] ];
greenLayer: REAL ← - brownLayer;
perturb: REALIF brownLayer > 0.0
THEN ABS[RealFns.Sin[40 * chaos + 50*spot.val[z] ]]
ELSE ABS[RealFns.Sin[30 * chaos + 30*spot.val[x] ]];
brownPerturb: REAL ← perturb * .6 + .3;  -- perturb up to .6
greenPerturb: REAL ← perturb * .2 + .8;  -- perturb up to .2
grnPerturb: REAL ← perturb * .15 + .85;  -- perturb up to .15
grn ← .5 * RealFns.Power[ABS[brownLayer], 0.3]; -- makes seams
brownLayer ← RealFns.Power[(brownLayer + 1.0) / 2.0, 0.6] * brownPerturb;
greenLayer ← RealFns.Power[(greenLayer + 1.0) / 2.0, 0.6] * greenPerturb;
red ← (.6 * brownLayer + .35 * greenLayer) * 2 * grn;
blu ← (.25 * brownLayer + .35 * greenLayer) * 2 * grn;
grn ← grn * MAX[brownLayer, greenLayer] * grnPerturb;
spot.val[r] ← spot.val[r] * red;
spot.val[g] ← spot.val[g] * grn;
spot.val[b] ← spot.val[b] * blu;
};
ZebraBurl: SpotProc ~ {
x: NAT ← spot.val.length-3; y: NAT ← x+1; z: NAT ← x+2; -- object space coordinate
r: NAT ~ 0; g: NAT ~ 1; b: NAT ~ 2; t: NAT ~ 3;
red, grn, blu: REAL;
chaos: REAL ← Chaos[ spot.val[x], spot.val[y], spot.val[z] ];
midBrown: REAL ← RealFns.Sin[ chaos*8 + 7*spot.val[x] + 3* spot.val[y] ];
brownLayer: REAL ← RealFns.Sin[midBrown];
greenLayer: REAL ← - brownLayer;
perturb: REALIF brownLayer > 0.0
THEN ABS[RealFns.Sin[40 * chaos + 50*spot.val[z] ]]
ELSE ABS[RealFns.Sin[24 * chaos + 30*spot.val[x] ]];
brownPerturb: REAL ← perturb * .6 + .3;  -- perturb up to .6
greenPerturb: REAL ← perturb * .2 + .8;  -- perturb up to .2
grnPerturb: REAL ← perturb * .15 + .85;  -- perturb up to .15
grn ← .5 * RealFns.Power[ABS[brownLayer], 0.3]; -- makes seams
brownLayer ← RealFns.Power[(brownLayer + 1.0) / 2.0, 0.6] * brownPerturb;
greenLayer ← RealFns.Power[(greenLayer + 1.0) / 2.0, 0.6] * greenPerturb;
red ← (.6 * brownLayer + .35 * greenLayer) * 2 * grn;
blu ← (.25 * brownLayer + .35 * greenLayer) * 2 * grn;
grn ← grn * MAX[brownLayer, greenLayer] * grnPerturb;
spot.val[r] ← spot.val[r] * red;
spot.val[g] ← spot.val[g] * grn;
spot.val[b] ← spot.val[b] * blu;
};
Marble: SpotProc ~ {
Perlin's marble texture
x: NAT ← spot.val.length-3; y: NAT ← x+1; z: NAT ← x+2; -- object space coordinate
r: NAT ~ 0; g: NAT ~ 1; b: NAT ~ 2; t: NAT ~ 3;
intensity: REAL ← RealFns.Sin[Chaos[ spot.val[x],
           spot.val[y],
           spot.val[z] ]*8 + 7*spot.val[z]];
intensity ← (intensity + 1.0) / 2.0;
intensity ← RealFns.Power[intensity, 0.77];
spot.val[r] ← spot.val[r] * intensity;
spot.val[g] ← spot.val[g] * intensity;
spot.val[b] ← spot.val[b] * intensity;
};
Chaos: PUBLIC PROC[x, y, z: REAL] RETURNS [REAL] ~ {
f: REAL ← 1.;
s, t: REAL ← 0.;
FOR n: INT IN [0..7) DO
s ← Noise[x * f, y * f, z * f];
t ← t + ABS[s] / f;
f ← 2 * f;
ENDLOOP;
RETURN [t];
};
realScale: REAL ← 2.0 / LAST[CARDINAL];
RTable: TYPE ~ RECORD[SEQUENCE length: NAT OF REAL];
rTable: REF RTable ← NIL;
Noise: PUBLIC PROC[vx, vy, vz: REAL] RETURNS [REAL] ~ {
returns band limited noise over R3.
R: PROC[i, j, k: REAL] RETURNS [CARDINAL] ~ TRUSTED {
A: TYPE ~ ARRAY [0..3) OF REAL;
a: A ← [i * .12345 , j * .12345 , k * .12345 ];
aPointer: LONG POINTER TO A ~ @a;
h: CARDINAL ← Checksum.ComputeChecksum[nWords: SIZE[A], p: LOOPHOLE[aPointer]];
RETURN [h];
};
SCurve: PROC[x: REAL] RETURNS [REAL] ~ {
map the unit interval into an "S shaped" cubic f[x] | f[0]=0, f'[0]=0, f[1]=1, f'[1]=0.
RETURN [x * x * (3 - 2 * x)];
};
declare local variables.
m: NAT;
ix, iy, iz: INT;
x, y, z, jx, jy, jz, sx, sy, sz, tx, ty, tz, s, f: REAL;
initialize random gradient table
IF rTable = NIL THEN {
rTable ← NEW[RTable[259]];
FOR n:INT IN [0..259) DO
r:REAL ← n;
rTable[n] ← R[r, r, r] * realScale - 1.;
ENDLOOP;
};
Force everything to be positive
x ← vx + 1000.;
y ← vy + 1000.;
z ← vz + 1000.;
ixyz ← the integer lattice point "just below" v (identifies the surrounding unit cube).
ix ← Real.Fix[x];
iy ← Real.Fix[y];
iz ← Real.Fix[z];
sxyz ← the vector difference v - ixyz biased with an S-Curve in each dimension.
sx ← SCurve[x - ix];
sy ← SCurve[y - iy];
sz ← SCurve[z - iz];
txyz ← the complementary set of S-Curves in each dimension.
tx ← 1. - sx;
ty ← 1. - sy;
tz ← 1. - sz;
f ← 0.; -- initialize sum to zero.
FOR n: INT IN [0..8) DO -- sum together 8 local fields from neighboring lattice pts.
SELECT n FROM -- each of 8 corners of the surrounding unit cube.
0 => {jx ← ix  ; jy ← iy  ; jz ← iz  ; s ← tx * ty * tz };
1 => {jx ← ix+1          ; s ← sx * ty * tz };
2 => {jx ← ix  ; jy ← iy+1      ; s ← tx * sy * tz };
3 => {jx ← ix+1          ; s ← sx * sy * tz };
4 => {jx ← ix  ; jy ← iy  ; jz ← iz+1 ; s ← tx * ty * sz };
5 => {jx ← ix+1          ; s ← sx * ty * sz };
6 => {jx ← ix  ; jy ← iy+1     ; s ← tx * sy * sz };
7 => {jx ← ix+1         ; s ← sx * sy * sz };
ENDCASE;
Add in each weighted component
m ← R[jx, jy, jz] MOD 256;
f ← f + s * ( rTable[m]/2 + rTable[m+1]*(x-jx) +
    rTable[m+2]*(y-jy) + rTable[m+3]*(z-jz) );
ENDLOOP;
RETURN [f];
};
Procedures for Recovering Texture Descriptions from streams
GetRope: PROC[input: IO.STREAM] RETURNS[ROPE] ~ {
Gets set of characters bound by white space
output: ROPENIL;
char: CHAR;
[] ← IO.SkipWhitespace[input];       -- Strip whitespace and comments
char ← IO.GetChar[input];
WHILE char # IO.SP AND char # IO.CR DO     -- do until trailing space or CR
output ← Rope.Cat[ output, Rope.FromChar[char] ];
char ← IO.GetChar[input];
ENDLOOP;
RETURN[output];
};
EnableStreamProcs: PUBLIC PROC[ context: Context ] ~ {
context.props ← Atom.PutPropOnList[
context.props,
$TextureMapFromStream,
NEW[ShapeProc ← TextureMapFromStream]
];
context.props ← Atom.PutPropOnList[
context.props,
$TextureFunctionFromStream,
NEW[ShapeProc ← TextureFunctionFromStream]
];
};
TextureMapFromStream: ShapeProc ~ {
PROC[context: Context, shape: Shape, data: REF ANYNIL] RETURNS[Shape];
input: IO.STREAMNARROW[data];
txtrMap: REF TextureMap ← TextureFromAIS[ context: context,
fileName: GetRope[input],
type: IO.GetAtom[input]
];
AddMappedTexture[ context, shape.name, txtrMap ];
SELECT IO.GetAtom[input] FROM
$FromVtxNos => MakeTxtrCoordsFromVtxNos[ context: context, shapeName: shape.name,
vtcesInRow: IO.GetInt[input], numberOfRows: IO.GetInt[input],
row0col0: [IO.GetInt[input], IO.GetInt[input]],
rowNcol0: [IO.GetInt[input], IO.GetInt[input]],
rowNcolM: [IO.GetInt[input], IO.GetInt[input]],
row0colM: [IO.GetInt[input], IO.GetInt[input]]
];
$FromNormals => MakeTxtrCoordsFromNormals[ context: context, shapeName: shape.name,
botLeft: [IO.GetInt[input], IO.GetInt[input]],
topLeft: [IO.GetInt[input], IO.GetInt[input]],
topRight: [IO.GetInt[input], IO.GetInt[input]],
botRight: [IO.GetInt[input], IO.GetInt[input]],
sw: [IO.GetInt[input], IO.GetInt[input]],
nw: [IO.GetInt[input], IO.GetInt[input]],
ne: [IO.GetInt[input], IO.GetInt[input]],
se: [IO.GetInt[input], IO.GetInt[input]]
];
$NoCoords => {};
ENDCASE => SIGNAL G3dRender.Error[$Unimplemented, "Unknown texture coordType"];
[] ← GetRope[input];   -- ignore word "Scale"
ScaleTxtrCoords[context, shape.name, IO.GetReal[input], IO.GetReal[input], IO.GetReal[input]];
RETURN[ shape ];
};
TextureFunctionFromStream: ShapeProc ~ {
PROC[context: Context, shape: Shape, data: REF ANYNIL] RETURNS[Shape];
input: IO.STREAMNARROW[data];
AddSolidTexture[ context, shape.name, IO.GetAtom[input] ];
RETURN[ shape ];
};
Procedures for Computing Texture Coordinates
ScaleTxtrCoords: PUBLIC PROC [
context: Context,
shapeName: Rope.ROPE,
scale: REAL,
xRatio, yRatio: REAL ← 1.0]
~ {
shape: Shape ← G3dRender.FindShape[context, shapeName];
oldScale, newScale: Pair ← G3dRender.GetTextureScale[shape];
IF oldScale.x # 0.0 AND oldScale.y # 0.0 THEN {
newScale.x ← scale*xRatio/oldScale.x; -- multiply by new, divide by old values
newScale.y ← scale*yRatio/oldScale.y;
};
FOR i: NAT IN [0..shape.vertices.length) DO
shape.vertices[i].texture.x ← shape.vertices[i].texture.x * newScale.x;
shape.vertices[i].texture.y ← shape.vertices[i].texture.y * newScale.y;
ENDLOOP;
G3dRender.SetTextureScale[shape, newScale];
};
SetPatchTextureCoords: PUBLIC PROC [
context: Context,
shapeName: ROPE,
corner0: Pair ← [0.,0.], corner1: Pair ← [0.,1.], corner2: Pair ← [1.,1.], corner3: Pair ← [1.,0.]
] ~ {
Sets all quadrilateral patches with the given texture coordinates at the corners, inner control points on Bezier patches are interpolated from the input corner values
shape: Shape ← G3dRender.FindShape[context, shapeName];
renderData: REF RenderData ← G3dRender.RenderDataFrom[ shape ];
patch: PatchSequence;
IF renderData = NIL OR renderData.patch = NIL
THEN [] ← G3dSortandDisplay.ValidateContext[ context ]; -- do whole context, just in case
patch ← renderData.patch;
SELECT shape.type FROM
$ConvexPolygon, $Poly => FOR i: NAT IN [0..patch.length) DO
IF patch[i].nVtces = 4 THEN {
patch[i][0].shade.txtrX ← corner0.x; patch[i][0].shade.txtrY ← corner0.y;
patch[i][1].shade.txtrX ← corner1.x; patch[i][1].shade.txtrY ← corner1.y;
patch[i][2].shade.txtrX ← corner2.x; patch[i][2].shade.txtrY ← corner2.y;
patch[i][3].shade.txtrX ← corner3.x; patch[i][3].shade.txtrY ← corner3.y;
};
ENDLOOP;
$Bezier => FOR i: NAT IN [0..patch.length) DO
IF patch[i].nVtces = 16 THEN {
 Corners (see diagram in G3dStandardPatchProcs.PolygonFromBezierPatch)
patch[i][ 0].shade.txtrX ← corner0.x;  patch[i][ 0].shade.txtrY ← corner0.y;
patch[i][12].shade.txtrX ← corner1.x;  patch[i][12].shade.txtrY ← corner1.y;
patch[i][15].shade.txtrX ← corner2.x;  patch[i][15].shade.txtrY ← corner2.y;
patch[i][ 3].shade.txtrX ← corner3.x;  patch[i][ 3].shade.txtrY ← corner3.y;
Inner control points on boundaries
patch[i][ 1].shade.txtrX ← (2.0 * corner0.x + corner3.x) / 3.0 ;  
patch[i][ 1].shade.txtrY ← (2.0 * corner0.y + corner3.y) / 3.0;
patch[i][ 2].shade.txtrX ← (2.0 * corner3.x + corner0.x) / 3.0 ;  
patch[i][ 2].shade.txtrY ← (2.0 * corner3.y + corner0.y) / 3.0;
patch[i][ 4].shade.txtrX ← (2.0 * corner0.x + corner1.x) / 3.0 ;  
patch[i][ 4].shade.txtrY ← (2.0 * corner0.y + corner1.y) / 3.0;
patch[i][ 8].shade.txtrX ← (2.0 * corner1.x + corner0.x) / 3.0 ;  
patch[i][ 8].shade.txtrY ← (2.0 * corner1.y + corner0.y) / 3.0;
patch[i][ 7].shade.txtrX ← (2.0 * corner3.x + corner2.x) / 3.0 ;  
patch[i][ 7].shade.txtrY ← (2.0 * corner3.y + corner2.y) / 3.0;
patch[i][11].shade.txtrX ← (2.0 * corner2.x + corner3.x) / 3.0 ;  
patch[i][11].shade.txtrY ← (2.0 * corner2.y + corner3.y) / 3.0;
patch[i][13].shade.txtrX ← (2.0 * corner1.x + corner2.x) / 3.0 ;  
patch[i][13].shade.txtrY ← (2.0 * corner1.y + corner2.y) / 3.0;
patch[i][14].shade.txtrX ← (2.0 * corner2.x + corner1.x) / 3.0 ;  
patch[i][14].shade.txtrY ← (2.0 * corner2.y + corner1.y) / 3.0;
Central control points
patch[i][5].shade.txtrX ← (2.0 * patch[i][4].shade.txtrX + patch[i][7].shade.txtrX) / 3.0 ;  
patch[i][5].shade.txtrY ← (2.0 * patch[i][4].shade.txtrY + patch[i][7].shade.txtrY) / 3.0;
patch[i][6].shade.txtrX ← (2.0 * patch[i][7].shade.txtrX + patch[i][4].shade.txtrX) / 3.0 ;  
patch[i][6].shade.txtrY ← (2.0 * patch[i][7].shade.txtrY + patch[i][4].shade.txtrY) / 3.0;
patch[i][9].shade.txtrX ← (2.0 * patch[i][8].shade.txtrX + patch[i][11].shade.txtrX) /3.0 ;  
patch[i][9].shade.txtrY ← (2.0 * patch[i][8].shade.txtrY + patch[i][11].shade.txtrY) /3.0;
patch[i][10].shade.txtrX ← (2.0 * patch[i][11].shade.txtrX + patch[i][8].shade.txtrX)/3.0 ;  
patch[i][10].shade.txtrY ← (2.0 * patch[i][11].shade.txtrY + patch[i][8].shade.txtrY)/3.0;
};
ENDLOOP;
ENDCASE => SIGNAL G3dRender.Error[$Unimplemented, "Unknown shape type"];
};
MakeTxtrCoordsFromVtxNos: PUBLIC PROC[ context: Context, shapeName: Rope.ROPE,
              vtcesInRow, numberOfRows: NAT,
        row0col0, rowNcol0, rowNcolM, row0colM: Pair] ~ {
shape: Shape ← G3dRender.FindShape[ context, shapeName ];
renderData: REF RenderData ← G3dRender.RenderDataFrom[shape];
args: LIST OF REAL;
IF shape.vertices.valid.texture
THEN SIGNAL G3dRender.Error[$MisMatch, "Overwriting original texture coords, OK?"];
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 G3dRender.Error[$MisMatch, "Texture mapping dangerously dense"];
FOR i: NAT IN [0..shape.vertices.length) DO
lPosX, lPosY, rPosX, rPosY: REAL;
shape.vertices[i].texture.x ← Real.Float[i MOD vtcesInRow] / vtcesInRow; -- pct along row
shape.vertices[i].texture.y ← Real.Float[i / vtcesInRow] / numberOfRows; -- pct across rows
Stretch as indicated by corner coordinates - interpolate across rows
lPosX ← row0col0.x + shape.vertices[i].texture.y * (rowNcol0.x - row0col0.x);
lPosY ← row0col0.y + shape.vertices[i].texture.y * (rowNcol0.y - row0col0.y);
rPosX ← row0colM.x + shape.vertices[i].texture.y * (rowNcolM.x - row0colM.x);
rPosY ← row0colM.y + shape.vertices[i].texture.y * (rowNcolM.y - row0colM.y);
Interpolate along row
shape.vertices[i].texture.x ← lPosX + shape.vertices[i].texture.x * (rPosX - lPosX);
shape.vertices[i].texture.y ← lPosY + shape.vertices[i].texture.x * (rPosY - lPosY);
ENDLOOP;
renderData.shadingProps ← PutProp[
renderData.shadingProps, $TxtrCoordType, $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];
renderData.shadingProps ← PutProp[ renderData.shadingProps, $TxtrCoordParams, args];
renderData.shadingProps ← PutProp[
renderData.shadingProps,
$TxtrCoordRange,
NEW[ Pair ← [ MIN[ rowNcol0.x - row0col0.x, rowNcolM.x - row0colM.x],
      MIN[ row0colM.y - row0col0.y, rowNcolM.y - rowNcol0.y]
 ]   ]
];
shape.vertices.valid.texture ← TRUE;
G3dRender.RenderDataFrom[shape].patch ← NIL;    -- force patches to be rebuilt
};
MakeTxtrCoordsFromNormals: PUBLIC PROC[ context: Context, shapeName: Rope.ROPE,
        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] ] ~ {
shape: Shape ← G3dRender.FindShape[ context, shapeName ];
renderData: REF RenderData ← G3dRender.RenderDataFrom[shape];
args: LIST OF REAL;
badTag: REAL ← -9999.99;
minTxtrX, minTxtrY: REAL ← Real.LargestNumber;
maxTxtrX: REAL ← 0.;
lngtShift: REAL ← 0.0;       -- longitude shift to allow < -180.0 and > 180.0
IF shape.vertices.valid.texture
THEN SIGNAL G3dRender.Error[$MisMatch, "Overwriting original texture coords, OK?"];
IF NOT shape.vertices.valid.normal THEN G3dClipXfmShade.GetVtxNmls[context, shape];
IF MAX[sw.x, nw.x, se.x, ne.x] - MIN[sw.x, nw.x, se.x, ne.x] > 360.0
THEN SIGNAL G3dRender.Error[$MisMatch, "Longitude range exceeds 360 degrees"];
IF MIN[sw.x, nw.x, se.x, ne.x] < -180.0
THEN lngtShift ← -180.0 - MIN[sw.x, nw.x, se.x, ne.x]  -- positive shift if past -180
ELSE IF MAX[sw.x, nw.x, se.x, ne.x] > 180.0
THEN lngtShift ← 180.0 - MAX[sw.x, nw.x, se.x, ne.x];  -- negative shift if past 180
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 i: NAT IN [0..shape.vertices.length) DO      -- calculate normals
vtx: Vertex ← shape.vertices[i];
Map from sphere to Cartesian coordinates 1st quadrant 0 - 1 range.
hypotenuse: REAL ← RealFns.SqRt[Sqr[vtx.normal.y] + Sqr[vtx.normal.x]];
longitude: REAL ← RealFns.ArcTanDeg[vtx.normal.y, vtx.normal.x];
latitude: REAL ← RealFns.ArcTanDeg[vtx.normal.z, 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);
IF longitude > 180.0 - lngtShift
THEN longitude ← -180.0 - (180.0 - longitude)
ELSE IF longitude < -180.0 - lngtShift
THEN longitude ← 180.0 + (longitude + 180.0);
vtx.texture.x ← (longitude - lPosX) / (rPosX - lPosX);  -- percentage across
vtx.texture.y ← lPosY + vtx.texture.x * (rPosY - lPosY); -- wtd av. %
vtx.texture.x ← MIN[ 1.0, MAX[0.0, vtx.texture.x]];
vtx.texture.y ← MIN[ 1.0, MAX[0.0, vtx.texture.y]];
IF vtx.texture.x < minTxtrX THEN minTxtrX ← vtx.texture.x;
IF vtx.texture.x > maxTxtrX THEN maxTxtrX ← vtx.texture.x;
IF hypotenuse < 0.00001 THEN vtx.texture.x ← badTag;   -- catch unstable arithmetic
ENDLOOP;
FOR i: NAT IN [0..shape.vertices.length) DO-- fix up unstable vertical normals
vtx: Vertex ← shape.vertices[i];
IF vtx.texture.x = badTag THEN vtx.texture.x ← (maxTxtrX + minTxtrX) / 2.;
ENDLOOP;
minTxtrX ← minTxtrY ← Real.LargestNumber;
FOR i: NAT IN [0..shape.vertices.length) DO   -- slew according to corner coords
vtx: Vertex ← shape.vertices[i];
lPosX: REAL ← botLeft.x + vtx.texture.y * (topLeft.x - botLeft.x);
lPosY: REAL ← botLeft.y + vtx.texture.y * (topLeft.y - botLeft.y);
rPosX: REAL ← botRight.x + vtx.texture.y * (topRight.x - botRight.x);
rPosY: REAL ← botRight.y + vtx.texture.y * (topRight.y - botRight.y);
vtx.texture.x ← lPosX + vtx.texture.x * (rPosX - lPosX);
vtx.texture.y ← lPosY + vtx.texture.x * (rPosY - lPosY);
IF vtx.texture.x < minTxtrX THEN minTxtrX ← vtx.texture.x;
IF vtx.texture.y < minTxtrY THEN minTxtrY ← vtx.texture.y;
ENDLOOP;
minTxtrX ← Real.Float[Real.Fix[minTxtrX]];
minTxtrY ← Real.Float[Real.Fix[minTxtrY]];
FOR i: NAT IN [0..shape.vertices.length) DO   -- translate to origin
vtx: Vertex ← shape.vertices[i];
vtx.texture.x ← vtx.texture.x - minTxtrX;
vtx.texture.y ← vtx.texture.y - minTxtrY;
ENDLOOP;
renderData.shadingProps ← PutProp[
renderData.shadingProps, $TxtrCoordType, $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];
args ← CONS[se.y, args];  args ← CONS[se.x, args];
args ← CONS[ne.y, args];  args ← CONS[ne.x, args];
args ← CONS[nw.y, args];  args ← CONS[nw.x, args];
args ← CONS[sw.y, args];  args ← CONS[sw.x, args];
renderData.shadingProps ← PutProp[ renderData.shadingProps, $TxtrCoordParams, args];
renderData.shadingProps ← PutProp[
renderData.shadingProps,
$TxtrCoordRange,
NEW[ Pair ← [ MIN[ botRight.x - botLeft.x, topRight.x - topLeft.x],
      MIN[ topLeft.y - botLeft.y, topRight.y - botRight.y]
 ]   ]
];
shape.vertices.valid.texture ← TRUE;
G3dRender.RenderDataFrom[shape].patch ← NIL;    -- force patches to be rebuilt
};
Procedures for Reading and Preparing Texture Files
TextureFromAIS: PUBLIC PROC[context: Context, fileName: Rope.ROPE,
          type: ATOM ← $Intensity, factor: REAL ← 1.0]
      RETURNS[texture: REF TextureMap] ~ {
width, height: INTEGER ← 512;
displayMode: DisplayMode;
bufContext: Context;
Previously used texture?
FOR i: NAT IN [0..context.shapes.length) DO
textureList: LORA ← G3dRender.ShadingClassFrom[context.shapes[i]].texture;
IF textureList # NIL THEN
FOR txtrList: LORA ← textureList, txtrList.rest UNTIL txtrList = NIL DO
WITH txtrList.first SELECT FROM
texture: REF TextureMap => IF texture.type = type AND Rope.Equal[
fileName,
NARROW[Atom.GetPropFromList[texture.props, $FileName], Rope.ROPE],
FALSE
]
THEN RETURN[texture]; -- if matched return texture map (may be summed)
ENDCASE;
ENDLOOP;
ENDLOOP;
Create context and load texture into it
displayMode ← IF type = $Color OR type = $ColorAndTransmittance
THEN fullColor
ELSE gray;
bufContext ← G3dRender.CreateUndisplayedContext[displayMode: displayMode, width: width, height: height];
IF type = $ColorAndTransmittance THEN G3dRenderWithPixels.AntiAliasing[bufContext];
bufContext.props ← context.props;      -- bring along working directory, etc.
[width, height] ← G3dColorDisplaySupport.GetAIS[    -- load pixelbuffer with AIS bits
    context: bufContext, fileRoot: fileName, center: FALSE];
IF width > bufContext.viewPort.w OR height > bufContext.viewPort.h THEN {
bufContext.viewPort^ ← [0.0, 0.0, Real.Fix[width], Real.Fix[height]];
G3dRenderWithPixels.AllocatePixelMemory[bufContext];    -- get bigger pixel buffer
[width, height] ← G3dColorDisplaySupport.GetAIS[         -- load it up
          context: bufContext, fileRoot: fileName, center: FALSE];
};
context.props ← Atom.RemPropFromList[context.props, $TempPixels]; -- GetAIS sideffect
bufContext.pixels.box.max.f ← width; bufContext.pixels.box.max.s ← MAX[height, 512];
bufContext.pixels.box.min.s ← MAX[0, 512 - height]; -- getAIS pushes image up against max
texture ← NEW[TextureMap];  -- now, gin up TextureMap, using context.pixels
texture.pixels ← bufContext.pixels;
bufContext.pixels ← NIL;           -- to help garbage collector?
bufContext.props ← Atom.RemPropFromList[bufContext.props, $FullDisplayMemory];
texture.type ← type;
texture.props ← PutProp[texture.props, $FileName, fileName];
IF type = $Bump
THEN texture.props ← PutProp[texture.props, $BumpScale, NEW[REAL ← factor]];
};
SumMappedTexture: PUBLIC PROC[texture: REF TextureMap] ~ {
SumOneLine: PROC[ line: ImagerSample.SampleBuffer, sumMap: REF SumSequence, y: NAT ]
     RETURNS[REF SumSequence] ~ {
IF sumMap = NIL THEN sumMap ← NEW[ SumSequence[box.max.s - box.min.s] ];
IF sumMap[y] = NIL THEN sumMap[y] ← NEW[ IntSequenceRep[box.max.f] ];
sumMap[y].length ← box.max.f;
FOR x: NAT IN [0..line.length) DO
sumMap[y][x] ← INT32[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];
};
pixels: ImagerPixel.PixelMap;
box: Box;
scanLine: ImagerPixel.PixelBuffer;
summedTexture: REF SummedTexture;
IF texture = NIL THEN SIGNAL G3dRender.Error[$MisMatch, "No texture to Sum"];
WITH texture.pixels SELECT FROM
pxMap: ImagerPixel.PixelMap => pixels ← pxMap;
summed: REF SummedTexture => RETURN;   -- previously summed
ENDCASE => SIGNAL G3dRender.Error[$MisMatch, "Unknown texture pixel type"];
box ← pixels.box;
scanLine ← ImagerPixel.ObtainScratchPixels[
pixels.samplesPerPixel, box.max.f
];
summedTexture ← NEW[ SummedTexture[pixels.samplesPerPixel] ];
FOR y: NAT IN [box.min.s..box.max.s) DO
ImagerPixel.GetPixels[self: pixels, pixels: scanLine, initIndex: [f: 0, s: y], count: box.max.f];
FOR i: NAT IN [0..pixels.samplesPerPixel) DO
summedTexture[i] ← SumOneLine[ scanLine[i], summedTexture[i], y-box.min.s ];
ENDLOOP;
ENDLOOP;
texture.pixels ← summedTexture;
ImagerPixel.ReleaseScratchPixels[scanLine];
};
Init[];
END.