DIRECTORY AIS, AtomButtonsTypes, SVCastRays, CoordSys, SVRay, DisplayListToTree, IO, Matrix3d, SV2d, SV3d, SVBasicTypes, SVModelTypes, SVRayTypes, SVSceneTypes; SVCastRaysImplB: CEDAR PROGRAM IMPORTS SVCastRays, CoordSys, SVRay, DisplayListToTree, Matrix3d EXPORTS SVCastRays = BEGIN Slice: TYPE = SVSceneTypes.Slice; BoundSphere: TYPE = SVBasicTypes.BoundSphere; Camera: TYPE = SVModelTypes.Camera; Classification: TYPE = SVRayTypes.Classification; Composite: TYPE = SVRayTypes.Composite; CoordSystem: TYPE = SVModelTypes.CoordSystem; FeedbackData: TYPE = AtomButtonsTypes.FeedbackData; Point2d: TYPE = SV2d.Point2d; Point3d: TYPE = SV3d.Point3d; Primitive: TYPE = SVRayTypes.Primitive; Ray: TYPE = SVRayTypes.Ray; Vector3d: TYPE = SV3d.Vector3d; DoesHit: PROC [class: Classification] RETURNS [BOOL] = { RETURN[class.count > 0 OR class.classifs[1] = TRUE]; }; RayCastBoundingSpheres: PUBLIC PROC [worldRay: Ray, node: REF ANY, consolidate: BOOL _ TRUE, feedback: FeedbackData, makeStream: BOOL _ FALSE, indent: NAT _ 0] RETURNS [class: Classification] = { OPEN SVCastRays; IF node = NIL THEN {class _ EmptyClass[]; RETURN}; WITH node SELECT FROM comp: Composite => { leftClass, rightClass: Classification; leftBoxHit, leftHit, rightBoxHit, rightHit: BOOL; totalMiss: BOOL _ FALSE; boundSphere: BoundSphere; WITH comp.leftSolid SELECT FROM p: Primitive => boundSphere _ p.boundSphere; c: Composite => boundSphere _ c.boundSphere; ENDCASE => ERROR; leftBoxHit _ SVRay.RayHitsBoundSphere[worldRay, boundSphere]; IF leftBoxHit THEN { leftClass _ RayCastBoundingSpheres[worldRay, comp.leftSolid, consolidate, feedback, makeStream, indent]; leftHit _ DoesHit[leftClass]; } ELSE {leftHit _ FALSE; leftClass _ EmptyClass[]}; IF NOT leftHit THEN IF comp.operation = intersection OR comp.operation = difference THEN { class _ leftClass; WriteStreamComp[comp, class, feedback, makeStream, indent]; RETURN}; WITH comp.rightSolid SELECT FROM p: Primitive => boundSphere _ p.boundSphere; c: Composite => boundSphere _ c.boundSphere; ENDCASE => ERROR; rightBoxHit _ SVRay.RayHitsBoundSphere[worldRay, boundSphere]; IF NOT rightBoxHit THEN SELECT comp.operation FROM union => {class _ leftClass; WriteStreamComp[comp, class, feedback, makeStream, indent]; RETURN}; intersection => IF NOT leftHit THEN RETURN[leftClass] ELSE { ReturnClassToPool[leftClass]; class _ EmptyClass[]; WriteStreamComp[comp, class, feedback, makeStream, indent]; RETURN}; difference => {class _ leftClass; WriteStreamComp[comp, class, feedback, makeStream, indent]; RETURN}; ENDCASE => ERROR; rightClass _ RayCastBoundingSpheres[worldRay, comp.rightSolid, consolidate, feedback, makeStream, indent]; rightHit _ DoesHit[rightClass]; SELECT comp.operation FROM union => IF rightHit THEN { IF leftHit THEN class _ UnionCombine[leftClass, rightClass, consolidate] ELSE {ReturnClassToPool[leftClass]; class _ rightClass} } ELSE { ReturnClassToPool[rightClass]; class _ leftClass}; intersection => IF rightHit THEN { IF leftHit THEN class _ IntersectionCombine[leftClass, rightClass, consolidate] ELSE {ReturnClassToPool[rightClass]; class _ leftClass;} } ELSE IF leftHit THEN {ReturnClassToPool[leftClass]; class _ rightClass} ELSE {ReturnClassToPool[rightClass]; class _ leftClass}; difference => IF rightHit THEN { IF leftHit THEN class _ DifferenceCombine[leftClass, rightClass, consolidate] ELSE {ReturnClassToPool[rightClass]; class _ leftClass} -- leftClass null } ELSE {ReturnClassToPool[rightClass]; class _ leftClass}; ENDCASE => ERROR; WriteStreamComp[comp, class, feedback, makeStream, indent]; RETURN}; prim: Primitive => { localRay: Ray; IF prim.ignoreMe THEN {class _ SVCastRays.GetClassFromPool[]; SVCastRays.MakeClassAMiss[class]; RETURN}; localRay _ SVRay.TransformRay[worldRay, prim.worldWRTPrim]; -- (takes a new ray from the pool) class _ prim.rayCastBoundingSpheres[localRay, prim.mo, prim]; WriteStreamPrim[prim, class, feedback, makeStream, 0]; SVRay.ReturnRayToPool[localRay]; -- returns ray to pool RETURN}; ENDCASE => ERROR; }; -- end of RayCastBoundingSpheres ClassAssign: PROC [to: Classification, i: NAT, from: Classification, j: NAT] = { to.params[i] _ from.params[j]; to.classifs[i] _ from.classifs[j]; to.normals[i] _ from.normals[j]; to.surfaces[i] _ from.surfaces[j]; to.primitives[i] _ from.primitives[j]; }; SortClassByPrimitive: PUBLIC PROC [class: Classification] = { newClass: Classification; used: ARRAY [1..SVRayTypes.maxSceneDepth] OF BOOL; thisPrim: Primitive; newClass _ SVRay.CopyClass[class]; FOR i: NAT IN [1..SVRayTypes.maxSceneDepth] DO used[i] _ FALSE; ENDLOOP; thisPrim _ newClass.primitives[1]; FOR i: NAT IN [1..class.count] DO FOR j: NAT IN [1..class.count] DO IF NOT used[j] AND newClass.primitives[j] = thisPrim THEN { ClassAssign[class, i, newClass, j]; used[j] _ TRUE; GOTO Found; }; REPEAT Found => NULL; FINISHED => FOR k: NAT IN [1..class.count] DO IF NOT used[k] THEN { thisPrim _ newClass.primitives[k]; ClassAssign[class, i, newClass, k]; used[k] _ TRUE; EXIT; }; ENDLOOP; ENDLOOP; ENDLOOP; }; NthSurfacePointAndNormal: PUBLIC PROC [class: Classification, rayWorld: Ray, n: NAT] RETURNS [surfacePtWorld: Point3d, normalWorld: Vector3d] = { firstPrim: Primitive; primitiveNormal: Vector3d; t: REAL; IF n = 0 OR n > class.count THEN ERROR; t _ class.params[n]; -- the parameter of the ray intersection primitiveNormal _ class.normals[1]; firstPrim _ class.primitives[1]; normalWorld _ Matrix3d.UpdateVectorWithInverse[firstPrim.worldWRTPrim, primitiveNormal]; surfacePtWorld _ SVRay.EvaluateWorldRay[rayWorld, t]; }; LastOfFirstPointAndNormal: PUBLIC PROC [class: Classification, rayWorld: Ray] RETURNS [surfacePtWorld: Point3d, normalWorld: Vector3d] = { primitiveNormal: Vector3d; t: REAL; lastNum: NAT _ class.count; lastOfFirstIndex: NAT _ 1; firstPrim: Primitive _ class.primitives[1]; IF lastNum = 0 THEN RETURN; FOR i: NAT DECREASING IN [2..lastNum] DO IF class.primitives[i] = firstPrim THEN GOTO Found; REPEAT Found => lastOfFirstIndex _ i; ENDLOOP; t _ class.params[lastOfFirstIndex]; -- the parameter of the last ray intersection primitiveNormal _ class.normals[lastOfFirstIndex]; normalWorld _ Matrix3d.UpdateVectorWithInverse[firstPrim.worldWRTPrim, primitiveNormal]; surfacePtWorld _ SVRay.EvaluateWorldRay[rayWorld, t]; }; LastOfFirstHitData: PUBLIC PROC [class: Classification] RETURNS [hitData: REF ANY] = { lastNum: NAT _ class.count; lastOfFirstIndex: NAT _ 1; firstPrim: Primitive _ class.primitives[1]; IF lastNum = 0 THEN RETURN; FOR i: NAT DECREASING IN [2..lastNum] DO IF class.primitives[i] = firstPrim THEN GOTO Found; REPEAT Found => lastOfFirstIndex _ i; ENDLOOP; hitData _ class.surfaces[lastOfFirstIndex]; }; LastTopLevelPointAndNormal: PUBLIC PROC [class: Classification, rayWorld: Ray, root: Slice] RETURNS [surfacePtWorld: Point3d, normalWorld: Vector3d] = { primitiveNormal: Vector3d; t: REAL; lastNum: NAT _ class.count; lastTopLevelIndex: NAT _ 1; firstPrim: Primitive _ class.primitives[1]; lastPrim: Primitive; firstAssembly, thisAssembly: Slice; firstTopLevel: Slice; firstAssembly _ NARROW[firstPrim.assembly]; firstTopLevel _ DisplayListToTree.AncestorAtLevel[firstAssembly, root, 1]; IF lastNum = 0 THEN RETURN; FOR i: NAT DECREASING IN [2..lastNum] DO thisAssembly _ NARROW[class.primitives[i].assembly]; IF DisplayListToTree.IsSuccessorOf[thisAssembly, firstTopLevel] THEN GOTO Found; REPEAT Found => lastTopLevelIndex _ i; ENDLOOP; t _ class.params[lastTopLevelIndex]; -- the parameter of the last ray intersection primitiveNormal _ class.normals[lastTopLevelIndex]; lastPrim _ class.primitives[lastTopLevelIndex]; normalWorld _ Matrix3d.UpdateVectorWithInverse[lastPrim.worldWRTPrim, primitiveNormal]; surfacePtWorld _ SVRay.EvaluateWorldRay[rayWorld, t]; }; NthAssemblyAndPrimitive: PUBLIC PROC [class: Classification, n: NAT] RETURNS [assembly: Slice, primitive: Primitive] = { IF n = 0 OR n > class.count THEN RETURN[NIL, NIL]; primitive _ class.primitives[n]; assembly _ NARROW[primitive.assembly]; }; NthHitData: PUBLIC PROC [class: Classification, n: NAT] RETURNS [hitData: REF ANY] = { IF n = 0 OR n > class.count THEN RETURN[NIL]; hitData _ class.surfaces[n]; }; LastTopLevelAssemAndPrim: PUBLIC PROC [class: Classification, root: Slice] RETURNS [assembly: Slice, primitive: Primitive] = { lastNum: NAT _ class.count; lastTopLevelIndex: NAT _ 1; firstPrim: Primitive _ class.primitives[1]; firstTopLevel, thisAssembly: Slice; IF lastNum = 0 THEN RETURN[NIL, NIL]; thisAssembly _ NARROW[firstPrim.assembly]; firstTopLevel _ DisplayListToTree.AncestorAtLevel[thisAssembly, root, 1]; FOR i: NAT DECREASING IN [2..lastNum] DO thisAssembly _ NARROW[class.primitives[i].assembly]; IF DisplayListToTree.IsSuccessorOf[thisAssembly, firstTopLevel] THEN GOTO Found; REPEAT Found => lastTopLevelIndex _ i; ENDLOOP; primitive _ class.primitives[lastTopLevelIndex]; assembly _ firstTopLevel; }; LastTopLevelHitData: PUBLIC PROC [class: Classification, root: Slice] RETURNS [hitData: REF ANY] = { lastNum: NAT _ class.count; lastTopLevelIndex: NAT _ 1; firstPrim: Primitive _ class.primitives[1]; firstTopLevel, thisAssembly: Slice; IF lastNum = 0 THEN RETURN[NIL]; thisAssembly _ NARROW[firstPrim.assembly]; firstTopLevel _ DisplayListToTree.AncestorAtLevel[thisAssembly, root, 1]; FOR i: NAT DECREASING IN [2..lastNum] DO thisAssembly _ NARROW[class.primitives[i].assembly]; IF DisplayListToTree.IsSuccessorOf[thisAssembly, firstTopLevel] THEN GOTO Found; REPEAT Found => lastTopLevelIndex _ i; ENDLOOP; hitData _ class.surfaces[lastTopLevelIndex]; }; RayMeetsZConstantPlane: PUBLIC PROC [localRay: Ray, z: REAL] RETURNS [hitPoint: Point3d, parallel: BOOL] = { almostZero: REAL _ 1.0e-12; t: REAL; p: Point3d; d: Vector3d; [p, d] _ SVRay.GetLocalRay[localRay]; IF ABS[d[3]] < almostZero THEN { hitPoint _ [0,0,0]; parallel _ TRUE; RETURN; } ELSE { t _ (z - p[3])/d[3]; hitPoint[1] _ p[1] + t*d[1]; hitPoint[2] _ p[2] + t*d[2]; hitPoint[3] _ z; parallel _ FALSE; }; }; RayMeetsPlaneOfCoordSystem: PUBLIC PROC [coordSys: CoordSystem, rayWorld: Ray, whichPlane: NAT, depth: REAL _ 0] RETURNS [pointWorld: Point3d, parallel: BOOL] = { almostZero: REAL _ 1.0e-12; t: REAL; localRay: Ray; hitPoint: Point3d; p: Point3d; d: Vector3d; localRay _ SVRay.TransformRay[rayWorld, CoordSys.FindWorldInTermsOf[coordSys]]; [p, d] _ SVRay.GetLocalRay[localRay]; SELECT whichPlane FROM 1 => { -- The x = depth plane IF ABS[d[1]] < almostZero THEN { pointWorld _ [depth,0,0]; parallel _ TRUE; RETURN; } ELSE { t _ (depth - p[1])/d[1]; hitPoint[1] _ depth; hitPoint[2] _ p[2] + t*d[2]; hitPoint[3] _ p[3] + t*d[3]; parallel _ FALSE; }; }; 2 => { -- The y = depth plane IF ABS[d[2]] < almostZero THEN { pointWorld _ [0,depth,0]; parallel _ TRUE; RETURN; } ELSE { t _ (depth - p[2])/d[2]; hitPoint[1] _ p[1] + t*d[1]; hitPoint[2] _ depth; hitPoint[3] _ p[3] + t*d[3]; parallel _ FALSE; }; }; 3 => { -- The z = depth plane IF ABS[d[3]] < almostZero THEN { pointWorld _ [0,0,depth]; parallel _ TRUE; RETURN; } ELSE { t _ (depth - p[3])/d[3]; hitPoint[1] _ p[1] + t*d[1]; hitPoint[2] _ p[2] + t*d[2]; hitPoint[3] _ depth; parallel _ FALSE; }; }; ENDCASE => ERROR; SVRay.ReturnRayToPool[localRay]; pointWorld _ Matrix3d.Update[hitPoint, CoordSys.WRTWorld[coordSys]]; }; END. File: SVCastRaysImplB.mesa Last edited by Bier on May 4, 1987 11:50:48 pm PDT Copyright c 1984 by Xerox Corporation. All rights reserved. Contents: The ray casting (as opposed to tree building) part of the SVRay package. SVRay.mesa builds the trees The main ray casting procedure. worldRay must be in WORLD coordinates before this procedure is called. Before casting each ray, see if the ray will be in the bounding sphere of the son node. For optimizing, here is the plan: 1) Check ray for left bound box. Set leftBoxHit if appropriate. 2) If leftBoxHit then cast the ray. Set leftHit if appropriate. 3) If not leftHit then if comp.operation = intersection or difference, return miss. 4) If hit, or union, then right box test. Set RightBoxMiss if appropriate. 5) If miss then return: leftclass for difference, empty for intersection, leftClass for union. 6) Else cast ray. 7) Return rightclass or combination if appropriate 1) Check ray for left bound box. Set leftBoxHit if appropriate. 2) If leftBoxHit then cast the ray. Set leftHit if appropriate. 3) If not leftHit then if comp.operation = intersection or difference, return miss. leftClass is (or is equivalent to) EmptyClass[]; 4) If hit, or union, then right sphere test. Set RightBoxMiss if appropriate. (we don't have to test for this state. It is the only one left.) 5) If miss then return EmptyClass. Else cast ray. This could be a union with or without a left miss or (intersection/difference) with an initial hit. 6) Else cast ray. We have Union, or (intersection/difference) with left hit. Ray hits box. 7) Return rightclass, combination or empty if appropriate ClassSwap: PROC [class: Classification, i, j: NAT] = { Swap the ith and jth columns of a classification record. paramTemp: REAL; classifTemp: BOOL; normalTemp: Vector3d; surfaceTemp: Surface; primTemp: Primitive; paramTemp _ class.params[i]; class.params[i] _ class.params[j]; class.params[j] _ paramTemp; classifTemp _ class.classifs[i]; class.classifs[i] _ class.classifs[j]; class.classifs[j] _ class.classifs[i]; normalTemp _ class.classifs[i] class.normals[i] _ class.normals[j]; class.normals[j] _ class.normals[i]; surfaceTemp _ class.surfaces[i]; class.surfaces[i] _ class.surfaces[j]; class.surfaces[j] _ class.surfaces[i]; primTemp _ class.primitives[i]; class.primitives[i] _ class.primitives[j]; class.primitives[j] _ class.primitives[i]; }; Reorders the classification so that all of the intersections corresponding to a particular primitive are grouped together (locally in ascending t value), and primitives are ordered by t value of the first hit on that primitive. Copy from newClass to class as you go. The SurfacePointAndNormal and AssemblyAndPrimitive procedures are distinct because Cedar can't handle a return record which both contains a pointer (assembly) and returns 6 REALS (long pointer-containing return record is unsafe) Find the surface point and surface normal of the nth primitive hit by the ray. Find the surface point and surface normal, belonging to the first primitive hit by the ray, when the ray last leaves that primitive. Find the surface point and surface normal, belonging to the first primitive hit by the ray, when the ray last leaves that primitive. Find the surface point and surface normal, belonging to the first top level assembly hit by the ray, when the ray last leaves that top level assembly. Note that the assembly may well be composite (composed of multiple primitives). "Top level" assemblies are direct children of "root". SurfacePoint: PRIVATE PROC [t: REAL, camera: Camera, cameraPoint: Point2d] RETURNS [surfacePtInWorld: Point3d] = { Given the parameter of intersection, camera, and the ray-screen intersection point (cameraPoint), find the surface point corresponding to t. We assume that the ray went thru cameraPoint and that camera.projection has not changed since the ray was cast. worldBasePt: Point3d; worldDirection: Vector3d; Express the ray which was cast in world coordinates. worldBasePt _ Matrix3d.Update[[cameraPoint[1], cameraPoint[2], 0], camera.coordSys.mat]; worldDirection depends on whether a perspective or orthogonal projection is in use. SELECT camera.projection FROM orthogonal => { worldDirection _ Matrix3d.UpdateVectorEvenScaling[camera.coordSys.mat, [0, 0, -camera.focalLength]]; }; perspective => { worldDirection _ Matrix3d.UpdateVectorEvenScaling[camera.coordSys.mat, [cameraPoint[1], cameraPoint[2], -camera.focalLength]]; }; ENDCASE => ERROR; Use the ray: worldBasePt + t*worldDirection to find the surface point in WORLD coordinates. surfacePtInWorld[1] _ worldBasePt[1] + t*worldDirection[1]; surfacePtInWorld[2] _ worldBasePt[2] + t*worldDirection[2]; surfacePtInWorld[3] _ worldBasePt[3] + t*worldDirection[3]; }; Find the nth primitive hit by the ray and its corresponding displaylist assembly. Find the nth primitive hit by the ray and its corresponding displaylist assembly. Find the first top level assembly hit by the ray and the primitive at which the ray last leaves that top level assembly. assembly _ NARROW[primitive.assembly]; Find the first top level assembly hit by the ray and the primitive at which the ray last leaves that top level assembly. We assume the ray and the z constant plane are in the same coordinate frame. As usual the ray equations are: x = localRay.basePt[1] + t*localRay.direction[1]; y = localRay.basePt[2] + t*localRay.direction[2]; z = localRay.basePt[3] + t*localRay.direction[3]; If direction[3] is almost zero then report parallel. Finds the intersection of the ray with one of the three orthogonal planes of coordSys. If whichPlane = 1 this will be the x=0 (yz) plane, whichPlane = 2 then the y=0 plane, whichPlane = 3 then the z = 0 plane. Assumes ray is initially in camera coordinates. Returns the ray-plane intersection in camera coordinates, or parallel = TRUE if the ray is parallel to the plane. As usual the ray equations are: x = localRay.basePt[1] + t*localRay.direction[1]; y = localRay.basePt[2] + t*localRay.direction[2]; z = localRay.basePt[3] + t*localRay.direction[3]; If direction[3] is almost zero then report parallel. ΚO˜Iheadšœ™Iprocšœ2™2Jšœ Οmœ1™L˜Lšœ3™3L™Lšžœžœ ž˜šœc™cšžœž˜LšœYžœ˜ašœ˜Lšžœžœ žœžœ ˜%šžœ˜Lšœpžœ˜x——Lšœ^žœ˜fLšžœžœ˜L˜——Lšœ]™]L™Lšœ  œG˜jLšœ˜L˜Lšœ:™:L™šžœž˜šœ˜šžœ žœ˜Lšžœ žœ   œ$˜HLšžœ4˜8Lšœ˜—šœžœ˜Lšœ3˜3——šœ˜šžœ žœ˜Lšžœ žœ  œ$˜OLšžœ5˜9Lšœ˜—šžœžœ žœ3˜GLšœžœ4˜;——šœ ˜ šžœ žœ˜Lšžœ žœ  œ$˜MLšžœ5Οc˜JLšœ˜—Lšžœ5˜9—Lšžœžœ˜—Lšœ;˜;Lšžœ˜—šœ˜Lšœ˜šžœž˜LšœJžœ˜R—Lšœ žœ,‘"˜^L˜Lšœ=˜=Lšœ6˜6Lšžœ‘˜7Lšžœ˜—Lšžœžœ˜Lšœ‘ ˜#L˜L˜—šŸ œžœžœžœ˜PJšœ˜Jšœ"˜"Jšœ ˜ Jšœ"˜"Jšœ&˜&J˜J˜—šœ žœžœ™6J™8Lšœ žœ™Lšœ žœ™L™L™L™L™Lšœ™Lšœ"™"Lšœ™L™Lšœ ™ Lšœ&™&Lšœ&™&L™Lšœ™Lšœ$™$Lšœ$™$L™Lšœ ™ Lšœ&™&Lšœ&™&L™Lšœ™Lšœ*™*Lšœ*™*L™J™—šŸœžœžœ˜=Jšœ‹™‹Jšœ˜Jšœžœžœžœ˜2Jšœ˜J˜Jšœ žœ˜"Jš žœžœžœžœ žœžœ˜HJšœ"˜"šžœžœžœž˜!šžœžœžœž˜!šžœžœ žœ#žœ˜;Jšœ#˜#Jšœ žœ˜Jšžœ˜ J˜——šž˜Jšœ žœ˜šžœ˜ šžœžœžœž˜!šžœžœ žœ˜Jšœ"˜"Jšœ#˜#Jšœ žœ˜Jšžœ˜J˜——Jšžœ˜——Jšžœ˜—Jšžœ˜J˜—JšœŸœŸœ²™δJ˜šŸœžœžœΟuœžœ ’œ’œ˜‘Jšœ1 œ  œ™NJšœ˜Jšœ˜Jšœžœ˜Jšžœžœžœžœ˜'Jšœ œ‘)˜=Jšœ#˜#Jšœ ˜ Jš ΠbuœM˜XJš  £œ’œ˜5Jšœ˜—J˜šŸœžœžœ’œžœ ’œ’œ˜ŠJšœ< œ  œ™„Jšœ˜Jšœžœ˜Jšœ žœ˜Jšœžœ˜Jšœ+˜+Jšžœ žœžœ˜š žœžœž œžœž˜(Jšžœ!žœžœ˜3—šž˜Jšœ˜—Jšžœ˜Jšœ œ‘-˜QJšœ  œ˜2Jš £œM˜XJš  £œ’œ˜5Jšœ˜J˜—š Ÿœžœžœžœ žœžœ˜VJšœ< œ  œ™„Jšœ žœ˜Jšœžœ˜Jšœ+˜+Jšžœ žœžœ˜š žœžœž œžœž˜(Jšžœ!žœžœ˜3—šž˜Jšœ˜—Jšžœ˜Jšœ œ˜+Jšœ˜J˜—šΠbnœžœžœ’œžœ ’œ’œ˜˜Jšœ< œ  œ‘™žJšœ˜Jšœžœ˜Jšœ žœ˜Jš œžœ˜Jšœ+˜+Jšœ˜J˜#Jšœ˜J˜Jšœžœ˜+JšœJ˜JJšžœ žœžœ˜š žœžœž œžœž˜(Jšœžœ˜4Jšžœ>žœžœ˜P—šž˜Jšœ  œ˜ —Jšžœ˜Jšœ%‘-˜RJšœ3˜3Jšœ/˜/Jš £œL˜WJš  £œ’œ˜5J˜J˜—š Ÿ œžœžœžœ(žœ ™rJšœύ™ύJšœ™šœ™Jšœ4™4—šœX™XJšœS™S—Jšžœž™™Jšœd™dJ™—™Jšœ~™~J™—šžœžœ™Jšœ]™]—Jšœ;™;Jšœ;™;Jšœ;™;Jšœ™J™—š Ÿœžœžœžœžœ,˜xJšœ  œ  œ;™QJš žœžœžœžœžœžœ˜2Jšœ œ˜ Jšœ žœ˜&Jšœ˜—J˜šŸ œžœžœžœžœ žœžœ˜VJšœ  œ  œ;™QJš žœžœžœžœžœ˜-Jšœ œ˜Jšœ˜—š€œžœžœ&žœ,˜~Jšœ  œ3  œ™xJšœ žœ˜Jšœžœ˜Jšœ+˜+Jšœ#˜#J˜Jš žœ žœžœžœžœ˜%Jšœžœ˜*Jš  œ<˜Iš žœžœž œžœž˜(Jšœžœ˜4Jšžœ  œžœžœ˜P—šž˜Jšœ ˜ —Jšžœ˜Jšœ0˜0Jšœ žœ™&Jšœ   œ˜J˜J˜—š €œžœžœ&žœ žœžœ˜dJšœ  œ3  œ™xJšœ žœ˜Jšœžœ˜Jšœ+˜+Jšœ#˜#J˜Jšžœ žœžœžœ˜ Jšœžœ˜*Jš  œ<˜Iš žœžœž œžœž˜(Jšœžœ˜4Jšžœ  œžœžœ˜P—šž˜Jšœ ˜ —Jšžœ˜Jšœ,˜,J˜J˜—š Ÿœžœžœžœžœžœ˜lJ™mJšœ1™1Jšœ1™1Jšœ1™1Jšœ4™4Lšœ žœ ˜Lšœžœ˜L˜ L˜ Lšœ žœ˜%šžœžœžœ˜ Lšœ˜Lšœ žœ˜Lšžœ˜L˜—šžœ˜Lšœ˜Lšœ˜Lšœ˜Lšœ˜Lšœ žœ˜L˜—L˜Lšœ˜—šŸœžœžœ’œžœ žœžœ’œžœ˜’Lšœ—™—Lšœ1™1Jšœ1™1Jšœ1™1Jšœ4™4Lšœ žœ ˜Lšœžœ˜Lšœ˜L˜L˜ L˜ Lšœ žœ’œ)˜OLšœ žœ˜%Jšžœ ž˜šœ‘ œ‘˜šžœžœžœ˜ Lšœ’œ˜Lšœ žœ˜Lšžœ˜L˜—šžœ˜Lšœ˜Lšœ˜Lšœ˜Lšœ˜Lšœ žœ˜L˜—L˜—šœ‘ œ‘˜šžœžœžœ˜ Lšœ’œ˜Lšœ žœ˜Lšžœ˜L˜—šžœ˜Lšœ˜Lšœ˜Lšœ˜Lšœ˜Lšœ žœ˜L˜—L˜—šœ‘ œ‘˜šžœžœžœ˜ Lšœ’œ˜Lšœ žœ˜Lšžœ˜L˜—šžœ˜Lšœ˜Lšœ˜Lšœ˜Lšœ˜Lšœ žœ˜L˜—L˜—Lšžœžœ˜Lšžœ˜ Lšœ’œ:˜DL˜L˜—Lšžœ˜L˜—…—+ϊU_