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 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.]; }; 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; 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 Init: PROC[] ~ { txtrShadingClass: ShadingClass _ [ type: $G3dMappedAndSolidTexture, cnvrtVtx: GetLerpedVals, getColor: RecoverColor, shadeVtx: G3dShade.ShadeVtx ]; G3dShade.RegisterShadingClass[txtrShadingClass, $G3dMappedAndSolidTexture]; RegisterTextureFunction[ $Spots, Spots ]; RegisterTextureFunction[ $Wurlitzer, Wurlitzer ]; RegisterTextureFunction[ $TwistedStripes, TwistedStripes ]; RegisterTextureFunction[ $BurlWood, BurlWood ]; RegisterTextureFunction[ $ZebraBurl, ZebraBurl ]; RegisterTextureFunction[ $Marble, Marble ]; }; CheckAndAddProcs: PUBLIC PROC[shape: Shape ] ~ { 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] ~ { 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; }; GetLerpedVals: G3dRender.CtlPtToRealSeqProc ~ { maxLength: NAT _ IF 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 ~ { IF shading.texture # NIL THEN GetTxtrAt[context, shading, spot]; IF shading.texture # NIL OR spot.val.length > 15 THEN G3dRenderWithPixels.ShadeSpot[context, shading, spot]; }; AdjustTexture: PUBLIC PROC[poly: REF Patch, texture: LORA, txtrRange: Pair] ~ { mappedTexture: BOOLEAN _ FALSE; halfXRange: REAL _ txtrRange.x / 2.0; halfYRange: REAL _ txtrRange.y / 2.0; FOR txtrList: LORA _ NARROW[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; }; }; GetTxtrAt: PUBLIC SpotProc ~ { 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: REAL _ IF 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; 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 maxXIncr, maxYIncr, area, length: REAL; txtrXIncr: Pair _ [spot.xIncr[txtrX], spot.xIncr[txtrY]]; txtrYIncr: Pair _ [-txtrXIncr.y, txtrXIncr.x]; -- rotate txtrXIncr 90 deg. 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]]; }; 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; 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]; 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]; 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"]; }; GetLerpedValue: PROC[ llVal, ulVal, urVal, lrVal: INT, xPos, yPos: REAL ] RETURNS[ intPart: INT, fracPart: REAL ] ~ { 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; }; 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 ~ { 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 ~ { 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 ~ { 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: REAL _ ABS[ RealFns.Sin[midBrown] ]; greenLayer: REAL _ - brownLayer; perturb: REAL _ IF 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: REAL _ IF 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 ~ { 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] ~ { 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] ~ { RETURN [x * x * (3 - 2 * x)]; }; m: NAT; ix, iy, iz: INT; x, y, z, jx, jy, jz, sx, sy, sz, tx, ty, tz, s, f: REAL; 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; }; x _ vx + 1000.; y _ vy + 1000.; z _ vz + 1000.; ix _ Real.Fix[x]; iy _ Real.Fix[y]; iz _ Real.Fix[z]; sx _ SCurve[x - ix]; sy _ SCurve[y - iy]; sz _ SCurve[z - iz]; 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; 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]; }; GetRope: PROC[input: IO.STREAM] RETURNS[ROPE] ~ { output: ROPE _ NIL; 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 ~ { input: IO.STREAM _ NARROW[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 ~ { input: IO.STREAM _ NARROW[data]; AddSolidTexture[ context, shape.name, IO.GetAtom[input] ]; RETURN[ shape ]; }; 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.] ] ~ { 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 { 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; 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; 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 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); 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]; 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]; 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 }; 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; 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; 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. ˜G3dMappedAndSolidTextureImpl.mesa Copyright c 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 Internal Declarations Renamed Procedures Global Variables Initialization Register solid texture procs Procedures for Controlling Texture Update the shadingClass with texture procs Append a mapped texture to the shape's texture list Procedures for VtxToRealSeq and computing color at a spot PROC[dest: RealSequence, source: CtlPtInfo, data: REF ANY] RTRNS[REF RealSequence]; PROC[context: Context, shading: REF ShadingClass, spot: REF Spot] Support Procedures for setting up texture for tiler 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. Procedures for Evaluating Texture at a Spot PROC[context: Context, shading: REF ShadingClass, spot: REF Spot] Increments in x and y This section could be executed once per scan segment txtrXIncr is vector for texture change to next pixel on scanline txtrYIncr is vector for texture change to corresponding pixel on scanline above scale txtrYIncr to projected length of texture increment along leading polygon edge Find max size of texture offsets to adjacent pixels to estimate texture spot spread 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) perturb in x (rotate about y) perturb in y (rotate about x) Support Procedures for Summed Textures lowerValue: REAL _ llVal + xPos * (lrVal - llVal); -- if we only had double precision!! upperValue: REAL _ ulVal + xPos * (urVal - ulVal); RETURN [ lowerValue + yPos * (upperValue - lowerValue) ]; Procedures for solid texture calculation Regular array of dark spots Wurlitzer colors, stripes in 3-d Rotating stripes (barber pole) Perlin's marble texture returns band limited noise over R3. map the unit interval into an "S shaped" cubic f[x] | f[0]=0, f'[0]=0, f[1]=1, f'[1]=0. declare local variables. initialize random gradient table Force everything to be positive ixyz _ the integer lattice point "just below" v (identifies the surrounding unit cube). sxyz _ the vector difference v - ixyz biased with an S-Curve in each dimension. txyz _ the complementary set of S-Curves in each dimension. Add in each weighted component Procedures for Recovering Texture Descriptions from streams Gets set of characters bound by white space PROC[context: Context, shape: Shape, data: REF ANY _ NIL] RETURNS[Shape]; PROC[context: Context, shape: Shape, data: REF ANY _ NIL] RETURNS[Shape]; Procedures for Computing Texture Coordinates 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 Corners (see diagram in G3dStandardPatchProcs.PolygonFromBezierPatch) Inner control points on boundaries Central control points Stretch as indicated by corner coordinates - interpolate across rows Interpolate along row Map from sphere to Cartesian coordinates 1st quadrant 0 - 1 range. Map polar coordinates into quadrilateral given by sw, nw, ne, se Procedures for Reading and Preparing Texture Files Previously used texture? Create context and load texture into it Κ&O˜™!Jšœ Οmœ7™BJ™3Icode™*J˜JšΟk œΫžœ%˜‹—head2šΟnœžœž˜+Iašžœžœ%˜½Mšžœ˜!J˜Jšœžœ˜—headšΠbl™Jšžœžœžœ˜Mšœ žœ˜%Jšžœžœ žœ˜Mšœ žœ˜!Mšœ žœ˜(Mšœ žœΟc˜8Jšœžœ˜.Mšœ žœ‘˜šžœžœ˜š œ žœžœžœžœ˜#Ošœ žœU˜dOšœžœ#˜4Ošžœ˜—˜Jšœ žœ˜$šœ žœ ˜J˜:J˜ —Ošœžœ‘˜Gšžœžœžœžœ˜šœ žœ ˜O˜:O˜ —Ošœ9‘œ4‘˜₯Ošœžœ˜%Ošžœ˜—Ošœ+‘˜EO˜—˜Ošœ žœU˜bJšœ žœ˜$Ošœ žœU˜dOšœ žœ˜Ošœžœ‘˜Fšžœžœžœžœ˜Ošœ:‘œ4‘˜§Ošœžœ˜%Ošžœ˜—Ošœ+‘˜EO˜—˜Ošœ žœU˜dOšžœžœ˜(šžœžœžœžœ˜Ošœžœ#˜4Ošžœ˜—O˜—Jšžœžœ4˜F—J˜—š Ÿ œžœžœžœžœ˜ROšœžœ˜!Ošœžœ ˜Jšœžœžœžœ ˜@Jšœ žœ‘(˜RJšœ žœ‘)œ˜RJ™4šœ"žœ˜'J™@—˜9J™O—šœ3‘˜NJ™S—šœžœ˜J˜NJ˜—Jšœ#žœ˜.šžœžœ‘,˜IJ˜:J˜DJ˜BJ˜J™S—Jšœžœžœžœ˜;Jšœžœžœžœ˜;˜6J™TJ™g—J˜%JšžœžœA˜^J˜%Jšžœžœ@˜]šžœžœ˜˜Ošœ7žœ˜˜‹Jšœžœ˜J˜$J˜#J˜*J˜&J˜.J˜&J˜—šŸœ˜Jšœžœžœžœ‘˜XJš œžœ žœ žœ žœ˜8Jšœžœ˜Jšœžœ3˜>Jšœ žœ<˜JJšœ žœžœ˜0Jšœ žœ˜ J˜šœ žœžœ˜$Jšžœžœ+˜3Jšžœžœ,˜4—Jšœžœ‘˜Jšœžœ‘œ˜?J˜IJ˜IJ˜6J˜7Jšœ žœ'˜6J˜ J˜ J˜ J˜—šŸ œ˜Jšœžœžœžœ‘˜XJš œžœ žœ žœ žœ˜8Jšœžœ˜Jšœžœ3˜>Jšœ žœ<˜JJšœ žœ˜)Jšœ žœ˜ J˜šœ žœžœ˜$Jšžœžœ+˜3Jšžœžœ,˜4—Jšœžœ‘˜Jšœžœ‘œ˜?J˜IJ˜IJ˜6J˜7Jšœ žœ'˜6J˜ J˜ J˜ J˜—š’œ˜J™Jšœžœžœžœ‘˜XJš œžœ žœ žœ žœ˜8Jšœ žœh˜wJ˜$J˜+J˜&J˜&J˜&J˜—š Ÿœžœžœ žœžœžœ˜4Jšœžœ˜ Jšœžœ˜šžœžœžœž˜J˜ Jšœžœ˜J˜ Jšžœ˜—Jšžœ˜ J˜—J˜Kšœ žœ žœžœ˜'Kš œžœžœžœ žœžœžœ˜4šœžœ žœ˜K˜—š ŸœžΟsžœ žœžœžœ˜7šœ žœ™#J˜š Ÿœžœ žœžœžœžœ˜5Jš œžœžœžœžœ˜K˜/Kšœ žœžœžœ˜!Kšœžœ$žœžœ ˜OKšžœ˜ J˜—š Ÿœžœžœžœžœ˜(™WJ˜Jšžœ˜—˜J˜——™Jšœžœ˜Jšœ žœ˜Jšœ3žœ˜8—™ šžœ žœžœ˜Jšœ žœ˜šžœžœžœ ž˜Jšœžœ˜ J˜(Jšžœ˜—J˜——™J˜J˜J˜—šœΟdœS™WJ˜J˜J˜—šœ€œ€œ*™OJ˜J˜J˜—šœ€œ7™;J˜ J˜ J˜ —Jšœ‘˜"š žœžœžœžœ‘<˜Tšžœžœ‘2˜@J˜:J˜.J˜5J˜.J˜;J˜.J˜4J˜-Jšžœ˜—™Jšœžœ˜J˜_—Jšžœ˜—Jšžœ˜ —J˜——š ;™;š Ÿœžœžœžœžœžœ‘˜2Jš‘+™+Jšœžœžœ˜Jšœžœ˜ Jšœžœ‘ ˜EJšœžœ˜š žœžœžœžœžœžœ‘ ˜KJ˜1Jšœžœž˜Jšžœ˜—Jšžœ ˜J˜—šŸœžœžœ˜6˜#O˜O˜Ošžœ"˜%O˜—˜#O˜O˜Ošžœ'˜*O˜—O˜—šŸœ˜#Jš žœ'žœžœžœžœ™IOšœžœžœžœ˜ šœ žœ0˜˜O—Ošœ‘˜-Ošœ%žœžœžœ˜^Ošžœ ˜O˜—šŸœ˜(Jš žœ'žœžœžœžœ™IOšœžœžœžœ˜ Ošœ&žœ˜:Ošžœ ˜O˜——š ,™,šŸœžœžœ˜O˜Ošœžœ˜Ošœžœ˜ Ošœžœ˜O˜J˜7J˜<šžœžœžœ˜/Jšœ&‘(˜NJ˜%J˜—šžœžœžœžœ˜,J˜GJ˜GOšžœ˜—K˜+O˜—šŸœž œ˜$O˜Ošœ žœ˜O˜bO˜O™¦O˜7Ošœ žœ0˜?O˜šžœžœžœž˜.Ošžœ4‘"˜Z—O˜šžœ ž˜š œžœžœžœžœ˜<šžœžœ˜O˜LO˜LO˜LO˜LO˜—Ošžœ˜—š œ žœžœžœžœ˜.šžœžœ˜O™FO˜LO˜LO˜L˜LO™"—O˜BO˜?O˜BO˜?O˜BO˜?O˜BO˜?O˜BO˜?O˜BO˜?O˜BO˜?O˜B˜?O™—O˜]O˜ZO˜]O˜ZO˜]O˜ZO˜]O˜ZO˜—Ošžœ˜—Ošžœžœ7˜H—O˜—Oš Ÿœžœžœ$žœ,žœ˜}˜OJ˜9Jšœ žœ.˜=Jšœžœžœžœ˜šžœ˜ JšžœžœH˜S—šžœ0žœ,žœ/žœ,˜ΏJšžœžœA˜L—šžœžœžœžœ˜,Jšœžœ˜!Jšœ+žœ‘˜YšœH‘˜[J™D—J˜NJ˜OJ˜M˜NJš‘™—J˜VJ˜TJšžœ˜ —˜#J˜4J˜—Jšœžœ žœžœ˜BJšœžœžœ˜BJšœžœžœ˜CJšœžœžœ˜DJšœžœ-žœ˜[J˜T˜#J˜J˜Jšžœ žœ;žœ=˜ŒJ˜—Jšœžœ˜$Jšœ(žœ‘˜NJ˜ —JšŸœžœžœ$žœ˜P˜ΕJ˜9Jšœ žœ.˜=Jšœžœžœžœ˜Jšœžœ ˜Jšœžœ˜.Jšœ žœ˜Jšœ žœ‘0˜MJ˜šžœ˜ Jšž œI˜T—Jšžœžœžœ,˜Sšžœžœžœ!˜EJšžœžœC˜N—šžœžœ"˜(Jšžœžœ‘˜Ušžœžœžœ!˜,Jšžœžœ‘˜T——J˜Jšžœžœžœ*‘˜ZJšžœžœžœ*˜KJšžœžœžœ*˜KJšžœžœžœ+˜Kš žœžœžœžœ‘˜EJ˜!J™BJšœ žœ7˜GJšœ žœ1˜@šœ žœ/˜=J™A—Jšœžœ&‘&˜WJšœžœ&˜1Jšœžœ#‘ ˜NJšœžœ ˜+šžœ˜!Jšžœ*˜.šžœžœ˜&Jšžœ)˜-——Jšœ8‘˜LJšœ9‘ ˜EJšœžœžœ˜3Jšœžœžœ˜3Jšžœžœ˜:Jšžœžœ˜:Jšžœžœ‘˜Sšžœ˜J˜——š žœžœžœžœ‘#˜PJ˜!Jšžœžœ,˜JJšžœ˜ —J˜*š žœžœžœžœ‘"˜PJ˜!Jšœžœ:˜EJšœžœ:˜EJšœžœ:˜EJšœžœ;˜FJ˜9J˜8Jšžœžœ˜:Jšžœžœ˜:Jšžœ˜ —J˜*J˜+š žœžœžœžœ‘˜DJ˜!J˜*J˜*Jšžœ˜ —˜#J˜5J˜—Jšœžœ žœžœ˜CJšœžœžœ˜CJšœžœ žœ˜CJšœžœ žœ˜DJšœžœžœ˜5Jšœžœžœ˜5Jšœžœžœ˜5Jšœžœžœ˜5J˜T˜#J˜J˜Jšžœ žœ9žœ;˜ˆJ˜—Jšœžœ˜$Jšœ(žœ‘˜NJ˜——š 2™2šŸœžœžœ"žœžœžœžœ žœ˜§Mšœžœ˜Jšœ˜J˜J™šžœžœžœž˜+Jšœ žœ9˜Jšžœžœžœ˜š žœ žœžœ žœž˜Hšžœžœž˜šœ žœžœžœ ˜AJ˜ Jšžœ6žœ˜BJšž˜J˜Jšžœžœ ‘0˜F—Jšžœ˜ —Jšžœ˜——Jšžœ˜—J™'šœžœžœ˜@Mšžœ ˜Mšžœ˜ —Mšœh˜hJšžœžœ.˜SJšœ'‘&˜MJšœ4‘!œ6žœ˜’šžœžœ žœ˜IM˜EMšœ8‘˜RJšœ9‘ œ<žœ˜‰J˜—MšœB‘˜UJšœFžœ˜WJšœžœ‘%˜[Jšœ žœ‘/˜KJ˜$Jšœžœ ‘˜@MšœO˜OJ˜J˜<šžœ˜Jšžœ4žœžœ ˜L—J˜J˜—šŸœžœžœ žœ˜;š Ÿ œžœ+žœžœ žœžœ˜wJšžœ žœžœ žœ(˜IJšžœ žœžœ žœ˜EJ˜J˜šžœžœžœž˜!Jšœžœ ˜šžœžœ˜Jšœ0‘˜Bšžœžœ‘@˜PJ˜I—J˜—šžœžœžœ˜Jšœ1‘˜A—Jšžœ˜—Jšžœ ˜J˜—J˜J˜ J˜"Ošœžœ˜!O˜Ošžœ žœžœžœ1˜Mšžœžœž˜J˜.Jšœžœžœ‘˜;Jšžœžœ:˜K—J˜˜+M˜!M˜—Ošœžœ+˜>šžœžœžœžœ˜)O˜ašžœžœžœž˜,O˜NOšžœ˜—Ošžœ˜—J˜J˜+J˜J˜——J˜Jšžœ˜—…—±†ζm