DIRECTORY Atom USING [ GetPropFromList, PropList, PutPropOnList, RemPropFromList ], List USING [ Nconc1 ], Rope USING [ ROPE, Equal ], Real USING [ Fix, Float, LargestNumber ], RealFns USING [ ArcTanDeg, Sin, Cos, Power, SqRt ], Checksum USING [ ComputeChecksum ], IO USING [ GetAtom, GetInt, GetReal, STREAM ], ImagerPixel USING [ GetPixels, ObtainScratchPixels, PixelBuffer, PixelMap, ReleaseScratchPixels ], ImagerSample USING [ SampleBuffer ], Vector2 USING [ Dot, Length, Mul, Unit ], G3dVector USING [ Cross, Normalize ], ScanConvert USING [ justNoticeable ], ThreeDBasics USING [ Box, Context, Error, IntSequence, LoadShadingClass, Pair, PairSequence, Patch, PtrPatchSequence, RealSequence, Rectangle, RegisterShadingClass, RGB, ShadingClass, ShapeClass, ShapeInstance, ShapeProc, Spot, SpotProc, SummedTexture, SumSequence, TextureFunction, TextureMap, Triple, TripleSequence, VertexInfo, VertexInfoProc, VertexInfoSequence, VtxToRealSeqProc ], RenderWithPixels USING [ AntiAliasing, GetContext, AllocatePixelMemory, ShadeSpot ], SurfaceRender USING [ ValidateContext ], ShapeUtilities USING [ ShadeVtx ], SceneUtilities USING [ FindShape, GetRope ], AISAnimation USING [ GetAIS ], MappedAndSolidTexture USING [ ]; MappedAndSolidTextureImpl: CEDAR PROGRAM IMPORTS AISAnimation, Atom, Checksum, G3dVector, ImagerPixel, IO, List, Real, RealFns, RenderWithPixels, Rope, SceneUtilities, ShapeUtilities, SurfaceRender, ThreeDBasics, Vector2 EXPORTS MappedAndSolidTexture = BEGIN Context: TYPE ~ ThreeDBasics.Context; RGB: TYPE ~ ThreeDBasics.RGB; Box: TYPE ~ ThreeDBasics.Box; Rectangle: TYPE ~ ThreeDBasics.Rectangle; Pair: TYPE ~ ThreeDBasics.Pair; -- [ x, y: REAL]; PairSequence: TYPE ~ ThreeDBasics.PairSequence; Triple: TYPE ~ ThreeDBasics.Triple; -- [ x, y, z: REAL] TripleSequence: TYPE ~ ThreeDBasics.TripleSequence; VertexInfo: TYPE ~ ThreeDBasics.VertexInfo; VertexInfoSequence: TYPE ~ ThreeDBasics.VertexInfoSequence; IntSequence: TYPE ~ ThreeDBasics.IntSequence; RealSequence: TYPE ~ ThreeDBasics.RealSequence; TextureFunction: TYPE ~ ThreeDBasics.TextureFunction; TextureMap: TYPE ~ ThreeDBasics.TextureMap; SumSequence: TYPE ~ ThreeDBasics.SumSequence; SummedTexture: TYPE ~ ThreeDBasics.SummedTexture; Patch: TYPE ~ ThreeDBasics.Patch; Spot: TYPE ~ ThreeDBasics.Spot; SpotProc: TYPE ~ ThreeDBasics.SpotProc; ShadingClass: TYPE ~ ThreeDBasics.ShadingClass; ShapeInstance: TYPE ~ ThreeDBasics.ShapeInstance; ShapeProc: TYPE ~ ThreeDBasics.ShapeProc; 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 ~ ScanConvert.justNoticeable; -- 0.02 registeredTextureFunctions: Atom.PropList _ NIL; -- keeps active solid texture functions defaultAuxiliaryData: REF Pair _ NEW[Pair _ [0.0, 0.0]]; maxTxtrRange: REAL _ 32.0; -- texture coordinate range, small numbers expected Init: PROC[] ~ { txtrShadingClass: ShadingClass _ [ type: $MappedAndSolidTexture, cnvrtVtx: GetLerpedVals, getColor: RecoverColor, loadShapeAux: LoadShapeAux, loadVtxAux: LoadVtxAux, lerpVtxAux: LerpVtxAux, shadeVtx: ShapeUtilities.ShadeVtx ]; ThreeDBasics.RegisterShadingClass[txtrShadingClass, $MappedAndSolidTexture]; RegisterTextureFunction[ $Spots, Spots ]; RegisterTextureFunction[ $Wurlitzer, Wurlitzer ]; RegisterTextureFunction[ $TwistedStripes, TwistedStripes ]; RegisterTextureFunction[ $BurlWood, BurlWood ]; RegisterTextureFunction[ $ZebraBurl, ZebraBurl ]; RegisterTextureFunction[ $Marble, Marble ]; }; CheckAndAddProcs: PUBLIC PROC[shape: REF ShapeInstance ] ~ { IF shape.shadingClass = NIL THEN ThreeDBasics.LoadShadingClass[shape, $MappedAndSolidTexture] ELSE IF shape.shadingClass.type # $MappedAndSolidTexture THEN { shape.shadingClass.type _ $MappedAndSolidTexture; shape.shadingClass.cnvrtVtx _ GetLerpedVals; shape.shadingClass.getColor _ RecoverColor; shape.shadingClass.loadShapeAux _ LoadShapeAux; shape.shadingClass.loadVtxAux _ LoadVtxAux; shape.shadingClass.lerpVtxAux _ LerpVtxAux; shape.shadingClass.shadeVtx _ ShapeUtilities.ShadeVtx; }; }; AddSolidTexture: PUBLIC PROC[context: REF Context, shapeName: Rope.ROPE, name: ATOM ] ~{ shape: REF ShapeInstance _ SceneUtilities.FindShape[ context, shapeName ]; txtrFn: TextureFunction _ GetRegisteredTextureFunction[name]; txtrFn.props _ PutProp[txtrFn.props, $Shape, shape]; CheckAndAddProcs[shape]; shape.shadingClass.texture _ List.Nconc1[ -- append to existing list shape.shadingClass.texture, NEW[TextureFunction _ txtrFn] ]; }; AddMappedTexture: PUBLIC PROC[context: REF Context, shapeName: Rope.ROPE, texture: REF TextureMap] ~ { shape: REF ShapeInstance _ SceneUtilities.FindShape[ context, shapeName ]; CheckAndAddProcs[shape]; shape.shadingClass.texture _ List.Nconc1[ shape.shadingClass.texture, texture ]; -- append }; SumAllMappedTextures: PUBLIC PROC[context: REF Context, shapeName: Rope.ROPE] ~ { shape: REF ShapeInstance _ SceneUtilities.FindShape[ context, shapeName ]; texture: LIST OF REF ANY _ shape.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: REF Context, shapeName: Rope.ROPE] ~ { shape: REF ShapeInstance _ SceneUtilities.FindShape[ context, shapeName ]; shape.shadingClass.texture _ NIL; }; GetLerpedVals: ThreeDBasics.VtxToRealSeqProc ~ { maxLength: NAT _ IF data = $PixelShading THEN 16 ELSE 10; sourceAux: REF Pair _ NARROW[ source.aux ]; IF sourceAux = NIL THEN sourceAux _ defaultAuxiliaryData; IF dest = NIL OR dest.maxLength < maxLength THEN dest _ NEW[RealSequence[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] _ sourceAux.x; dest[12] _ sourceAux.y; 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] _ sourceAux.x; dest[6] _ sourceAux.y; 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 RenderWithPixels.ShadeSpot[context, shading, spot]; }; LoadShapeAux: PUBLIC ShapeProc ~ { -- load aux field in vtx xMin, yMin, xRng, yRng: REAL _ 0.5; auxInfo: REF PairSequence _ NEW[ PairSequence[shape.shade.length] ]; WITH data SELECT FROM vtces: REF VertexInfoSequence => { FOR i: NAT IN [0..shape.shade.length) DO auxInfo[i] _ NARROW[vtces[i].aux, REF Pair]^; ENDLOOP; auxInfo.length _ shape.shade.length; }; pairs: REF PairSequence => auxInfo _ pairs; ENDCASE => SIGNAL ThreeDBasics.Error[[$Unimplemented, "Unrecognized type"]]; FOR i: NAT IN [0..shape.shade.length) DO IF i = 0 THEN { xRng _ xMin _ auxInfo[i].x; yRng _ yMin _ auxInfo[i].y; } ELSE { IF xRng < auxInfo[i].x THEN xRng _ auxInfo[i].x; IF yRng < auxInfo[i].y THEN yRng _ auxInfo[i].y; IF xMin > auxInfo[i].x THEN xMin _ auxInfo[i].x; IF yMin > auxInfo[i].y THEN yMin _ auxInfo[i].y; }; ENDLOOP; xRng _ xRng - xMin; yRng _ yRng - yMin; shape.shadingProps _ PutProp[ shape.shadingProps, $TxtrCoordRange, NEW[ Pair _ [xRng, yRng ] ] ]; IF xRng > maxTxtrRange OR yRng > maxTxtrRange THEN SIGNAL ThreeDBasics.Error[[$MisMatch, "Suspiciously big texture coords"]]; shape.shadingProps _ PutProp[ shape.shadingProps, $AuxiliaryVtxData, auxInfo]; RETURN[ shape ]; }; LoadVtxAux: PUBLIC ThreeDBasics.VertexInfoProc ~ { -- load aux field in vtx vtxAux: REF Pair _ NEW[Pair]; WITH data SELECT FROM input: LORA => { auxInfo: REF PairSequence _ NARROW[ input.first ]; index: INTEGER _ NARROW[ input.rest.first, REF INTEGER ]^; vtxAux^ _ auxInfo[index]; -- texture coords }; txtr: REF Pair => { vtxAux.x _ txtr.x; vtxAux.y _ txtr.y; }; ENDCASE => SIGNAL ThreeDBasics.Error[[$Unimplemented, "Unrecognized type"]]; vtx.aux _ vtxAux; RETURN[ vtx ]; }; LerpVtxAux: PUBLIC ThreeDBasics.VertexInfoProc ~ { -- linear interpolation for texture coords input: LORA _ NARROW[data]; vtxAux: REF Pair _ NEW[Pair]; vtxa: REF Pair _ NARROW[ input.first ]; vtxb: REF Pair _ NARROW[ input.rest.first ]; a: REAL _ NARROW[input.rest.rest.first, REF REAL]^; b: REAL _ NARROW[input.rest.rest.rest.first, REF REAL]^; vtxAux.x _ vtxa.x*a + vtxb.x*b; vtxAux.y _ vtxa.y*a + vtxb.y*b; vtx.aux _ vtxAux; RETURN[ vtx ]; }; AdjustTexture: PUBLIC PROC[poly: REF Patch, texture: LORA, halfXRange, halfYRange: REAL _ .5] ~ { mappedTexture: BOOLEAN _ FALSE; FOR txtrList: LORA _ NARROW[texture], txtrList.rest UNTIL txtrList = NIL DO WITH txtrList.first SELECT FROM -- look for mapped texture texture: REF ThreeDBasics.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 txtr: REF Pair _ NARROW[poly[i].aux]; IF i = 0 THEN { maxXtxtr _ minXtxtr _ txtr.x; maxYtxtr _ minYtxtr _ txtr.y; } ELSE { IF maxXtxtr < txtr.x THEN maxXtxtr _ txtr.x; IF maxYtxtr < txtr.y THEN maxYtxtr _ txtr.y; IF minXtxtr > txtr.x THEN minXtxtr _ txtr.x; IF minYtxtr > txtr.y THEN minYtxtr _ txtr.y; }; ENDLOOP; IF maxXtxtr - minXtxtr > halfXRange THEN { -- seam in x FOR i: CARDINAL IN [0..poly.nVtces) DO -- push small ones beyond maximum txtr: REF Pair _ NARROW[poly[i].aux]; WHILE maxXtxtr - txtr.x > halfXRange DO txtr.x _ txtr.x + 1.0; ENDLOOP; ENDLOOP; minXtxtr _ maxXtxtr; FOR i: CARDINAL IN [0..poly.nVtces) DO -- find minimum txtr: REF Pair _ NARROW[poly[i].aux]; IF minXtxtr > txtr.x THEN minXtxtr _ txtr.x; ENDLOOP; }; IF maxYtxtr - minYtxtr > halfYRange THEN { -- seam in y FOR i: CARDINAL IN [0..poly.nVtces) DO -- push small ones beyond maximum txtr: REF Pair _ NARROW[poly[i].aux]; WHILE maxYtxtr - txtr.y > halfYRange DO txtr.y _ txtr.y + 1.0; ENDLOOP; ENDLOOP; minYtxtr _ maxYtxtr; FOR i: CARDINAL IN [0..poly.nVtces) DO -- find minimum txtr: REF Pair _ NARROW[poly[i].aux]; IF minYtxtr > txtr.y THEN minYtxtr _ txtr.y; ENDLOOP; }; minXtxtr _ Real.Float[Real.Fix[minXtxtr]]; minYtxtr _ Real.Float[Real.Fix[minYtxtr]]; FOR i: CARDINAL IN [0..poly.nVtces) DO -- adjust everything to minima under 1.0 txtr: REF Pair _ NARROW[poly[i].aux]; txtr.x _ txtr.x - minXtxtr; txtr.y _ txtr.y - minYtxtr; ENDLOOP; }; }; GetTxtrAt: PUBLIC SpotProc ~ { texture: LORA _ shading.texture; 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 ThreeDBasics.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 ThreeDBasics.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 ThreeDBasics.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; WHILE spot.val[txtrX] - txtrXIncr < 0.0 DO spot.val[txtrX] _ spot.val[txtrX] + 1.0; ENDLOOP; WHILE spot.val[txtrY] - txtrYIncr < 0.0 DO spot.val[txtrY] _ spot.val[txtrY] + 1.0; ENDLOOP; SELECT 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; IF spot.xySwapped THEN txtrValue _ 0.5 * txtrValue; FOR i: NAT IN [0..3) DO spot.val[i] _ MAX[ 0.0, spot.val[i] * txtrValue ]; ENDLOOP; }; ENDCASE => SIGNAL ThreeDBasics.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; Positivize: PROC[] ~ { WHILE spot.val[txtrX] - maxXIncr < 0.0 DO spot.val[txtrX] _ spot.val[txtrX] + 1.0; ENDLOOP; WHILE spot.val[txtrY] - maxYIncr < 0.0 DO spot.val[txtrY] _ spot.val[txtrY] + 1.0; ENDLOOP; }; 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; Positivize[]; -- ensure positive texture coordinate values 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; Positivize[]; 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; Positivize[]; 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.Normalize[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.Normalize[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.Normalize[tnml]; spot.val[nmlX] _ nml.x; spot.val[nmlY] _ nml.y; spot.val[nmlZ] _ nml.z; }; ENDCASE => SIGNAL ThreeDBasics.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; }; [ 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 ThreeDBasics.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]; }; EnableStreamProcs: PUBLIC PROC[ context: REF 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: SceneUtilities.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 ThreeDBasics.Error[ [$Unimplemented, "Unknown texture coordType"] ]; [] _ SceneUtilities.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: REF Context, shapeName: Rope.ROPE, scale: REAL, xRatio, yRatio: REAL _ 1.0 ] ~ { xScale, yScale: REAL; shape: REF ShapeInstance _ SceneUtilities.FindShape[ context, shapeName ]; auxInfo: REF PairSequence _ NARROW[GetProp[ shape.shadingProps, $AuxiliaryVtxData]]; refTriple: REF Triple _ NARROW[GetProp[shape.shadingProps, $TextureScale]]; IF refTriple = NIL THEN refTriple _ NEW[Triple _ [1.0, 1.0, 1.0]]; IF refTriple.x # 0.0 AND refTriple.y # 0.0 AND refTriple.z # 0.0 THEN { xScale _ scale*xRatio/(refTriple.x*refTriple.y); -- multiply by new, divide by old values yScale _ scale*yRatio/(refTriple.x*refTriple.z); }; refTriple^ _ [scale, xRatio, yRatio]; FOR i: NAT IN [0..auxInfo.length) DO auxInfo[i].x _ auxInfo[i].x * xScale; auxInfo[i].y _ auxInfo[i].y * yScale; ENDLOOP; shape.shadingProps _ PutProp[shape.shadingProps, $TextureScale, refTriple]; }; MakeTxtrCoordsFromVtxNos: PUBLIC PROC[ context: REF Context, shapeName: Rope.ROPE, vtcesInRow, numberOfRows: NAT, row0col0, rowNcol0, rowNcolM, row0ColM: Pair] ~ { shape: REF ShapeInstance _ SceneUtilities.FindShape[ context, shapeName ]; auxInfo: REF PairSequence _ NARROW[GetProp[shape.shadingProps, $AuxiliaryVtxData]]; args: LIST OF REAL; IF GetProp[ shape.fixedProps, $VertexTextureInFile] # NIL THEN { SIGNAL ThreeDBasics.Error[[$MisMatch, "Overwriting original texture coords, OK?"]]; shape.shadingProps _ Atom.RemPropFromList[shape.shadingProps, $VertexTextureInFile]; }; IF auxInfo = NIL THEN auxInfo _ NEW[ PairSequence[shape.shade.length] ]; 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 ThreeDBasics.Error[[$MisMatch, "Texture mapping dangerously dense"]]; FOR i: NAT IN [0..shape.shade.length) DO lPosX, lPosY, rPosX, rPosY: REAL; auxInfo[i].x _ Real.Float[i MOD vtcesInRow] / vtcesInRow; -- pct along row auxInfo[i].y _ Real.Float[i / vtcesInRow] / numberOfRows; -- pct across rows lPosX _ row0col0.x + auxInfo[i].y * (rowNcol0.x - row0col0.x);-- interp across rows lPosY _ row0col0.y + auxInfo[i].y * (rowNcol0.y - row0col0.y); rPosX _ row0ColM.x + auxInfo[i].y * (rowNcolM.x - row0ColM.x); rPosY _ row0ColM.y + auxInfo[i].y * (rowNcolM.y - row0ColM.y); auxInfo[i].x _ lPosX + auxInfo[i].x * (rPosX - lPosX); -- interp along row auxInfo[i].y _ lPosY + auxInfo[i].x * (rPosY - lPosY); ENDLOOP; auxInfo.length _ shape.shade.length; shape.shadingProps _ PutProp[ shape.shadingProps, $AuxiliaryVtxData, auxInfo]; shape.shadingProps _ PutProp[ shape.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]; shape.shadingProps _ PutProp[ shape.shadingProps, $TxtrCoordParams, args]; }; GetTxtrCoordsFromNormal: PUBLIC PROC[ context: REF Context, vtx: VertexInfo ] ~ { }; MakeTxtrCoordsFromNormals: PUBLIC PROC[ context: REF 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: REF ShapeInstance _ SceneUtilities.FindShape[ context, shapeName ]; auxInfo: REF PairSequence _ NARROW[GetProp[shape.shadingProps, $AuxiliaryVtxData]]; args: LIST OF REAL; poly: REF ThreeDBasics.PtrPatchSequence _ NARROW[shape.surface]; polyTags: ARRAY [0..16) OF BOOLEAN; lngtShift: REAL _ 0.0; -- latitude shift to allow < -180.0 and > 180.0 IF GetProp[shape.fixedProps, $VertexTextureInFile] # NIL THEN { SIGNAL ThreeDBasics.Error[[$MisMatch, "Overwriting original texture coords, OK?"]]; shape.shadingProps _ Atom.RemPropFromList[shape.shadingProps, $VertexTextureInFile]; }; IF auxInfo = NIL THEN auxInfo _ NEW[ PairSequence[shape.shade.length] ]; IF GetProp[shape.shadingProps, $VtxInfoComputed] = NIL THEN { shape.shadingClass.shadingType _ $Smooth; -- smooth shading forces computed normals SurfaceRender.ValidateContext[context]; -- make sure viewport, etc. is kosher }; IF MAX[sw.x, nw.x, se.x, ne.x] - MIN[sw.x, nw.x, se.x, ne.x] > 360.0 THEN SIGNAL ThreeDBasics.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 polyNumber: NAT IN [0..poly.length) DO minTxtrX, minTxtrY: REAL _ Real.LargestNumber; maxTxtrX: REAL _ 0.; FOR i: NAT IN [0..poly[polyNumber].nVtces) DO vtx: NAT _ poly[polyNumber].vtxPtr[i]; hypotenuse: REAL _ RealFns.SqRt[Sqr[shape.shade[vtx].yn] + Sqr[shape.shade[vtx].xn]]; longitude: REAL _ RealFns.ArcTanDeg[shape.shade[vtx].yn, shape.shade[vtx].xn]; latitude: REAL _ RealFns.ArcTanDeg[shape.shade[vtx].zn, hypotenuse]; 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); auxInfo[vtx].x _ (longitude - lPosX) / (rPosX - lPosX); -- percentage across auxInfo[vtx].y _ lPosY + auxInfo[vtx].x * (rPosY - lPosY); -- wtd av. % auxInfo[vtx].x _ MIN[ 1.0, MAX[0.0, auxInfo[vtx].x]]; auxInfo[vtx].y _ MIN[ 1.0, MAX[0.0, auxInfo[vtx].y]]; IF hypotenuse < 0.00001 THEN polyTags[i] _ TRUE -- catch unstable arithmetic ELSE { polyTags[i] _ FALSE; IF auxInfo[vtx].x < minTxtrX THEN minTxtrX _ auxInfo[vtx].x; IF auxInfo[vtx].x > maxTxtrX THEN maxTxtrX _ auxInfo[vtx].x; }; ENDLOOP; auxInfo.length _ shape.shade.length; shape.shadingProps _ PutProp[ shape.shadingProps, $AuxiliaryVtxData, auxInfo]; IF maxTxtrX - minTxtrX > .5 -- wrapping around seam, fix up coords THEN FOR i: NAT IN [0..poly[polyNumber].nVtces) DO vtx: NAT _ poly[polyNumber].vtxPtr[i]; IF maxTxtrX - auxInfo[vtx].x > .5 THEN auxInfo[vtx].x _ auxInfo[vtx].x + 1.; ENDLOOP; minTxtrX _ Real.LargestNumber; maxTxtrX _ 0.; FOR i: NAT IN [0..poly[polyNumber].nVtces) DO -- get corrected max and min IF polyTags[i] = FALSE THEN { vtx: NAT _ poly[polyNumber].vtxPtr[i]; IF auxInfo[vtx].x < minTxtrX THEN minTxtrX _ auxInfo[vtx].x; IF auxInfo[vtx].x > maxTxtrX THEN maxTxtrX _ auxInfo[vtx].x; }; ENDLOOP; FOR i: NAT IN [0..poly[polyNumber].nVtces) DO -- fix up unstable vertical normals vtx: NAT _ poly[polyNumber].vtxPtr[i]; IF polyTags[i] = TRUE THEN auxInfo[vtx].x _ (maxTxtrX + minTxtrX) / 2.; ENDLOOP; minTxtrX _ minTxtrY _ Real.LargestNumber; FOR i: NAT IN [0..poly[polyNumber].nVtces) DO -- slew according to corner coords vtx: NAT _ poly[polyNumber].vtxPtr[i]; lPosX: REAL _ botLeft.x + auxInfo[vtx].y * (topLeft.x - botLeft.x); lPosY: REAL _ botLeft.y + auxInfo[vtx].y * (topLeft.y - botLeft.y); rPosX: REAL _ botRight.x + auxInfo[vtx].y * (topRight.x - botRight.x); rPosY: REAL _ botRight.y + auxInfo[vtx].y * (topRight.y - botRight.y); auxInfo[vtx].x _ lPosX + auxInfo[vtx].x * (rPosX - lPosX); auxInfo[vtx].y _ lPosY + auxInfo[vtx].x * (rPosY - lPosY); IF auxInfo[vtx].x < minTxtrX THEN minTxtrX _ auxInfo[vtx].x; IF auxInfo[vtx].y < minTxtrY THEN minTxtrY _ auxInfo[vtx].y; ENDLOOP; minTxtrX _ Real.Float[Real.Fix[minTxtrX]]; minTxtrY _ Real.Float[Real.Fix[minTxtrY]]; FOR i: NAT IN [0..poly[polyNumber].nVtces) DO -- translate to origin vtx: NAT _ poly[polyNumber].vtxPtr[i]; auxInfo[vtx].x _ auxInfo[vtx].x - minTxtrX; auxInfo[vtx].y _ auxInfo[vtx].y - minTxtrY; ENDLOOP; ENDLOOP; shape.shadingProps _ PutProp[ shape.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]; shape.shadingProps _ PutProp[ shape.shadingProps, $TxtrCoordParams, args]; }; TextureFromAIS: PUBLIC PROC[context: REF Context, fileName: Rope.ROPE, type: ATOM _ $Intensity, factor: REAL _ 1.0] RETURNS[texture: REF TextureMap] ~ { width, height: INTEGER _ 1024; renderMode: ATOM; bufContext: REF Context; FOR i: NAT IN [0..context.shapes.length) DO IF context.shapes[i].shadingClass.texture # NIL THEN FOR txtrList: LORA _ context.shapes[i].shadingClass.texture, 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; renderMode _ IF type = $Color OR type = $ColorAndTransmittance THEN $FullColor ELSE $Gray; bufContext _ RenderWithPixels.GetContext[renderMode, width, height]; IF type = $ColorAndTransmittance THEN RenderWithPixels.AntiAliasing[bufContext]; bufContext.props _ context.props; -- bring along working directory, etc. [width, height] _ AISAnimation.GetAIS[ -- load pixelbuffer with AIS bits context: bufContext, fileRoot: fileName, center: FALSE]; IF width > bufContext.viewPort.w OR height > bufContext.viewPort.h THEN { bufContext.viewPort^ _ [0.0, 0.0, Real.Fix[width], Real.Fix[height]]; RenderWithPixels.AllocatePixelMemory[bufContext]; -- get bigger pixel buffer [width, height] _ AISAnimation.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, 1024]; bufContext.pixels.box.min.s _ MAX[0, 1024 - height]; -- getAIS pushes image up against max texture _ NEW[TextureMap]; -- now, gin up TextureMap, using context.pixels texture.pixels _ bufContext.pixels; texture.type _ type; texture.props _ Atom.PutPropOnList[texture.props, $FileName, fileName]; IF type = $Bump THEN texture.props _ Atom.PutPropOnList[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[ IntSequence[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 ThreeDBasics.Error[[$MisMatch, "No texture to Sum"]]; WITH texture.pixels SELECT FROM pxMap: ImagerPixel.PixelMap => pixels _ pxMap; summed: REF SummedTexture => RETURN; -- previously summed ENDCASE => SIGNAL ThreeDBasics.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. 2MappedAndSolidTextureImpl.mesa Copyright c 1984, 1986 by Xerox Corporation. All rights reserved. Last Edited by: Crow, March 16, 1989 9:50:21 am PST Internal Declarations Renamed Procedures Global Variables Initialization Register solid texture procs Procedures for Controlling Texture Procedures for VtxToRealSeq and computing color at a spot PROC[dest: REF RealSequence, source: VertexInfo, data: REF ANY] RTRNS[REF RealSequence]; PROC[context: REF Context, shading: REF ShadingClass, spot: REF Spot] Procedures for Auxiliary Clipping and Shading PROC[context: REF Context, shape: REF ShapeInstance, data: REF ANY _ NIL] RETURNS[REF ShapeInstance]; PROC[ context: REF Context, vtx: REF VertexInfo, data: REF ANY _ NIL ] RETURNS[REF VertexInfo]; PROC[ context: REF Context, vtx: REF VertexInfo, data: REF ANY _ NIL ] RETURNS[REF VertexInfo]; Support Procedures for setting up texture for tiler Procedures for Evaluating Texture at a Spot PROC[context: REF 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 PROC[context: REF Context, shape: REF ShapeInstance, data: REF ANY _ NIL] RETURNS[REF ShapeInstance]; PROC[context: REF Context, shape: REF ShapeInstance, data: REF ANY _ NIL] RETURNS[REF ShapeInstance]; Procedures for Computing Texture Coordinates Stretch as indicated by corner coordinates 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 Κ*~˜Ihead™šœ Οmœ7™BJ™3J˜šΟk ˜ Jšœ žœ?˜MJšœ žœ ˜Jšœ žœžœ ˜Jšœ žœ˜-Jšœ žœ&˜6Jšœ žœ˜%Jšžœžœžœ˜3JšœžœZ˜mJšœžœ˜%Jšœ žœ˜,Jšœ žœ˜'Jšœžœ˜'Jšœžœͺžœ˜ΟJšœžœ>˜TJšœžœ˜)Jšœžœ˜$Jšœžœ˜.Jšœžœ ˜Jšœžœ˜ —J˜—head2šœžœž˜(Iašžœ5žœt˜΄Mšžœ˜J˜Jšœž˜J˜—head3šΟb™Mšœ žœ˜%Jšžœžœžœ˜Mšœžœ˜Mšœ žœ˜)MšœžœΟc˜;Jšœžœ˜0Mšœžœ! ˜@Jšœžœ ˜4Jšœ žœ˜,Jšœžœ$˜Jšžœ#žœ+žœ˜]Jšžœ#žœ+žœ˜^šžœžœ˜š œ žœžœžœžœ˜#Ošœ žœU˜dOšœžœ#˜4Ošžœ˜—˜Jšœ žœ˜$šœ žœ ˜J˜:J˜ —Ošœžœ  ˜Gšžœžœžœžœ˜šœ žœ ˜O˜:O˜ —Ošœ5 "œ4 ˜₯Ošœžœ˜%Ošžœ˜—Ošœ+ ˜EO˜—˜Ošœ žœU˜bJšœ žœ˜$Ošœ žœU˜dOšœ žœ˜Ošœžœ ˜Fšžœžœžœžœ˜Ošœ6 "œ4 œ˜§Ošœžœ˜%Ošžœ˜—Ošœ+ ˜EO˜—˜Ošœ žœU˜dOšžœžœ˜(Ošž˜Ošœžœ˜0šžœžœžœžœ˜Ošœžœ#˜4Ošžœ˜—O˜—Jšžœžœ9˜K—J˜—š ‘ œžœžœžœžœ˜ROšœžœ˜!Ošœžœ ˜š‘ œžœ˜Jšžœ"žœ+žœ˜\Jšžœ"žœ+žœ˜\O˜—Jšœžœžœžœ ˜@Jšœ žœ (˜RJšœ žœ *˜RJšœ4™4šœ"žœ˜'Jšœ@™@—šœ9˜9JšœO™O—šœN˜NJšœS™S—šœžœ˜JšœN˜NJšœ˜—Jšœ#žœ˜.šžœžœ ,˜IJšœ:˜:JšœD˜DJšœB˜BJšœ˜JšœS™S—Jšœžœžœžœ˜;Jšœžœžœžœ˜;šœ6˜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˜Icodešœ žœ žœžœ˜'Q˜4 ˜Q˜—š ‘œžΟsžœžœžœ˜7™#J˜š ‘œžœ žœžœžœžœ˜5Jš œžœžœžœžœ˜Q˜/Qšœ žœžœžœ˜!Qšœžœ$žœžœ ˜OQšžœ˜ 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˜——šŸ;™;š‘œžœžœ žœ˜:˜#O˜O˜Ošžœ"˜%O˜—˜#O˜O˜Ošžœ'˜*O˜—O˜—š‘œ˜#Jšžœ žœžœžœžœžœžœžœ™eOšœžœžœžœ˜ šœ žœ0˜ ˜SJ˜?J˜>J˜?Jšœ8 ˜KJ˜6Jšžœ˜ —J˜$J˜NJ˜OJšœžœ žœžœ˜BJšœžœžœ˜BJšœžœžœ˜CJšœžœžœ˜DJšœžœ-žœ˜[J˜JJ˜ —š‘œžœžœ žœ˜QJ˜—Jš ‘œžœžœ žœžœ˜T˜ΕJšœžœ@˜JJšœ žœžœ1˜SJšœžœžœžœ˜Ošœžœ!žœ˜@Jšœ žœ žœžœ˜#Jšœ žœ /˜MJ˜šžœ3žœžœ˜?JšžœM˜SJ˜TJ˜—Jšžœ žœžœ žœ%˜Hšžœ1žœžœ˜>Jšœ, *˜VJšœ* %˜OJ˜—šžœžœžœ!˜EJšžœžœH˜S—šžœžœ"˜(Jšžœžœ ˜Ušžœžœžœ!˜,Jšžœžœ ˜T——J˜Jšžœžœžœ* ˜ZJšžœžœžœ*˜KJšžœžœžœ*˜KJšžœžœžœ+˜Kšžœ žœžœžœ˜+Jšœžœ˜.Jšœ žœ˜šžœžœžœž˜-J™BJšœžœ˜'Jšœ žœE˜UJšœ žœ?˜Nšœ žœ6˜DJ™A—Jšœžœ& &˜WJšœžœ&˜1Jšœžœ#  ˜NJšœžœ ˜+šžœ˜!Jšžœ*˜.šžœžœ˜&Jšžœ)˜-——Jšœ9 ˜MJšœ;  ˜GJšœžœžœ˜5Jšœžœžœ˜5Jšžœžœžœ ˜Ošžœ˜Jšœžœ˜Jšžœžœ˜šžœžœžœžœ˜)O˜ašžœžœžœž˜,O˜NOšžœ˜—Ošžœ˜—J˜J˜+J˜J˜——J˜Jšžœ˜—…—³ιΜ