Repository: w0rm/elm-physics Branch: main Commit: 8f1d0a30812c Files: 105 Total size: 991.5 KB Directory structure: gitextract_5f8dznnk/ ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── gh-pages.yml │ ├── publish.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── benchmarks/ │ ├── README.md │ ├── elm.json │ └── src/ │ ├── AssignIds.elm │ ├── Convex.elm │ ├── ConvexConvex.elm │ ├── EigenDecomposition.elm │ └── SphereConvex.elm ├── docs.json ├── elm.json ├── examples/ │ ├── elm.json │ ├── review/ │ │ ├── elm.json │ │ └── src/ │ │ └── ReviewConfig.elm │ └── src/ │ ├── Duckling.elm │ ├── Duckling.obj.txt │ ├── Jeep.obj.txt │ ├── Lack.elm │ ├── Raycast.elm │ ├── RaycastCar/ │ │ ├── Car.elm │ │ └── Jeep.elm │ └── RaycastCar.elm ├── flake.nix ├── review/ │ ├── elm.json │ └── src/ │ └── ReviewConfig.elm ├── sandbox/ │ ├── elm.json │ ├── review/ │ │ ├── elm.json │ │ └── src/ │ │ └── ReviewConfig.elm │ ├── src/ │ │ ├── Boxes.elm │ │ ├── Car.elm │ │ ├── Character.elm │ │ ├── Character2D.elm │ │ ├── Cloth.elm │ │ ├── Common/ │ │ │ ├── Camera.elm │ │ │ ├── Fps.elm │ │ │ ├── Math.elm │ │ │ ├── Meshes.elm │ │ │ ├── Scene.elm │ │ │ ├── Settings.elm │ │ │ └── Shaders.elm │ │ ├── CompoundVsLock.elm │ │ ├── Dominoes.elm │ │ ├── Kinematic.elm │ │ ├── Randomize.elm │ │ ├── Stability/ │ │ │ ├── Metrics.elm │ │ │ └── Scenarios.elm │ │ ├── StabilityScenes.elm │ │ └── UnsafeConvex.elm │ └── tests/ │ └── StabilityTest.elm ├── scripts/ │ ├── elm-publish.sh │ └── gh-pages.sh ├── src/ │ ├── Collision/ │ │ ├── ConvexConvex.elm │ │ ├── ParticleConvex.elm │ │ ├── PlaneConvex.elm │ │ ├── PlaneParticle.elm │ │ ├── PlaneSphere.elm │ │ ├── SphereConvex.elm │ │ ├── SphereParticle.elm │ │ └── SphereSphere.elm │ ├── Internal/ │ │ ├── AssignIds.elm │ │ ├── Body.elm │ │ ├── BroadPhase.elm │ │ ├── Const.elm │ │ ├── Constraint.elm │ │ ├── Contact.elm │ │ ├── Coordinates.elm │ │ ├── Equation.elm │ │ ├── Lock.elm │ │ ├── Material.elm │ │ ├── Matrix3.elm │ │ ├── NarrowPhase.elm │ │ ├── Shape.elm │ │ ├── Solver.elm │ │ ├── SolverBody.elm │ │ ├── Transform3d.elm │ │ └── Vector3.elm │ ├── Physics/ │ │ ├── Constraint.elm │ │ ├── Lock.elm │ │ ├── Material.elm │ │ ├── Shape.elm │ │ └── Types.elm │ ├── Physics.elm │ └── Shapes/ │ ├── Convex.elm │ ├── Plane.elm │ └── Sphere.elm └── tests/ ├── BodyTest.elm ├── Collision/ │ ├── ConvexConvexTest.elm │ ├── PlaneSphereTest.elm │ └── SphereConvexTest.elm ├── EigenDecompositionTest.elm ├── Extra/ │ └── Expect.elm ├── Fixtures/ │ ├── Convex.elm │ └── NarrowPhase.elm ├── KinematicTest.elm ├── Matrix3Test.elm ├── PlaceInTest.elm ├── Shapes/ │ └── ConvexTest.elm ├── SimulateTest.elm ├── Transform3dFromFrame3dTest.elm └── Transform3dTest.elm ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ github: w0rm ================================================ FILE: .github/workflows/gh-pages.yml ================================================ name: GitHub Pages on: push: branches: [main] jobs: test: uses: ./.github/workflows/test.yml secrets: inherit gh-pages: needs: test runs-on: ubuntu-latest steps: - run: git config --global user.name "Andrey Kuzmin" - run: git config --global user.email "hi@unsoundscapes.com" - uses: actions/checkout@v4 - uses: cachix/install-nix-action@v29 with: github_access_token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/checkout@v4 with: path: gh-pages ref: gh-pages - run: nix develop --command ./scripts/gh-pages.sh ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish on: workflow_dispatch: inputs: version: description: "Confirm the new version" required: true branches: [main] jobs: test: uses: ./.github/workflows/test.yml secrets: inherit publish: needs: test runs-on: ubuntu-latest steps: - run: git config --global user.name "Andrey Kuzmin" - run: git config --global user.email "hi@unsoundscapes.com" - uses: actions/checkout@v4 - uses: cachix/install-nix-action@v29 with: github_access_token: ${{ secrets.GITHUB_TOKEN }} - run: nix develop --command ./scripts/elm-publish.sh ${{ github.event.inputs.version }} - uses: actions/checkout@v4 with: path: gh-pages ref: gh-pages - run: nix develop --command ./scripts/gh-pages.sh ${{ github.event.inputs.version }} ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: pull_request: branches: [main] workflow_call: jobs: elm-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: cachix/install-nix-action@v29 with: github_access_token: ${{ secrets.GITHUB_TOKEN }} - run: nix develop --command elm-format --validate . - run: nix develop --command elm-review - run: nix develop --command elm-test - run: cd sandbox && nix develop --command elm-test - run: nix develop --command elm make --docs docs.json - run: git diff --exit-code docs.json ================================================ FILE: .gitignore ================================================ release gh-pages elm-stuff .DS_Store .idea ================================================ FILE: LICENSE ================================================ Copyright (c) 2018-2026, Andrey Kuzmin All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ # 3D Physics Engine ![elm-physics](https://unsoundscapes.com/elm-physics/examples/elm-physics.gif) ```elm type Id = Ball | Floor bodies : List ( Id, Body ) bodies = [ ( Ball , Physics.sphere (Sphere3d.atOrigin (Length.meters 0.5)) Material.rubber |> Physics.moveTo (Point3d.meters 0 0 5) ) , ( Floor, Physics.plane Plane3d.xy Material.wood ) ] step model = let ( newBodies, newContacts ) = Physics.simulate { onEarth | contacts = model.contacts } model.bodies in { model | bodies = newBodies, contacts = newContacts } ``` # Features - **Pure** — `simulate` is a function, not a stateful world; deterministic, replayable, time-travel friendly. - **Your list is the world** — bodies live in `List ( id, Body )`; add with `(::)`, remove with `List.filter`. - **Type-safe coordinates and units** — built on [elm-geometry](https://package.elm-lang.org/packages/ianmackenzie/elm-geometry/latest/) and [elm-units](https://package.elm-lang.org/packages/ianmackenzie/elm-units/latest/); phantom types keep `WorldCoordinates` and `BodyCoordinates` apart, and forces, masses, velocities, and durations all carry units. - **Compound bodies** — combine shapes with `Shape.plus` / `minus` / `sum` and per-shape densities; mass, center of mass, and the full inertia tensor are derived for you. - **Declarative constraints and collisions** — pass `constrain` and `collide` as functions to `simulate`; the engine asks per body-pair what applies, so there's no constraint registry and no filter-group API. - **Warm-started solver** — feed last frame's contacts back into `simulate` for stable stacks. # Examples - Lack ([source](https://github.com/w0rm/elm-physics/tree/main/examples/src/Lack.elm), [demo](https://unsoundscapes.com/elm-physics/examples/lack/)) - Duckling ([source](https://github.com/w0rm/elm-physics/tree/main/examples/src/Duckling.elm), [demo](https://unsoundscapes.com/elm-physics/examples/duckling/)) - Raycast ([source](https://github.com/w0rm/elm-physics/tree/main/examples/src/Raycast.elm), [demo](https://unsoundscapes.com/elm-physics/examples/raycast/)) - RaycastCar ([source](https://github.com/w0rm/elm-physics/tree/main/examples/src/RaycastCar.elm), [demo](https://unsoundscapes.com/elm-physics/examples/raycast-car/)) # Prior Work Inspired by [Cannon.js](https://github.com/schteppe/cannon.js) and [Bullet](https://github.com/bulletphysics/bullet3) — this project is an experiment in what a purely functional 3D physics engine can look like. ================================================ FILE: benchmarks/README.md ================================================ # Elm Performance Rules When writing or reviewing Elm code in this repo, apply these performance rules to avoid slow JavaScript output, and then check if the tests pass: 1. **Tail-optimized recursion over List.*** — Replace `List.map`, `List.foldl`, `List.filter`, etc. with explicit tail-recursive helper functions. They compile to `while` loops in JS; higher-order List functions do not. Unpack any intermediate records used for recursion state into separate arguments to reduce object allocation. But keep in mind that mutual recursion is not optimised and must be avoided. 2. **Compare to zero instead of comparing two bindings** — Prefer `a - b == 0` over `a == b` when both sides are bindings. Direct equality between two bindings calls the Elm structural equality function; subtracting and comparing to 0 uses JS `===` on a number. Comparing against a numeric literal (e.g. `a == -1`, `idx > -1`) is fine as-is — the compiler already emits a direct JS comparison for literals. 3. **Unroll tuple pattern matches into nested case expressions** — Replace `case (a, b) of` with nested `case a of` / `case b of`. Tuple patterns allocate a temporary object at runtime; nested matches do not. 4. **Use sentinel values instead of Maybe for Int** — Avoid wrapping Int results in `Just`/`Nothing` when a sentinel like `-1` can represent the absent case. This eliminates allocation and unwrapping overhead. A similar technique: use an impossible filler element in a List or Array to avoid `Maybe` at boundaries. 5. **Pattern match Result/Maybe directly instead of using map/andThen** — Replace `Maybe.map f x` and `Result.andThen f x` with explicit `case` expressions. This avoids lambda allocation and indirect calls. 6. **Accumulate in reverse order when safe** — When making multiple recursive passes over a list, intermediate passes may produce a reversed list as long as the final output is correct. Avoid unnecessary `List.reverse` calls between passes. It is OK to use `List.reverse` when you must fix element order without modifying the elements themselves. 7. **Never use record update syntax** — `{ r | field = value }` generates slow JavaScript. Always spell out all record fields explicitly when constructing an updated record, even if most values are unchanged. 8. **Never use List.length** — `List.length` traverses the entire list and is O(n). Track counts explicitly as a separate argument rather than computing them from list lengths. 9. **Prefer `Set.fromList` over repeated `Set.insert`** — When building a `Set` from multiple elements, accumulate them into a list first and call `Set.fromList` at the end. Repeatedly calling `Set.insert` is slower than a single `Set.fromList` call. 10. **Avoid `|>` on the hot path** — Do not use the pipe operator in tight loops or recursive functions that process faces or vertices. It introduces lambda wrapping and indirect calls. `|>` is fine outside the hot path (e.g. at the decoder level or in one-time setup code). 11. **Prefer `Dict.get` + `Dict.insert` over `Dict.update`** — `Dict.update` accepts a lambda, which introduces indirect calls and closure allocation. Use `Dict.get` + a `case` expression + `Dict.insert` instead when conditionally inserting or modifying a value in a `Dict`. 12. **Minimize `Just wrappers`** use `(Just value) as passThroug ->` in and then return `passThrough` from the case of, instead of returning `(Just value)`. ================================================ FILE: benchmarks/elm.json ================================================ { "type": "application", "source-directories": [ "src", "../src", "../tests" ], "elm-version": "0.19.1", "dependencies": { "direct": { "elm/core": "1.0.5", "elm/html": "1.0.1", "elm/json": "1.1.4", "elm/random": "1.0.0", "elm-explorations/benchmark": "1.0.2", "ianmackenzie/elm-geometry": "4.0.0", "ianmackenzie/elm-units": "2.10.0" }, "indirect": { "BrianHicks/elm-trend": "2.1.3", "elm/browser": "1.0.2", "elm/regex": "1.0.0", "elm/time": "1.0.0", "elm/url": "1.0.0", "elm/virtual-dom": "1.0.5", "ianmackenzie/elm-1d-parameter": "1.0.1", "ianmackenzie/elm-float-extra": "1.1.0", "ianmackenzie/elm-interval": "3.1.0", "ianmackenzie/elm-triangular-mesh": "1.1.0", "ianmackenzie/elm-units-interval": "3.2.0", "mdgriffith/style-elements": "5.0.2", "robinheghan/murmur3": "1.0.0" } }, "test-dependencies": { "direct": {}, "indirect": {} } } ================================================ FILE: benchmarks/src/AssignIds.elm ================================================ module AssignIds exposing (main) {-| Benchmarks for the ID-assignment pass in Physics.simulate. Both candidates use the same four-phase structure: - Collect: scan bodies once, prepend existing IDs (O(1) each), count id=-1. - Sort: List.sort once (native JS sort, O(n log n)). - Analyse: single scan of sorted IDs to find duplicate IDs and free IDs. - Assign: scan bodies, hand out pre-computed free IDs to id=-1 and duplicates. twoPassOld — assign phase tracks claimed dups with two lists: dupIds (fixed) + claimedDups (grows). Two List.member calls in the dup branch. twoPass — assign phase uses findAndRemove on a single remainingDups list: one pass checks membership and removes in one go; allDupIds (fixed, tiny) only consulted for the rare second-occurrence case. Three scenarios: - stable — all bodies already have IDs (the common per-frame case) - allNew — all bodies have id = -1 (first frame / full restart) - withGaps — new bodies prepended (::) before stable even-ID bodies -} import Benchmark exposing (Benchmark, describe) import Benchmark.Runner exposing (BenchmarkProgram, program) import Internal.Body as InternalBody import Internal.Material as Material import Internal.Matrix3 as Mat3 import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 main : BenchmarkProgram main = program <| describe "AssignIds" [ Benchmark.compare "stable" "twoPass" (\_ -> assignIdsTwoPass stableInput) "twoPassFast" (\_ -> assignIdsTwoPassFast stableInput) , Benchmark.compare "allNew" "twoPass" (\_ -> assignIdsTwoPass allNewInput) "twoPassFast" (\_ -> assignIdsTwoPassFast allNewInput) , Benchmark.compare "withGaps" "twoPass" (\_ -> assignIdsTwoPass withGapsInput) "twoPassFast" (\_ -> assignIdsTwoPassFast withGapsInput) , Benchmark.compare "withDups" "twoPass" (\_ -> assignIdsTwoPass withDupsInput) "twoPassFast" (\_ -> assignIdsTwoPassFast withDupsInput) , Benchmark.compare "withDupsAndNew" "twoPass" (\_ -> assignIdsTwoPass withDupsAndNewInput) "twoPassFast" (\_ -> assignIdsTwoPassFast withDupsAndNewInput) ] -- ── Test data ──────────────────────────────────────────────────────────────── emptyBody : Int -> InternalBody.Body emptyBody id = { id = id , material = Material.default , transform3d = Transform3d.atOrigin , centerOfMassTransform3d = Transform3d.atOrigin , velocity = Vec3.zero , angularVelocity = Vec3.zero , mass = 0 , volume = 0 , shapes = [] , worldShapes = [] , force = Vec3.zero , torque = Vec3.zero , boundingSphereRadius = 0 , linearDamping = 0 , angularDamping = 0 , invMass = 0 , invInertia = Mat3.zero , invInertiaWorld = Mat3.zero } {-| 125 bodies, all with IDs 0–124 — the steady-state per-frame case. -} stableInput : List ( Int, InternalBody.Protected ) stableInput = List.map (\i -> ( i, InternalBody.Protected (emptyBody i) )) (List.range 0 124) {-| 125 bodies, all with id = -1 — first frame or full restart. -} allNewInput : List ( Int, InternalBody.Protected ) allNewInput = List.map (\i -> ( i, InternalBody.Protected (emptyBody -1) )) (List.range 0 124) {-| 62 new bodies (id = -1) prepended to 63 stable bodies with IDs 0, 2, 4, …, 124. Reflects the realistic usage pattern: new bodies are added with (::) to the front. -} withGapsInput : List ( Int, InternalBody.Protected ) withGapsInput = List.map (\i -> ( 200 + i, InternalBody.Protected (emptyBody -1) )) (List.range 0 61) ++ List.map (\i -> ( 2 * i, InternalBody.Protected (emptyBody (2 * i)) )) (List.range 0 62) {-| 20 extra bodies with duplicate IDs 0–19 appended to 105 stable bodies with IDs 0–104. Models a scenario where some bodies are re-added without clearing their old IDs. -} withDupsInput : List ( Int, InternalBody.Protected ) withDupsInput = List.map (\i -> ( i, InternalBody.Protected (emptyBody i) )) (List.range 0 104) ++ List.map (\i -> ( 200 + i, InternalBody.Protected (emptyBody i) )) (List.range 0 19) {-| 20 duplicates of IDs 0–19, 20 new bodies (id = -1), and 85 stable bodies with IDs 0–84. Exercises the assign phase with all three body kinds at once. -} withDupsAndNewInput : List ( Int, InternalBody.Protected ) withDupsAndNewInput = List.map (\i -> ( 300 + i, InternalBody.Protected (emptyBody i) )) (List.range 0 19) ++ List.map (\i -> ( 200 + i, InternalBody.Protected (emptyBody -1) )) (List.range 0 19) ++ List.map (\i -> ( i, InternalBody.Protected (emptyBody i) )) (List.range 0 84) -- ── twoPassOld ─────────────────────────────────────────────────────────────── -- Same as twoPass but uses two lists in the assign phase: -- dupIds (all dup IDs, fixed) + claimedDups (grows as first occurrences are seen). assignIdsTwoPassOld : List ( id, InternalBody.Protected ) -> ( List ( id, InternalBody.Body ), Int ) assignIdsTwoPassOld bodies = let ( existingIds, newCount, mx ) = tpCollect bodies [] 0 -1 sorted = List.sort existingIds ( dupIds, dupCount ) = tpFindDups sorted -2 [] 0 freeIds = tpFreeIds 0 sorted (newCount + dupCount) [] in tpAssignOld bodies freeIds dupIds [] mx [] tpAssignOld : List ( id, InternalBody.Protected ) -> List Int -> List Int -> List Int -> Int -> List ( id, InternalBody.Body ) -> ( List ( id, InternalBody.Body ), Int ) tpAssignOld bodies freeIds dupIds claimedDups mx acc = case bodies of [] -> ( acc, mx ) ( extId, InternalBody.Protected body ) :: rest -> if body.id == -1 then case freeIds of freshId :: remainingFree -> tpAssignOld rest remainingFree dupIds claimedDups (max mx freshId) (( extId, withId freshId body ) :: acc) [] -> tpAssignOld rest freeIds dupIds claimedDups mx acc else if not (List.member body.id dupIds) then tpAssignOld rest freeIds dupIds claimedDups mx (( extId, body ) :: acc) else if List.member body.id claimedDups then case freeIds of freshId :: remainingFree -> tpAssignOld rest remainingFree dupIds claimedDups (max mx freshId) (( extId, withId freshId body ) :: acc) [] -> tpAssignOld rest freeIds dupIds claimedDups mx acc else tpAssignOld rest freeIds dupIds (body.id :: claimedDups) mx (( extId, body ) :: acc) -- ── twoPass ────────────────────────────────────────────────────────────────── -- -- Phase 1 — tpCollect: one scan, O(1) per body. -- Builds existingIds (unsorted, prepend only) and counts id=-1 bodies. -- -- Sort — List.sort: native JS sort, O(n log n). -- -- Phase 2a — tpFindDups: one scan of sorted, O(1) per element. -- Detects duplicate IDs using a prev/prevAdded flag — no inner lookups. -- -- Phase 2b — tpFreeIds: one scan of sorted, O(1) per element. -- Finds the first (newCount + dupCount) gaps by merging counter with sorted. -- -- Phase 3 — tpAssign: one scan of bodies, O(|dupIds|) per body. -- dupIds is a multiset (one slot per replacement needed). -- List.member finds the slot; removeFirst removes it in the same pass. -- dupIds is empty for stable, so the common case has zero membership tests. assignIdsTwoPass : List ( id, InternalBody.Protected ) -> ( List ( id, InternalBody.Body ), Int ) assignIdsTwoPass bodies = let ( existingIds, newCount, mx ) = tpCollect bodies [] 0 -1 sorted = List.sort existingIds ( dupIds, dupCount ) = tpFindDups sorted -2 [] 0 freeIds = tpFreeIds 0 sorted (newCount + dupCount) [] in tpAssign bodies freeIds dupIds -1 mx [] {-| Pass 1: collect existing (non -1) IDs, count new bodies, track max id. -} tpCollect : List ( id, InternalBody.Protected ) -> List Int -> Int -> Int -> ( List Int, Int, Int ) tpCollect bodies existingIds newCount mx = case bodies of [] -> ( existingIds, newCount, mx ) ( _, InternalBody.Protected body ) :: rest -> if body.id == -1 then tpCollect rest existingIds (newCount + 1) mx else tpCollect rest (body.id :: existingIds) newCount (max mx body.id) {-| Scan sorted IDs, adding one entry per extra occurrence of each ID. Duplicate → one entry. Triplicate → two entries. And so on. Result is sorted descending — a natural consequence of prepending during an ascending scan. Called with prev=-2 (impossible value) as initial sentinel. -} tpFindDups : List Int -> Int -> List Int -> Int -> ( List Int, Int ) tpFindDups sorted prev acc count = case sorted of [] -> ( acc, count ) x :: rest -> if x - prev == 0 then tpFindDups rest x (x :: acc) (count + 1) else tpFindDups rest x acc count {-| Collect the first `needed` integers not present in sortedIds, starting from n. Advances n past each taken slot; skips elements of sortedIds below n. Returns results in ascending order via reversed accumulator. -} tpFreeIds : Int -> List Int -> Int -> List Int -> List Int tpFreeIds n sorted needed revAcc = if needed == 0 then List.reverse revAcc else case sorted of [] -> tpFillFrom n needed revAcc x :: rest -> if x > n then -- n is free tpFreeIds (n + 1) sorted (needed - 1) (n :: revAcc) else if x == n then -- n is taken tpFreeIds (n + 1) rest needed revAcc else -- x < n, stale entry — skip tpFreeIds n rest needed revAcc tpFillFrom : Int -> Int -> List Int -> List Int tpFillFrom n needed revAcc = if needed == 0 then List.reverse revAcc else tpFillFrom (n + 1) (needed - 1) (n :: revAcc) {-| Pass 3: assign free IDs to bodies that need them. dupIds is a sorted multiset (descending initially, flipping on each removal). memberSorted does an early-exit scan; removeFirstReversing removes the first occurrence in one TCO pass that naturally reverses the list as a byproduct. isAscending tracks the current direction so memberSorted exits the right way. When dupIds is empty (the common stable case) both calls are skipped entirely. -} tpAssign : List ( id, InternalBody.Protected ) -> List Int -> List Int -> Int -> Int -> List ( id, InternalBody.Body ) -> ( List ( id, InternalBody.Body ), Int ) tpAssign bodies freeIds dupIds dir mx acc = case bodies of [] -> ( acc, mx ) ( extId, InternalBody.Protected body ) :: rest -> if body.id == -1 then case freeIds of freshId :: remainingFree -> tpAssign rest remainingFree dupIds dir (max mx freshId) (( extId, withId freshId body ) :: acc) [] -> tpAssign rest freeIds dupIds dir mx acc else case dupIds of [] -> tpAssign rest freeIds [] dir mx (( extId, body ) :: acc) _ -> if memberSorted dir body.id dupIds then case freeIds of freshId :: remainingFree -> tpAssign rest remainingFree (removeFirstReversing body.id [] dupIds) (negate dir) (max mx freshId) (( extId, withId freshId body ) :: acc) [] -> tpAssign rest freeIds (removeFirstReversing body.id [] dupIds) (negate dir) mx acc else tpAssign rest freeIds dupIds dir mx (( extId, body ) :: acc) withId : Int -> InternalBody.Body -> InternalBody.Body withId freshId body = { id = freshId, material = body.material, transform3d = body.transform3d, centerOfMassTransform3d = body.centerOfMassTransform3d, velocity = body.velocity, angularVelocity = body.angularVelocity, mass = body.mass, volume = body.volume, shapes = body.shapes, worldShapes = body.worldShapes, force = body.force, torque = body.torque, boundingSphereRadius = body.boundingSphereRadius, linearDamping = body.linearDamping, angularDamping = body.angularDamping, invMass = body.invMass, invInertia = body.invInertia, invInertiaWorld = body.invInertiaWorld } memberSorted : Int -> Int -> List Int -> Bool memberSorted dir x list = case list of [] -> False y :: rest -> if y - x == 0 then True else if dir * (y - x) > 0 then False else memberSorted dir x rest {-| Remove the first occurrence of x and reverse the list in one TCO pass. When x is found, skip it and accumulate the rest into acc — no prependReversed needed. -} removeFirstReversing : Int -> List Int -> List Int -> List Int removeFirstReversing x acc remaining = case remaining of [] -> acc y :: rest -> if y == x then accumulate acc rest else removeFirstReversing x (y :: acc) rest accumulate : List Int -> List Int -> List Int accumulate acc remaining = case remaining of [] -> acc y :: rest -> accumulate (y :: acc) rest -- ── twoPassFast ─────────────────────────────────────────────────────────────── -- Same as twoPass but switches to tpAssignNoDups once dupIds is exhausted, -- eliminating the case dupIds and dir checks for all remaining bodies. assignIdsTwoPassFast : List ( id, InternalBody.Protected ) -> ( List ( id, InternalBody.Body ), Int ) assignIdsTwoPassFast bodies = let ( existingIds, newCount, mx ) = tpCollect bodies [] 0 -1 sorted = List.sort existingIds ( dupIds, dupCount ) = tpFindDups sorted -2 [] 0 freeIds = tpFreeIds 0 sorted (newCount + dupCount) [] in case freeIds of [] -> tpAssignStable bodies mx [] _ -> tpAssignFast bodies freeIds dupIds -1 mx [] tpAssignFast : List ( id, InternalBody.Protected ) -> List Int -> List Int -> Int -> Int -> List ( id, InternalBody.Body ) -> ( List ( id, InternalBody.Body ), Int ) tpAssignFast bodies freeIds dupIds dir mx acc = case freeIds of [] -> tpAssignStable bodies mx acc freshId :: remainingFree -> case bodies of [] -> ( acc, mx ) ( extId, InternalBody.Protected body ) :: rest -> if body.id == -1 then tpAssignFast rest remainingFree dupIds dir (max mx freshId) (( extId, withId freshId body ) :: acc) else if memberSorted dir body.id dupIds then case removeFirstReversing body.id [] dupIds of [] -> tpAssignFast rest remainingFree [] dir (max mx freshId) (( extId, withId freshId body ) :: acc) newDupIds -> tpAssignFast rest remainingFree newDupIds (negate dir) (max mx freshId) (( extId, withId freshId body ) :: acc) else tpAssignFast rest freeIds dupIds dir mx (( extId, body ) :: acc) tpAssignStable : List ( id, InternalBody.Protected ) -> Int -> List ( id, InternalBody.Body ) -> ( List ( id, InternalBody.Body ), Int ) tpAssignStable bodies mx acc = case bodies of [] -> ( acc, mx ) ( extId, InternalBody.Protected body ) :: rest -> tpAssignStable rest mx (( extId, body ) :: acc) ================================================ FILE: benchmarks/src/Convex.elm ================================================ module Convex exposing (main) import Benchmark exposing (Benchmark, describe) import Benchmark.Runner exposing (BenchmarkProgram, program) import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 import Shapes.Convex as Convex main : BenchmarkProgram main = program <| describe "Convex" [ placeIn ] placeIn : Benchmark placeIn = let sampleHull = Convex.fromBlock 2 2 2 transform = Transform3d.atPoint { x = 0, y = 0, z = 2.5 } |> Transform3d.rotateAroundOwn Vec3.yAxis (pi / 4) |> Transform3d.rotateAroundOwn Vec3.xAxis (pi / 20) in Benchmark.compare "placeIn" "baseline" (\_ -> {- Convex.placeInOld -} Convex.placeIn transform sampleHull ) "latest code" (\_ -> Convex.placeIn transform sampleHull ) ================================================ FILE: benchmarks/src/ConvexConvex.elm ================================================ module ConvexConvex exposing (main) import Benchmark exposing (Benchmark, describe) import Benchmark.Runner exposing (BenchmarkProgram, program) import Collision.ConvexConvex import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 import Shapes.Convex as Convex exposing (Convex) main : BenchmarkProgram main = program <| describe "ConvexConvex.getContacts" [ colliding , separated ] colliding : Benchmark colliding = let -- Move the box 0.9 units up and rotate 45 around the y. -- only 0.1 units of the box will be overlapping -- we expect 4 collision points transform3d = Transform3d.atPoint { x = 0, y = 0, z = 0.9 } |> Transform3d.rotateAroundOwn Vec3.yAxis (pi / 4) |> Transform3d.rotateAroundOwn Vec3.xAxis (pi / 20) firstConvex = Convex.placeIn transform3d box secondConvex = Convex.placeIn Transform3d.atOrigin box in Benchmark.compare "colliding" "baseline" (\_ -> {- Collision.ConvexConvex.oldAddContacts -} Collision.ConvexConvex.addContacts firstConvex secondConvex [] ) "latest code" (\_ -> Collision.ConvexConvex.addContacts firstConvex secondConvex [] ) separated : Benchmark separated = let -- Move the box 2.5 units up -- so that boxes don’t overlap transform3d = Transform3d.atPoint { x = 0, y = 0, z = 2.5 } |> Transform3d.rotateAroundOwn Vec3.yAxis (pi / 4) |> Transform3d.rotateAroundOwn Vec3.xAxis (pi / 20) firstConvex = Convex.placeIn transform3d box secondConvex = Convex.placeIn Transform3d.atOrigin box in Benchmark.compare "separated" "baseline" (\_ -> {- Collision.ConvexConvex.oldAddContacts -} Collision.ConvexConvex.addContacts firstConvex secondConvex [] ) "latest code" (\_ -> Collision.ConvexConvex.addContacts firstConvex secondConvex [] ) box : Convex box = Convex.fromBlock 2 2 2 ================================================ FILE: benchmarks/src/EigenDecomposition.elm ================================================ module EigenDecomposition exposing (main) import Benchmark exposing (Benchmark, describe) import Benchmark.Runner exposing (BenchmarkProgram, program) import Internal.Matrix3 as Mat3 exposing (Mat3) import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 exposing (Vec3) main : BenchmarkProgram main = program <| describe "EigenDecomposition" [ sphere , rotatedCylinder ] sphere : Benchmark sphere = let m = Mat3.sphereInertia 5.0 2.0 in Benchmark.compare "sphere" "analytical" (\_ -> analyticalEigenDecomposition m) "jacobi" (\_ -> Mat3.eigenDecomposition m) rotatedCylinder : Benchmark rotatedCylinder = let rotation = Transform3d.rotateAroundOwn (Vec3.normalize { x = 1, y = 1, z = 0 }) (pi / 3) Transform3d.atOrigin m = Transform3d.inertiaRotateIn rotation (Mat3.cylinderInertia 5.0 1.0 3.0) in Benchmark.compare "rotated cylinder" "analytical" (\_ -> analyticalEigenDecomposition m) "jacobi" (\_ -> Mat3.eigenDecomposition m) analyticalEigenDecomposition : Mat3 -> { eigenvalues : Vec3, v1 : Vec3, v2 : Vec3, v3 : Vec3 } analyticalEigenDecomposition m = let p1 = m.m12 * m.m12 + m.m13 * m.m13 + m.m23 * m.m23 in if p1 == 0 then { eigenvalues = { x = m.m11, y = m.m22, z = m.m33 } , v1 = Vec3.xAxis , v2 = Vec3.yAxis , v3 = Vec3.zAxis } else nonDiagonalEigen m p1 nonDiagonalEigen : Mat3 -> Float -> { eigenvalues : Vec3, v1 : Vec3, v2 : Vec3, v3 : Vec3 } nonDiagonalEigen m p1 = let q = (m.m11 + m.m22 + m.m33) / 3 p2 = (m.m11 - q) * (m.m11 - q) + (m.m22 - q) * (m.m22 - q) + (m.m33 - q) * (m.m33 - q) + 2 * p1 p = sqrt (p2 / 6) invP = 1 / p b11 = (m.m11 - q) * invP b22 = (m.m22 - q) * invP b33 = (m.m33 - q) * invP b12 = m.m12 * invP b13 = m.m13 * invP b23 = m.m23 * invP detB = b11 * (b22 * b33 - b23 * b23) - b12 * (b12 * b33 - b23 * b13) + b13 * (b12 * b23 - b22 * b13) r = clamp -1 1 (detB / 2) phi = acos r / 3 eig1 = q + 2 * p * cos phi eig3 = q + 2 * p * cos (phi + 2 * pi / 3) eig2 = 3 * q - eig1 - eig3 gap1 = eig1 - eig2 gap2 = eig2 - eig3 eigenvalues = { x = eig1, y = eig2, z = eig3 } result ev1 ev2 ev3 = { eigenvalues = eigenvalues, v1 = ev1, v2 = ev2, v3 = ev3 } in if gap1 < 1.0e-10 && gap2 < 1.0e-10 then result Vec3.xAxis Vec3.yAxis Vec3.zAxis else if gap1 < 1.0e-10 then let u3 = eigenvectorForEigenvalue m eig3 ( t1, t2 ) = Vec3.tangents u3 in result t1 t2 u3 else if gap2 < 1.0e-10 then let u1 = eigenvectorForEigenvalue m eig1 ( t2, t3 ) = Vec3.tangents u1 in result u1 t2 t3 else let u1 = eigenvectorForEigenvalue m eig1 u3 = eigenvectorForEigenvalue m eig3 u2 = Vec3.cross u3 u1 in result u1 u2 u3 eigenvectorForEigenvalue : Mat3 -> Float -> Vec3 eigenvectorForEigenvalue m eigenvalue = let a11 = m.m11 - eigenvalue a22 = m.m22 - eigenvalue a33 = m.m33 - eigenvalue row0 = { x = a11, y = m.m12, z = m.m13 } row1 = { x = m.m12, y = a22, z = m.m23 } row2 = { x = m.m13, y = m.m23, z = a33 } r0xr1 = Vec3.cross row0 row1 r0xr2 = Vec3.cross row0 row2 r1xr2 = Vec3.cross row1 row2 d0 = Vec3.lengthSquared r0xr1 d1 = Vec3.lengthSquared r0xr2 d2 = Vec3.lengthSquared r1xr2 in if d0 >= d1 && d0 >= d2 then Vec3.scale (1 / sqrt d0) r0xr1 else if d1 >= d2 then Vec3.scale (1 / sqrt d1) r0xr2 else Vec3.scale (1 / sqrt d2) r1xr2 ================================================ FILE: benchmarks/src/SphereConvex.elm ================================================ module SphereConvex exposing (main) import Benchmark exposing (Benchmark, describe) import Benchmark.Runner exposing (BenchmarkProgram, program) import Collision.SphereConvex import Fixtures.Convex import Fixtures.NarrowPhase import Internal.Transform3d as Transform3d import Shapes.Convex as Convex import Shapes.Sphere as Sphere main : BenchmarkProgram main = program suite suite : Benchmark suite = let center = { x = 0, y = 0, z = 7 } radius = 5 boxSize = 2 boxHalfExtent = boxSize / 2 boxHull = Convex.fromBlock boxSize boxSize boxSize boxPositions = Fixtures.NarrowPhase.sphereContactBoxPositions center radius boxHalfExtent |> List.map Tuple.first boxFarPositions = Fixtures.NarrowPhase.sphereContactBoxPositions center (radius * 2) boxHalfExtent |> List.map Tuple.first octoHalfExtent = 3 octoHull = Fixtures.Convex.octoHull octoHalfExtent octoPositions = Fixtures.NarrowPhase.sphereContactOctohedronPositions center radius octoHalfExtent |> List.map Tuple.first octoFarPositions = Fixtures.NarrowPhase.sphereContactOctohedronPositions center (radius * 2) octoHalfExtent |> List.map Tuple.first in describe "SphereConvex" [ Benchmark.compare "box colliding" "baseline" (\_ -> boxPositions |> List.map (\position -> {- Collision.SphereConvex.oldAddContacts -} Collision.SphereConvex.addContacts identity (Sphere.atOrigin radius) (Convex.placeIn (Transform3d.atPoint position) boxHull) [] ) ) "latest code" (\_ -> boxPositions |> List.map (\position -> Collision.SphereConvex.addContacts identity (Sphere.placeIn (Transform3d.atPoint center) (Sphere.atOrigin radius)) (Convex.placeIn (Transform3d.atPoint position) boxHull) [] ) ) , Benchmark.compare "box separated" "baseline" (\_ -> boxFarPositions |> List.map (\position -> {- Collision.SphereConvex.oldAddContacts -} Collision.SphereConvex.addContacts identity (Sphere.placeIn (Transform3d.atPoint center) (Sphere.atOrigin radius)) (Convex.placeIn (Transform3d.atPoint position) boxHull) [] ) ) "latest code" (\_ -> boxFarPositions |> List.map (\position -> Collision.SphereConvex.addContacts identity (Sphere.placeIn (Transform3d.atPoint center) (Sphere.atOrigin radius)) (Convex.placeIn (Transform3d.atPoint position) boxHull) [] ) ) , Benchmark.compare "octahedron colliding" "baseline" (\_ -> octoPositions |> List.map (\position -> {- Collision.SphereConvex.oldAddContacts -} Collision.SphereConvex.addContacts identity (Sphere.placeIn (Transform3d.atPoint center) (Sphere.atOrigin radius)) (Convex.placeIn (Transform3d.atPoint position) octoHull) [] ) ) "latest code" (\_ -> octoPositions |> List.map (\position -> Collision.SphereConvex.addContacts identity (Sphere.placeIn (Transform3d.atPoint center) (Sphere.atOrigin radius)) (Convex.placeIn (Transform3d.atPoint position) octoHull) [] ) ) , Benchmark.compare "octahedron failing" "baseline" (\_ -> octoFarPositions |> List.map (\position -> {- Collision.SphereConvex.oldAddContacts -} Collision.SphereConvex.addContacts identity (Sphere.placeIn (Transform3d.atPoint center) (Sphere.atOrigin radius)) (Convex.placeIn (Transform3d.atPoint position) octoHull) [] ) ) "latest code" (\_ -> octoFarPositions |> List.map (\position -> Collision.SphereConvex.addContacts identity (Sphere.placeIn (Transform3d.atPoint center) (Sphere.atOrigin radius)) (Convex.placeIn (Transform3d.atPoint position) octoHull) [] ) ) ] ================================================ FILE: docs.json ================================================ [{"name":"Physics","comment":"\n\n\n# Bodies\n\n@docs Body, BodyCoordinates, WorldCoordinates\n\n@docs block, plane, sphere, cylinder, pointMass\n\n\n# Positioning\n\n@docs moveTo, translateBy, rotateAround, place\n\n\n# Simulation\n\n@docs simulate, onEarth, Config\n\n@docs Contacts, emptyContacts, contactPoints\n\n\n# Properties\n\n@docs frame, originPoint, velocity, angularVelocity, velocityAt\n\n@docs centerOfMass, mass\n\n\n# Interaction\n\n@docs raycast, applyForce, applyImpulse, applyTorque, applyAngularImpulse\n\n\n# Composite bodies\n\n@docs dynamic, static, kinematic\n\n\n# Overrides\n\nChange body state directly, bypassing the simulation.\n\n@docs setVelocityTo, setAngularVelocityTo, scaleMassTo\n\n\n# Advanced\n\n@docs damp, lock, applyInverseInertia, angularAccelerationFromTorque, angularVelocityDeltaFromAngularImpulse\n\n","unions":[],"aliases":[{"name":"Body","comment":" A body is anything the simulation moves or collides — a ball, a box, a wall, a moving platform.\nIt is defined in [BodyCoordinates](#BodyCoordinates) and positioned in [WorldCoordinates](#WorldCoordinates).\nBodies start out centered on the origin; use [moveTo](#moveTo) to set the position.\n\nThere are three kinds of bodies:\n\n - **dynamic** — moved by the engine in response to forces, gravity, and contacts.\n The default for [block](#block), [sphere](#sphere), [cylinder](#cylinder), and\n [pointMass](#pointMass); use [dynamic](#dynamic) to combine several\n [shapes](Physics-Shape#Shape) into one body.\n\n - **static** — never moves. Used for floors, walls, and other immovable scenery.\n The default for [plane](#plane); use [static](#static) to combine several\n [shapes](Physics-Shape#Shape) into one body.\n\n - **kinematic** — moves at the velocity you set via [setVelocityTo](#setVelocityTo)\n and [setAngularVelocityTo](#setAngularVelocityTo); ignores forces, gravity, and\n contacts, but other dynamic bodies feel its motion through friction. Used for\n moving platforms, elevators, and turntables. Construct with [kinematic](#kinematic).\n\n","args":[],"type":"Physics.Types.Body"},{"name":"BodyCoordinates","comment":" ","args":[],"type":"Internal.Coordinates.BodyCoordinates"},{"name":"Config","comment":" Configures a simulation.\n\n onEarth =\n { gravity = Vector3d.gees 0 0 -1\n , duration = Duration.seconds (1 / 60)\n , solverIterations = 20\n , contacts = emptyContacts\n , constrain = \\_ -> Nothing\n , collide = \\_ _ -> True\n }\n\n - `gravity` — set the gravity vector, or `Vector3d.zero` for no gravity\n\n - `duration` — set to `Duration.seconds (1 / 60)` for 60 fps\n\n - `solverIterations` — balance between precision and performance, 20 is a sweet spot\n\n - `contacts` — pass [Contacts](#Contacts) from the previous frame for warm starting, or leave as default for cold start\n\n - `constrain` — limit body movement relative to each other, see [Constraint](Physics-Constraint#Constraint)\n\n - `collide` — decide which bodies can collide with each other\n\n","args":["id"],"type":"{ gravity : Vector3d.Vector3d Acceleration.MetersPerSecondSquared Physics.WorldCoordinates, duration : Duration.Duration, solverIterations : Basics.Int, contacts : Physics.Contacts id, constrain : id -> Maybe.Maybe (id -> List.List Physics.Constraint.Constraint), collide : id -> id -> Basics.Bool }"},{"name":"Contacts","comment":" Contacts from the most recent simulation frame. Contains contact points\nand solver state for warm starting.\n","args":["id"],"type":"Physics.Types.Contacts id"},{"name":"WorldCoordinates","comment":" ","args":[],"type":"Internal.Coordinates.WorldCoordinates"}],"values":[{"name":"angularAccelerationFromTorque","comment":" Compute angular acceleration from torque: α = I⁻¹τ\n\nHow fast will this torque make the body spin up?\n\n","type":"Physics.Body -> Vector3d.Vector3d Torque.NewtonMeters Physics.WorldCoordinates -> Vector3d.Vector3d AngularAcceleration.RadiansPerSecondSquared Physics.WorldCoordinates"},{"name":"angularVelocity","comment":" Get the current angular velocity of a body.\n","type":"Physics.Body -> Vector3d.Vector3d AngularSpeed.RadiansPerSecond Physics.WorldCoordinates"},{"name":"angularVelocityDeltaFromAngularImpulse","comment":" Compute angular velocity change from an angular impulse: Δω = I⁻¹L\n\nHow much does this impulse add to my current spin?\n\n","type":"Physics.Body -> Vector3d.Vector3d (Quantity.Product Torque.NewtonMeters Duration.Seconds) Physics.WorldCoordinates -> Vector3d.Vector3d AngularSpeed.RadiansPerSecond Physics.WorldCoordinates"},{"name":"applyAngularImpulse","comment":" Apply an angular impulse to a body, adding to its angular velocity.\n\n angularImpulse =\n Vector3d.withLength\n (Quantity.times (Duration.seconds 0.005)\n (Torque.newtonMeters 50)\n )\n Direction3d.positiveZ\n\n spunUp =\n body\n |> applyAngularImpulse angularImpulse\n\n","type":"Vector3d.Vector3d (Quantity.Product Torque.NewtonMeters Duration.Seconds) Physics.WorldCoordinates -> Physics.Body -> Physics.Body"},{"name":"applyForce","comment":" Apply a force at a point on a body.\n\nKeep applying the force every simulation step to accelerate.\n\n force =\n Vector3d.withLength (Force.newtons 50)\n Direction3d.positiveY\n\n pushedBox =\n box\n |> applyForce force pointOnBox\n\n","type":"Vector3d.Vector3d Force.Newtons Physics.WorldCoordinates -> Point3d.Point3d Length.Meters Physics.WorldCoordinates -> Physics.Body -> Physics.Body"},{"name":"applyImpulse","comment":" Apply an impulse at a point on a body, adding to its velocity and angular velocity.\n\n impulse =\n Vector3d.withLength\n (Quantity.times (Duration.seconds 0.005)\n (Force.newtons 50)\n )\n Direction3d.positiveY\n\n hitCueBall =\n cueBall\n |> applyImpulse impulse hitPoint\n\n","type":"Vector3d.Vector3d (Quantity.Product Force.Newtons Duration.Seconds) Physics.WorldCoordinates -> Point3d.Point3d Length.Meters Physics.WorldCoordinates -> Physics.Body -> Physics.Body"},{"name":"applyInverseInertia","comment":" Apply the inverse inertia tensor of a body to a vector.\nReturns Vector3d.zero for static and kinematic bodies.\nFor common cases, see [angularAccelerationFromTorque](#angularAccelerationFromTorque)\nand [angularVelocityDeltaFromAngularImpulse](#angularVelocityDeltaFromAngularImpulse).\n","type":"Physics.Body -> Vector3d.Vector3d units Physics.WorldCoordinates -> Vector3d.Vector3d (Quantity.Rate units (Quantity.Product Mass.Kilograms Area.SquareMeters)) Physics.WorldCoordinates"},{"name":"applyTorque","comment":" Apply a pure torque to a body — spin without translation.\n\nKeep applying the torque every simulation step to spin up.\n\n torque =\n Vector3d.withLength (Torque.newtonMeters 5)\n Direction3d.positiveZ\n\n spinningTop =\n top\n |> applyTorque torque\n\n","type":"Vector3d.Vector3d Torque.NewtonMeters Physics.WorldCoordinates -> Physics.Body -> Physics.Body"},{"name":"block","comment":" ","type":"Block3d.Block3d Length.Meters Physics.BodyCoordinates -> Physics.Material.Material Physics.Material.Dense -> Physics.Body"},{"name":"centerOfMass","comment":" Get the center of mass of a body. Returns Nothing for static and\nkinematic bodies (which have infinite mass).\n","type":"Physics.Body -> Maybe.Maybe (Point3d.Point3d Length.Meters Physics.WorldCoordinates)"},{"name":"contactPoints","comment":" Get contact points from the most recent simulation frame, filtered by a predicate.\nEach entry is a pair of body ids and a list of contact points between them.\n\n crash =\n Physics.contactPoints\n (\\a b -> a == \"jeep\" && b == \"wall\")\n contacts\n\n","type":"(id -> id -> Basics.Bool) -> Physics.Contacts id -> List.List ( id, id, List.List (Point3d.Point3d Length.Meters Physics.WorldCoordinates) )"},{"name":"cylinder","comment":" Create a cylinder, approximated with 12 side faces.\nFor more subdivisions, use [dynamic](#dynamic) with [Shape.cylinder](Physics-Shape#cylinder).\n","type":"Cylinder3d.Cylinder3d Length.Meters Physics.BodyCoordinates -> Physics.Material.Material Physics.Material.Dense -> Physics.Body"},{"name":"damp","comment":" Set linear and angular damping, in order to decrease velocity over time.\nThese parameters specify the proportion of velocity lost per second.\nValues are clamped to [0, 1]. Default: 0.01 for both.\n","type":"{ linear : Basics.Float, angular : Basics.Float } -> Physics.Body -> Physics.Body"},{"name":"dynamic","comment":" Create a dynamic body from shapes and materials. Mass and center of mass\nare derived from geometry and density.\n","type":"List.List ( Physics.Shape.Shape, Physics.Material.Material Physics.Material.Dense ) -> Physics.Body"},{"name":"emptyContacts","comment":" Empty contacts for the first simulation frame (no warm starting).\n","type":"Physics.Contacts id"},{"name":"frame","comment":" Get the position and orientation of the body as Frame3d.\nUseful to transform points and directions between world and body coordinates,\ne.g. for rendering.\n","type":"Physics.Body -> Frame3d.Frame3d Length.Meters Physics.WorldCoordinates { defines : Physics.BodyCoordinates }"},{"name":"kinematic","comment":" Create a kinematic body from shapes and materials. Kinematic bodies\nhave infinite mass like static bodies — forces, gravity, and contacts don’t\npush them — but they’re moved by the engine according to their velocity, set\nvia [setVelocityTo](#setVelocityTo) and [setAngularVelocityTo](#setAngularVelocityTo).\n\nUseful for moving platforms, elevators, conveyor belts, and turntables.\nDynamic bodies see the kinematic’s velocity at the contact, so a box rests\non a moving elevator without sliding off, and a conveyor carries crates\nalong its surface.\n\n elevator =\n Physics.kinematic [ ( Shape.block platform, Material.steel ) ]\n |> Physics.setVelocityTo (Vector3d.metersPerSecond 0 0 0.5)\n\n turntable =\n Physics.kinematic [ ( Shape.cylinder 16 disc, Material.plastic ) ]\n |> Physics.setAngularVelocityTo\n (Vector3d.withLength (AngularSpeed.radiansPerSecond 1)\n Direction3d.positiveZ\n )\n\n","type":"List.List ( Physics.Shape.Shape, Physics.Material.Material any ) -> Physics.Body"},{"name":"lock","comment":" Restrict a body’s degrees of freedom along world axes. The list fully\ndescribes the lock state — calling `lock` again replaces the previous\nmasks. An empty list clears all locks.\n\n -- 2D-in-3D gameplay on the XY plane\n body |> lock [ Lock.translateZ, Lock.rotateX, Lock.rotateY ]\n\n -- character controller: slides freely, never tips\n body |> lock Lock.allRotation\n\nSee [Physics.Lock](Physics-Lock) for the available tokens. Has no effect on\nstatic or kinematic bodies.\n\n","type":"List.List Physics.Lock.Lock -> Physics.Body -> Physics.Body"},{"name":"mass","comment":" Get the mass of a body. Returns Nothing for static and kinematic\nbodies (which have infinite mass).\n","type":"Physics.Body -> Maybe.Maybe Mass.Mass"},{"name":"moveTo","comment":" Set the position of the body, e.g. to raise a body 5 meters above the origin:\n\n movedBody =\n body\n |> moveTo (Point3d.meters 0 0 5)\n\n","type":"Point3d.Point3d Length.Meters Physics.WorldCoordinates -> Physics.Body -> Physics.Body"},{"name":"onEarth","comment":" A ready-to-use simulation config with Earth gravity pointing down (-Z)\nat 60 fps. Customize it by updating individual fields,\nsee [Config](#Config) for available options.\n","type":"Physics.Config id"},{"name":"originPoint","comment":" Get the origin point of a body.\n","type":"Physics.Body -> Point3d.Point3d Length.Meters Physics.WorldCoordinates"},{"name":"place","comment":" Set the position and orientation of a body. Like [moveTo](#moveTo)\nbut also sets the orientation.\n\n placedBody =\n body\n |> place\n (Frame3d.atPoint (Point3d.meters 2 0 1)\n |> Frame3d.rotateAround Axis3d.z\n (Angle.degrees 45)\n )\n\nLeft-handed frames are not supported — Z is recomputed from X and Y.\n\n","type":"Frame3d.Frame3d Length.Meters Physics.WorldCoordinates { defines : Physics.BodyCoordinates } -> Physics.Body -> Physics.Body"},{"name":"plane","comment":" Create a static plane, collidable only from the direction of the normal,\ne.g. for +Z:\n\n floor =\n Physics.plane Plane3d.xy Material.wood\n\n","type":"Plane3d.Plane3d Length.Meters Physics.BodyCoordinates -> Physics.Material.Material any -> Physics.Body"},{"name":"pointMass","comment":" Create a point mass — a body with mass but no extent. Two point masses\npass through each other; with all other bodies they collide normally.\n","type":"Point3d.Point3d Length.Meters Physics.WorldCoordinates -> Mass.Mass -> Physics.Material.Material any -> Physics.Body"},{"name":"raycast","comment":" Find the closest intersection of a ray against a list of bodies.\n\n - point masses are always excluded because they are infinitely small\n - a plane only intersects when the ray is facing the plane’s normal\n\n","type":"Axis3d.Axis3d Length.Meters Physics.WorldCoordinates -> List.List ( id, Physics.Body ) -> Maybe.Maybe ( id, Physics.Body, { point : Point3d.Point3d Length.Meters Physics.WorldCoordinates, normal : Direction3d.Direction3d Physics.WorldCoordinates } )"},{"name":"rotateAround","comment":" Rotate the body around an axis in the world,\ne.g. to rotate a body 45 degrees around the Z axis:\n\n rotatedBody =\n body\n |> rotateAround Axis3d.z (Angle.degrees 45)\n\n","type":"Axis3d.Axis3d Length.Meters Physics.WorldCoordinates -> Angle.Angle -> Physics.Body -> Physics.Body"},{"name":"scaleMassTo","comment":" Scale a body to the given mass. The volume and center of mass\nare preserved. Has no effect on static or kinematic bodies.\n","type":"Mass.Mass -> Physics.Body -> Physics.Body"},{"name":"setAngularVelocityTo","comment":" Replace the angular velocity of a body. See [setVelocityTo](#setVelocityTo)\nfor guidance. Has no effect on static bodies.\n","type":"Vector3d.Vector3d AngularSpeed.RadiansPerSecond Physics.WorldCoordinates -> Physics.Body -> Physics.Body"},{"name":"setVelocityTo","comment":" Replace the linear velocity of a body. Works on both [dynamic](#dynamic)\nand [kinematic](#kinematic) bodies.\n\nFor dynamic bodies, prefer [applyImpulse](#applyImpulse) — using `setVelocityTo`\nafter `applyImpulse` silently discards the impulse:\n\n body\n |> applyImpulse impulse point\n -- erased by the next line\n |> setVelocityTo newVelocity\n\nFor kinematic bodies, this is the primary way to drive motion — the engine\nintegrates the position from the velocity each frame.\n\nHas no effect on static bodies.\n\n","type":"Vector3d.Vector3d Speed.MetersPerSecond Physics.WorldCoordinates -> Physics.Body -> Physics.Body"},{"name":"simulate","comment":" Simulates one frame. Returns updated bodies and contacts.\nCall this on a message from the `onAnimationFrame` subscription\nwith [onEarth](#onEarth) to simulate 1/60th of a second in Earth gravity:\n\n ( simulated, contacts ) =\n simulate onEarth model.bodies\n\nTo improve solver stability for stacked objects, pass contacts\nfrom the previous frame back via the config’s `contacts` field:\n\n ( simulated, contacts ) =\n simulate { onEarth | contacts = model.contacts }\n model.bodies\n\n","type":"Physics.Config id -> List.List ( id, Physics.Body ) -> ( List.List ( id, Physics.Body ), Physics.Contacts id )"},{"name":"sphere","comment":" ","type":"Sphere3d.Sphere3d Length.Meters Physics.BodyCoordinates -> Physics.Material.Material Physics.Material.Dense -> Physics.Body"},{"name":"static","comment":" Create a static body from shapes and materials.\nStatic bodies only collide with dynamic bodies, not other static bodies.\n","type":"List.List ( Physics.Shape.Shape, Physics.Material.Material any ) -> Physics.Body"},{"name":"translateBy","comment":" Move the body relative to its current position,\ne.g. to translate a body down by 5 meters:\n\n translatedBody =\n body\n |> translateBy (Vector3d.meters 0 0 -5)\n\n","type":"Vector3d.Vector3d Length.Meters Physics.WorldCoordinates -> Physics.Body -> Physics.Body"},{"name":"velocity","comment":" Get the current linear velocity of a body.\n","type":"Physics.Body -> Vector3d.Vector3d Speed.MetersPerSecond Physics.WorldCoordinates"},{"name":"velocityAt","comment":" Get the linear velocity of a point on a body.\nTakes into account both linear and angular velocities.\n","type":"Point3d.Point3d Length.Meters Physics.WorldCoordinates -> Physics.Body -> Vector3d.Vector3d Speed.MetersPerSecond Physics.WorldCoordinates"}],"binops":[]},{"name":"Physics.Constraint","comment":"\n\n@docs Constraint\n\n@docs pointToPoint, hinge, distance, lock\n\n","unions":[],"aliases":[{"name":"Constraint","comment":" Limit the freedom of movement of two bodies relative to each other.\n\nPass a `constrain` function in the [simulation config](Physics#Config). For example, to drag\na body with the mouse, connect a mouse to a point on the box with\n[pointToPoint](#pointToPoint):\n\n constrain id =\n if id == \"mouse\" then\n Just\n (\\otherId ->\n if otherId == \"box\" then\n [ pointToPoint Point3d.origin pointOnBox ]\n\n else\n []\n )\n\n else\n Nothing\n\nConstraints are skipped unless at least one body is [dynamic](Physics-Body#dynamic).\n\n","args":[],"type":"Physics.Types.Constraint"}],"values":[{"name":"distance","comment":" Keep the centers of mass of two bodies at the constant distance\nfrom each other.\n","type":"Length.Length -> Physics.Constraint.Constraint"},{"name":"hinge","comment":" Keep two bodies connected with each other and limit the freedom of rotation.\nUseful for e.g. connecting a window to a window frame, or to connect a wheel to a car.\n","type":"Axis3d.Axis3d Length.Meters Internal.Coordinates.BodyCoordinates -> Axis3d.Axis3d Length.Meters Internal.Coordinates.BodyCoordinates -> Physics.Constraint.Constraint"},{"name":"lock","comment":" Keep two bodies connected with each other and remove all degrees of freedom between bodies.\n","type":"Frame3d.Frame3d Length.Meters Internal.Coordinates.BodyCoordinates {} -> Frame3d.Frame3d Length.Meters Internal.Coordinates.BodyCoordinates {} -> Physics.Constraint.Constraint"},{"name":"pointToPoint","comment":" Connect a point on the first body with a point on the second body.\nThis doesn’t limit the freedom of rotation of two bodies.\n","type":"Point3d.Point3d Length.Meters Internal.Coordinates.BodyCoordinates -> Point3d.Point3d Length.Meters Internal.Coordinates.BodyCoordinates -> Physics.Constraint.Constraint"}],"binops":[]},{"name":"Physics.Lock","comment":" Restrict a body’s degrees of freedom along world axes.\n\nPass a list of `Lock` tokens to [Physics.lock](Physics#lock). Each entry\nremoves one degree of freedom from the body. The list fully describes the\nbody’s lock state — calling `lock` again with a different list replaces the\nprevious one. An empty list clears all locks.\n\n@docs Lock\n\n\n# Translation\n\n@docs translateX, translateY, translateZ\n\n\n# Rotation\n\n@docs rotateX, rotateY, rotateZ\n\n\n# Presets\n\n@docs allTranslation, allRotation\n\n","unions":[],"aliases":[{"name":"Lock","comment":" A single degree of freedom to lock.\n","args":[],"type":"Physics.Types.Lock"}],"values":[{"name":"allRotation","comment":" All three rotation axes locked. The body cannot tip or spin.\nUseful for character capsules whose orientation is driven outside\nof physics.\n","type":"List.List Physics.Lock.Lock"},{"name":"allTranslation","comment":" All three translation axes locked. The body cannot move.\n","type":"List.List Physics.Lock.Lock"},{"name":"rotateX","comment":" Lock rotation about the world X axis.\n","type":"Physics.Lock.Lock"},{"name":"rotateY","comment":" Lock rotation about the world Y axis.\n","type":"Physics.Lock.Lock"},{"name":"rotateZ","comment":" Lock rotation about the world Z axis.\n","type":"Physics.Lock.Lock"},{"name":"translateX","comment":" Lock translation along the world X axis.\n","type":"Physics.Lock.Lock"},{"name":"translateY","comment":" Lock translation along the world Y axis.\n","type":"Physics.Lock.Lock"},{"name":"translateZ","comment":" Lock translation along the world Z axis.\n","type":"Physics.Lock.Lock"}],"binops":[]},{"name":"Physics.Material","comment":"\n\n@docs Material\n\n@docs wood, rubber, steel, ice, plastic\n\n\n# Custom materials\n\n@docs Dense, dense, Surface, surface\n\n","unions":[{"name":"Dense","comment":" Material with density, required for dynamic bodies\nwhere mass is computed from geometry.\n","args":[],"cases":[]},{"name":"Surface","comment":" Material with surface properties only — friction and bounciness,\nno density. Used for static bodies and point masses.\n","args":[],"cases":[]}],"aliases":[{"name":"Material","comment":" Material encodes friction, bounciness, and optionally density.\n\nThe type parameter tracks capabilities:\n\n - `Material Dense` — carries density (required for volumetric bodies)\n - `Material Surface` — surface properties only (for static bodies and point masses)\n\n**Friction** controls how much a body resists sliding against another.\n0 means frictionless (like ice), 1 means maximum grip (like rubber).\n\n**Bounciness** (coefficient of restitution) controls how much kinetic energy\nis preserved after a collision. 0 means no bounce (the body absorbs the impact),\n1 means a perfectly elastic bounce.\n\nWhen two shapes collide, their friction values are combined using the geometric\nmean √(f1 · f2), so a slippery surface dominates. Bounciness uses the\nmaximum of the two values, so the bouncier surface wins.\n\n","args":["kind"],"type":"Physics.Types.Material kind"}],"values":[{"name":"dense","comment":" Create a dense material.\n\nDensity is clamped to at least 1 kg/m³. Friction and bounciness are clamped to [0, 1].\n\n","type":"{ density : Density.Density, friction : Basics.Float, bounciness : Basics.Float } -> Physics.Material.Material Physics.Material.Dense"},{"name":"ice","comment":" Density 900 kg/m³, friction 0.03, bounciness 0.1.\n","type":"Physics.Material.Material any"},{"name":"plastic","comment":" Density 1050 kg/m³, friction 0.35, bounciness 0.45.\n","type":"Physics.Material.Material any"},{"name":"rubber","comment":" Density 1100 kg/m³, friction 0.8, bounciness 0.7.\n","type":"Physics.Material.Material any"},{"name":"steel","comment":" Density 7800 kg/m³, friction 0.3, bounciness 0.2.\n","type":"Physics.Material.Material any"},{"name":"surface","comment":" Create a surface material.\n\nFriction and bounciness are clamped to [0, 1].\n\n","type":"{ friction : Basics.Float, bounciness : Basics.Float } -> Physics.Material.Material Physics.Material.Surface"},{"name":"wood","comment":" Density 700 kg/m³, friction 0.4, bounciness 0.3.\n","type":"Physics.Material.Material any"}],"binops":[]},{"name":"Physics.Shape","comment":"\n\n@docs Shape, block, sphere, cylinder\n\n\n# Complex shapes\n\n@docs minus, plus, sum, unsafeConvex\n\n","unions":[],"aliases":[{"name":"Shape","comment":" Shapes are needed for creating compound [dynamic](Physics#dynamic)\nand [static](Physics#static) bodies.\n\nThe supported primitive shapes are [block](#block), [sphere](#sphere),\nand [cylinder](#cylinder). For complex geometry use [unsafeConvex](#unsafeConvex).\n\nShapes within a body **should not overlap** — composing shapes only affects physical\nproperties like mass, inertia, and center of mass. Use [plus](#plus) and\n[minus](#minus) to combine shapes, for example to create hollow bodies.\n\n","args":[],"type":"Physics.Types.Shape"}],"values":[{"name":"block","comment":" ","type":"Block3d.Block3d Length.Meters Internal.Coordinates.BodyCoordinates -> Physics.Shape.Shape"},{"name":"cylinder","comment":" Create a cylinder shape with the given number of side faces, clamped to at least 3.\nEven numbers are more efficient, because collision performance depends\non the number of unique non-parallel faces and edges.\n","type":"Basics.Int -> Cylinder3d.Cylinder3d Length.Meters Internal.Coordinates.BodyCoordinates -> Physics.Shape.Shape"},{"name":"minus","comment":" Subtract the first shape from the second. The subtracted shape must be\nfully contained within the other. It reduces volume, mass, and inertia,\nand is excluded from collision detection.\n\nUseful for hollow bodies.\n\n crate =\n Shape.block outer\n |> Shape.minus (Shape.block inner)\n\n","type":"Physics.Shape.Shape -> Physics.Shape.Shape -> Physics.Shape.Shape"},{"name":"plus","comment":" Add a shape to another.\n\n snowman =\n Shape.sphere bottom\n |> Shape.plus (Shape.sphere top)\n\n","type":"Physics.Shape.Shape -> Physics.Shape.Shape -> Physics.Shape.Shape"},{"name":"sphere","comment":" ","type":"Sphere3d.Sphere3d Length.Meters Internal.Coordinates.BodyCoordinates -> Physics.Shape.Shape"},{"name":"sum","comment":" Combine a list of shapes.\n\n dumbbell =\n Shape.sum\n [ Shape.cylinder 12 leftWeight\n , Shape.cylinder 12 bar\n , Shape.cylinder 12 rightWeight\n ]\n\n","type":"List.List Physics.Shape.Shape -> Physics.Shape.Shape"},{"name":"unsafeConvex","comment":" Create a shape from a triangular mesh. This is useful if you want\nto import from Blender using [elm-obj-file](https://package.elm-lang.org/packages/w0rm/elm-obj-file/latest).\n\n**Note:** this may cause unexpected behavior, unless you make sure that:\n\n - the mesh is a [convex polyhedron](https://en.wikipedia.org/wiki/Convex_polytope);\n - the mesh is watertight, consisting of one closed surface;\n - all faces have counterclockwise [winding order](https://cmichel.io/understanding-front-faces-winding-order-and-normals).\n\n","type":"TriangularMesh.TriangularMesh (Point3d.Point3d Length.Meters Internal.Coordinates.BodyCoordinates) -> Physics.Shape.Shape"}],"binops":[]}] ================================================ FILE: elm.json ================================================ { "type": "package", "name": "w0rm/elm-physics", "summary": "3D physics engine", "license": "BSD-3-Clause", "version": "6.0.1", "exposed-modules": [ "Physics", "Physics.Material", "Physics.Constraint", "Physics.Lock", "Physics.Shape" ], "elm-version": "0.19.0 <= v < 0.20.0", "dependencies": { "elm/core": "1.0.0 <= v < 2.0.0", "ianmackenzie/elm-geometry": "4.0.0 <= v < 5.0.0", "ianmackenzie/elm-triangular-mesh": "1.1.0 <= v < 2.0.0", "ianmackenzie/elm-units": "2.7.0 <= v < 3.0.0" }, "test-dependencies": { "elm-explorations/test": "2.2.0 <= v < 3.0.0" } } ================================================ FILE: examples/elm.json ================================================ { "type": "application", "source-directories": [ "src", "../src" ], "elm-version": "0.19.1", "dependencies": { "direct": { "avh4/elm-color": "1.0.0", "elm/browser": "1.0.2", "elm/core": "1.0.5", "elm/html": "1.0.1", "elm/http": "2.0.0", "elm/json": "1.1.4", "elm-explorations/webgl": "1.1.3", "ianmackenzie/elm-3d-camera": "4.0.1", "ianmackenzie/elm-3d-scene": "1.1.0", "ianmackenzie/elm-geometry": "4.0.0", "ianmackenzie/elm-triangular-mesh": "1.1.0", "ianmackenzie/elm-units": "2.10.0", "w0rm/elm-obj-file": "1.4.0" }, "indirect": { "elm/bytes": "1.0.8", "elm/file": "1.0.5", "elm/random": "1.0.0", "elm/time": "1.0.0", "elm/url": "1.0.0", "elm/virtual-dom": "1.0.5", "elm-explorations/linear-algebra": "1.0.3", "ianmackenzie/elm-1d-parameter": "1.0.1", "ianmackenzie/elm-float-extra": "1.1.0", "ianmackenzie/elm-geometry-linear-algebra-interop": "2.0.3", "ianmackenzie/elm-interval": "3.1.0", "ianmackenzie/elm-units-interval": "3.2.0" } }, "test-dependencies": { "direct": {}, "indirect": {} } } ================================================ FILE: examples/review/elm.json ================================================ { "type": "application", "source-directories": [ "src" ], "elm-version": "0.19.1", "dependencies": { "direct": { "elm/core": "1.0.5", "elm/json": "1.1.4", "elm/project-metadata-utils": "1.0.2", "jfmengels/elm-review": "2.16.6", "jfmengels/elm-review-performance": "1.0.2", "jfmengels/elm-review-simplify": "2.1.15", "jfmengels/elm-review-unused": "1.2.6", "stil4m/elm-syntax": "7.3.9" }, "indirect": { "elm/bytes": "1.0.8", "elm/html": "1.0.1", "elm/parser": "1.1.0", "elm/random": "1.0.0", "elm/regex": "1.0.0", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.5", "elm-explorations/test": "2.2.1", "pzp1997/assoc-list": "1.0.0", "rtfeldman/elm-hex": "1.0.0", "stil4m/structured-writer": "1.0.3" } }, "test-dependencies": { "direct": { "elm-explorations/test": "2.2.1" }, "indirect": {} } } ================================================ FILE: examples/review/src/ReviewConfig.elm ================================================ module ReviewConfig exposing (config) {-| Do not rename the ReviewConfig module or the config function, because `elm-review` will look for these. To add packages that contain rules, add them to this review project using `elm install author/packagename` when inside the directory containing this file. -} import NoUnoptimizedRecursion import NoUnused.CustomTypeConstructorArgs import NoUnused.CustomTypeConstructors import NoUnused.Dependencies import NoUnused.Exports import NoUnused.Modules import NoUnused.Parameters import NoUnused.Patterns import NoUnused.Variables import Review.Rule exposing (Rule) import Simplify config : List Rule config = List.map (Review.Rule.ignoreErrorsForDirectories [ "../tests" ]) [ NoUnused.CustomTypeConstructors.rule [] , NoUnused.CustomTypeConstructorArgs.rule , NoUnused.Dependencies.rule , NoUnused.Exports.rule |> Review.Rule.ignoreErrorsForDirectories [ "../src" ] , NoUnused.Modules.rule , NoUnused.Parameters.rule , NoUnused.Patterns.rule , NoUnused.Variables.rule , Simplify.rule (Simplify.expectNaN Simplify.defaults) , NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optOutWithComment "IGNORE TCO") ] ================================================ FILE: examples/src/Duckling.elm ================================================ module Duckling exposing (main) {-| This demo loads a convex shape and a mesh from the same OBJ file. - elm-physics is used for simulation - elm-3d-scene is used for rendering - elm-obj-file is used for loading OBJ file It is important to keep the convex shape as small as possible, because this affects the simulation performance. -} import Angle import Axis3d import Block3d import Browser import Browser.Dom import Browser.Events import Camera3d import Color exposing (Color) import Direction3d import Frame3d import Html exposing (Html) import Http import Length import Obj.Decode exposing (Decoder) import Physics exposing (Body, BodyCoordinates, onEarth) import Physics.Material import Physics.Shape exposing (Shape) import Pixels exposing (Pixels) import Plane3d import Point3d import Quantity exposing (Quantity) import Scene3d import Scene3d.Material exposing (Texture) import Scene3d.Mesh exposing (Shadow, Textured) import Task import WebGL.Texture type Id = Duckling | Floor type alias Model = { material : Maybe (Scene3d.Material.Textured BodyCoordinates) , meshData : Maybe { mesh : Textured BodyCoordinates, shadow : Shadow BodyCoordinates } , bodies : List ( Id, Body ) , contacts : Physics.Contacts Id , dimensions : ( Quantity Int Pixels, Quantity Int Pixels ) } type Msg = LoadedTexture (Result WebGL.Texture.Error (Texture Color)) | LoadedMeshes (Result Http.Error ( Textured BodyCoordinates, Shape )) | Resize Int Int | Tick main : Program () Model Msg main = Browser.element { init = init , update = \msg model -> ( update msg model, Cmd.none ) , view = view , subscriptions = subscriptions } init : () -> ( Model, Cmd Msg ) init () = ( { material = Nothing , meshData = Nothing , bodies = [] , contacts = Physics.emptyContacts , dimensions = ( Pixels.int 0, Pixels.int 0 ) } , Cmd.batch [ Scene3d.Material.load "Duckling.png" |> Task.attempt LoadedTexture , Http.get { url = "Duckling.obj.txt" -- .txt is required to work with `elm reactor` , expect = Obj.Decode.expectObj LoadedMeshes Length.meters meshAndCollider } , Task.perform (\{ viewport } -> Resize (round viewport.width) (round viewport.height)) Browser.Dom.getViewport ] ) {-| Decode a render mesh and physics collider pair from the OBJ file. -} meshAndCollider : Decoder ( Textured BodyCoordinates, Shape ) meshAndCollider = Obj.Decode.map2 (\mesh collider -> ( Scene3d.Mesh.texturedFaces mesh , Physics.Shape.unsafeConvex collider ) ) (Obj.Decode.object "mesh" (Obj.Decode.texturedFacesIn Frame3d.atOrigin)) (Obj.Decode.object "convex" (Obj.Decode.trianglesIn Frame3d.atOrigin)) update : Msg -> Model -> Model update msg model = case msg of LoadedTexture (Ok texture) -> { model | material = Just (Scene3d.Material.texturedMatte texture) } LoadedTexture (Err _) -> model LoadedMeshes (Ok ( mesh, shape )) -> { model | meshData = Just { mesh = mesh, shadow = Scene3d.Mesh.shadow mesh } , bodies = let ducklingAt ( axis, degrees, position ) = ( Duckling , Physics.dynamic [ ( shape, Physics.Material.rubber ) ] |> Physics.rotateAround axis degrees |> Physics.moveTo position ) in List.foldl (\location bodies -> ducklingAt location :: bodies) [ ( Floor , Physics.plane Plane3d.xy Physics.Material.wood |> Physics.moveTo (Point3d.meters 0 0 -3) ) ] [ ( Axis3d.x, Angle.degrees 45, Point3d.meters 0 0 6 ) , ( Axis3d.y, Angle.degrees -35, Point3d.meters 0.1 -0.5 4 ) , ( Axis3d.y, Angle.degrees 35, Point3d.meters 0 0.5 8 ) , ( Axis3d.x, Angle.degrees -45, Point3d.meters 0 0 10 ) ] } LoadedMeshes (Err _) -> model Tick -> let ( simulated, newContacts ) = Physics.simulate { onEarth | contacts = model.contacts } model.bodies in { model | bodies = simulated, contacts = newContacts } Resize width height -> { model | dimensions = ( Pixels.int width, Pixels.int height ) } view : Model -> Html Msg view model = let camera = Camera3d.orbitZ { focalPoint = Point3d.meters 0 0 0 , azimuth = Angle.degrees 45 , elevation = Angle.degrees 25 , distance = Length.meters 25 , projection = Camera3d.Perspective , fov = Camera3d.angle (Angle.degrees 30) } in case ( model.material, model.meshData ) of ( Just material, Just { mesh, shadow } ) -> Scene3d.sunny { upDirection = Direction3d.positiveZ , sunlightDirection = Direction3d.negativeZ , shadows = True , camera = camera , dimensions = model.dimensions , background = Scene3d.transparentBackground , clipDepth = Length.meters 0.1 , entities = List.map (\( data, body ) -> Scene3d.placeIn (Physics.frame body) <| case data of Duckling -> Scene3d.meshWithShadow material mesh shadow Floor -> ( Length.meters 25, Length.meters 25, Length.millimeters 10 ) |> Block3d.centeredOn Frame3d.atOrigin |> Scene3d.block (Scene3d.Material.matte Color.darkCharcoal) ) model.bodies } _ -> Html.text "Loading texture and meshes…" subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Browser.Events.onResize Resize , Browser.Events.onAnimationFrame (\_ -> Tick) ] ================================================ FILE: examples/src/Duckling.obj.txt ================================================ # Blender v2.83.3 OBJ File: 'duckling.blend' # www.blender.org o convex v 0.539748 -0.217039 0.099674 v -0.466622 -0.250595 0.180423 v 0.770754 -0.163748 0.219660 v 0.622595 -0.417535 0.335006 v -0.631297 0.136631 0.856073 v -0.603059 -0.292105 0.600769 v -0.031727 -0.384307 0.117068 v -0.277162 -0.488889 0.397387 v 0.226935 -0.541566 0.379049 v 0.748814 0.186307 0.170598 v -0.217868 0.429769 0.097289 v -0.311506 0.504301 0.227875 v 0.599979 0.507101 0.428845 v -0.503824 0.482696 0.507469 v -0.702860 0.079971 0.424965 v 0.268738 0.599490 0.321229 v 0.151210 -0.204527 1.538399 v 0.123199 0.096572 1.610852 v 0.408792 -0.321258 1.471410 v 0.373507 -0.031451 1.635867 v 0.981508 0.197007 1.360425 v 0.305546 0.365010 1.527507 vn -0.1093 -0.8962 -0.4300 vn 0.1958 0.5286 -0.8260 vn -0.7454 0.4660 -0.4767 vn -0.0785 -0.0422 -0.9960 vn -0.2209 -0.2475 0.9434 vn -0.5241 0.6925 0.4958 vn -0.1569 0.9871 -0.0316 vn 0.2094 0.9610 0.1804 vn 0.2821 0.9389 0.1970 vn 0.8636 -0.5042 -0.0001 vn 0.3102 -0.9406 0.1381 vn 0.6788 -0.7099 0.1879 vn -0.5660 0.5695 0.5960 vn -0.1011 0.9760 0.1928 vn -0.9677 -0.1729 0.1834 vn 0.1049 0.1192 -0.9873 vn 0.4966 -0.3819 0.7795 vn 0.2983 0.2989 0.9065 vn -0.6951 -0.4011 0.5966 vn -0.6945 -0.2288 0.6821 vn -0.6768 0.3152 -0.6652 vn -0.9000 0.4257 0.0935 vn 0.8356 0.5230 -0.1681 vn -0.0550 0.8497 -0.5244 vn -0.3168 -0.7638 -0.5623 vn -0.0939 -0.9728 0.2118 vn -0.3199 -0.8835 0.3422 vn -0.3148 -0.8876 0.3362 vn 0.0420 0.2701 0.9619 vn 0.0027 -0.4933 0.8699 vn 0.4757 -0.0928 -0.8747 vn -0.6183 0.1314 -0.7749 vn 0.3911 0.6804 -0.6198 vn 0.4770 -0.5790 -0.6612 vn 0.1884 -0.7138 -0.6745 vn 0.1694 -0.7622 -0.6248 vn -0.6164 -0.7386 -0.2730 vn -0.8667 -0.3833 -0.3192 vn 0.9808 0.0345 -0.1921 s off f 7//1 9//1 8//1 f 16//2 10//2 11//2 f 15//3 14//3 12//3 f 7//4 2//4 11//4 1//4 f 18//5 17//5 20//5 f 5//6 22//6 14//6 f 14//7 16//7 12//7 f 22//8 13//8 16//8 f 22//9 21//9 13//9 f 21//10 4//10 3//10 f 19//11 9//11 4//11 f 21//12 19//12 4//12 f 22//13 5//13 18//13 f 22//14 16//14 14//14 f 5//15 15//15 6//15 f 10//16 1//16 11//16 f 20//17 19//17 21//17 f 22//18 20//18 21//18 f 17//19 5//19 6//19 f 17//20 18//20 5//20 f 15//21 12//21 11//21 f 15//22 5//22 14//22 f 21//23 10//23 13//23 f 12//24 16//24 11//24 f 8//25 2//25 7//25 f 9//26 19//26 8//26 f 19//27 6//27 8//27 f 19//28 17//28 6//28 f 20//29 22//29 18//29 f 20//30 17//30 19//30 f 10//31 3//31 1//31 f 15//32 11//32 2//32 f 13//33 10//33 16//33 f 4//34 1//34 3//34 f 4//35 7//35 1//35 f 4//36 9//36 7//36 f 8//37 6//37 2//37 f 6//38 15//38 2//38 f 21//39 3//39 10//39 o mesh v -0.115390 -0.199241 0.101510 v -0.159268 -0.163800 0.101577 v 0.125907 0.038053 0.101733 v -0.193300 -0.116357 0.101651 v -0.225381 -0.044015 0.101681 v 0.465807 -0.008041 0.101555 v 0.444157 -0.075697 0.101281 v -0.251531 0.022068 0.101622 v -0.227316 0.087988 0.101651 v -0.216462 0.142928 0.101607 v -0.239364 -0.306125 0.115353 v -0.187264 -0.266814 0.101080 v -0.156992 -0.342321 0.114278 v -0.114590 -0.298243 0.100413 v -0.245326 -0.222069 0.101147 v -0.305196 -0.251326 0.116272 v -0.351528 -0.177191 0.116836 v -0.285504 -0.157995 0.101785 v -0.311142 -0.063849 0.101703 v -0.379272 -0.069973 0.117800 v 0.477677 -0.336212 0.141162 v 0.615472 -0.252082 0.142726 v 0.518411 -0.365602 0.177055 v 0.657206 -0.270862 0.183720 v 0.560117 -0.226963 0.114345 v 0.436854 -0.300164 0.114300 v -0.286467 -0.339793 0.144120 v -0.194590 -0.379267 0.143090 v -0.360670 -0.276816 0.145054 v -0.329292 -0.367248 0.185574 v -0.409218 -0.296382 0.186322 v -0.232343 -0.410429 0.181488 v -0.409463 -0.192219 0.146426 v -0.441714 -0.072783 0.146552 v -0.461733 -0.205246 0.186604 v -0.502675 -0.073635 0.183890 v 0.686182 -0.140757 0.142022 v 0.624487 -0.129806 0.113989 v 0.733418 -0.149194 0.183468 v 0.557803 -0.385465 0.224988 v 0.688732 -0.284208 0.234107 v 0.602326 -0.395949 0.281944 v 0.712984 -0.293335 0.290359 v -0.369819 -0.385844 0.237436 v -0.270171 -0.430366 0.234671 v -0.452302 -0.309246 0.236628 v -0.410731 -0.394763 0.293325 v -0.489500 -0.314102 0.289358 v -0.329737 -0.434770 0.296914 v 0.769844 -0.155749 0.234678 v 0.794615 -0.160672 0.291894 v 0.728865 -0.298369 0.351223 v 0.632406 -0.398307 0.345574 v 0.652513 -0.398840 0.413955 v 0.738118 -0.298584 0.417425 v 0.809318 -0.164090 0.353877 v 0.818608 -0.167292 0.421140 v 0.646263 -0.382648 0.554849 v 0.660817 -0.392449 0.485555 v 0.726597 -0.292468 0.550030 v 0.739987 -0.296857 0.485347 v 0.815894 -0.169628 0.488387 v 0.801688 -0.169480 0.551853 v 0.555179 -0.357358 0.677340 v 0.611927 -0.372950 0.620057 v 0.665829 -0.279218 0.664165 v 0.701700 -0.286395 0.610152 v 0.777874 -0.167611 0.610196 v 0.748647 -0.166010 0.663268 v 0.434971 -0.318699 0.756992 v 0.485084 -0.339029 0.721529 v 0.569340 -0.257495 0.750037 v 0.617637 -0.268557 0.710370 v 0.716321 -0.166670 0.711772 v 0.671835 -0.166366 0.754493 v -0.121248 -0.289191 0.739991 v 0.019364 -0.338763 0.728387 v -0.074004 -0.263759 0.761581 v 0.036476 -0.305480 0.759142 v -0.033796 -0.231953 0.780576 v 0.053181 -0.269639 0.784269 v 0.148335 -0.320471 0.763145 v 0.147016 -0.288886 0.788465 v 0.147601 -0.351574 0.738701 v -0.147042 -0.197209 0.766096 v -0.192017 -0.073361 0.778115 v -0.209470 -0.213995 0.750059 v -0.247824 -0.076512 0.782304 v -0.132799 -0.067808 0.782816 v -0.093096 -0.176635 0.780917 v 0.023331 -0.107014 0.812658 v 0.006560 -0.039938 0.810723 v 0.049422 -0.158195 0.814267 v -0.553055 -0.201865 0.814823 v -0.495150 -0.207678 0.838778 v -0.547257 -0.088449 0.899761 v -0.485489 -0.090651 0.922656 v -0.424217 -0.090221 0.915301 v -0.441025 -0.210800 0.828784 v -0.483998 -0.269595 0.772606 v -0.441714 -0.261268 0.778938 v -0.536566 -0.269735 0.747375 v -0.638341 -0.177050 0.698359 v -0.603339 -0.190618 0.763635 v -0.647587 -0.071530 0.738997 v -0.601596 -0.082814 0.844057 v -0.582579 -0.264175 0.706396 v -0.615691 -0.252253 0.656958 v -0.581459 -0.315845 0.604109 v -0.545522 -0.329243 0.648765 v -0.501918 -0.330970 0.687831 v -0.429296 -0.394496 0.612502 v -0.463972 -0.403341 0.555620 v -0.489596 -0.398218 0.492561 v -0.535557 -0.349928 0.461592 v -0.608158 -0.285728 0.559957 v -0.583698 -0.280360 0.439468 v -0.630690 -0.240553 0.527290 v -0.649663 -0.217398 0.572673 v -0.636495 -0.233658 0.607883 v -0.506442 -0.212438 0.234975 v -0.557570 -0.077306 0.230430 v -0.544261 -0.216382 0.285392 v -0.601092 -0.079278 0.282359 v -0.046697 -0.522177 0.357333 v -0.051828 -0.528687 0.393551 v -0.103735 -0.518336 0.363160 v -0.124228 -0.519419 0.392454 v -0.059546 -0.531660 0.433855 v -0.142645 -0.518892 0.434767 v -0.451383 -0.390803 0.351824 v -0.521766 -0.309987 0.341006 v -0.393582 -0.428245 0.371753 v -0.577470 -0.215100 0.335446 v -0.632047 -0.077735 0.333562 v -0.624832 -0.196053 0.427738 v -0.662519 -0.075912 0.423750 v -0.063446 -0.530926 0.475345 v -0.062067 -0.524750 0.514293 v -0.149689 -0.515712 0.480973 v -0.145047 -0.510455 0.522745 v -0.241626 -0.396298 0.668013 v -0.210960 -0.417131 0.645792 v -0.132184 -0.385488 0.681588 v -0.106093 -0.412676 0.655757 v -0.292013 -0.080857 0.796561 v -0.275531 -0.228942 0.736877 v -0.208410 -0.315875 0.719312 v -0.317993 -0.334366 0.708398 v -0.340814 -0.231685 0.736573 v -0.331153 -0.086247 0.823349 v 0.341225 -0.266473 0.805718 v 0.390864 -0.294959 0.776536 v 0.482452 -0.223270 0.810789 v 0.522860 -0.242985 0.782905 v 0.286752 -0.347400 0.750467 v 0.266207 -0.319144 0.769559 v 0.245284 -0.290977 0.794248 v 0.625867 -0.161198 0.788584 v 0.582456 -0.149372 0.816454 v 0.087642 -0.202429 0.815601 v 0.155527 -0.244230 0.813807 v -0.400580 -0.290169 0.733058 v -0.393930 -0.222647 0.779086 v -0.371064 -0.089146 0.881752 v -0.655928 -0.172594 0.516658 v -0.680046 -0.071537 0.515783 v -0.662542 -0.160138 0.577677 v -0.679758 -0.069980 0.585114 v -0.448336 -0.321213 0.713336 v -0.380814 -0.376368 0.666122 v -0.655690 -0.165565 0.635509 v -0.669147 -0.070774 0.656180 v 0.551672 -0.132216 0.837325 v 0.461403 -0.200590 0.832928 v 0.533737 -0.123674 0.855572 v 0.448651 -0.187578 0.851990 v 0.253055 -0.237810 0.831594 v 0.175457 -0.216568 0.832299 v 0.243957 -0.264145 0.812413 v 0.190500 -0.204734 0.850093 v 0.260550 -0.223278 0.849410 v 0.351961 -0.235111 0.830430 v 0.352584 -0.220089 0.849759 v 0.121192 -0.180750 0.832358 v 0.086500 -0.137124 0.831534 v 0.102211 -0.131445 0.849641 v 0.138638 -0.172846 0.850248 v 0.392717 -0.259897 0.100042 v 0.497414 -0.197068 0.099968 v 0.146156 -0.317958 0.100124 v 0.259631 -0.300393 0.100257 v 0.151798 -0.364164 0.114100 v 0.282119 -0.345791 0.114278 v -0.006794 -0.318299 0.100168 v -0.026968 -0.364653 0.113425 v 0.551790 -0.114214 0.100309 v 0.305288 -0.382233 0.140969 v 0.161259 -0.402177 0.139798 v 0.172973 -0.432976 0.173674 v 0.330793 -0.413765 0.177336 v -0.052198 -0.402548 0.140791 v -0.083205 -0.433717 0.178723 v 0.189195 -0.525892 0.362041 v 0.184628 -0.532994 0.399920 v 0.112191 -0.525988 0.356243 v 0.106044 -0.534314 0.396524 v 0.182523 -0.537955 0.443116 v 0.101522 -0.539252 0.438267 v 0.259535 -0.527745 0.399994 v 0.270248 -0.528835 0.445585 v 0.245685 -0.524691 0.369247 v 0.176910 -0.538221 0.489863 v 0.097355 -0.538815 0.480914 v 0.093544 -0.535056 0.521796 v 0.169236 -0.532898 0.536009 v 0.254374 -0.520724 0.550852 v 0.268721 -0.526952 0.498693 v 0.161429 -0.523171 0.576002 v 0.091260 -0.527181 0.558675 v 0.092313 -0.512115 0.592580 v 0.159783 -0.508542 0.608706 v 0.212751 -0.503945 0.616417 v 0.232428 -0.511997 0.592083 v 0.322615 -0.379727 0.714100 v 0.152006 -0.384909 0.709348 v 0.438649 -0.398677 0.677941 v 0.514408 -0.414618 0.626760 v 0.559835 -0.427808 0.561625 v 0.576406 -0.438433 0.489151 v 0.564187 -0.444371 0.417648 v 0.529177 -0.445773 0.350586 v 0.469566 -0.442399 0.290085 v 0.362170 -0.439056 0.231483 v 0.180410 -0.454158 0.213370 v -0.130679 -0.456457 0.235531 v -0.242634 -0.464694 0.297596 v -0.317681 -0.457769 0.366178 v -0.371346 -0.444312 0.446474 v -0.378300 -0.436927 0.515724 v -0.358668 -0.427333 0.578953 v -0.314745 -0.412950 0.632943 v -0.057351 -0.518166 0.546841 v -0.129337 -0.506592 0.554359 v -0.055476 -0.506459 0.574675 v -0.110645 -0.503033 0.573325 v 0.013848 -0.385873 0.692606 v -0.279498 -0.466355 0.364747 v -0.320076 -0.457562 0.433195 v -0.238534 -0.487626 0.366519 v -0.272981 -0.480687 0.428866 v -0.259576 -0.472776 0.365644 v -0.298093 -0.465354 0.429681 v -0.164436 -0.493054 0.322248 v -0.186612 -0.478255 0.311764 v -0.206037 -0.470640 0.306107 v -0.333289 -0.448568 0.501288 v -0.321367 -0.438329 0.564888 v -0.287520 -0.472864 0.492710 v -0.278897 -0.465161 0.550430 v -0.312662 -0.456583 0.496491 v -0.302660 -0.447033 0.558423 v 0.299349 -0.465236 0.259004 v 0.164046 -0.470581 0.239305 v 0.136265 -0.499986 0.280639 v 0.149477 -0.483904 0.257121 v 0.239612 -0.499126 0.295453 v 0.267542 -0.481161 0.275116 v 0.323868 -0.498711 0.327171 v 0.362897 -0.480605 0.312217 v 0.401118 -0.465139 0.302660 v -0.098419 -0.471530 0.261273 v -0.071594 -0.497079 0.293978 v -0.084280 -0.482347 0.274619 v 0.468217 -0.465762 0.358170 v 0.381551 -0.497814 0.375127 v 0.426645 -0.480598 0.365214 v 0.413922 -0.494685 0.434352 v 0.465110 -0.477899 0.427776 v 0.507757 -0.463478 0.422756 v 0.520569 -0.457651 0.493051 v 0.422701 -0.488776 0.499123 v 0.477344 -0.471827 0.496380 v 0.404699 -0.480345 0.563449 v 0.456636 -0.462574 0.564673 v 0.501277 -0.447582 0.563990 v 0.264851 -0.412994 0.692584 v 0.371171 -0.423456 0.669377 v 0.205055 -0.459037 0.662430 v 0.289733 -0.463945 0.650389 v 0.232102 -0.435534 0.678126 v 0.328524 -0.442955 0.661703 v 0.110523 -0.456909 0.656624 v 0.123579 -0.432175 0.672602 v 0.137088 -0.411082 0.687742 v 0.450638 -0.435645 0.625551 v 0.358278 -0.471419 0.616884 v 0.405551 -0.452372 0.622549 v -0.282701 -0.426785 0.615994 v -0.185848 -0.455990 0.623861 v -0.201774 -0.425992 0.638423 v -0.246964 -0.458889 0.595316 v -0.267479 -0.436698 0.607542 v -0.089566 -0.455834 0.632617 v -0.098115 -0.425347 0.645785 v 0.013225 -0.456605 0.641537 v 0.015627 -0.430262 0.654519 v -0.223883 -0.498488 0.433299 v -0.191490 -0.504005 0.381518 v -0.133437 -0.507719 0.344469 v -0.228339 -0.486047 0.538174 v -0.236628 -0.492194 0.488825 v 0.211045 -0.514785 0.327401 v 0.123031 -0.514681 0.316369 v 0.282000 -0.514911 0.349703 v -0.057025 -0.511085 0.324450 v 0.324268 -0.514600 0.389651 v 0.349314 -0.512583 0.441559 v 0.352465 -0.507771 0.500413 v 0.337281 -0.500475 0.558993 v 0.248495 -0.486114 0.633700 v 0.179201 -0.484371 0.637837 v 0.099616 -0.484038 0.628873 v 0.300180 -0.492453 0.605970 v -0.204236 -0.481257 0.576424 v -0.150193 -0.480109 0.597488 v -0.071135 -0.482081 0.605511 v 0.015220 -0.483586 0.615001 v 0.017674 -0.510099 0.580161 v 0.017029 -0.524802 0.548295 v 0.016376 -0.533032 0.513863 v 0.017577 -0.537873 0.475190 v 0.021136 -0.538266 0.434389 v 0.026133 -0.533009 0.394033 v 0.030300 -0.524675 0.354574 v 0.031101 -0.513265 0.316361 v 0.030752 -0.499356 0.282293 v 0.030945 -0.484757 0.258960 v 0.031234 -0.472805 0.240639 v 0.030293 -0.458963 0.214474 v 0.040821 -0.439419 0.173859 v 0.044817 -0.409628 0.139220 v 0.046308 -0.370599 0.113477 v 0.049555 -0.323904 0.099983 v 0.706407 -0.029283 0.143913 v 0.758256 -0.029395 0.183928 v 0.642756 -0.026599 0.116576 v 0.799353 -0.029395 0.234130 v 0.828128 -0.029395 0.291368 v 0.845803 -0.029395 0.353544 v 0.858726 -0.029395 0.421384 v 0.859631 -0.032575 0.488595 v 0.849147 -0.032575 0.552091 v 0.828254 -0.032583 0.610226 v 0.799360 -0.032575 0.662007 v 0.767546 -0.032553 0.713433 v 0.729207 -0.031167 0.756569 v 0.690133 -0.030099 0.791475 v 0.649644 -0.028920 0.821214 v 0.615583 -0.025176 0.842063 v 0.591501 -0.021172 0.858263 v 0.570756 -0.022855 0.101525 v 0.066400 -0.086410 0.830734 v 0.056606 -0.029728 0.830193 v 0.079197 -0.081836 0.847475 v 0.067334 -0.028379 0.845266 v 0.103479 -0.139949 0.874003 v 0.076706 -0.086381 0.867590 v 0.061663 -0.033376 0.863527 v 0.043305 -0.045773 0.887008 v 0.059928 -0.101098 0.895698 v 0.092291 -0.171037 0.915427 v 0.195238 -0.206484 0.870994 v 0.143983 -0.178748 0.872706 v 0.145318 -0.205795 0.904728 v 0.195676 -0.224879 0.897173 v 0.262100 -0.221654 0.869437 v 0.258400 -0.235593 0.893162 v 0.350226 -0.233732 0.893874 v 0.350308 -0.219178 0.869496 v 0.450571 -0.198240 0.894178 v 0.444291 -0.186599 0.871742 v 0.528472 -0.123304 0.875160 v 0.541484 -0.131393 0.901992 v 0.585778 -0.022336 0.875953 v 0.591034 -0.033769 0.900324 v 0.015805 -0.412357 0.668139 v -0.318022 0.019710 0.100858 v -0.388006 0.019443 0.116880 v -0.452458 0.019814 0.145410 v -0.515643 0.021067 0.182037 v 0.707816 0.105137 0.142259 v 0.759308 0.106190 0.182734 v 0.643713 0.101319 0.115264 v 0.799368 0.106420 0.233648 v 0.826956 0.107480 0.291294 v 0.843683 0.110661 0.353670 v 0.856139 0.115317 0.421370 v 0.858037 0.120255 0.489262 v 0.847924 0.124133 0.553455 v 0.827112 0.126921 0.612146 v 0.796343 0.129219 0.663979 v 0.716684 0.127061 0.756332 v 0.760168 0.133949 0.714278 v -0.197310 0.019443 0.779019 v -0.202604 0.112248 0.779924 v -0.250219 0.018220 0.786923 v -0.252621 0.112952 0.791542 v -0.138211 0.018998 0.783245 v -0.143624 0.105812 0.783676 v 0.005996 0.011139 0.810967 v 0.005433 0.062216 0.811205 v -0.554464 0.033871 0.924777 v -0.489670 0.034331 0.943801 v -0.493845 0.153182 0.929811 v -0.431913 0.149719 0.920224 v -0.427435 0.032448 0.927505 v -0.663149 0.021186 0.754782 v -0.616254 0.029586 0.866775 v -0.568395 0.020429 0.229229 v -0.610434 0.113123 0.280454 v -0.613273 0.020007 0.281670 v -0.643531 0.111677 0.342467 v -0.646830 0.019280 0.341592 v -0.291664 0.016796 0.803716 v -0.291309 0.114450 0.810871 v -0.328632 0.019525 0.835116 v -0.326111 0.125304 0.846875 v 0.675801 0.122650 0.791089 v 0.634852 0.115406 0.820057 v -0.371383 0.029771 0.889529 v -0.373177 0.145323 0.893021 v -0.677244 0.018895 0.426760 v -0.692985 0.019836 0.520358 v -0.690782 0.019858 0.590674 v -0.680128 0.019636 0.665870 v 0.599983 0.107406 0.840135 v 0.577629 0.103506 0.856098 v 0.056880 0.007276 0.830274 v 0.057155 0.044281 0.830356 v 0.065095 0.009671 0.844391 v 0.063553 0.037215 0.843783 v 0.570919 0.095929 0.100917 v 0.057251 0.003317 0.862104 v 0.054597 0.032603 0.861214 v 0.037381 -0.011089 0.885303 v 0.032473 0.027243 0.885844 v 0.569280 0.105352 0.897848 v 0.564936 0.100800 0.874619 v -0.243405 0.383603 0.114359 v -0.161893 0.418242 0.114130 v -0.191772 0.345627 0.099701 v -0.120514 0.374698 0.099939 v -0.311060 0.329686 0.114775 v -0.251190 0.301860 0.099294 v -0.354745 0.256774 0.116324 v -0.290093 0.239684 0.100524 v -0.378441 0.179362 0.117199 v -0.309667 0.169998 0.101577 v 0.654167 0.346672 0.183720 v 0.610000 0.326913 0.143030 v 0.511145 0.437764 0.178270 v 0.469722 0.407944 0.142133 v 0.555386 0.302365 0.114663 v 0.429826 0.374209 0.115093 v -0.195197 0.453897 0.143549 v -0.289966 0.416404 0.143787 v -0.364880 0.353605 0.144595 v -0.412258 0.372200 0.186322 v -0.331887 0.442309 0.185722 v -0.227101 0.481723 0.183816 v -0.414341 0.271351 0.144313 v -0.441233 0.186976 0.144447 v -0.499064 0.194546 0.181281 v -0.466619 0.282843 0.183928 v 0.620944 0.205393 0.114196 v 0.682653 0.216559 0.142141 v 0.730378 0.225011 0.183468 v 0.709277 0.368544 0.290352 v 0.685692 0.360025 0.234107 v 0.593140 0.465397 0.284606 v 0.548395 0.457182 0.227093 v -0.268814 0.501401 0.236917 v -0.371724 0.458917 0.237510 v -0.455350 0.385063 0.236628 v -0.493615 0.390246 0.288780 v -0.413718 0.467703 0.293444 v -0.331991 0.507109 0.298730 v 0.766804 0.231566 0.234678 v 0.791575 0.236481 0.291894 v 0.725633 0.373883 0.351268 v 0.625170 0.466606 0.348109 v 0.734812 0.373453 0.417559 v 0.646226 0.465931 0.415905 v 0.806278 0.239899 0.353877 v 0.815568 0.243110 0.421140 v 0.639264 0.455736 0.552231 v 0.729066 0.370309 0.550971 v 0.652083 0.461149 0.485511 v 0.738400 0.373000 0.485629 v 0.816740 0.246283 0.488988 v 0.806871 0.248070 0.553477 v 0.558026 0.441694 0.667664 v 0.673080 0.361160 0.664328 v 0.607961 0.450316 0.614452 v 0.707794 0.367907 0.611190 v 0.786126 0.248040 0.612650 v 0.755475 0.246246 0.665218 v 0.439561 0.398150 0.746649 v 0.578297 0.341393 0.752380 v 0.491935 0.423484 0.709503 v 0.629885 0.351951 0.710490 v 0.714756 0.244489 0.712009 v 0.668647 0.244251 0.754648 v -0.122174 0.364385 0.738819 v -0.068569 0.332993 0.761307 v 0.022026 0.412252 0.728765 v 0.040999 0.374454 0.758378 v -0.020421 0.294750 0.784120 v 0.061870 0.334350 0.785203 v 0.153978 0.355147 0.787998 v 0.153904 0.389186 0.761240 v 0.155401 0.425115 0.731894 v -0.247876 0.197267 0.765792 v -0.186590 0.189697 0.771776 v -0.206749 0.288188 0.749933 v -0.145648 0.270075 0.766133 v -0.125340 0.176678 0.782897 v -0.083190 0.242702 0.783549 v 0.019646 0.183514 0.812562 v 0.007279 0.124148 0.811872 v 0.043446 0.241642 0.811227 v -0.558430 0.285504 0.813140 v -0.558163 0.220756 0.872424 v -0.498916 0.289872 0.836109 v -0.496254 0.224648 0.893466 v -0.437036 0.223158 0.883494 v -0.444451 0.289686 0.826048 v -0.444888 0.336648 0.778752 v -0.486808 0.345316 0.772324 v -0.541303 0.346324 0.747924 v -0.643131 0.258984 0.696573 v -0.655053 0.185049 0.725896 v -0.611227 0.272671 0.761114 v -0.617107 0.209783 0.818990 v -0.587835 0.341097 0.707123 v -0.619613 0.329553 0.657536 v -0.584469 0.391736 0.603879 v -0.550601 0.405186 0.650337 v -0.505077 0.406817 0.687809 v -0.434634 0.474894 0.604480 v -0.467190 0.480974 0.553544 v -0.492087 0.473723 0.491998 v -0.541830 0.422824 0.459931 v -0.589689 0.355858 0.437199 v -0.613029 0.362984 0.558348 v -0.638549 0.320063 0.525711 v -0.651954 0.295143 0.570723 v -0.639335 0.311655 0.607290 v -0.512017 0.290783 0.231727 v -0.549570 0.199766 0.228131 v -0.592654 0.202361 0.279119 v -0.551520 0.295499 0.282115 v -0.039639 0.595080 0.360409 v -0.093266 0.590505 0.366667 v -0.043791 0.598409 0.395768 v -0.112032 0.590290 0.394137 v -0.130864 0.588481 0.433447 v -0.052221 0.598809 0.434715 v -0.458464 0.463143 0.354359 v -0.530760 0.385249 0.342815 v -0.397533 0.501979 0.373377 v -0.586945 0.294149 0.336906 v -0.631283 0.201464 0.334630 v -0.663446 0.194391 0.421948 v -0.631328 0.278513 0.425522 v -0.142200 0.580496 0.519030 v -0.060065 0.590490 0.513551 v -0.142141 0.584915 0.477244 v -0.058419 0.595984 0.475145 v -0.111349 0.486957 0.654890 v -0.217418 0.492903 0.640098 v -0.139368 0.459206 0.678282 v -0.250582 0.472878 0.659138 v -0.300695 0.205683 0.773155 v -0.273062 0.303291 0.738878 v -0.215757 0.393886 0.712928 v -0.331287 0.414713 0.693852 v -0.342579 0.308400 0.739026 v -0.344892 0.215618 0.800254 v 0.342382 0.342498 0.805703 v 0.486990 0.308689 0.811865 v 0.392161 0.370836 0.776454 v 0.527939 0.328685 0.784766 v 0.297051 0.420511 0.737336 v 0.271279 0.388845 0.767275 v 0.252180 0.358083 0.793136 v 0.623879 0.242442 0.789177 v 0.579394 0.232211 0.817173 v 0.095242 0.287966 0.810708 v 0.165759 0.318950 0.811153 v -0.403947 0.365831 0.733170 v -0.396428 0.298339 0.780687 v -0.386256 0.221512 0.846334 v -0.661326 0.258257 0.513373 v -0.680395 0.183870 0.512550 v -0.680180 0.178183 0.582133 v -0.666864 0.246884 0.575891 v -0.451368 0.396934 0.712869 v -0.391943 0.457138 0.648054 v -0.658464 0.250094 0.634174 v -0.670838 0.179110 0.651042 v 0.544673 0.215529 0.836613 v 0.460958 0.286417 0.832499 v 0.441941 0.267866 0.851190 v 0.521903 0.204029 0.853592 v 0.179216 0.290153 0.832039 v 0.252513 0.333601 0.810708 v 0.258474 0.308770 0.831357 v 0.190219 0.274843 0.850345 v 0.261633 0.293275 0.850641 v 0.347653 0.294209 0.850426 v 0.350315 0.312656 0.830667 v 0.121451 0.255855 0.832039 v 0.083757 0.211125 0.831824 v 0.099409 0.200693 0.849232 v 0.137244 0.242465 0.849789 v 0.493922 0.273241 0.100272 v 0.387965 0.336129 0.100502 v 0.141870 0.395629 0.099360 v 0.147802 0.440174 0.113848 v 0.254849 0.379992 0.099383 v 0.277425 0.421534 0.114278 v -0.032588 0.439714 0.113870 v -0.009262 0.395747 0.099627 v 0.550359 0.190023 0.100176 v 0.156372 0.478631 0.139546 v 0.300780 0.457664 0.141043 v 0.169221 0.508066 0.174452 v 0.327953 0.488099 0.177996 v -0.059761 0.475917 0.142111 v -0.090641 0.505723 0.181577 v 0.188513 0.605645 0.362745 v 0.113896 0.602776 0.357436 v 0.184480 0.612347 0.400625 v 0.108610 0.608997 0.397903 v 0.104495 0.610858 0.439097 v 0.183879 0.613282 0.443219 v 0.265918 0.610071 0.446467 v 0.255709 0.609249 0.401514 v 0.242460 0.605519 0.370634 v 0.179876 0.610605 0.487616 v 0.099846 0.607803 0.480417 v 0.094560 0.600277 0.519980 v 0.171720 0.601664 0.530256 v 0.254626 0.595273 0.544929 v 0.267053 0.606231 0.497018 v 0.162348 0.588674 0.567727 v 0.090245 0.588755 0.556280 v 0.090067 0.572808 0.589948 v 0.159709 0.573289 0.599690 v 0.213366 0.572059 0.605555 v 0.232776 0.582090 0.582326 v 0.155861 0.460785 0.700443 v 0.329162 0.455996 0.700487 v 0.441377 0.474420 0.664765 v 0.512480 0.487462 0.616595 v 0.555401 0.497715 0.555242 v 0.570734 0.505693 0.488091 v 0.560065 0.512803 0.419561 v 0.525663 0.516029 0.353329 v 0.465132 0.515050 0.292250 v 0.175056 0.531414 0.214015 v 0.357529 0.514249 0.232580 v -0.135854 0.526112 0.239342 v -0.248654 0.528774 0.303579 v -0.323879 0.526379 0.370560 v -0.375950 0.517631 0.446801 v -0.363175 0.506094 0.565451 v -0.380947 0.514190 0.508613 v -0.322767 0.492051 0.620154 v -0.130315 0.575699 0.552283 v -0.057574 0.582557 0.547064 v -0.056580 0.570761 0.575512 v -0.111972 0.569708 0.572725 v 0.013818 0.459569 0.689774 v -0.322894 0.531362 0.431438 v -0.278334 0.538620 0.368602 v -0.216936 0.562842 0.374949 v -0.248684 0.548511 0.370434 v -0.262193 0.556006 0.426871 v -0.295994 0.540504 0.427331 v -0.150875 0.567928 0.333422 v -0.177196 0.553997 0.321054 v -0.203917 0.542831 0.312343 v -0.328298 0.514331 0.553737 v -0.337448 0.523954 0.492984 v -0.285177 0.547769 0.485533 v -0.315250 0.531740 0.488313 v -0.285919 0.538865 0.544558 v -0.310452 0.521567 0.549518 v 0.295034 0.540829 0.260458 v 0.158893 0.547310 0.239164 v 0.133618 0.577174 0.277844 v 0.237499 0.576722 0.294563 v 0.144814 0.559766 0.256447 v 0.264258 0.556637 0.275071 v 0.318122 0.576826 0.331212 v 0.356595 0.556473 0.316265 v 0.396795 0.538806 0.305618 v -0.100503 0.545085 0.265855 v -0.068028 0.572111 0.301029 v -0.083361 0.557549 0.281588 v 0.462530 0.537901 0.362100 v 0.370326 0.577227 0.381125 v 0.416947 0.556696 0.370493 v 0.398055 0.575439 0.439520 v 0.450786 0.554835 0.432372 v 0.500128 0.535439 0.425536 v 0.511390 0.529597 0.492547 v 0.406137 0.570286 0.501599 v 0.461069 0.549594 0.497648 v 0.389151 0.560878 0.561492 v 0.442682 0.540718 0.561306 v 0.493210 0.520848 0.558341 v 0.371431 0.498316 0.657944 v 0.267320 0.487847 0.680261 v 0.204387 0.531414 0.657863 v 0.232554 0.508845 0.670170 v 0.285307 0.538731 0.643375 v 0.326130 0.518098 0.653459 v 0.108825 0.528604 0.652576 v 0.122638 0.503239 0.666664 v 0.136339 0.486371 0.678667 v 0.446241 0.510127 0.616202 v 0.348580 0.549200 0.611842 v 0.396610 0.529449 0.615616 v -0.292399 0.502557 0.607943 v -0.188250 0.528915 0.619909 v -0.254638 0.531910 0.591765 v -0.204769 0.503046 0.631994 v -0.276280 0.511217 0.601306 v -0.091835 0.528225 0.631068 v -0.101563 0.500444 0.644547 v 0.010964 0.528700 0.639090 v 0.014789 0.502728 0.650285 v -0.203457 0.573067 0.430689 v -0.168788 0.577812 0.385536 v -0.121122 0.581689 0.350371 v -0.222438 0.560203 0.531256 v -0.222779 0.566920 0.481633 v 0.209614 0.593975 0.327638 v 0.122904 0.591973 0.317184 v 0.277581 0.594961 0.352002 v -0.051561 0.585686 0.328988 v 0.317892 0.596303 0.392914 v 0.338971 0.595273 0.444969 v 0.342463 0.589927 0.501711 v 0.327486 0.579969 0.555991 v 0.245692 0.558246 0.625262 v 0.177651 0.554049 0.631379 v 0.096903 0.552589 0.623164 v 0.293959 0.567944 0.599275 v -0.199928 0.554805 0.570804 v -0.149222 0.552218 0.593774 v -0.072388 0.552040 0.603598 v 0.012461 0.552299 0.612169 v 0.015449 0.571999 0.581214 v 0.016428 0.586553 0.549177 v 0.018074 0.596459 0.514352 v 0.021514 0.602798 0.476250 v 0.026356 0.605215 0.436332 v 0.031420 0.604303 0.396169 v 0.034549 0.599047 0.356754 v 0.033214 0.589081 0.318267 v 0.028180 0.560077 0.259316 v 0.030196 0.575284 0.283271 v 0.025518 0.533934 0.216054 v 0.027290 0.548289 0.241781 v 0.036239 0.512515 0.175846 v 0.042793 0.483510 0.140176 v 0.045662 0.445415 0.114122 v 0.045989 0.400908 0.099768 v -0.387872 0.103899 0.117814 v -0.317926 0.099777 0.101666 v -0.452866 0.108326 0.145017 v -0.513804 0.111306 0.182081 v -0.554174 0.151558 0.911379 v -0.655402 0.111558 0.747902 v -0.608729 0.142238 0.856046 v -0.564851 0.112211 0.228762 v -0.672833 0.109141 0.424839 v -0.690093 0.106220 0.516317 v -0.688802 0.104374 0.586841 v -0.676547 0.104878 0.661837 v 0.051772 0.104967 0.830067 v 0.063101 0.095907 0.846215 v 0.060372 0.161330 0.830742 v 0.074630 0.151358 0.848157 v 0.055308 0.092444 0.866678 v 0.023931 0.098568 0.903705 v 0.099194 0.199543 0.869399 v 0.071205 0.150149 0.868962 v 0.052550 0.162020 0.898204 v 0.086752 0.209938 0.895668 v 0.191716 0.271848 0.869829 v 0.138867 0.240596 0.869385 v 0.129607 0.249078 0.894230 v 0.183501 0.279721 0.894171 v 0.259809 0.289590 0.870964 v 0.250482 0.299540 0.895646 v 0.327991 0.304233 0.897151 v 0.340372 0.290287 0.871164 v 0.413633 0.287054 0.901718 v 0.428321 0.265835 0.872394 v 0.509529 0.206038 0.873143 v 0.508728 0.236459 0.906708 v -0.112981 -0.096968 1.226920 v -0.104921 -0.104464 1.277670 v -0.116043 -0.086848 1.227810 v -0.111023 -0.084156 1.279440 v -0.109770 -0.106888 1.223660 v -0.098597 -0.124230 1.271220 v -0.106656 -0.115860 1.218200 v -0.092465 -0.142114 1.260370 v -0.103765 -0.123445 1.210780 v -0.086808 -0.157276 1.245610 v -0.101237 -0.129302 1.201750 v -0.081863 -0.168946 1.227640 v -0.099183 -0.133142 1.191560 v -0.077896 -0.176635 1.207330 v -0.097722 -0.134803 1.180670 v -0.074931 -0.179719 1.185630 v -0.096877 -0.134188 1.169600 v -0.073344 -0.178451 1.163630 v -0.096706 -0.131341 1.158870 v -0.073085 -0.172735 1.142330 v -0.097233 -0.126358 1.148990 v -0.074182 -0.162733 1.122730 v -0.098582 -0.118907 1.140210 v -0.076999 -0.147860 1.105340 v -0.101429 -0.107089 1.132540 v -0.082775 -0.124727 1.090330 v -0.107227 -0.087189 1.129140 v -0.093578 -0.085424 1.080800 v -0.104380 -0.096753 1.130060 v -0.088499 -0.104182 1.083950 v -0.090493 -0.109810 1.327370 v -0.099561 -0.079626 1.329980 v -0.081114 -0.139103 1.317810 v -0.072032 -0.165602 1.301730 v -0.063661 -0.188075 1.279880 v -0.056402 -0.205468 1.253280 v -0.050582 -0.216961 1.223180 v -0.046482 -0.222025 1.190990 v -0.044228 -0.220238 1.158240 v -0.043976 -0.211845 1.126500 v -0.045629 -0.196875 1.097460 v -0.049707 -0.173891 1.072110 v -0.058308 -0.140223 1.049900 v -0.067205 -0.109765 1.038920 v -0.070141 -0.112983 1.375370 v -0.082048 -0.073369 1.378790 v -0.057855 -0.151374 1.362840 v -0.045956 -0.186110 1.341760 v -0.034983 -0.215559 1.313130 v -0.025463 -0.238351 1.278270 v -0.017841 -0.253417 1.238830 v -0.012465 -0.260060 1.196640 v -0.009589 -0.257954 1.153670 v -0.009322 -0.247018 1.112020 v -0.011568 -0.226977 1.074110 v -0.017196 -0.196809 1.040940 v -0.028562 -0.153198 1.011430 v -0.040121 -0.113213 0.996658 v -0.058745 -0.065480 1.425160 v -0.044176 -0.113954 1.420980 v -0.029162 -0.160879 1.405660 v -0.014608 -0.203341 1.379890 v -0.001196 -0.239337 1.344890 v 0.010437 -0.267200 1.302290 v 0.019757 -0.285617 1.254070 v 0.026326 -0.293728 1.202490 v 0.029840 -0.291163 1.149980 v 0.030130 -0.278025 1.098970 v 0.027223 -0.253810 1.052480 v 0.020039 -0.214907 1.013150 v 0.006908 -0.163207 0.979167 v -0.008061 -0.113643 0.958341 v -0.029985 -0.056071 1.468400 v -0.012969 -0.112694 1.463530 v 0.004558 -0.167478 1.445640 v 0.021544 -0.217035 1.415560 v 0.037203 -0.259059 1.374700 v 0.050786 -0.291585 1.324970 v 0.061663 -0.313079 1.268690 v 0.069336 -0.322555 1.208480 v 0.073429 -0.319552 1.147180 v 0.073770 -0.304219 1.087640 v 0.070256 -0.275927 1.033350 v 0.061596 -0.229217 0.988146 v 0.046730 -0.169057 0.948688 v 0.027386 -0.109750 0.925778 v 0.003809 -0.045284 1.507890 v 0.023019 -0.109231 1.502390 v 0.042808 -0.171066 1.482200 v 0.061981 -0.227007 1.448250 v 0.079650 -0.274444 1.402130 v 0.094982 -0.311152 1.346000 v 0.107268 -0.335419 1.282460 v 0.115920 -0.346110 1.214500 v 0.120547 -0.342722 1.145310 v 0.120932 -0.325417 1.078100 v 0.116995 -0.294492 1.016280 v 0.107461 -0.241724 0.964739 v 0.042141 -0.033265 1.543050 v 0.063271 -0.103604 1.537000 v 0.085032 -0.171593 1.514810 v 0.106111 -0.233102 1.477490 v 0.125537 -0.285254 1.426770 v 0.142397 -0.325617 1.365050 v 0.155905 -0.352301 1.295190 v 0.165418 -0.364053 1.220480 v 0.170504 -0.360331 1.144390 v 0.170919 -0.341306 1.070500 v 0.166589 -0.307400 1.002580 v 0.157233 -0.257910 0.947279 v 0.084454 -0.020208 1.573380 v 0.107201 -0.095908 1.566870 v 0.130608 -0.169057 1.542990 v 0.153288 -0.235229 1.502830 v 0.174189 -0.291348 1.448270 v 0.192332 -0.334774 1.381860 v 0.206856 -0.363481 1.306700 v 0.217103 -0.376123 1.226310 v 0.222575 -0.372119 1.144450 v 0.223019 -0.351648 1.064950 v 0.218259 -0.314888 0.992209 v 0.208250 -0.265962 0.933985 v 0.130133 -0.006284 1.598420 v 0.154163 -0.086247 1.591540 v 0.178875 -0.163489 1.566330 v 0.202830 -0.233369 1.523920 v 0.224903 -0.292623 1.466310 v 0.244054 -0.338481 1.396180 v 0.259394 -0.368790 1.316820 v 0.270211 -0.382151 1.231930 v 0.275987 -0.377917 1.145490 v 0.276461 -0.356297 1.061530 v 0.271561 -0.318069 0.984202 v 0.262026 -0.267949 0.923353 v 0.178512 0.008292 1.617810 v 0.203468 -0.074755 1.610670 v 0.229136 -0.154970 1.584490 v 0.254011 -0.227534 1.540450 v 0.276929 -0.289072 1.480630 v 0.296814 -0.336686 1.407800 v 0.312747 -0.368168 1.325390 v 0.323972 -0.382032 1.237240 v 0.330378 -0.377776 1.147320 v 0.331394 -0.355511 1.060040 v 0.325863 -0.315919 0.979694 v 0.319650 -0.262299 0.913737 v 0.254700 0.030935 1.634940 v 0.280317 -0.054322 1.627610 v 0.306652 -0.136627 1.600740 v 0.332180 -0.211096 1.555560 v 0.355698 -0.274236 1.494160 v 0.376109 -0.323103 1.419430 v 0.392458 -0.355408 1.334870 v 0.403779 -0.368931 1.244400 v 0.410778 -0.363645 1.151820 v 0.413685 -0.343441 1.062510 v 0.405714 -0.300964 0.980124 v 0.394830 -0.245869 0.908428 v 0.332647 0.053801 1.639700 v 0.358181 -0.031167 1.632410 v 0.384420 -0.113176 1.605640 v 0.409852 -0.187370 1.560620 v 0.455605 -0.241539 1.501640 v 0.469596 -0.285313 1.435120 v 0.487731 -0.309839 1.361430 v 0.481273 -0.341698 1.251120 v 0.495294 -0.342469 1.162890 v 0.499179 -0.324260 1.071130 v 0.486893 -0.278254 0.988206 v 0.472243 -0.221780 0.916280 v 0.384532 0.068822 1.634550 v 0.409503 -0.014292 1.627420 v 0.435171 -0.094507 1.601230 v 0.477967 -0.173906 1.551730 v 0.531913 -0.304605 1.293920 v 0.574434 -0.303530 1.201870 v 0.577555 -0.291971 1.088810 v 0.565010 -0.248901 1.003490 v 0.544406 -0.186510 0.934897 v 0.435401 0.083413 1.623220 v 0.459453 0.003354 1.616360 v 0.504154 -0.099519 1.581240 v 0.484521 0.097345 1.605890 v 0.507305 0.021512 1.599390 v 0.543182 -0.062314 1.576140 v 0.639101 -0.247226 1.100600 v 0.638033 -0.253861 1.195650 v 0.629010 -0.209027 1.022190 v 0.607679 -0.147059 0.960876 v 0.531171 0.110416 1.582800 v 0.552347 0.039921 1.576750 v 0.581900 -0.028075 1.558720 v 0.672969 -0.210355 1.195730 v 0.677537 -0.200049 1.112500 v 0.673733 -0.171459 1.039200 v 0.650430 -0.115222 0.981407 v 0.574671 0.122450 1.554290 v 0.593940 0.058316 1.548790 v 0.621915 -0.001035 1.529820 v 0.708076 -0.156490 1.122690 v 0.696977 -0.165973 1.198480 v 0.704413 -0.128968 1.060390 v 0.685447 -0.081991 1.007250 v 0.614382 0.133260 1.520770 v 0.631457 0.076422 1.515900 v 0.657800 0.020437 1.494270 v 0.728213 -0.109157 1.199410 v 0.729933 -0.101899 1.135600 v 0.726093 -0.072301 1.091230 v 0.716506 -0.006840 1.049860 v 0.663361 -0.001532 0.952358 v 0.714208 0.059369 1.026920 v 0.643527 0.125052 0.948043 v 0.600369 0.110824 0.914033 v 0.623057 -0.029313 0.925874 v 0.661158 0.145560 1.463140 v 0.676306 0.088737 1.456560 v 0.689370 0.047313 1.446410 v 0.679679 0.150231 1.431890 v 0.698430 0.093082 1.418360 v 0.699913 0.155436 1.401690 v 0.718760 0.096907 1.387490 v -0.113759 -0.087782 1.176340 v 0.617689 -0.169517 1.387380 v 0.634504 -0.152998 1.401090 v 0.607657 -0.166648 1.404450 v 0.623546 -0.151878 1.414470 v 0.596128 -0.175893 1.397500 v 0.605626 -0.180038 1.375760 v 0.601192 -0.159826 1.420660 v 0.591790 -0.165439 1.418150 v 0.614471 -0.149150 1.425870 v 0.641066 -0.164201 1.356200 v 0.655909 -0.149847 1.372420 v 0.629418 -0.168738 1.371000 v 0.645685 -0.152167 1.386720 v 0.617458 -0.178214 1.355830 v 0.630381 -0.171452 1.339840 v 0.659460 -0.147222 1.333900 v 0.667994 -0.141328 1.346350 v 0.651157 -0.156497 1.343610 v 0.663464 -0.145969 1.358610 v 0.643364 -0.161250 1.328660 v 0.654233 -0.149091 1.320970 v 0.673889 -0.131986 1.336510 v 0.669492 -0.138169 1.337140 v 0.672903 -0.128657 1.329390 v 0.666044 -0.138036 1.328320 v 0.663694 -0.136738 1.317910 v 0.672154 -0.124453 1.320190 v 0.679657 -0.113154 1.328940 v 0.685151 -0.103248 1.343060 v 0.679094 -0.120538 1.337280 v 0.682793 -0.114955 1.350090 v 0.677907 -0.127419 1.342830 v 0.677410 -0.128723 1.352820 v 0.668854 -0.129695 1.381870 v 0.674912 -0.129257 1.366730 v 0.677633 -0.107993 1.383550 v 0.682460 -0.110736 1.366220 v 0.686738 -0.095144 1.360940 v 0.682882 -0.089658 1.380590 v 0.648184 -0.131519 1.411260 v 0.659438 -0.130347 1.396930 v 0.656517 -0.109157 1.416850 v 0.668721 -0.107355 1.400780 v 0.673615 -0.087849 1.400390 v 0.660150 -0.090614 1.418920 v 0.625229 -0.132497 1.433430 v 0.636091 -0.132305 1.423650 v 0.628610 -0.117343 1.439670 v 0.642593 -0.112634 1.430150 v 0.644194 -0.096968 1.433740 v 0.627831 -0.105976 1.443630 v 0.610674 -0.138533 1.439460 v 0.617844 -0.131407 1.440050 v 0.606115 -0.136175 1.441970 v 0.616205 -0.124393 1.444010 v 0.613218 -0.117684 1.447720 v 0.601096 -0.133409 1.444640 v 0.600361 -0.149402 1.433450 v 0.608028 -0.146881 1.433410 v 0.593318 -0.150788 1.434570 v 0.599546 -0.212023 1.375790 v 0.615272 -0.206551 1.345310 v 0.604639 -0.200420 1.374640 v 0.618763 -0.195882 1.348310 v 0.635846 -0.177798 1.334490 v 0.622167 -0.185205 1.351300 v 0.634845 -0.187674 1.328570 v 0.605959 -0.145628 1.487290 v 0.595052 -0.173142 1.473310 v 0.607479 -0.141165 1.470300 v 0.597648 -0.165098 1.457920 v 0.596002 -0.173936 1.423930 v 0.600235 -0.157061 1.442530 v 0.591931 -0.184382 1.435850 v 0.587861 -0.194836 1.447770 v 0.588328 -0.208056 1.412040 v 0.594081 -0.196623 1.405660 v 0.609733 -0.188824 1.373500 v 0.599835 -0.185190 1.399270 v 0.653699 -0.186303 1.307060 v 0.651653 -0.176961 1.315030 v 0.633644 -0.197795 1.322440 v 0.661373 -0.154970 1.315230 v 0.649258 -0.167923 1.322800 v 0.665740 -0.161776 1.306540 v 0.670122 -0.168798 1.297640 v 0.683045 -0.147556 1.293870 v 0.677426 -0.143856 1.303220 v 0.671776 -0.140179 1.312360 v 0.680977 -0.124801 1.314720 v 0.687983 -0.125261 1.305810 v 0.689044 -0.109928 1.323350 v 0.698230 -0.106896 1.317360 v 0.695345 -0.096486 1.338850 v 0.706207 -0.089984 1.336360 v 0.707571 -0.103129 1.310990 v 0.695049 -0.125735 1.297050 v 0.714393 -0.066659 1.360840 v 0.706489 -0.077083 1.359760 v 0.715801 -0.082043 1.333930 v 0.697696 -0.085068 1.359520 v 0.694233 -0.077617 1.382730 v 0.700306 -0.068787 1.385290 v 0.691838 -0.056301 1.417040 v 0.687916 -0.066273 1.411090 v 0.706155 -0.058347 1.388550 v 0.683794 -0.075586 1.405730 v 0.668358 -0.079953 1.426310 v 0.670790 -0.071374 1.435100 v 0.652209 -0.076386 1.467150 v 0.651046 -0.082837 1.454640 v 0.673192 -0.062766 1.443950 v 0.649874 -0.089287 1.442120 v 0.633399 -0.101906 1.452410 v 0.634081 -0.098429 1.468120 v 0.619527 -0.118158 1.490980 v 0.619742 -0.117839 1.473540 v 0.634756 -0.094952 1.483830 v 0.619957 -0.117513 1.456110 v 0.608999 -0.136701 1.453300 v 0.641940 -0.163667 1.320520 v 0.653848 -0.150306 1.313230 v 0.628195 -0.174640 1.331840 v 0.592072 -0.180623 1.394850 v 0.601659 -0.184575 1.370600 v 0.588061 -0.168434 1.417700 v 0.614122 -0.181943 1.348870 v 0.664087 -0.136130 1.310470 v 0.673103 -0.121873 1.313110 v 0.590411 -0.151678 1.435720 v 0.598997 -0.132097 1.446270 v 0.686945 -0.096301 1.336970 v 0.680821 -0.108290 1.321890 v 0.611490 -0.114258 1.449740 v 0.626779 -0.100134 1.445730 v 0.685944 -0.079419 1.377830 v 0.689548 -0.086166 1.356240 v 0.643898 -0.088650 1.435340 v 0.661373 -0.080323 1.419720 v 0.675801 -0.077291 1.399480 v 0.717477 -0.095671 1.300250 v 0.725233 -0.067081 1.329830 v 0.703049 -0.127300 1.281940 v 0.721177 -0.047730 1.363200 v 0.708876 -0.038848 1.397010 v 0.623546 -0.227541 1.311570 v 0.592932 -0.238217 1.342010 v 0.573232 -0.244749 1.381680 v 0.650801 -0.059719 1.493590 v 0.626852 -0.081636 1.515750 v 0.673926 -0.044134 1.462790 v 0.560087 -0.216990 1.473270 v 0.569459 -0.185398 1.504090 v 0.585355 -0.147897 1.521520 v 0.561303 -0.237928 1.429470 v 0.674163 -0.185732 1.282030 v 0.652135 -0.209902 1.292830 v 0.689562 -0.157988 1.277830 v 0.692988 -0.036957 1.429880 v 0.605470 -0.111863 1.526100 v 0.726026 -0.084757 1.275660 v 0.729533 -0.043348 1.316260 v 0.713592 -0.126099 1.253400 v 0.723965 -0.017050 1.360970 v 0.708958 -0.008005 1.402880 v 0.565247 -0.276949 1.326140 v 0.604142 -0.253476 1.284850 v 0.533566 -0.280990 1.373680 v 0.636929 -0.030878 1.516850 v 0.601785 -0.056561 1.544540 v 0.668521 -0.015463 1.481520 v 0.520220 -0.189305 1.530090 v 0.504599 -0.234570 1.493210 v 0.544777 -0.140957 1.551900 v 0.511249 -0.268356 1.442260 v 0.639865 -0.225154 1.265080 v 0.668083 -0.196379 1.256100 v 0.692202 -0.165350 1.252380 v 0.690541 -0.006929 1.443170 v 0.572187 -0.095612 1.556610 v 0.739550 -0.009562 1.298720 v 0.738222 -0.068876 1.240510 v 0.729303 0.032537 1.354320 v 0.711249 0.038720 1.402380 v 0.759205 0.094261 1.175270 v 0.761644 0.088314 1.165720 v 0.749633 0.097389 1.165530 v 0.759716 0.099236 1.160370 v 0.750849 0.090413 1.163660 v 0.746763 0.133023 1.172260 v 0.754185 0.138516 1.158820 v 0.740417 0.161597 1.171570 v 0.748654 0.163999 1.158130 v 0.757054 0.117089 1.158950 v 0.747512 0.112522 1.169600 v 0.760488 0.126624 1.194380 v 0.757440 0.108711 1.187320 v 0.778207 0.173927 1.215290 v 0.787980 0.136567 1.215280 v 0.750456 0.164370 1.191620 v 0.781707 0.044585 1.271100 v 0.766107 0.038134 1.274950 v 0.769644 -0.029432 1.222050 v 0.756269 -0.036572 1.218570 v 0.749870 -0.062003 1.208080 v 0.752258 0.027554 1.283800 v 0.725032 0.020948 1.082230 v 0.730430 0.103143 1.068750 v 0.758359 0.044555 1.081570 v 0.758715 0.109986 1.073380 v 0.752065 0.162769 1.065250 v 0.723201 0.152641 1.059690 v 0.756543 0.074183 1.314570 v 0.742945 0.068385 1.317870 v 0.741744 0.119588 1.347690 v 0.730089 0.116266 1.347230 v 0.759894 0.124407 1.341760 v 0.776673 0.079721 1.309200 v 0.817666 0.059139 1.276210 v 0.799509 0.051265 1.273830 v 0.815976 -0.008286 1.228730 v 0.795957 -0.019964 1.227830 v 0.764239 -0.074970 1.173640 v 0.794986 -0.067296 1.176560 v 0.766345 -0.060772 1.202050 v 0.796098 -0.048864 1.203600 v 0.821877 -0.046662 1.171110 v 0.823917 -0.032813 1.199360 v 0.795468 -0.059066 1.143050 v 0.766804 -0.071975 1.140450 v 0.783479 0.058005 1.084710 v 0.790322 -0.016598 1.100080 v 0.764373 -0.033851 1.098760 v 0.814612 0.002969 1.109020 v 0.807256 0.071291 1.087850 v 0.780128 0.120211 1.076350 v 0.801169 0.136233 1.081170 v 0.773477 0.169108 1.069010 v 0.798634 0.176552 1.073080 v 0.778467 0.129456 1.335640 v 0.795268 0.084778 1.307580 v 0.801251 0.135803 1.328420 v 0.813351 0.090620 1.306110 v 0.835075 -0.024687 1.176850 v 0.834830 -0.026288 1.161490 v 0.830011 -0.035037 1.140260 v 0.903783 0.091577 1.106340 v 0.917610 0.141260 1.086720 v 0.911709 0.101186 1.122570 v 0.924261 0.148022 1.110420 v 0.892610 0.127825 1.078960 v 0.888584 0.092718 1.091040 v 0.921266 0.180081 1.098890 v 0.913177 0.176789 1.072670 v 0.913867 0.211013 1.094570 v 0.904702 0.207351 1.070670 v 0.883001 0.200804 1.061640 v 0.889763 0.167455 1.066660 v 0.943976 0.171755 1.383990 v 0.943523 0.113701 1.358040 v 0.960984 0.174832 1.367890 v 0.961799 0.114887 1.340370 v 0.938845 0.074546 1.316450 v 0.954037 0.075814 1.294020 v 0.791990 0.077482 1.163970 v 0.789833 0.093363 1.186590 v 0.819698 0.065760 1.177060 v 0.823405 0.092659 1.195790 v 0.823865 0.033997 1.157920 v 0.816235 0.053727 1.160260 v 0.782737 0.097916 1.157730 v 0.808413 0.096537 1.153690 v 0.798693 0.148934 1.144930 v 0.774160 0.143959 1.150640 v 0.803171 0.124815 1.148150 v 0.778096 0.120804 1.152650 v 0.768324 0.169597 1.149440 v 0.792991 0.176596 1.142950 v 0.831835 0.005660 1.233750 v 0.835372 0.067851 1.280100 v 0.848272 0.074805 1.292100 v 0.852625 0.018108 1.245870 v 0.835372 0.030972 1.168960 v 0.841318 0.063610 1.185710 v 0.857451 0.035258 1.178060 v 0.862686 0.066583 1.193130 v 0.847701 0.093734 1.204770 v 0.868232 0.095276 1.214140 v 0.833881 0.003228 1.170340 v 0.857155 0.010983 1.178140 v 0.828751 0.005445 1.156580 v 0.828336 0.002435 1.160650 v 0.840376 -0.012490 1.183100 v 0.859097 -0.003252 1.188860 v 0.850712 0.118706 1.232250 v 0.824806 0.115955 1.220500 v 0.871182 0.120018 1.244050 v 0.846456 0.151551 1.240170 v 0.864413 0.156051 1.249100 v 0.824754 0.145108 1.230100 v 0.888191 0.045667 1.282750 v 0.901959 0.064655 1.307080 v 0.871990 0.091606 1.317760 v 0.880999 0.102009 1.332290 v 0.887516 0.111099 1.344160 v 0.911968 0.090101 1.334190 v 0.922897 0.072025 1.238180 v 0.918070 0.090338 1.243320 v 0.942515 0.096262 1.269910 v 0.934241 0.108934 1.268200 v 0.915661 0.110513 1.253000 v 0.927753 0.118772 1.268380 v 0.899564 0.102795 1.236240 v 0.899394 0.079002 1.222500 v 0.901003 0.057886 1.213410 v 0.904806 0.040559 1.214590 v 0.929703 0.055906 1.240470 v 0.908128 0.029371 1.225360 v 0.934426 0.045556 1.253580 v 0.950537 0.084207 1.278610 v 0.866067 0.115288 1.335980 v 0.875542 0.123095 1.347820 v 0.859460 0.153694 1.340220 v 0.868728 0.158513 1.351080 v 0.885707 0.129256 1.358520 v 0.880213 0.164059 1.363270 v 0.913940 0.132948 1.278080 v 0.898207 0.126372 1.264650 v 0.929117 0.139117 1.288170 v 0.909752 0.168885 1.284780 v 0.928436 0.175358 1.300180 v 0.892928 0.163569 1.269740 v 0.915424 0.122591 1.365450 v 0.913103 0.169330 1.385820 v 0.940891 0.138324 1.294640 v 0.943716 0.179443 1.312540 v 0.950404 0.132941 1.303440 v 0.953962 0.178798 1.327640 v 0.829299 0.083621 1.090110 v 0.836906 0.020748 1.110700 v 0.857392 0.036363 1.107230 v 0.848317 0.096381 1.088560 v 0.836929 -0.000968 1.139090 v 0.856702 0.020926 1.133760 v 0.839642 0.007343 1.149270 v 0.860135 0.027673 1.147460 v 0.836402 0.062298 1.155860 v 0.832265 0.103417 1.151220 v 0.851809 0.135766 1.141350 v 0.827372 0.130324 1.146180 v 0.855323 0.107629 1.146830 v 0.822292 0.140185 1.081450 v 0.841511 0.146242 1.079940 v 0.818660 0.182320 1.069770 v 0.835950 0.187273 1.070640 v 0.822626 0.153812 1.141080 v 0.818267 0.183173 1.137090 v 0.846693 0.160203 1.138300 v 0.841132 0.189875 1.134610 v 0.865792 0.110609 1.081620 v 0.875631 0.063758 1.100720 v 0.881600 0.052066 1.123140 v 0.886597 0.056433 1.139500 v 0.885351 0.143610 1.139060 v 0.882163 0.112463 1.140840 v 0.853967 0.192404 1.064880 v 0.859475 0.153597 1.074390 v 0.882578 0.171125 1.134560 v 0.875994 0.200641 1.131720 v 0.831168 0.098160 1.306140 v 0.844929 0.102884 1.314250 v 0.820565 0.141586 1.323880 v 0.839442 0.146398 1.324850 v 0.878886 0.159581 1.258170 v 0.885233 0.122287 1.253320 v 0.884714 0.097908 1.224120 v 0.907371 0.025367 1.250970 v 0.885596 0.009901 1.227180 v 0.884424 0.010650 1.201480 v 0.859995 -0.005602 1.213230 v 0.872109 0.030335 1.262270 v 0.861099 0.082605 1.304340 v 0.878345 0.044251 1.188720 v 0.881236 0.071432 1.205680 v 0.924098 0.044659 1.280850 v 0.837426 -0.017754 1.206250 v 0.743672 -0.078803 1.172950 v 0.801607 -0.041465 1.123040 v 0.716609 0.159358 1.361110 v 0.728517 0.164526 1.364630 v 0.743568 0.167173 1.358830 v 0.766211 0.173616 1.351680 v 0.789611 0.180244 1.343050 v 0.810749 0.186235 1.335600 v 0.829529 0.191618 1.331260 v 0.851416 0.149897 1.331140 v 0.841259 0.195125 1.334780 v 0.849244 0.197638 1.342480 v 0.857941 0.200404 1.352220 v 0.869900 0.204237 1.366830 v 0.904947 0.215114 1.394330 v 0.937133 0.224507 1.394470 v 0.960880 0.176366 1.347210 v 0.955972 0.229201 1.360050 v 0.954994 0.229393 1.380230 v 0.949967 0.226969 1.339680 v 0.937963 0.223054 1.322090 v 0.920955 0.217701 1.305360 v 0.901255 0.211495 1.286040 v 0.883638 0.205942 1.268320 v 0.870197 0.201745 1.256940 v 0.856599 0.197608 1.249600 v 0.837714 0.191907 1.241690 v 0.814537 0.184885 1.230620 v 0.916357 0.179377 1.115800 v 0.918960 0.149156 1.124860 v 0.909181 0.210087 1.111960 v 0.842971 0.025011 1.155190 v 0.829974 -0.017673 1.152770 v 0.862805 0.045000 1.151880 v 0.857510 0.073130 1.151290 v 0.885833 0.069719 1.144510 v 0.876284 0.084474 1.146160 v 0.880828 0.022616 1.189760 v 0.909233 0.108133 1.131980 v 0.958359 0.124400 1.319120 v 0.785266 0.113783 1.204520 v 0.857325 0.107807 1.324790 v 0.713295 0.158765 1.377320 v 0.729748 0.106843 1.362880 v 0.741581 0.052763 1.331030 v 0.752109 0.014001 1.288250 v 0.750730 -0.062114 1.221230 v 0.742901 -0.088383 1.186160 v 0.740083 -0.079930 1.141330 v 0.734597 -0.049183 1.103470 v 0.739950 -0.069995 1.142900 v 0.731142 -0.035630 1.108500 v 0.713963 0.151099 1.041770 v 0.727842 0.072581 1.053820 v 0.694560 0.144700 1.007270 v 0.728198 0.009679 1.069300 v 0.824554 -0.021817 1.132900 v 0.795720 -0.032642 1.115210 v -0.116777 -0.063856 1.276720 v -0.118920 -0.076727 1.226450 v -0.121989 -0.044090 1.269340 v -0.121529 -0.066800 1.222740 v -0.126304 -0.026199 1.257650 v -0.123643 -0.057806 1.216850 v -0.129514 -0.011030 1.242190 v -0.125340 -0.050184 1.209090 v -0.131465 0.000715 1.223690 v -0.126260 -0.044290 1.199780 v -0.132072 0.008485 1.203010 v -0.126460 -0.040405 1.189380 v -0.131242 0.011881 1.181130 v -0.125956 -0.038714 1.178400 v -0.128996 0.010739 1.159100 v -0.124777 -0.039285 1.167340 v -0.122953 -0.042080 1.156740 v -0.125437 0.005119 1.137980 v -0.120595 -0.047003 1.147070 v -0.120803 -0.004683 1.118760 v -0.115131 -0.019067 1.102010 v -0.117659 -0.054336 1.138620 v -0.107279 -0.043556 1.086840 v -0.113552 -0.066333 1.131330 v -0.101652 -0.062158 1.082620 v -0.110734 -0.075763 1.129650 v -0.108109 -0.049450 1.325960 v -0.115843 -0.020157 1.315020 v -0.122226 0.006350 1.297690 v -0.126979 0.028830 1.274790 v -0.129870 0.046231 1.247380 v -0.130768 0.057745 1.216740 v -0.129633 0.062824 1.184310 v -0.126504 0.061237 1.151600 v -0.121492 0.053008 1.120180 v -0.114627 0.038394 1.091680 v -0.106360 0.017434 1.066940 v -0.094504 -0.019541 1.044010 v -0.086200 -0.047389 1.037050 v -0.093274 -0.033762 1.373520 v -0.103401 0.004629 1.359190 v -0.111772 0.039373 1.336480 v -0.118000 0.068830 1.306460 v -0.121796 0.091636 1.270540 v -0.122968 0.106717 1.230390 v -0.121477 0.113375 1.187890 v -0.117385 0.111299 1.145020 v -0.110883 0.100578 1.103790 v -0.101881 0.081286 1.066490 v -0.091138 0.053949 1.034150 v -0.075605 0.006275 1.004920 v -0.064513 -0.029506 0.994990 v -0.072476 -0.017013 1.418710 v -0.084858 0.029919 1.401190 v -0.095090 0.072381 1.373440 v -0.102704 0.108392 1.336740 v -0.107339 0.136270 1.292830 v -0.108777 0.154702 1.243750 v -0.106953 0.162843 1.191790 v -0.101948 0.160300 1.139400 v -0.093800 0.146924 1.089160 v -0.082404 0.122576 1.044100 v -0.068117 0.086587 1.005500 v -0.050686 0.034694 0.972087 v -0.037318 -0.009858 0.956969 v -0.046030 0.000544 1.460870 v -0.060480 0.055328 1.440420 v -0.072432 0.104893 1.408020 v -0.081314 0.146932 1.365190 v -0.086727 0.179473 1.313930 v -0.088402 0.200997 1.256640 v -0.086274 0.210494 1.195990 v -0.080432 0.207529 1.134820 v -0.070860 0.191759 1.076250 v -0.057796 0.163577 1.023530 v -0.041270 0.122769 0.978226 v -0.018849 0.063855 0.939531 v -0.004814 0.005030 0.918178 v -0.014311 0.018657 1.499390 v -0.030623 0.080492 1.476310 v -0.044109 0.136441 1.439740 v -0.054141 0.183892 1.391390 v -0.060250 0.220622 1.333530 v -0.062141 0.244919 1.268860 v -0.059739 0.255640 1.200400 v -0.053140 0.252289 1.131360 v -0.042538 0.234776 1.065090 v -0.028147 0.203577 1.005140 v -0.008573 0.159247 0.953878 v 0.004424 -0.028201 0.915093 v 0.022211 0.037059 1.533710 v 0.004276 0.105049 1.508320 v -0.010552 0.166565 1.468120 v -0.021585 0.218739 1.414960 v -0.028295 0.259124 1.351340 v -0.030378 0.285838 1.280240 v -0.027731 0.297627 1.204970 v -0.020488 0.293942 1.129060 v -0.008973 0.274954 1.056060 v 0.006582 0.241115 0.989955 v 0.026697 0.197623 0.937269 v 0.063004 0.055484 1.563320 v 0.043705 0.128633 1.536020 v 0.027750 0.194821 1.492750 v 0.015887 0.250954 1.435560 v 0.008665 0.294409 1.367110 v 0.006426 0.323147 1.290610 v 0.009266 0.335833 1.209630 v 0.017066 0.331866 1.127960 v 0.029455 0.311440 1.049410 v 0.046434 0.274910 0.979398 v 0.066934 0.234650 0.928958 v 0.107483 0.073664 1.587800 v 0.087101 0.150906 1.558960 v 0.070256 0.220800 1.513280 v 0.057718 0.280077 1.452890 v 0.050096 0.325957 1.380610 v 0.047731 0.356311 1.299830 v 0.050734 0.369701 1.214310 v 0.058971 0.365512 1.128070 v 0.072050 0.343944 1.045130 v 0.090549 0.305560 0.972925 v 0.112406 0.269734 0.925807 v 0.154979 0.091332 1.606780 v 0.133818 0.171547 1.576840 v 0.116321 0.244125 1.529400 v 0.103309 0.305671 1.466690 v 0.095390 0.353323 1.391630 v 0.092936 0.384834 1.307750 v 0.096057 0.398743 1.218950 v 0.104606 0.394398 1.129390 v 0.118189 0.372000 1.043260 v 0.140588 0.333467 0.969514 v 0.166434 0.299718 0.924725 v 0.230545 0.116177 1.623620 v 0.208829 0.198483 1.592890 v 0.190871 0.272967 1.544210 v 0.177525 0.336129 1.479860 v 0.169392 0.385019 1.402840 v 0.166871 0.417360 1.316750 v 0.170074 0.431632 1.225630 v 0.178853 0.427169 1.133730 v 0.192791 0.404185 1.045350 v 0.212106 0.362628 0.970107 v 0.234200 0.324103 0.927149 v 0.308573 0.138754 1.628430 v 0.286938 0.220763 1.597810 v 0.269055 0.294973 1.549310 v 0.279242 0.362635 1.487490 v 0.268335 0.404148 1.418960 v 0.271308 0.431558 1.344030 v 0.248332 0.453060 1.231900 v 0.257073 0.448611 1.140330 v 0.270960 0.425709 1.052270 v 0.289770 0.383173 0.977766 v 0.310100 0.337501 0.931798 v 0.360984 0.151936 1.623520 v 0.339816 0.232144 1.593580 v 0.333826 0.319877 1.540150 v 0.312043 0.451888 1.277970 v 0.341114 0.467154 1.180810 v 0.352740 0.447195 1.069580 v 0.368836 0.394702 0.993018 v 0.389366 0.337738 0.942630 v 0.412721 0.163458 1.612610 v 0.395527 0.272589 1.572520 v 0.463034 0.173163 1.595830 v 0.448502 0.262038 1.568540 v 0.429789 0.452964 1.085910 v 0.425748 0.466924 1.175210 v 0.445010 0.406187 1.009510 v 0.472910 0.334268 0.948665 v 0.511197 0.180904 1.573450 v 0.499772 0.253275 1.552130 v 0.487168 0.440908 1.099610 v 0.482059 0.457612 1.177080 v 0.503568 0.400270 1.025090 v 0.534827 0.329174 0.967468 v 0.556498 0.186568 1.545780 v 0.548409 0.250769 1.523920 v 0.536228 0.412941 1.113510 v 0.527864 0.432581 1.182090 v 0.552413 0.380407 1.046560 v 0.587364 0.318202 0.994700 v 0.598286 0.190083 1.513230 v 0.590656 0.250487 1.488880 v 0.586371 0.372162 1.126470 v 0.583546 0.382809 1.187430 v 0.600213 0.343455 1.083580 v 0.636573 0.296181 1.031640 v 0.619223 0.233508 0.966682 v 0.667364 0.232055 1.012150 v 0.568080 0.233279 0.933547 v 0.643461 0.201264 1.453930 v 0.632939 0.242324 1.441710 v 0.664762 0.208641 1.416040 v 0.684454 0.215291 1.382920 v 0.456050 0.384211 1.374390 v 0.448917 0.377145 1.391710 v 0.478930 0.379940 1.388610 v 0.470130 0.373683 1.402150 v 0.434311 0.378428 1.384510 v 0.440339 0.386168 1.362490 v 0.446923 0.368618 1.408280 v 0.436017 0.368188 1.405640 v 0.463798 0.366995 1.413770 v 0.478930 0.391277 1.343160 v 0.466541 0.389245 1.357910 v 0.499030 0.387555 1.359830 v 0.488984 0.384633 1.374140 v 0.451446 0.390580 1.342460 v 0.466052 0.391566 1.326560 v 0.503776 0.386198 1.321340 v 0.491683 0.389875 1.330760 v 0.514089 0.385931 1.333980 v 0.507646 0.387822 1.346090 v 0.482504 0.389341 1.315670 v 0.498356 0.384441 1.308460 v 0.514356 0.381690 1.316090 v 0.517129 0.383810 1.324880 v 0.525225 0.377278 1.317500 v 0.524194 0.380845 1.324470 v 0.513088 0.378872 1.305730 v 0.526931 0.373023 1.308510 v 0.540743 0.371666 1.338690 v 0.549106 0.362776 1.332140 v 0.534767 0.373890 1.325690 v 0.539312 0.367632 1.317670 v 0.529993 0.379288 1.330950 v 0.528762 0.380496 1.340880 v 0.542530 0.368633 1.354980 v 0.526196 0.380192 1.354790 v 0.539713 0.364482 1.372480 v 0.520658 0.377961 1.369970 v 0.554037 0.351736 1.370250 v 0.554586 0.357586 1.350330 v 0.532321 0.359892 1.389830 v 0.512183 0.374098 1.385110 v 0.520865 0.355540 1.405960 v 0.501892 0.369649 1.399520 v 0.533870 0.341972 1.408780 v 0.546942 0.346087 1.390210 v 0.507090 0.351558 1.419260 v 0.491112 0.364341 1.412010 v 0.492647 0.348407 1.428750 v 0.481733 0.359091 1.421900 v 0.498059 0.338591 1.433210 v 0.516817 0.339384 1.423510 v 0.478345 0.347866 1.432940 v 0.476009 0.354487 1.428670 v 0.463531 0.352278 1.430520 v 0.466141 0.356607 1.427850 v 0.460758 0.347362 1.433370 v 0.479390 0.340771 1.436980 v 0.451675 0.359951 1.421520 v 0.459490 0.361946 1.421480 v 0.444980 0.357379 1.422670 v 0.417955 0.410087 1.361190 v 0.428521 0.402939 1.360480 v 0.434222 0.413875 1.330630 v 0.442964 0.406498 1.334080 v 0.467260 0.399847 1.320910 v 0.460899 0.407647 1.314510 v 0.451713 0.399113 1.337540 v 0.457748 0.362109 1.475390 v 0.461640 0.358431 1.458590 v 0.433933 0.378806 1.460370 v 0.440636 0.372763 1.445310 v 0.434927 0.377857 1.410990 v 0.425725 0.384974 1.422500 v 0.447331 0.366728 1.430250 v 0.416524 0.392100 1.434010 v 0.410259 0.401938 1.397750 v 0.421344 0.395132 1.391790 v 0.439086 0.395785 1.359780 v 0.432421 0.388326 1.385830 v 0.477811 0.415937 1.293120 v 0.454530 0.415454 1.308090 v 0.480895 0.407032 1.301390 v 0.501181 0.393182 1.302450 v 0.501107 0.401160 1.293390 v 0.483979 0.398120 1.309670 v 0.501010 0.409145 1.284330 v 0.523542 0.397631 1.281200 v 0.520814 0.391855 1.290650 v 0.518085 0.386079 1.300090 v 0.534219 0.377879 1.302910 v 0.539920 0.381720 1.293840 v 0.548980 0.369842 1.312140 v 0.561362 0.362509 1.328070 v 0.558478 0.371918 1.306200 v 0.572832 0.362405 1.326390 v 0.568747 0.373653 1.300030 v 0.545637 0.385583 1.284860 v 0.592710 0.348148 1.351220 v 0.585718 0.360552 1.323820 v 0.580113 0.352811 1.350020 v 0.569414 0.355317 1.349260 v 0.570422 0.348170 1.372900 v 0.579772 0.343714 1.375690 v 0.579468 0.330391 1.407520 v 0.590129 0.337931 1.379570 v 0.570771 0.335929 1.401720 v 0.562052 0.341460 1.395950 v 0.546430 0.337723 1.416530 v 0.552984 0.332185 1.425650 v 0.534219 0.327803 1.457680 v 0.559539 0.326654 1.434770 v 0.529926 0.332066 1.444920 v 0.525633 0.336329 1.432140 v 0.504828 0.338546 1.442080 v 0.507075 0.336656 1.457920 v 0.483905 0.346435 1.480090 v 0.509314 0.334765 1.473760 v 0.484484 0.345523 1.462680 v 0.485055 0.344611 1.445280 v 0.465533 0.354761 1.441790 v 0.497303 0.384737 1.300610 v 0.479983 0.390142 1.307600 v 0.462433 0.392708 1.318540 v 0.434601 0.387740 1.357180 v 0.428387 0.380111 1.381700 v 0.431271 0.368678 1.405110 v 0.446634 0.391929 1.335350 v 0.529147 0.370872 1.301380 v 0.513763 0.378198 1.298450 v 0.442029 0.356615 1.423820 v 0.459668 0.345197 1.435090 v 0.554430 0.357653 1.326360 v 0.542990 0.363859 1.310780 v 0.479753 0.337041 1.439170 v 0.500284 0.333201 1.435570 v 0.562208 0.344582 1.367840 v 0.561837 0.351328 1.345990 v 0.521021 0.332281 1.425470 v 0.540432 0.334001 1.410010 v 0.554482 0.338331 1.389730 v 0.600643 0.352485 1.318070 v 0.582159 0.373334 1.289070 v 0.550842 0.390439 1.269610 v 0.603282 0.324200 1.388730 v 0.608235 0.335811 1.353050 v 0.429500 0.436504 1.296200 v 0.398159 0.428845 1.326270 v 0.378104 0.423736 1.365990 v 0.509410 0.320693 1.506320 v 0.541663 0.314153 1.484830 v 0.570126 0.312322 1.454490 v 0.380876 0.396926 1.458890 v 0.405373 0.376693 1.490920 v 0.438723 0.354420 1.509750 v 0.371201 0.413319 1.414210 v 0.495108 0.426850 1.268020 v 0.463145 0.436823 1.278040 v 0.523171 0.409857 1.264770 v 0.591168 0.315495 1.421330 v 0.474993 0.335084 1.515620 v 0.619319 0.335996 1.302800 v 0.594660 0.368574 1.261010 v 0.559843 0.398068 1.242140 v 0.627690 0.311499 1.349180 v 0.619357 0.298517 1.395120 v 0.400547 0.448752 1.271590 v 0.353370 0.447544 1.309170 v 0.325262 0.432641 1.356930 v 0.545184 0.283414 1.509490 v 0.501396 0.287343 1.536480 v 0.580699 0.286001 1.474570 v 0.361436 0.354628 1.517340 v 0.324395 0.382750 1.478740 v 0.407865 0.328062 1.540900 v 0.312473 0.412578 1.426300 v 0.485485 0.442502 1.238910 v 0.444825 0.450680 1.247500 v 0.522230 0.420800 1.237670 v 0.603461 0.290309 1.437350 v 0.455294 0.304826 1.547230 v 0.648265 0.310498 1.281370 v 0.614092 0.360211 1.213440 v 0.646093 0.258976 1.395630 v 0.660209 0.272240 1.342400 v 0.726559 0.236348 1.163660 v 0.722444 0.223254 1.164420 v 0.720220 0.229037 1.162410 v 0.728888 0.226383 1.158840 v 0.733892 0.190068 1.171410 v 0.740565 0.188756 1.157380 v 0.727523 0.209560 1.168610 v 0.735880 0.210027 1.158370 v 0.743546 0.203332 1.193710 v 0.734137 0.218902 1.185810 v 0.766708 0.211288 1.214560 v 0.647835 0.337531 1.205210 v 0.697533 0.281175 1.258950 v 0.662834 0.339154 1.209580 v 0.713889 0.283703 1.257950 v 0.679487 0.285074 1.268330 v 0.626749 0.357438 1.196160 v 0.729185 0.211147 1.071010 v 0.701714 0.201493 1.066450 v 0.693603 0.266398 1.076370 v 0.654323 0.269171 1.075280 v 0.706786 0.249797 1.302520 v 0.691965 0.248144 1.304840 v 0.705081 0.202732 1.343190 v 0.716743 0.207640 1.343560 v 0.734782 0.211310 1.338840 v 0.726997 0.254795 1.298970 v 0.689993 0.345597 1.215980 v 0.731742 0.288100 1.262940 v 0.712569 0.347844 1.219890 v 0.750515 0.292103 1.267570 v 0.631531 0.373542 1.163580 v 0.642148 0.364696 1.192060 v 0.663234 0.384240 1.165590 v 0.673688 0.370517 1.193590 v 0.705985 0.371681 1.189580 v 0.697229 0.380771 1.160440 v 0.667935 0.375877 1.132860 v 0.635342 0.369642 1.131200 v 0.721970 0.268719 1.079770 v 0.687427 0.335877 1.091820 v 0.655901 0.335959 1.090290 v 0.749121 0.270453 1.083190 v 0.718315 0.332852 1.101290 v 0.752695 0.214172 1.074150 v 0.778994 0.212215 1.079390 v 0.753384 0.216337 1.332980 v 0.776228 0.222276 1.324640 v 0.745162 0.260103 1.299230 v 0.763201 0.264826 1.298800 v 0.719998 0.369872 1.167420 v 0.719026 0.370598 1.152250 v 0.710463 0.374483 1.130660 v 0.888094 0.269527 1.105780 v 0.879791 0.270824 1.083690 v 0.852891 0.301957 1.116530 v 0.841169 0.306072 1.101310 v 0.851586 0.268363 1.075670 v 0.829166 0.296277 1.086270 v 0.903568 0.241197 1.097870 v 0.895331 0.237920 1.071240 v 0.870649 0.232930 1.065130 v 0.914667 0.272159 1.381630 v 0.930867 0.278016 1.365470 v 0.883401 0.319677 1.353200 v 0.899668 0.327744 1.335380 v 0.872695 0.354465 1.287490 v 0.858919 0.348340 1.310030 v 0.775760 0.266205 1.189130 v 0.742589 0.248974 1.182730 v 0.753807 0.286335 1.172610 v 0.741388 0.260645 1.159500 v 0.726589 0.230980 1.172780 v 0.747186 0.292771 1.152010 v 0.741796 0.314161 1.150130 v 0.745081 0.239173 1.155550 v 0.762215 0.253972 1.149810 v 0.773173 0.226413 1.146690 v 0.752124 0.217041 1.151610 v 0.782730 0.202702 1.144140 v 0.759612 0.194391 1.149480 v 0.733188 0.345204 1.226300 v 0.769377 0.295447 1.273370 v 0.756780 0.346450 1.238170 v 0.783012 0.298368 1.286860 v 0.749877 0.323036 1.161900 v 0.770845 0.331740 1.170970 v 0.771416 0.300044 1.181140 v 0.792280 0.308741 1.186100 v 0.812906 0.288656 1.208220 v 0.797455 0.277520 1.195900 v 0.757618 0.351966 1.170150 v 0.733922 0.345642 1.162310 v 0.730971 0.340422 1.148720 v 0.728947 0.342906 1.152680 v 0.730867 0.362917 1.174450 v 0.751464 0.365460 1.180210 v 0.788617 0.251110 1.221500 v 0.809955 0.261556 1.231210 v 0.827305 0.270743 1.240790 v 0.840844 0.236771 1.247210 v 0.823397 0.230520 1.238330 v 0.801888 0.223988 1.228340 v 0.824725 0.294795 1.327770 v 0.822626 0.336418 1.300710 v 0.811720 0.298079 1.312930 v 0.801110 0.343959 1.275760 v 0.844402 0.321553 1.328770 v 0.834949 0.291154 1.339940 v 0.845114 0.338509 1.231930 v 0.874282 0.330020 1.264440 v 0.850815 0.320715 1.237930 v 0.874141 0.314821 1.263380 v 0.873956 0.303062 1.264050 v 0.859520 0.302847 1.248490 v 0.842007 0.299962 1.231620 v 0.829247 0.319314 1.216870 v 0.819364 0.337567 1.206850 v 0.813232 0.354257 1.207240 v 0.809881 0.365927 1.217470 v 0.842148 0.355836 1.233440 v 0.840399 0.367655 1.246030 v 0.874445 0.344856 1.272500 v 0.819223 0.275740 1.332220 v 0.834260 0.240033 1.338200 v 0.831264 0.274776 1.344270 v 0.844528 0.241434 1.349140 v 0.857036 0.243458 1.361410 v 0.843016 0.275510 1.355090 v 0.853181 0.280604 1.261040 v 0.869811 0.284118 1.274540 v 0.885803 0.287521 1.284690 v 0.904562 0.257152 1.298270 v 0.885515 0.251888 1.282830 v 0.868669 0.246669 1.267800 v 0.864391 0.297404 1.361350 v 0.887308 0.257679 1.383750 v 0.927583 0.269171 1.325520 v 0.919487 0.262461 1.310590 v 0.900231 0.304819 1.299410 v 0.895220 0.294795 1.290980 v 0.774300 0.272025 1.085700 v 0.746652 0.329938 1.103460 v 0.797217 0.271440 1.084460 v 0.772358 0.327655 1.100400 v 0.768458 0.337746 1.137370 v 0.763149 0.341430 1.126250 v 0.741210 0.344307 1.141130 v 0.734634 0.349460 1.130870 v 0.768228 0.295521 1.148880 v 0.785236 0.260741 1.147520 v 0.796802 0.233746 1.143580 v 0.809021 0.267177 1.146380 v 0.820676 0.240982 1.140190 v 0.798915 0.220252 1.079560 v 0.818400 0.225419 1.078090 v 0.806122 0.210487 1.140730 v 0.828432 0.217471 1.134770 v 0.819683 0.268548 1.077920 v 0.802549 0.314116 1.094850 v 0.801006 0.328137 1.116670 v 0.806945 0.327484 1.131980 v 0.833651 0.276845 1.136980 v 0.851483 0.252630 1.133120 v 0.837573 0.228645 1.072620 v 0.864999 0.228837 1.131950 v 0.813655 0.234746 1.322770 v 0.795016 0.273879 1.310240 v 0.795527 0.227740 1.320540 v 0.781729 0.268719 1.300270 v 0.854827 0.241983 1.256240 v 0.840191 0.276585 1.249700 v 0.827149 0.295647 1.219010 v 0.806767 0.369998 1.242890 v 0.780395 0.370279 1.218740 v 0.780128 0.367914 1.193110 v 0.750649 0.368974 1.204450 v 0.779572 0.347339 1.254840 v 0.797863 0.299221 1.299260 v 0.793229 0.335803 1.181870 v 0.809955 0.315310 1.199530 v 0.830864 0.364037 1.273360 v 0.725418 0.366595 1.197120 v 0.611201 0.364563 1.162880 v 0.683445 0.363747 1.113310 v 0.825547 0.238513 1.329060 v 0.931861 0.275777 1.344880 v 0.898690 0.239848 1.114570 v 0.884224 0.266420 1.119800 v 0.719612 0.360381 1.143910 v 0.752791 0.331310 1.144710 v 0.792235 0.297308 1.148610 v 0.780365 0.324652 1.142570 v 0.813907 0.315584 1.138760 v 0.814167 0.297768 1.144450 v 0.783679 0.355399 1.181960 v 0.854308 0.295180 1.125410 v 0.902145 0.316971 1.314600 v 0.752428 0.232937 1.208610 v 0.807976 0.276852 1.320830 v 0.699438 0.211621 1.358910 v 0.682393 0.262038 1.316700 v 0.674378 0.295202 1.271360 v 0.628091 0.359907 1.206870 v 0.605767 0.373935 1.176530 v 0.626030 0.318602 1.100790 v 0.620921 0.331525 1.095560 v 0.612017 0.352759 1.134640 v 0.607761 0.361998 1.132470 v 0.684684 0.225375 1.048050 v 0.652417 0.281842 1.059350 v 0.713073 0.360092 1.123940 v 0.683164 0.352945 1.106170 v 0.015383 0.487276 0.662289 v -0.109333 -0.080279 1.129310 v -0.097982 -0.072242 1.081280 v -0.080350 -0.063404 1.034650 v -0.056677 -0.052742 0.991208 v -0.028213 -0.040472 0.951602 v 0.013106 -0.059222 0.915316 v -0.074249 -0.082177 1.033830 v -0.049099 -0.077476 0.990215 v -0.019034 -0.070885 0.951527 v 0.591887 -0.089524 0.918044 v -0.054986 -0.218177 0.101436 v 0.402660 -0.136679 0.101244 v 0.327190 -0.185309 0.101303 v 0.227994 -0.217028 0.101333 v 0.142263 -0.230462 0.101377 v 0.027016 -0.231159 0.101370 v 0.068209 -0.236438 0.101370 v 0.465392 0.083562 0.101481 v -0.125993 0.282717 0.101510 v -0.065737 0.300845 0.101422 v -0.184966 0.256115 0.101644 v -0.216988 0.205282 0.101659 v 0.406619 0.219510 0.101362 v 0.329614 0.271618 0.101496 v 0.229625 0.318320 0.101599 v 0.138408 0.336470 0.101637 v 0.021166 0.324430 0.101488 v 0.447679 0.155147 0.101347 v 0.060432 0.325260 0.101488 vt 0.877975 0.397658 vt 0.873951 0.397664 vt 0.900103 0.397679 vt 0.870831 0.397671 vt 0.867889 0.397674 vt 0.931273 0.397662 vt 0.929288 0.397637 vt 0.865490 0.397668 vt 0.867711 0.397671 vt 0.868707 0.397667 vt 0.866606 0.398924 vt 0.871384 0.397619 vt 0.874160 0.398826 vt 0.878048 0.397558 vt 0.866060 0.397625 vt 0.860570 0.399008 vt 0.856321 0.399060 vt 0.862375 0.397683 vt 0.860024 0.397676 vt 0.853776 0.399148 vt 0.932361 0.401286 vt 0.944998 0.401429 vt 0.936097 0.404570 vt 0.948825 0.405179 vt 0.939922 0.398832 vt 0.928618 0.398828 vt 0.862287 0.401557 vt 0.870712 0.401462 vt 0.855482 0.401642 vt 0.858360 0.405349 vt 0.851030 0.405417 vt 0.867250 0.404976 vt 0.851008 0.401767 vt 0.848050 0.401779 vt 0.846214 0.405443 vt 0.842460 0.405195 vt 0.951482 0.401365 vt 0.945825 0.398800 vt 0.955814 0.405156 vt 0.939709 0.408955 vt 0.951716 0.409790 vt 0.943792 0.414167 vt 0.953940 0.414936 vt 0.854643 0.410095 vt 0.863781 0.409841 vt 0.847079 0.410021 vt 0.850892 0.415208 vt 0.843668 0.414845 vt 0.858319 0.415536 vt 0.959154 0.409842 vt 0.961426 0.415077 vt 0.955396 0.420505 vt 0.946551 0.419988 vt 0.948394 0.426244 vt 0.956245 0.426562 vt 0.962774 0.420748 vt 0.963626 0.426902 vt 0.947821 0.439136 vt 0.949156 0.432796 vt 0.955188 0.438695 vt 0.956416 0.432776 vt 0.963377 0.433055 vt 0.962074 0.438861 vt 0.939469 0.450342 vt 0.944673 0.445101 vt 0.949616 0.449137 vt 0.952905 0.444195 vt 0.959891 0.444199 vt 0.957210 0.449055 vt 0.928445 0.457630 vt 0.933041 0.454385 vt 0.940767 0.456994 vt 0.945196 0.453364 vt 0.954246 0.453493 vt 0.950166 0.457401 vt 0.877438 0.456074 vt 0.890333 0.455013 vt 0.881771 0.458050 vt 0.891902 0.457826 vt 0.885457 0.459788 vt 0.893433 0.460126 vt 0.902160 0.458193 vt 0.902038 0.460510 vt 0.902093 0.455956 vt 0.875073 0.458463 vt 0.870949 0.459563 vt 0.869348 0.456995 vt 0.865831 0.459946 vt 0.876379 0.459992 vt 0.880020 0.459819 vt 0.890696 0.462723 vt 0.889158 0.462546 vt 0.893089 0.462870 vt 0.245158 0.423975 vt 0.250110 0.468112 vt 0.148569 0.498875 vt 0.150442 0.543685 vt 0.595914 0.442419 vt 0.701364 0.364720 vt 0.595493 0.387203 vt 0.704143 0.312403 vt 0.757075 0.288915 vt 0.749567 0.262372 vt 0.302841 0.416173 vt 0.302959 0.373132 vt 0.224027 0.289653 vt 0.235580 0.359702 vt 0.134156 0.320572 vt 0.143764 0.429085 vt 0.298228 0.319321 vt 0.288073 0.263566 vt 0.342229 0.232442 vt 0.353643 0.285267 vt 0.355112 0.336459 vt 0.409212 0.301844 vt 0.416748 0.239090 vt 0.412385 0.174765 vt 0.371257 0.129527 vt 0.316583 0.183819 vt 0.312010 0.090946 vt 0.278107 0.146712 vt 0.258385 0.177707 vt 0.272238 0.213152 vt 0.842115 0.409869 vt 0.837426 0.409453 vt 0.838646 0.414482 vt 0.833434 0.414204 vt 0.345064 0.636086 vt 0.339502 0.673242 vt 0.283208 0.642063 vt 0.260986 0.672117 vt 0.331126 0.714598 vt 0.241009 0.715530 vt 0.847163 0.420560 vt 0.840709 0.419570 vt 0.839445 0.430603 vt 0.835030 0.428579 vt 0.843659 0.433436 vt 0.852464 0.422383 vt 0.835601 0.419062 vt 0.830596 0.418889 vt 0.831257 0.427506 vt 0.827802 0.427141 vt 0.326898 0.757163 vt 0.328393 0.797118 vt 0.233372 0.762934 vt 0.238410 0.805793 vt 0.866399 0.449489 vt 0.869211 0.447456 vt 0.876435 0.450731 vt 0.878828 0.448367 vt 0.861778 0.461251 vt 0.863290 0.455790 vt 0.869445 0.454182 vt 0.798670 0.019963 vt 0.720188 0.090411 vt 0.815355 0.094326 vt 0.722678 0.141004 vt 0.586596 0.164171 vt 0.591566 0.221736 vt 0.919848 0.462088 vt 0.924400 0.459418 vt 0.932799 0.462552 vt 0.936505 0.460001 vt 0.914853 0.457033 vt 0.912969 0.458780 vt 0.911051 0.461039 vt 0.945951 0.460521 vt 0.941970 0.463070 vt 0.896594 0.462992 vt 0.902819 0.462828 vt 0.775513 0.183818 vt 0.714664 0.225413 vt 0.594393 0.311859 vt 0.240207 0.064107 vt 0.220228 0.127313 vt 0.137886 0.045291 vt 0.134162 0.116701 vt 0.209620 0.176711 vt 0.132836 0.176022 vt 0.803506 0.201004 vt 0.853165 0.100422 vt 0.393776 0.367451 vt 0.346805 0.380154 vt 0.214240 0.228892 vt 0.133514 0.241035 vt 0.939147 0.464980 vt 0.930869 0.464578 vt 0.937502 0.466649 vt 0.929700 0.466322 vt 0.911763 0.464455 vt 0.904647 0.464520 vt 0.910929 0.462701 vt 0.906026 0.466148 vt 0.912450 0.466086 vt 0.920833 0.464349 vt 0.920890 0.466118 vt 0.899670 0.464526 vt 0.896490 0.464450 vt 0.897930 0.466107 vt 0.901270 0.466162 vt 0.924570 0.397524 vt 0.934172 0.397517 vt 0.901960 0.397532 vt 0.912366 0.397543 vt 0.902477 0.398810 vt 0.914428 0.398827 vt 0.887934 0.397535 vt 0.886084 0.398748 vt 0.939158 0.397548 vt 0.916553 0.401268 vt 0.903345 0.401161 vt 0.904419 0.404260 vt 0.918892 0.404595 vt 0.883770 0.401252 vt 0.880927 0.404722 vt 0.600870 0.640918 vt 0.595916 0.679782 vt 0.517366 0.634965 vt 0.510699 0.676291 vt 0.593633 0.724091 vt 0.505796 0.719121 vt 0.905488 0.424961 vt 0.912357 0.424967 vt 0.905294 0.428912 vt 0.913340 0.429138 vt 0.911087 0.422154 vt 0.905906 0.421495 vt 0.587548 0.772055 vt 0.501279 0.762876 vt 0.497144 0.804816 vt 0.579229 0.819401 vt 0.904076 0.437412 vt 0.904780 0.433190 vt 0.911884 0.438770 vt 0.913199 0.433998 vt 0.570764 0.860428 vt 0.494669 0.842655 vt 0.495808 0.877438 vt 0.568980 0.893979 vt 0.903210 0.444063 vt 0.903361 0.441071 vt 0.908067 0.444769 vt 0.909872 0.442542 vt 0.918142 0.453706 vt 0.902496 0.453271 vt 0.928783 0.450398 vt 0.935730 0.445715 vt 0.939896 0.439756 vt 0.941415 0.433124 vt 0.940295 0.426582 vt 0.937084 0.420446 vt 0.931618 0.414911 vt 0.921769 0.409549 vt 0.905101 0.407893 vt 0.876573 0.409920 vt 0.866307 0.415598 vt 0.859425 0.421874 vt 0.854503 0.429219 vt 0.853866 0.435555 vt 0.855666 0.441340 vt 0.846009 0.439206 vt 0.849189 0.444410 vt 0.859693 0.446280 vt 0.853635 0.449316 vt 0.859396 0.453185 vt 0.333511 0.830512 vt 0.255441 0.838224 vt 0.335539 0.859065 vt 0.275714 0.857682 vt 0.889827 0.451740 vt 0.862926 0.421743 vt 0.859205 0.428005 vt 0.137030 0.645509 vt 0.099669 0.709478 vt 0.114209 0.644610 vt 0.072436 0.710311 vt 0.217381 0.600094 vt 0.193334 0.589335 vt 0.869662 0.416377 vt 0.857993 0.434235 vt 0.859086 0.440054 vt 0.083907 0.774978 vt 0.093256 0.834193 vt 0.056643 0.778851 vt 0.067488 0.842396 vt 0.916008 0.412068 vt 0.903601 0.410265 vt 0.543475 0.557405 vt 0.557800 0.533271 vt 0.655545 0.572596 vt 0.685834 0.551732 vt 0.910530 0.415402 vt 0.913091 0.413541 vt 0.918257 0.418305 vt 0.921836 0.416936 vt 0.925340 0.416062 vt 0.879532 0.412275 vt 0.318062 0.571086 vt 0.304302 0.551225 vt 0.931494 0.421140 vt 0.923547 0.422692 vt 0.927682 0.421785 vt 0.926515 0.428111 vt 0.931209 0.427509 vt 0.935120 0.427050 vt 0.936295 0.433481 vt 0.927320 0.434037 vt 0.932331 0.433786 vt 0.925669 0.439922 vt 0.930432 0.440034 vt 0.934525 0.439972 vt 0.912845 0.451737 vt 0.922595 0.449614 vt 0.907361 0.448978 vt 0.915126 0.447877 vt 0.909842 0.450414 vt 0.918684 0.448912 vt 0.515560 0.943140 vt 0.618068 0.949096 vt 0.529717 0.959537 vt 0.647404 0.965201 vt 0.901128 0.451294 vt 0.929882 0.445604 vt 0.921412 0.444811 vt 0.925747 0.445329 vt 0.862632 0.444730 vt 0.194162 0.909528 vt 0.176891 0.924466 vt 0.127886 0.880246 vt 0.105635 0.892789 vt 0.298575 0.918513 vt 0.289302 0.932026 vt 0.410040 0.927663 vt 0.412647 0.940987 vt 0.152917 0.714025 vt 0.188039 0.660899 vt 0.251002 0.622891 vt 0.148083 0.821618 vt 0.139091 0.770988 vt 0.624566 0.605374 vt 0.529124 0.594060 vt 0.907910 0.418325 vt 0.914417 0.420366 vt 0.333860 0.602351 vt 0.918293 0.424021 vt 0.920590 0.428770 vt 0.920879 0.434155 vt 0.919487 0.439514 vt 0.911345 0.446349 vt 0.904990 0.446728 vt 0.503730 0.914675 vt 0.590033 0.923870 vt 0.916084 0.443813 vt 0.174218 0.860862 vt 0.232830 0.882474 vt 0.318561 0.890705 vt 0.412205 0.900441 vt 0.414872 0.864697 vt 0.414169 0.832004 vt 0.413463 0.796679 vt 0.414763 0.757000 vt 0.418623 0.715141 vt 0.424042 0.673740 vt 0.428561 0.633258 vt 0.429426 0.594048 vt 0.429051 0.559095 vt 0.429259 0.535162 vt 0.891421 0.410387 vt 0.891335 0.407994 vt 0.892300 0.404278 vt 0.892667 0.401108 vt 0.892803 0.398753 vt 0.893101 0.397518 vt 0.953337 0.401538 vt 0.958091 0.405199 vt 0.947500 0.399036 vt 0.961860 0.409791 vt 0.964499 0.415028 vt 0.966120 0.420717 vt 0.967305 0.426924 vt 0.967388 0.433074 vt 0.966427 0.438883 vt 0.964510 0.444202 vt 0.961861 0.448940 vt 0.958944 0.453645 vt 0.955428 0.457592 vt 0.951844 0.460785 vt 0.948132 0.463506 vt 0.945008 0.465414 vt 0.942800 0.466896 vt 0.940897 0.397660 vt 0.894646 0.464377 vt 0.893748 0.464327 vt 0.895819 0.465909 vt 0.894732 0.465707 vt 0.898046 0.468336 vt 0.895591 0.467749 vt 0.894211 0.467377 vt 0.892528 0.469525 vt 0.894053 0.470321 vt 0.897020 0.472125 vt 0.906461 0.468060 vt 0.901761 0.468217 vt 0.901883 0.471147 vt 0.906501 0.470456 vt 0.912592 0.467918 vt 0.912253 0.470089 vt 0.920674 0.470154 vt 0.920682 0.467923 vt 0.929876 0.470181 vt 0.929300 0.468129 vt 0.937020 0.468441 vt 0.938213 0.470896 vt 0.942275 0.468514 vt 0.942757 0.470744 vt 0.890006 0.449500 vt 0.859393 0.397599 vt 0.852976 0.399065 vt 0.847065 0.401675 vt 0.841270 0.405026 vt 0.953466 0.401386 vt 0.958188 0.405089 vt 0.947588 0.398916 vt 0.961862 0.409747 vt 0.964391 0.415022 vt 0.965926 0.420729 vt 0.967068 0.426923 vt 0.967242 0.433135 vt 0.966314 0.439008 vt 0.964406 0.444378 vt 0.961584 0.449120 vt 0.954279 0.457569 vt 0.958267 0.453722 vt 0.870463 0.459645 vt 0.869977 0.459728 vt 0.865611 0.460368 vt 0.865391 0.460791 vt 0.875883 0.460032 vt 0.875386 0.460071 vt 0.889107 0.462568 vt 0.889055 0.462590 vt 0.044392 0.517280 vt 0.043999 0.560029 vt 0.579657 0.457140 vt 0.472222 0.467543 vt 0.576570 0.399149 vt 0.470583 0.402538 vt 0.055194 0.327681 vt 0.048040 0.442488 vt 0.836433 0.409343 vt 0.832578 0.414030 vt 0.832317 0.414141 vt 0.829542 0.419704 vt 0.829240 0.419624 vt 0.456894 0.171519 vt 0.545104 0.178866 vt 0.459246 0.232082 vt 0.554780 0.242428 vt 0.861810 0.461905 vt 0.861843 0.462560 vt 0.950530 0.460750 vt 0.946775 0.463400 vt 0.468306 0.320445 vt 0.572701 0.325840 vt 0.057144 0.041836 vt 0.056343 0.115320 vt 0.056323 0.176259 vt 0.056512 0.244825 vt 0.943577 0.465237 vt 0.941527 0.466697 vt 0.893773 0.464335 vt 0.893798 0.464342 vt 0.894526 0.465626 vt 0.894385 0.465571 vt 0.940912 0.397604 vt 0.893807 0.467247 vt 0.893563 0.467166 vt 0.891985 0.469369 vt 0.891535 0.469419 vt 0.940762 0.470517 vt 0.940364 0.468392 vt 0.866236 0.398834 vt 0.873711 0.398813 vt 0.870971 0.397493 vt 0.877506 0.397514 vt 0.860032 0.398872 vt 0.865522 0.397455 vt 0.856026 0.399014 vt 0.861954 0.397568 vt 0.853853 0.399094 vt 0.860159 0.397664 vt 0.948546 0.405179 vt 0.944496 0.401457 vt 0.935431 0.404681 vt 0.931632 0.401375 vt 0.939488 0.398861 vt 0.927974 0.398901 vt 0.870657 0.401504 vt 0.861966 0.401526 vt 0.855096 0.401600 vt 0.850751 0.405417 vt 0.858121 0.405363 vt 0.867731 0.405188 vt 0.850561 0.401575 vt 0.848094 0.401586 vt 0.842791 0.404957 vt 0.845766 0.405198 vt 0.945500 0.398818 vt 0.951158 0.401375 vt 0.955535 0.405156 vt 0.953600 0.414936 vt 0.951437 0.409790 vt 0.942950 0.414410 vt 0.938847 0.409148 vt 0.863906 0.410047 vt 0.854469 0.410101 vt 0.846800 0.410021 vt 0.843291 0.414792 vt 0.850618 0.415218 vt 0.858112 0.415702 vt 0.958875 0.409842 vt 0.961147 0.415077 vt 0.955100 0.420509 vt 0.945887 0.420220 vt 0.955942 0.426574 vt 0.947818 0.426423 vt 0.962495 0.420748 vt 0.963347 0.426902 vt 0.947179 0.438896 vt 0.955415 0.438781 vt 0.948355 0.432791 vt 0.956270 0.432802 vt 0.963455 0.433109 vt 0.962550 0.439010 vt 0.939730 0.449457 vt 0.950281 0.449152 vt 0.944309 0.444589 vt 0.953464 0.444290 vt 0.960647 0.444424 vt 0.957836 0.449233 vt 0.928866 0.456683 vt 0.941589 0.457208 vt 0.933669 0.453285 vt 0.946319 0.453375 vt 0.954103 0.453514 vt 0.949874 0.457416 vt 0.877353 0.455968 vt 0.882269 0.458025 vt 0.890577 0.455047 vt 0.892317 0.457757 vt 0.886684 0.460112 vt 0.894230 0.460211 vt 0.902677 0.460467 vt 0.902670 0.458019 vt 0.902808 0.455334 vt 0.865825 0.458435 vt 0.871446 0.458982 vt 0.869597 0.456984 vt 0.875200 0.458466 vt 0.877063 0.460000 vt 0.880928 0.460060 vt 0.890358 0.462714 vt 0.889224 0.462652 vt 0.892541 0.462593 vt 0.253473 0.420340 vt 0.198325 0.471074 vt 0.257191 0.464291 vt 0.201641 0.514357 vt 0.644363 0.422582 vt 0.643061 0.366324 vt 0.703499 0.366966 vt 0.703370 0.314326 vt 0.745969 0.267070 vt 0.753820 0.293297 vt 0.305266 0.371658 vt 0.304409 0.414784 vt 0.230885 0.286168 vt 0.167912 0.306331 vt 0.242536 0.354324 vt 0.188981 0.401339 vt 0.300813 0.317790 vt 0.290984 0.262458 vt 0.343946 0.231017 vt 0.355397 0.284532 vt 0.356783 0.335152 vt 0.414764 0.292810 vt 0.419941 0.236001 vt 0.413771 0.173263 vt 0.370421 0.125545 vt 0.313385 0.086563 vt 0.319456 0.180460 vt 0.282902 0.142155 vt 0.261679 0.175104 vt 0.275739 0.211488 vt 0.841603 0.409572 vt 0.838159 0.409243 vt 0.834209 0.413908 vt 0.837981 0.414182 vt 0.352720 0.639241 vt 0.294558 0.645659 vt 0.348213 0.675519 vt 0.274212 0.673847 vt 0.253789 0.714174 vt 0.339072 0.715472 vt 0.846514 0.420792 vt 0.839885 0.419736 vt 0.834481 0.428371 vt 0.838869 0.430451 vt 0.843430 0.433385 vt 0.852102 0.422532 vt 0.834732 0.419195 vt 0.830666 0.418987 vt 0.827717 0.426976 vt 0.830662 0.427303 vt 0.241492 0.801977 vt 0.330569 0.796358 vt 0.241562 0.759109 vt 0.332348 0.756954 vt 0.878346 0.448288 vt 0.868619 0.446935 vt 0.875777 0.450429 vt 0.865577 0.448677 vt 0.860982 0.459109 vt 0.863516 0.455972 vt 0.868771 0.453598 vt 0.797949 0.022112 vt 0.816816 0.093012 vt 0.715991 0.092651 vt 0.720595 0.147032 vt 0.636546 0.210325 vt 0.627672 0.148444 vt 0.919955 0.462087 vt 0.933215 0.462650 vt 0.924520 0.459410 vt 0.936971 0.460171 vt 0.915797 0.455832 vt 0.913434 0.458571 vt 0.911682 0.460937 vt 0.945768 0.460575 vt 0.941689 0.463136 vt 0.897291 0.462545 vt 0.903757 0.462585 vt 0.772502 0.189202 vt 0.711355 0.231084 vt 0.641706 0.289174 vt 0.247515 0.059560 vt 0.175872 0.043369 vt 0.230266 0.122298 vt 0.166908 0.113803 vt 0.162066 0.173298 vt 0.220575 0.173418 vt 0.800674 0.205793 vt 0.855305 0.094200 vt 0.399643 0.347472 vt 0.348371 0.378515 vt 0.223314 0.226619 vt 0.162858 0.235961 vt 0.938505 0.464915 vt 0.930828 0.464538 vt 0.929084 0.466249 vt 0.936418 0.466469 vt 0.904992 0.464496 vt 0.911714 0.462545 vt 0.912260 0.464434 vt 0.906001 0.466171 vt 0.912549 0.466198 vt 0.920438 0.466178 vt 0.920682 0.464371 vt 0.899694 0.464496 vt 0.896238 0.464476 vt 0.897673 0.466070 vt 0.901142 0.466120 vt 0.933851 0.397545 vt 0.924135 0.397565 vt 0.901567 0.397461 vt 0.902111 0.398787 vt 0.911927 0.397463 vt 0.913998 0.398826 vt 0.885568 0.398789 vt 0.887707 0.397486 vt 0.939027 0.397536 vt 0.902897 0.401138 vt 0.916139 0.401275 vt 0.904075 0.404332 vt 0.918631 0.404656 vt 0.883077 0.401373 vt 0.880245 0.404983 vt 0.600135 0.641640 vt 0.519216 0.636196 vt 0.595756 0.680499 vt 0.513483 0.677706 vt 0.509018 0.719971 vt 0.595108 0.724198 vt 0.912942 0.429219 vt 0.912006 0.425106 vt 0.905419 0.428922 vt 0.905474 0.425025 vt 0.910791 0.422281 vt 0.905844 0.421559 vt 0.590769 0.769750 vt 0.503982 0.762367 vt 0.498249 0.802950 vt 0.581926 0.813498 vt 0.904305 0.436885 vt 0.911907 0.438227 vt 0.905052 0.432984 vt 0.913047 0.433844 vt 0.571758 0.851938 vt 0.493564 0.840197 vt 0.493370 0.874734 vt 0.568900 0.884732 vt 0.903203 0.443238 vt 0.908124 0.443775 vt 0.903445 0.440313 vt 0.909903 0.441649 vt 0.902850 0.452456 vt 0.918742 0.452460 vt 0.929033 0.449192 vt 0.935553 0.444785 vt 0.939489 0.439171 vt 0.940895 0.433027 vt 0.939916 0.426757 vt 0.936762 0.420698 vt 0.931211 0.415109 vt 0.904610 0.407951 vt 0.921344 0.409650 vt 0.876099 0.410269 vt 0.865754 0.416146 vt 0.858856 0.422274 vt 0.854080 0.429250 vt 0.848700 0.443676 vt 0.855252 0.440105 vt 0.845714 0.439016 vt 0.853622 0.434905 vt 0.852614 0.447663 vt 0.858958 0.445110 vt 0.858176 0.451853 vt 0.254385 0.836095 vt 0.333265 0.830744 vt 0.334347 0.859927 vt 0.274272 0.857068 vt 0.889824 0.451480 vt 0.858947 0.427844 vt 0.863033 0.422095 vt 0.160452 0.654158 vt 0.126019 0.649524 vt 0.111374 0.707428 vt 0.074711 0.707898 vt 0.232092 0.611555 vt 0.203545 0.598866 vt 0.869857 0.416948 vt 0.858451 0.439034 vt 0.857612 0.433475 vt 0.086444 0.767612 vt 0.053832 0.770468 vt 0.085639 0.828171 vt 0.059040 0.833261 vt 0.915613 0.412200 vt 0.903128 0.410252 vt 0.540600 0.554531 vt 0.653253 0.571690 vt 0.552741 0.532581 vt 0.682275 0.551691 vt 0.910336 0.415321 vt 0.917730 0.418674 vt 0.912790 0.413538 vt 0.921258 0.417307 vt 0.924944 0.416332 vt 0.879341 0.412694 vt 0.321928 0.578317 vt 0.305300 0.558373 vt 0.930972 0.421500 vt 0.922517 0.423241 vt 0.926792 0.422268 vt 0.925060 0.428584 vt 0.929895 0.427930 vt 0.934420 0.427304 vt 0.935453 0.433435 vt 0.925801 0.434263 vt 0.930838 0.433902 vt 0.924243 0.439743 vt 0.929152 0.439726 vt 0.933786 0.439455 vt 0.922618 0.448568 vt 0.913071 0.450609 vt 0.907300 0.448560 vt 0.909883 0.449686 vt 0.914720 0.447235 vt 0.918464 0.448158 vt 0.513713 0.938992 vt 0.528694 0.953445 vt 0.617347 0.944413 vt 0.647892 0.957038 vt 0.901060 0.450464 vt 0.929479 0.444749 vt 0.920523 0.444350 vt 0.924927 0.444695 vt 0.861743 0.443993 vt 0.191556 0.905473 vt 0.119561 0.876603 vt 0.173642 0.917872 vt 0.096090 0.886393 vt 0.296111 0.916924 vt 0.285563 0.930752 vt 0.407594 0.925155 vt 0.411737 0.936637 vt 0.175067 0.711347 vt 0.212659 0.665019 vt 0.264355 0.628942 vt 0.154483 0.814520 vt 0.154113 0.763611 vt 0.623018 0.605621 vt 0.528987 0.594894 vt 0.907779 0.418347 vt 0.914012 0.420577 vt 0.339791 0.607004 vt 0.917709 0.424320 vt 0.919641 0.429082 vt 0.919962 0.434273 vt 0.918588 0.439240 vt 0.911088 0.445577 vt 0.904848 0.446138 vt 0.588358 0.917245 vt 0.500784 0.908814 vt 0.915514 0.443200 vt 0.178893 0.855096 vt 0.233880 0.878659 vt 0.317203 0.888741 vt 0.409219 0.897533 vt 0.412456 0.865776 vt 0.413517 0.832907 vt 0.415303 0.797177 vt 0.419030 0.758087 vt 0.424284 0.717135 vt 0.429773 0.675932 vt 0.433166 0.635493 vt 0.431719 0.596003 vt 0.426262 0.535529 vt 0.428450 0.560105 vt 0.890897 0.408138 vt 0.891059 0.410492 vt 0.891880 0.404459 vt 0.892481 0.401195 vt 0.892744 0.398811 vt 0.892774 0.397499 vt 0.852987 0.399150 vt 0.859402 0.397672 vt 0.847027 0.401638 vt 0.841440 0.405030 vt 0.139391 0.505963 vt 0.140772 0.546375 vt 0.105327 0.324975 vt 0.131454 0.436403 vt 0.836758 0.409301 vt 0.826856 0.427241 vt 0.103269 0.042003 vt 0.100779 0.113050 vt 0.099208 0.173793 vt 0.099637 0.242841 vt 0.893304 0.464316 vt 0.894343 0.465793 vt 0.894093 0.464377 vt 0.895401 0.465971 vt 0.893628 0.467665 vt 0.890751 0.471053 vt 0.897653 0.467915 vt 0.895086 0.467874 vt 0.893376 0.470550 vt 0.896513 0.470318 vt 0.906138 0.467954 vt 0.901291 0.467913 vt 0.900442 0.470186 vt 0.905385 0.470181 vt 0.912382 0.468058 vt 0.911527 0.470316 vt 0.918635 0.470453 vt 0.919770 0.468076 vt 0.926488 0.470872 vt 0.927835 0.468189 vt 0.935282 0.468257 vt 0.935209 0.471328 vt 0.878197 0.500625 vt 0.878935 0.505269 vt 0.877915 0.500707 vt 0.878376 0.505430 vt 0.878490 0.500327 vt 0.879515 0.504678 vt 0.878776 0.499827 vt 0.880077 0.503685 vt 0.879041 0.499148 vt 0.880596 0.502336 vt 0.879273 0.498323 vt 0.881050 0.500691 vt 0.879461 0.497390 vt 0.881414 0.498833 vt 0.879595 0.496393 vt 0.881685 0.496848 vt 0.879673 0.495381 vt 0.881831 0.494834 vt 0.879688 0.494400 vt 0.881855 0.492886 vt 0.879641 0.493495 vt 0.881754 0.491093 vt 0.879517 0.492692 vt 0.881496 0.489502 vt 0.879256 0.491990 vt 0.880966 0.488128 vt 0.878724 0.491679 vt 0.879976 0.487255 vt 0.878985 0.491764 vt 0.880441 0.487544 vt 0.880259 0.509816 vt 0.879427 0.510054 vt 0.881118 0.508941 vt 0.881951 0.507469 vt 0.882719 0.505470 vt 0.883385 0.503037 vt 0.883918 0.500283 vt 0.884295 0.497337 vt 0.884501 0.494341 vt 0.884524 0.491438 vt 0.884373 0.488781 vt 0.883999 0.486461 vt 0.883210 0.484429 vt 0.882394 0.483424 vt 0.882124 0.514208 vt 0.881033 0.514520 vt 0.883251 0.513061 vt 0.884343 0.511133 vt 0.885349 0.508513 vt 0.886222 0.505324 vt 0.886921 0.501715 vt 0.887414 0.497854 vt 0.887677 0.493924 vt 0.887702 0.490113 vt 0.887496 0.486645 vt 0.886980 0.483610 vt 0.885938 0.480910 vt 0.884878 0.479558 vt 0.883170 0.518762 vt 0.884506 0.518380 vt 0.885883 0.516979 vt 0.887217 0.514621 vt 0.888447 0.511419 vt 0.889514 0.507521 vt 0.890369 0.503109 vt 0.890971 0.498390 vt 0.891293 0.493586 vt 0.891320 0.488919 vt 0.891054 0.484665 vt 0.890395 0.481066 vt 0.889190 0.477958 vt 0.887817 0.476052 vt 0.885807 0.522719 vt 0.887367 0.522273 vt 0.888975 0.520637 vt 0.890533 0.517885 vt 0.891968 0.514147 vt 0.893214 0.509596 vt 0.894212 0.504446 vt 0.894915 0.498938 vt 0.895291 0.493329 vt 0.895322 0.487882 vt 0.895000 0.482915 vt 0.894205 0.478779 vt 0.892842 0.475169 vt 0.891068 0.473073 vt 0.888906 0.526332 vt 0.890668 0.525829 vt 0.892483 0.523982 vt 0.894241 0.520876 vt 0.895861 0.516656 vt 0.897267 0.511520 vt 0.898394 0.505707 vt 0.899187 0.499489 vt 0.899612 0.493158 vt 0.899646 0.487009 vt 0.899286 0.481354 vt 0.898411 0.476638 vt 0.892421 0.529549 vt 0.894359 0.528996 vt 0.896354 0.526965 vt 0.898288 0.523550 vt 0.900069 0.518911 vt 0.901615 0.513263 vt 0.902854 0.506872 vt 0.903726 0.500036 vt 0.904193 0.493075 vt 0.904231 0.486314 vt 0.903834 0.480100 vt 0.902975 0.475040 vt 0.896302 0.532323 vt 0.898388 0.531728 vt 0.900534 0.529543 vt 0.902614 0.525869 vt 0.904531 0.520877 vt 0.906194 0.514801 vt 0.907527 0.507925 vt 0.908466 0.500570 vt 0.908967 0.493081 vt 0.909009 0.485806 vt 0.908572 0.479151 vt 0.907654 0.473824 vt 0.900491 0.534614 vt 0.902694 0.533986 vt 0.904960 0.531679 vt 0.907157 0.527799 vt 0.909181 0.522528 vt 0.910937 0.516111 vt 0.912345 0.508850 vt 0.913336 0.501084 vt 0.913866 0.493175 vt 0.913909 0.485494 vt 0.913460 0.478418 vt 0.912585 0.472851 vt 0.904927 0.536388 vt 0.907215 0.535736 vt 0.909569 0.533340 vt 0.911850 0.529311 vt 0.913952 0.523838 vt 0.915776 0.517175 vt 0.917237 0.509635 vt 0.918266 0.501570 vt 0.918854 0.493342 vt 0.918947 0.485357 vt 0.918440 0.478006 vt 0.917870 0.471971 vt 0.911914 0.537955 vt 0.914263 0.537286 vt 0.916678 0.534827 vt 0.919019 0.530693 vt 0.921175 0.525076 vt 0.923047 0.518239 vt 0.924546 0.510502 vt 0.925585 0.502224 vt 0.926227 0.493754 vt 0.926493 0.485583 vt 0.925762 0.478045 vt 0.924764 0.471485 vt 0.919062 0.538392 vt 0.921403 0.537724 vt 0.923810 0.535275 vt 0.926142 0.531156 vt 0.930338 0.525761 vt 0.931620 0.519674 vt 0.933283 0.512932 vt 0.932692 0.502839 vt 0.933977 0.494768 vt 0.934333 0.486371 vt 0.933206 0.478784 vt 0.931863 0.472204 vt 0.923820 0.537920 vt 0.926110 0.537268 vt 0.928464 0.534872 vt 0.932388 0.530343 vt 0.937335 0.506755 vt 0.941235 0.498334 vt 0.941521 0.487989 vt 0.940371 0.480184 vt 0.938481 0.473907 vt 0.928485 0.536884 vt 0.930691 0.536256 vt 0.934789 0.533043 vt 0.932989 0.535298 vt 0.935078 0.534703 vt 0.938368 0.532576 vt 0.947164 0.489068 vt 0.947066 0.497764 vt 0.946239 0.481893 vt 0.944283 0.476284 vt 0.937267 0.533185 vt 0.939209 0.532632 vt 0.941919 0.530982 vt 0.950271 0.497772 vt 0.950689 0.490156 vt 0.950340 0.483450 vt 0.948203 0.478162 vt 0.941256 0.530577 vt 0.943023 0.530074 vt 0.945589 0.528338 vt 0.953490 0.491089 vt 0.952472 0.498024 vt 0.953154 0.485389 vt 0.951415 0.480527 vt 0.944898 0.527510 vt 0.946464 0.527065 vt 0.948879 0.525086 vt 0.955336 0.498108 vt 0.955494 0.492270 vt 0.955142 0.488211 vt 0.954263 0.484425 vt 0.949389 0.475505 vt 0.954052 0.482326 vt 0.947570 0.475109 vt 0.943613 0.471998 vt 0.945693 0.473082 vt 0.949187 0.522238 vt 0.950577 0.521636 vt 0.951775 0.520708 vt 0.950886 0.519379 vt 0.952605 0.518141 vt 0.952742 0.516616 vt 0.954470 0.515316 vt 0.878125 0.495997 vt 0.699705 0.808301 vt 0.767751 0.813767 vt 0.710393 0.859091 vt 0.770203 0.857847 vt 0.670460 0.860010 vt 0.652167 0.800156 vt 0.732575 0.902022 vt 0.706983 0.910753 vt 0.776574 0.894725 vt 0.709069 0.706824 vt 0.772884 0.721001 vt 0.699610 0.756109 vt 0.769128 0.766819 vt 0.650715 0.739559 vt 0.665169 0.685443 vt 0.747389 0.629544 vt 0.781976 0.645207 vt 0.726035 0.663915 vt 0.777571 0.679447 vt 0.692510 0.641696 vt 0.725600 0.608173 vt 0.800844 0.613722 vt 0.782354 0.622030 vt 0.800842 0.598447 vt 0.770640 0.606739 vt 0.761516 0.586356 vt 0.801211 0.578120 vt 0.843418 0.585771 vt 0.884582 0.608278 vt 0.833474 0.606525 vt 0.861681 0.629270 vt 0.821482 0.621769 vt 0.826927 0.645008 vt 0.841154 0.721344 vt 0.835351 0.679466 vt 0.901508 0.709495 vt 0.885190 0.664947 vt 0.920582 0.644640 vt 0.946246 0.692573 vt 0.840491 0.814502 vt 0.842749 0.767471 vt 0.905321 0.811872 vt 0.908491 0.759591 vt 0.957769 0.748636 vt 0.953229 0.808266 vt 0.832824 0.894364 vt 0.836470 0.858222 vt 0.876480 0.901039 vt 0.893987 0.860506 vt 0.934495 0.863509 vt 0.904941 0.908789 vt 0.808557 0.928096 vt 0.833115 0.918674 vt 0.811165 0.939260 vt 0.850764 0.928355 vt 0.866017 0.939324 vt 0.815782 0.955298 vt 0.767445 0.930389 vt 0.781351 0.919619 vt 0.756778 0.947206 vt 0.943537 0.514246 vt 0.944979 0.511457 vt 0.944004 0.514141 vt 0.945299 0.511731 vt 0.946866 0.510467 vt 0.945612 0.512005 vt 0.946775 0.509925 vt 0.944125 0.524447 vt 0.943125 0.523168 vt 0.944265 0.522893 vt 0.943363 0.521760 vt 0.943213 0.518650 vt 0.943600 0.520352 vt 0.942839 0.519741 vt 0.942465 0.520831 vt 0.942508 0.517563 vt 0.943036 0.516979 vt 0.944472 0.514036 vt 0.943564 0.516394 vt 0.948503 0.507957 vt 0.948316 0.508687 vt 0.946664 0.509365 vt 0.949207 0.508705 vt 0.948096 0.509397 vt 0.949608 0.507910 vt 0.950009 0.507096 vt 0.951195 0.506751 vt 0.950679 0.507606 vt 0.950161 0.508443 vt 0.951005 0.508659 vt 0.951647 0.507843 vt 0.951744 0.509448 vt 0.952587 0.508900 vt 0.952323 0.510866 vt 0.953318 0.510638 vt 0.953444 0.508317 vt 0.952296 0.507042 vt 0.954069 0.512878 vt 0.953344 0.512779 vt 0.954198 0.510416 vt 0.952538 0.512757 vt 0.952220 0.514880 vt 0.952777 0.515115 vt 0.952001 0.518020 vt 0.951641 0.517475 vt 0.953314 0.515413 vt 0.951263 0.516984 vt 0.949848 0.518868 vt 0.950071 0.519673 vt 0.948367 0.522605 vt 0.948260 0.521460 vt 0.950291 0.520481 vt 0.948152 0.520315 vt 0.946642 0.521256 vt 0.946704 0.522693 vt 0.945370 0.524785 vt 0.945389 0.523190 vt 0.946766 0.524131 vt 0.945409 0.521595 vt 0.944404 0.521338 vt 0.678283 0.626368 vt 0.715348 0.591757 vt 0.648199 0.671421 vt 0.648584 0.866073 vt 0.628808 0.796368 vt 0.694447 0.921544 vt 0.627873 0.728326 vt 0.756739 0.569509 vt 0.801790 0.560389 vt 0.753465 0.960832 vt 0.819304 0.969461 vt 0.896919 0.591012 vt 0.849419 0.567507 vt 0.878442 0.952645 vt 0.925282 0.919811 vt 0.970524 0.680324 vt 0.939853 0.628765 vt 0.962143 0.870229 vt 0.978626 0.806509 vt 0.983346 0.741752 vt 0.947425 0.509189 vt 0.948517 0.508521 vt 0.946164 0.510225 vt 0.942852 0.515989 vt 0.943731 0.513771 vt 0.942484 0.518080 vt 0.944874 0.511783 vt 0.949456 0.508269 vt 0.950283 0.508511 vt 0.942699 0.519730 vt 0.943487 0.520694 vt 0.950990 0.509314 vt 0.951552 0.510694 vt 0.944633 0.521012 vt 0.946034 0.520645 vt 0.951791 0.512457 vt 0.951461 0.514432 vt 0.947604 0.519694 vt 0.949207 0.518265 vt 0.950530 0.516413 vt 0.954352 0.507334 vt 0.955064 0.510040 vt 0.953029 0.505660 vt 0.954691 0.513093 vt 0.953563 0.516187 vt 0.945738 0.508370 vt 0.942931 0.511155 vt 0.941124 0.514785 vt 0.948237 0.525023 vt 0.946042 0.527051 vt 0.950358 0.522206 vt 0.939919 0.523165 vt 0.940778 0.525984 vt 0.942236 0.527579 vt 0.940031 0.519157 vt 0.950380 0.505668 vt 0.948360 0.506655 vt 0.951792 0.505283 vt 0.952106 0.519194 vt 0.944080 0.527997 vt 0.955136 0.505085 vt 0.955458 0.508799 vt 0.953996 0.503048 vt 0.954947 0.512890 vt 0.953571 0.516724 vt 0.940392 0.509704 vt 0.943959 0.505926 vt 0.937487 0.514053 vt 0.946965 0.527152 vt 0.943743 0.529685 vt 0.949863 0.523920 vt 0.936263 0.528363 vt 0.934831 0.524989 vt 0.938515 0.530358 vt 0.935440 0.520327 vt 0.947235 0.504116 vt 0.949823 0.503295 vt 0.952034 0.502955 vt 0.951882 0.520410 vt 0.941028 0.530790 vt 0.956376 0.507194 vt 0.956255 0.501869 vt 0.955436 0.512282 vt 0.953780 0.516679 vt 0.874321 0.260529 vt 0.874570 0.258357 vt 0.870642 0.262514 vt 0.875205 0.262091 vt 0.870232 0.260129 vt 0.874180 0.274349 vt 0.878088 0.275377 vt 0.875282 0.284248 vt 0.879128 0.284194 vt 0.876451 0.268146 vt 0.871760 0.267630 vt 0.879196 0.270887 vt 0.875496 0.265391 vt 0.893101 0.284420 vt 0.892287 0.271326 vt 0.879947 0.284131 vt 0.877309 0.242158 vt 0.869772 0.241647 vt 0.862255 0.219399 vt 0.855575 0.218438 vt 0.849440 0.210845 vt 0.862432 0.239621 vt 0.849901 0.240235 vt 0.863194 0.266321 vt 0.867315 0.244511 vt 0.876212 0.265675 vt 0.880423 0.283451 vt 0.866718 0.283089 vt 0.870498 0.254293 vt 0.863904 0.253791 vt 0.870234 0.270504 vt 0.864805 0.270607 vt 0.878645 0.270230 vt 0.879852 0.254050 vt 0.894641 0.243235 vt 0.885819 0.242521 vt 0.884908 0.221559 vt 0.874780 0.219802 vt 0.853855 0.205189 vt 0.868038 0.204565 vt 0.856654 0.209577 vt 0.870975 0.210424 vt 0.882303 0.208527 vt 0.885027 0.212810 vt 0.869343 0.207181 vt 0.855354 0.205901 vt 0.879862 0.246326 vt 0.872817 0.221463 vt 0.859409 0.218500 vt 0.885826 0.225344 vt 0.891813 0.248223 vt 0.886742 0.266822 vt 0.897889 0.269881 vt 0.890434 0.283337 vt 0.902191 0.283202 vt 0.887267 0.269985 vt 0.888486 0.253806 vt 0.897865 0.269734 vt 0.897003 0.253871 vt 0.890889 0.214313 vt 0.890567 0.213820 vt 0.887336 0.211472 vt 0.935827 0.245027 vt 0.948386 0.259727 vt 0.940502 0.247339 vt 0.952135 0.261243 vt 0.935892 0.257903 vt 0.929477 0.246937 vt 0.955141 0.271935 vt 0.951237 0.271687 vt 0.956107 0.282705 vt 0.951697 0.282445 vt 0.941535 0.282520 vt 0.939971 0.271031 vt 0.963745 0.266939 vt 0.955791 0.248173 vt 0.971434 0.266214 vt 0.963770 0.246708 vt 0.948554 0.235961 vt 0.955224 0.234835 vt 0.886107 0.251776 vt 0.887306 0.257140 vt 0.896396 0.245174 vt 0.901578 0.253513 vt 0.893935 0.234459 vt 0.893306 0.241625 vt 0.884880 0.259333 vt 0.895682 0.256287 vt 0.898524 0.274248 vt 0.887363 0.275119 vt 0.897217 0.265981 vt 0.885952 0.267220 vt 0.888292 0.284017 vt 0.899782 0.283788 vt 0.893557 0.224473 vt 0.903384 0.244263 vt 0.909834 0.245212 vt 0.904117 0.226402 vt 0.898452 0.232315 vt 0.905360 0.242289 vt 0.908475 0.231470 vt 0.914901 0.241090 vt 0.912117 0.251402 vt 0.921110 0.249824 vt 0.894105 0.223477 vt 0.905103 0.223634 vt 0.892209 0.224715 vt 0.891627 0.223783 vt 0.894785 0.217728 vt 0.904032 0.218825 vt 0.916745 0.259188 vt 0.905292 0.260920 vt 0.925679 0.257542 vt 0.919312 0.270264 vt 0.927598 0.269904 vt 0.909165 0.270372 vt 0.923018 0.231732 vt 0.931448 0.236491 vt 0.922229 0.248254 vt 0.927474 0.250713 vt 0.931475 0.252998 vt 0.939132 0.243721 vt 0.941395 0.236759 vt 0.941774 0.243180 vt 0.953028 0.242625 vt 0.951181 0.247570 vt 0.943442 0.249961 vt 0.949719 0.251412 vt 0.935522 0.249090 vt 0.932269 0.241398 vt 0.930136 0.234395 vt 0.929446 0.228394 vt 0.942151 0.230847 vt 0.929372 0.224433 vt 0.942787 0.227016 vt 0.954848 0.237908 vt 0.922857 0.256527 vt 0.927955 0.258098 vt 0.925166 0.269641 vt 0.929776 0.270263 vt 0.933130 0.259066 vt 0.935431 0.270899 vt 0.945703 0.257403 vt 0.938091 0.256867 vt 0.953024 0.257866 vt 0.948714 0.269472 vt 0.957575 0.269677 vt 0.940805 0.269454 vt 0.944953 0.253898 vt 0.950208 0.269278 vt 0.957956 0.256418 vt 0.964661 0.269455 vt 0.961307 0.253711 vt 0.968960 0.268209 vt 0.902892 0.249987 vt 0.897745 0.228849 vt 0.908596 0.231834 vt 0.912735 0.252197 vt 0.894850 0.221810 vt 0.906240 0.226903 vt 0.897122 0.224227 vt 0.908607 0.228742 vt 0.903082 0.242361 vt 0.906809 0.256103 vt 0.919494 0.264606 vt 0.908311 0.265315 vt 0.917237 0.255133 vt 0.907454 0.269025 vt 0.916487 0.269043 vt 0.911531 0.283044 vt 0.919593 0.282900 vt 0.909419 0.273405 vt 0.911476 0.283360 vt 0.920571 0.273041 vt 0.922157 0.283218 vt 0.922114 0.255039 vt 0.920062 0.238863 vt 0.921052 0.234472 vt 0.923775 0.235380 vt 0.934895 0.263753 vt 0.929369 0.253983 vt 0.927988 0.282737 vt 0.925160 0.269608 vt 0.937386 0.272947 vt 0.938515 0.283177 vt 0.905634 0.254510 vt 0.912156 0.254648 vt 0.906903 0.269654 vt 0.915624 0.269303 vt 0.934262 0.269581 vt 0.931993 0.256855 vt 0.928513 0.249010 vt 0.928512 0.223212 vt 0.917128 0.220406 vt 0.916725 0.220767 vt 0.904100 0.217973 vt 0.914088 0.228391 vt 0.916363 0.246441 vt 0.918617 0.232270 vt 0.923487 0.240784 vt 0.938248 0.227770 vt 0.892821 0.216321 vt 0.844542 0.206031 vt 0.874322 0.212263 vt 0.864795 0.285932 vt 0.870581 0.286403 vt 0.877377 0.285736 vt 0.887924 0.285532 vt 0.898823 0.285311 vt 0.908672 0.285115 vt 0.917425 0.284957 vt 0.921216 0.269224 vt 0.922914 0.284906 vt 0.926665 0.284912 vt 0.930758 0.284928 vt 0.936388 0.284960 vt 0.952837 0.284936 vt 0.967867 0.284724 vt 0.971593 0.266723 vt 0.976556 0.284338 vt 0.976161 0.284497 vt 0.973686 0.284223 vt 0.968029 0.284169 vt 0.960033 0.284154 vt 0.950775 0.284138 vt 0.942494 0.284121 vt 0.936182 0.284123 vt 0.929809 0.284158 vt 0.920968 0.284223 vt 0.910111 0.284292 vt 0.952943 0.272204 vt 0.950019 0.262148 vt 0.953976 0.282879 vt 0.900909 0.229617 vt 0.889642 0.217102 vt 0.912068 0.234085 vt 0.913562 0.243735 vt 0.925227 0.239763 vt 0.923110 0.245509 vt 0.916786 0.225007 vt 0.940372 0.249840 vt 0.963568 0.250138 vt 0.888081 0.264217 vt 0.918118 0.254988 vt 0.953969 0.514386 vt 0.955477 0.513065 vt 0.954272 0.512902 vt 0.955509 0.511632 vt 0.956562 0.510151 vt 0.957528 0.506237 vt 0.957401 0.500104 vt 0.957541 0.505830 vt 0.957323 0.498901 vt 0.956684 0.496896 vt 0.956425 0.492794 vt 0.955922 0.489330 vt 0.956413 0.492938 vt 0.955605 0.489791 vt 0.954030 0.483685 vt 0.955302 0.484788 vt 0.952251 0.480529 vt 0.844126 0.209259 vt 0.844951 0.221286 vt 0.955335 0.486204 vt 0.955539 0.486154 vt 0.954877 0.485325 vt 0.956687 0.508947 vt 0.956754 0.495687 vt 0.955045 0.487387 vt 0.886769 0.216308 vt 0.872985 0.215718 vt 0.877848 0.505182 vt 0.877652 0.500582 vt 0.877370 0.504506 vt 0.877412 0.500243 vt 0.876974 0.503437 vt 0.877218 0.499703 vt 0.876680 0.502023 vt 0.877063 0.498994 vt 0.876501 0.500330 vt 0.876979 0.498142 vt 0.876446 0.498438 vt 0.876960 0.497191 vt 0.876522 0.496436 vt 0.877006 0.496186 vt 0.876728 0.494420 vt 0.877115 0.495174 vt 0.877281 0.494204 vt 0.877054 0.492488 vt 0.877498 0.493320 vt 0.877479 0.490730 vt 0.877999 0.489197 vt 0.877767 0.492546 vt 0.878719 0.487808 vt 0.878144 0.491879 vt 0.879235 0.487423 vt 0.878402 0.491725 vt 0.878643 0.509686 vt 0.877934 0.508686 vt 0.877348 0.507101 vt 0.876912 0.505005 vt 0.876647 0.502497 vt 0.876565 0.499694 vt 0.876669 0.496727 vt 0.876956 0.493734 vt 0.877416 0.490859 vt 0.878045 0.488251 vt 0.878803 0.485988 vt 0.879891 0.483890 vt 0.880652 0.483253 vt 0.880004 0.514038 vt 0.879075 0.512727 vt 0.878307 0.510649 vt 0.877736 0.507903 vt 0.877388 0.504616 vt 0.877280 0.500942 vt 0.877417 0.497054 vt 0.877793 0.493132 vt 0.878389 0.489360 vt 0.879214 0.485947 vt 0.880199 0.482989 vt 0.881623 0.480314 vt 0.882641 0.479405 vt 0.881911 0.518172 vt 0.880775 0.516569 vt 0.879836 0.514030 vt 0.879138 0.510673 vt 0.878713 0.506655 vt 0.878582 0.502165 vt 0.878749 0.497412 vt 0.879208 0.492618 vt 0.879955 0.488021 vt 0.881001 0.483899 vt 0.882310 0.480367 vt 0.883909 0.477310 vt 0.885135 0.475926 vt 0.884336 0.522030 vt 0.883011 0.520159 vt 0.881915 0.517195 vt 0.881100 0.513276 vt 0.880604 0.508586 vt 0.880450 0.503344 vt 0.880646 0.497795 vt 0.881181 0.492199 vt 0.882059 0.486840 vt 0.883257 0.482017 vt 0.884772 0.477872 vt 0.886828 0.474331 vt 0.888116 0.472377 vt 0.887245 0.525554 vt 0.885749 0.523442 vt 0.884512 0.520096 vt 0.883592 0.515673 vt 0.883032 0.510379 vt 0.882858 0.504462 vt 0.883079 0.498199 vt 0.883683 0.491882 vt 0.884656 0.485819 vt 0.885976 0.480334 vt 0.887771 0.475644 vt 0.888962 0.472096 vt 0.890594 0.528694 vt 0.888949 0.526372 vt 0.887589 0.522693 vt 0.886578 0.517830 vt 0.885962 0.512009 vt 0.885771 0.505504 vt 0.886014 0.498617 vt 0.886678 0.491672 vt 0.887734 0.484992 vt 0.889161 0.478945 vt 0.891005 0.474124 vt 0.894335 0.531403 vt 0.892565 0.528905 vt 0.891102 0.524947 vt 0.890014 0.519714 vt 0.889351 0.513452 vt 0.889146 0.506453 vt 0.889407 0.499043 vt 0.890122 0.491571 vt 0.891258 0.484384 vt 0.892815 0.477978 vt 0.894695 0.473363 vt 0.898413 0.533643 vt 0.896544 0.531004 vt 0.894999 0.526825 vt 0.893850 0.521300 vt 0.893151 0.514687 vt 0.892934 0.507295 vt 0.893209 0.499472 vt 0.893965 0.491581 vt 0.895164 0.483992 vt 0.896860 0.477387 vt 0.898865 0.473076 vt 0.902769 0.535380 vt 0.900829 0.532640 vt 0.899224 0.528300 vt 0.898031 0.522562 vt 0.897305 0.515695 vt 0.897079 0.508020 vt 0.897366 0.499895 vt 0.898150 0.491701 vt 0.899396 0.483821 vt 0.901449 0.477074 vt 0.903819 0.472976 vt 0.909699 0.536920 vt 0.907707 0.534109 vt 0.906061 0.529655 vt 0.904836 0.523767 vt 0.904091 0.516721 vt 0.903860 0.508844 vt 0.904153 0.500507 vt 0.904958 0.492099 vt 0.906237 0.484012 vt 0.908008 0.477129 vt 0.910034 0.473198 vt 0.916854 0.537360 vt 0.914870 0.534559 vt 0.913230 0.530122 vt 0.914164 0.524465 vt 0.913164 0.518196 vt 0.913437 0.511340 vt 0.911330 0.501080 vt 0.912131 0.492703 vt 0.913405 0.484646 vt 0.915130 0.477830 vt 0.916994 0.473623 vt 0.921660 0.536911 vt 0.919719 0.534172 vt 0.919170 0.529284 vt 0.917172 0.505296 vt 0.919838 0.496406 vt 0.920904 0.486229 vt 0.922380 0.479225 vt 0.924263 0.474614 vt 0.926405 0.535913 vt 0.924828 0.532245 vt 0.931019 0.534378 vt 0.929686 0.531880 vt 0.927970 0.487724 vt 0.927599 0.495894 vt 0.929366 0.480734 vt 0.931925 0.475167 vt 0.935435 0.532330 vt 0.934387 0.530379 vt 0.933232 0.488977 vt 0.932764 0.496065 vt 0.934736 0.482159 vt 0.937602 0.476887 vt 0.939590 0.529799 vt 0.938848 0.527798 vt 0.937731 0.490249 vt 0.936964 0.496523 vt 0.939215 0.484123 vt 0.942421 0.479379 vt 0.943421 0.526821 vt 0.942722 0.524593 vt 0.942329 0.491435 vt 0.942070 0.497013 vt 0.943599 0.487511 vt 0.946933 0.482758 vt 0.945342 0.476815 vt 0.949756 0.480976 vt 0.940652 0.473783 vt 0.947564 0.521395 vt 0.946600 0.520277 vt 0.949518 0.517928 vt 0.951324 0.514898 vt 0.699423 0.809253 vt 0.710111 0.860044 vt 0.767468 0.814720 vt 0.769921 0.858800 vt 0.669688 0.860514 vt 0.651885 0.801109 vt 0.732293 0.902975 vt 0.706453 0.911484 vt 0.776292 0.895678 vt 0.708787 0.707777 vt 0.699329 0.757062 vt 0.772602 0.721954 vt 0.768846 0.767772 vt 0.650433 0.740512 vt 0.664887 0.686395 vt 0.747107 0.630497 vt 0.725753 0.664868 vt 0.781693 0.646160 vt 0.777288 0.680400 vt 0.692228 0.642648 vt 0.725318 0.609127 vt 0.770358 0.607692 vt 0.782072 0.622983 vt 0.800560 0.599400 vt 0.800562 0.614675 vt 0.761234 0.587309 vt 0.800928 0.579073 vt 0.861399 0.630223 vt 0.884300 0.609231 vt 0.833192 0.607478 vt 0.843136 0.586725 vt 0.821200 0.622722 vt 0.826645 0.645961 vt 0.884907 0.665900 vt 0.835069 0.680418 vt 0.901226 0.710448 vt 0.840872 0.722297 vt 0.945964 0.693526 vt 0.920299 0.645594 vt 0.908209 0.760544 vt 0.842467 0.768423 vt 0.905039 0.812825 vt 0.840209 0.815454 vt 0.952947 0.809219 vt 0.957487 0.749588 vt 0.893705 0.861459 vt 0.836187 0.859175 vt 0.876198 0.901993 vt 0.832541 0.895317 vt 0.904659 0.909742 vt 0.934213 0.864462 vt 0.850482 0.929308 vt 0.832833 0.919627 vt 0.810883 0.940213 vt 0.808275 0.929049 vt 0.815494 0.955807 vt 0.865735 0.940277 vt 0.767163 0.931342 vt 0.781069 0.920572 vt 0.757688 0.947696 vt 0.926885 0.512910 vt 0.927854 0.512845 vt 0.928377 0.510114 vt 0.929178 0.510430 vt 0.931406 0.509225 vt 0.930823 0.508639 vt 0.929980 0.510746 vt 0.930534 0.523359 vt 0.930891 0.521821 vt 0.928350 0.521984 vt 0.928964 0.520607 vt 0.928441 0.517467 vt 0.927597 0.518520 vt 0.929579 0.519229 vt 0.926753 0.519573 vt 0.926179 0.516255 vt 0.927195 0.515710 vt 0.928823 0.512781 vt 0.928211 0.515165 vt 0.932374 0.506682 vt 0.930239 0.508053 vt 0.932657 0.507439 vt 0.934517 0.507536 vt 0.934510 0.506707 vt 0.932940 0.508196 vt 0.934502 0.505878 vt 0.936567 0.505592 vt 0.936317 0.506456 vt 0.936067 0.507320 vt 0.937546 0.507578 vt 0.938070 0.506748 vt 0.938901 0.508422 vt 0.940036 0.509880 vt 0.939772 0.507879 vt 0.941088 0.509726 vt 0.940713 0.507314 vt 0.938594 0.505926 vt 0.942911 0.511998 vt 0.942269 0.509491 vt 0.941755 0.511888 vt 0.940774 0.511818 vt 0.940867 0.513981 vt 0.941724 0.514237 vt 0.941696 0.517149 vt 0.942674 0.514592 vt 0.940898 0.516619 vt 0.940099 0.516090 vt 0.938667 0.517973 vt 0.939267 0.518807 vt 0.937547 0.521739 vt 0.939868 0.519641 vt 0.937153 0.520570 vt 0.936759 0.519402 vt 0.934851 0.520311 vt 0.935057 0.521760 vt 0.932933 0.523788 vt 0.935263 0.523209 vt 0.932985 0.522196 vt 0.933038 0.520604 vt 0.931248 0.520284 vt 0.715065 0.592710 vt 0.678001 0.627321 vt 0.647916 0.672373 vt 0.628526 0.797321 vt 0.648988 0.865355 vt 0.693916 0.922274 vt 0.627590 0.729278 vt 0.801508 0.561342 vt 0.756457 0.570463 vt 0.753635 0.961316 vt 0.820182 0.969676 vt 0.896636 0.591965 vt 0.849136 0.568460 vt 0.878160 0.953598 vt 0.925000 0.920764 vt 0.970242 0.681277 vt 0.939570 0.629718 vt 0.961861 0.871181 vt 0.978343 0.807461 vt 0.983064 0.742705 vt 0.932573 0.508007 vt 0.934161 0.507367 vt 0.930964 0.509008 vt 0.927841 0.514787 vt 0.928411 0.512543 vt 0.928106 0.516928 vt 0.929515 0.510546 vt 0.935671 0.507169 vt 0.937081 0.507438 vt 0.929093 0.518640 vt 0.930710 0.519671 vt 0.938351 0.508298 vt 0.939400 0.509723 vt 0.932552 0.520045 vt 0.934434 0.519715 vt 0.940079 0.511519 vt 0.940113 0.513519 vt 0.936336 0.518792 vt 0.938116 0.517377 vt 0.939405 0.515521 vt 0.943638 0.508965 vt 0.941943 0.506311 vt 0.939071 0.504531 vt 0.943880 0.515430 vt 0.944335 0.512165 vt 0.927944 0.506963 vt 0.925069 0.509715 vt 0.923231 0.513349 vt 0.935271 0.526189 vt 0.938229 0.524222 vt 0.940839 0.521447 vt 0.923484 0.521848 vt 0.925731 0.524779 vt 0.928789 0.526502 vt 0.922597 0.517761 vt 0.933960 0.504385 vt 0.931029 0.505302 vt 0.936534 0.504088 vt 0.942769 0.518413 vt 0.932116 0.527039 vt 0.945351 0.507568 vt 0.943089 0.503744 vt 0.939896 0.502018 vt 0.946118 0.511811 vt 0.945354 0.516015 vt 0.925289 0.504712 vt 0.920962 0.508150 vt 0.918385 0.512520 vt 0.938552 0.526478 vt 0.934537 0.528948 vt 0.941809 0.523283 vt 0.921702 0.527196 vt 0.918305 0.523665 vt 0.925959 0.529352 vt 0.917212 0.518867 vt 0.933078 0.501722 vt 0.929349 0.502508 vt 0.936447 0.501609 vt 0.943896 0.519878 vt 0.930309 0.529931 vt 0.948005 0.505607 vt 0.944871 0.499392 vt 0.947806 0.516062 vt 0.949100 0.511191 vt 0.879346 0.309871 vt 0.875833 0.306046 vt 0.875654 0.308144 vt 0.879007 0.306407 vt 0.876295 0.294136 vt 0.878977 0.293033 vt 0.876175 0.301094 vt 0.879812 0.300399 vt 0.882198 0.297454 vt 0.880254 0.303450 vt 0.893175 0.297687 vt 0.859181 0.350621 vt 0.872916 0.327332 vt 0.865817 0.349629 vt 0.880252 0.326495 vt 0.865713 0.330422 vt 0.852822 0.359205 vt 0.877097 0.301439 vt 0.864055 0.301093 vt 0.869257 0.322943 vt 0.852819 0.327816 vt 0.872679 0.316230 vt 0.866118 0.317194 vt 0.865658 0.301152 vt 0.871305 0.301562 vt 0.879516 0.300927 vt 0.881998 0.315802 vt 0.878301 0.348969 vt 0.888479 0.326114 vt 0.888262 0.347411 vt 0.897047 0.325511 vt 0.857019 0.363938 vt 0.860381 0.359999 vt 0.872015 0.364197 vt 0.874655 0.358691 vt 0.888629 0.355800 vt 0.886097 0.359632 vt 0.872907 0.361011 vt 0.858128 0.362290 vt 0.881706 0.320824 vt 0.875903 0.346079 vt 0.862423 0.349295 vt 0.893556 0.318638 vt 0.888714 0.341972 vt 0.887564 0.300041 vt 0.898554 0.296744 vt 0.888147 0.300674 vt 0.898715 0.300283 vt 0.890480 0.315686 vt 0.898830 0.315390 vt 0.894383 0.353795 vt 0.894065 0.354129 vt 0.890921 0.356254 vt 0.952900 0.304272 vt 0.949521 0.305533 vt 0.942171 0.318343 vt 0.937705 0.320863 vt 0.937122 0.307590 vt 0.931259 0.318903 vt 0.955735 0.293528 vt 0.951772 0.293299 vt 0.940544 0.294181 vt 0.964624 0.302435 vt 0.972337 0.302695 vt 0.957594 0.320997 vt 0.965634 0.321964 vt 0.957663 0.333351 vt 0.950951 0.332759 vt 0.904388 0.314564 vt 0.887892 0.312338 vt 0.897685 0.323309 vt 0.888936 0.316243 vt 0.878641 0.308129 vt 0.895710 0.326063 vt 0.896265 0.333541 vt 0.887647 0.308911 vt 0.896958 0.311973 vt 0.897963 0.301935 vt 0.887703 0.301029 vt 0.898883 0.293285 vt 0.887878 0.292930 vt 0.896731 0.344468 vt 0.905567 0.324686 vt 0.906991 0.342485 vt 0.911791 0.324251 vt 0.900908 0.335596 vt 0.911043 0.336295 vt 0.907054 0.325968 vt 0.917143 0.326675 vt 0.923285 0.318080 vt 0.915184 0.316034 vt 0.908088 0.344187 vt 0.897103 0.344536 vt 0.895142 0.343144 vt 0.894607 0.344154 vt 0.898106 0.350443 vt 0.907260 0.349181 vt 0.907871 0.308374 vt 0.918398 0.309598 vt 0.927049 0.310818 vt 0.928305 0.298442 vt 0.920004 0.298182 vt 0.909927 0.298243 vt 0.929162 0.318872 vt 0.933826 0.332572 vt 0.924036 0.321254 vt 0.925629 0.337191 vt 0.941158 0.325550 vt 0.933051 0.316658 vt 0.943727 0.330971 vt 0.955075 0.325269 vt 0.943791 0.324629 vt 0.952983 0.320359 vt 0.951332 0.316568 vt 0.945126 0.317960 vt 0.937248 0.318798 vt 0.934373 0.326359 vt 0.932584 0.333273 vt 0.932192 0.339300 vt 0.932318 0.343421 vt 0.944776 0.336887 vt 0.945607 0.340893 vt 0.957129 0.330060 vt 0.924262 0.313255 vt 0.925922 0.300164 vt 0.929283 0.311724 vt 0.930502 0.299579 vt 0.936126 0.298969 vt 0.934410 0.310774 vt 0.939442 0.311396 vt 0.947027 0.310849 vt 0.954323 0.310334 vt 0.958291 0.298597 vt 0.949440 0.298819 vt 0.941532 0.298832 vt 0.946483 0.315703 vt 0.950981 0.300512 vt 0.969751 0.300159 vt 0.965388 0.298807 vt 0.962811 0.314479 vt 0.959326 0.311738 vt 0.904541 0.316598 vt 0.900452 0.338160 vt 0.914268 0.314088 vt 0.911146 0.334819 vt 0.910827 0.338483 vt 0.909045 0.340214 vt 0.900043 0.343367 vt 0.897918 0.345703 vt 0.905082 0.324826 vt 0.907714 0.311836 vt 0.909052 0.301920 vt 0.918749 0.311513 vt 0.920236 0.301846 vt 0.908155 0.297332 vt 0.917180 0.297035 vt 0.909931 0.293440 vt 0.920410 0.293444 vt 0.923496 0.310879 vt 0.922253 0.327378 vt 0.923469 0.332076 vt 0.925923 0.331262 vt 0.930583 0.312153 vt 0.934974 0.302503 vt 0.925817 0.296140 vt 0.937580 0.293425 vt 0.916398 0.300537 vt 0.913653 0.315103 vt 0.907706 0.300101 vt 0.907277 0.314776 vt 0.934984 0.298714 vt 0.933344 0.311407 vt 0.930312 0.318902 vt 0.931529 0.345055 vt 0.920283 0.347817 vt 0.919853 0.347075 vt 0.907379 0.350402 vt 0.916863 0.340467 vt 0.918259 0.323025 vt 0.921166 0.335346 vt 0.925584 0.327014 vt 0.941044 0.340685 vt 0.896265 0.352186 vt 0.847118 0.363087 vt 0.877923 0.355511 vt 0.921992 0.300554 vt 0.972463 0.301867 vt 0.953466 0.293583 vt 0.950828 0.303658 vt 0.892951 0.350759 vt 0.903260 0.337983 vt 0.915596 0.322974 vt 0.914170 0.333036 vt 0.927312 0.326701 vt 0.925040 0.320904 vt 0.919699 0.342662 vt 0.941871 0.316003 vt 0.965254 0.318221 vt 0.889956 0.306147 vt 0.919598 0.314755 vt 0.953215 0.511264 vt 0.952698 0.512701 vt 0.951135 0.508840 vt 0.950868 0.504414 vt 0.946032 0.497811 vt 0.950400 0.504692 vt 0.946155 0.498790 vt 0.944108 0.496015 vt 0.945966 0.489086 vt 0.945497 0.488606 vt 0.944681 0.492183 vt 0.944291 0.491984 vt 0.951345 0.484260 vt 0.845891 0.359181 vt 0.847322 0.346695 vt 0.948386 0.485293 vt 0.952907 0.485943 vt 0.952013 0.507755 vt 0.944606 0.494766 vt 0.948560 0.486751 vt 0.890112 0.351330 vt 0.876360 0.352039 vt 0.889967 0.448966 vt 0.878531 0.491695 vt 0.879572 0.487300 vt 0.881189 0.483034 vt 0.883359 0.479059 vt 0.885970 0.475436 vt 0.826451 0.427416 vt 0.889758 0.472115 vt 0.881748 0.482959 vt 0.884054 0.478968 vt 0.886811 0.475429 vt 0.942835 0.472365 vt 0.883515 0.397651 vt 0.925482 0.397634 vt 0.918562 0.397639 vt 0.909465 0.397642 vt 0.901603 0.397646 vt 0.891034 0.397645 vt 0.894812 0.397645 vt 0.931235 0.397655 vt 0.877003 0.397658 vt 0.882529 0.397650 vt 0.871595 0.397671 vt 0.868659 0.397671 vt 0.925845 0.397644 vt 0.918784 0.397657 vt 0.909614 0.397666 vt 0.901249 0.397670 vt 0.890498 0.397656 vt 0.929610 0.397643 vt 0.894099 0.397656 vt 0.484696 0.402538 vt 0.483058 0.467543 vt 0.026409 0.041836 vt 0.027211 0.115320 vt 0.027231 0.176259 vt 0.027041 0.244825 vt 0.028360 0.327681 vt 0.035514 0.442488 vt 0.039161 0.517280 vt 0.039555 0.560029 vt 0.486973 0.320445 vt 0.496033 0.232082 vt 0.498386 0.171519 vt 0.166927 0.932027 vt 0.089131 0.901460 vt 0.412839 0.954955 vt 0.280654 0.942250 vt 0.172265 0.583534 vt 0.092604 0.643694 vt 0.048602 0.713913 vt 0.034269 0.783775 vt 0.288973 0.537530 vt 0.047197 0.849027 vt 0.429574 0.516361 vt 0.573602 0.514995 vt 0.720327 0.535205 vt 0.544365 0.975068 vt 0.682918 0.980037 vt 0.412382 0.948959 vt 0.274953 0.941360 vt 0.159929 0.926191 vt 0.078614 0.893197 vt 0.045546 0.712112 vt 0.029763 0.775258 vt 0.174568 0.589926 vt 0.093871 0.647648 vt 0.286715 0.542233 vt 0.039682 0.837587 vt 0.425296 0.517534 vt 0.568016 0.514851 vt 0.715649 0.536695 vt 0.543555 0.965760 vt 0.685594 0.967391 vn -0.0001 0.0035 -1.0000 vn 0.0003 0.0020 -1.0000 vn -0.0001 0.0005 -1.0000 vn -0.0003 0.0013 -1.0000 vn -0.0001 -0.0005 -1.0000 vn -0.0011 0.0030 -1.0000 vn -0.0023 0.0045 -1.0000 vn 0.0017 -0.0001 -1.0000 vn 0.0000 -0.0002 -1.0000 vn 0.0003 -0.0000 -1.0000 vn -0.1940 -0.3125 -0.9299 vn -0.0632 -0.1038 -0.9926 vn -0.1172 -0.3845 -0.9157 vn -0.0373 -0.1234 -0.9917 vn -0.0938 -0.0805 -0.9923 vn -0.2630 -0.2241 -0.9384 vn -0.3169 -0.1278 -0.9398 vn -0.1101 -0.0458 -0.9929 vn -0.1174 -0.0232 -0.9928 vn -0.3276 -0.0616 -0.9428 vn 0.2177 -0.5326 -0.8179 vn 0.4138 -0.4095 -0.8130 vn 0.2979 -0.6846 -0.6653 vn 0.5708 -0.5184 -0.6368 vn 0.2361 -0.2588 -0.9366 vn 0.1441 -0.3535 -0.9243 vn -0.3113 -0.4865 -0.8163 vn -0.1759 -0.5903 -0.7878 vn -0.4258 -0.3503 -0.8342 vn -0.4087 -0.6281 -0.6621 vn -0.5509 -0.4478 -0.7043 vn -0.2321 -0.7574 -0.6103 vn -0.4926 -0.1994 -0.8471 vn -0.4728 -0.1037 -0.8751 vn -0.6176 -0.2732 -0.7375 vn -0.5814 -0.1535 -0.7990 vn 0.5165 -0.1981 -0.8331 vn 0.2952 -0.1134 -0.9487 vn 0.6938 -0.2756 -0.6654 vn 0.3765 -0.7659 -0.5212 vn 0.6702 -0.5661 -0.4800 vn 0.4809 -0.7882 -0.3841 vn 0.7419 -0.5845 -0.3286 vn -0.4646 -0.7126 -0.5257 vn -0.2851 -0.8443 -0.4538 vn -0.6361 -0.5075 -0.5811 vn -0.5190 -0.7554 -0.3999 vn -0.6983 -0.5403 -0.4694 vn -0.3609 -0.8735 -0.3266 vn 0.8067 -0.3297 -0.4905 vn 0.8759 -0.3624 -0.3186 vn 0.7882 -0.5860 -0.1879 vn 0.5652 -0.7913 -0.2333 vn 0.6158 -0.7829 -0.0886 vn 0.8114 -0.5809 -0.0648 vn 0.9049 -0.3815 -0.1888 vn 0.9139 -0.4002 -0.0682 vn 0.5971 -0.7382 0.3140 vn 0.6378 -0.7621 0.1113 vn 0.7745 -0.5648 0.2848 vn 0.8115 -0.5760 0.0979 vn 0.9045 -0.4125 0.1079 vn 0.8678 -0.4132 0.2759 vn 0.3653 -0.6470 0.6693 vn 0.5016 -0.7048 0.5017 vn 0.5926 -0.5542 0.5845 vn 0.7076 -0.5546 0.4379 vn 0.8193 -0.4054 0.4054 vn 0.7766 -0.4059 0.4817 vn 0.1713 -0.6100 0.7737 vn 0.2607 -0.6403 0.7225 vn 0.3890 -0.5821 0.7141 vn 0.4691 -0.5660 0.6779 vn 0.6906 -0.4192 0.5894 vn 0.5689 -0.4282 0.7021 vn -0.1351 -0.4210 0.8969 vn -0.1220 -0.5957 0.7939 vn -0.1754 -0.3935 0.9025 vn -0.1624 -0.5624 0.8108 vn -0.1830 -0.2842 0.9411 vn -0.1723 -0.4250 0.8886 vn -0.0847 -0.6172 0.7823 vn -0.1254 -0.5445 0.8294 vn -0.0820 -0.6412 0.7629 vn -0.1845 -0.2175 0.9585 vn -0.0103 -0.0885 0.9960 vn -0.1352 -0.2584 0.9565 vn 0.1913 -0.1559 0.9691 vn -0.1423 -0.0561 0.9882 vn -0.1979 -0.1635 0.9665 vn -0.2794 -0.0947 0.9555 vn -0.2699 -0.0237 0.9626 vn -0.2915 -0.1850 0.9385 vn -0.5178 -0.5824 0.6267 vn -0.1359 -0.6531 0.7450 vn -0.5075 -0.3576 0.7839 vn -0.0878 -0.3679 0.9257 vn 0.3220 -0.3330 0.8862 vn 0.2951 -0.6388 0.7105 vn -0.1296 -0.7417 0.6581 vn 0.2285 -0.6781 0.6985 vn -0.4368 -0.7025 0.5619 vn -0.8957 -0.3220 0.3065 vn -0.7686 -0.4437 0.4608 vn -0.9280 -0.2060 0.3105 vn -0.7945 -0.2937 0.5315 vn -0.6527 -0.6203 0.4350 vn -0.8080 -0.5195 0.2779 vn -0.6957 -0.7024 0.1502 vn -0.5264 -0.7748 0.3500 vn -0.3081 -0.8025 0.5110 vn -0.2743 -0.8682 0.4135 vn -0.3976 -0.8941 0.2060 vn -0.5387 -0.8419 -0.0309 vn -0.7142 -0.6749 -0.1854 vn -0.8014 -0.5966 -0.0431 vn -0.8032 -0.5319 -0.2683 vn -0.8685 -0.4605 -0.1833 vn -0.9270 -0.3750 0.0008 vn -0.8782 -0.4497 0.1630 vn -0.7019 -0.3365 -0.6278 vn -0.6977 -0.1918 -0.6902 vn -0.7510 -0.3844 -0.5368 vn -0.7945 -0.2149 -0.5680 vn -0.0666 -0.9673 -0.2446 vn -0.0866 -0.9867 -0.1376 vn -0.1428 -0.9640 -0.2242 vn -0.1740 -0.9775 -0.1193 vn -0.1133 -0.9927 -0.0402 vn -0.1993 -0.9798 -0.0155 vn -0.5882 -0.7559 -0.2875 vn -0.7416 -0.5594 -0.3704 vn -0.4174 -0.8906 -0.1807 vn -0.8064 -0.4092 -0.4270 vn -0.8848 -0.2162 -0.4127 vn -0.8793 -0.3759 -0.2924 vn -0.9446 -0.2070 -0.2546 vn -0.1271 -0.9885 0.0820 vn -0.1261 -0.9750 0.1827 vn -0.2089 -0.9735 0.0926 vn -0.2032 -0.9617 0.1839 vn -0.0811 -0.6879 0.7213 vn -0.0916 -0.7408 0.6654 vn -0.0561 -0.6199 0.7827 vn -0.0550 -0.6763 0.7346 vn 0.4262 -0.2373 0.8729 vn -0.0378 -0.2945 0.9549 vn -0.0669 -0.4082 0.9104 vn -0.0334 -0.4956 0.8679 vn 0.2449 -0.3887 0.8882 vn 0.6882 -0.2965 0.6622 vn 0.0886 -0.6212 0.7787 vn 0.1115 -0.5615 0.8199 vn 0.2495 -0.5599 0.7901 vn 0.3050 -0.5587 0.7712 vn 0.0262 -0.6436 0.7649 vn 0.0107 -0.6074 0.7943 vn -0.0036 -0.6106 0.7920 vn 0.4658 -0.4189 0.7795 vn 0.3886 -0.4011 0.8295 vn -0.2610 -0.3057 0.9157 vn -0.1812 -0.4509 0.8740 vn 0.2056 -0.5642 0.7996 vn 0.5070 -0.5385 0.6730 vn 0.6562 -0.3046 0.6904 vn -0.9442 -0.2890 -0.1577 vn -0.9799 -0.1782 -0.0897 vn -0.9767 -0.2132 0.0253 vn -0.9847 -0.1572 0.0750 vn -0.0425 -0.7535 0.6561 vn -0.1502 -0.7644 0.6271 vn -0.9517 -0.2535 0.1732 vn -0.9681 -0.1566 0.1954 vn 0.4289 -0.4247 0.7973 vn 0.2847 -0.6049 0.7437 vn 0.6390 -0.5835 0.5012 vn 0.4039 -0.8041 0.4363 vn -0.0688 -0.6663 0.7425 vn -0.2413 -0.5596 0.7929 vn -0.0359 -0.5572 0.8296 vn -0.3498 -0.8592 0.3734 vn -0.0874 -0.9166 0.3901 vn 0.1206 -0.7073 0.6966 vn 0.1744 -0.9157 0.3619 vn -0.3755 -0.4208 0.8258 vn -0.4868 -0.2747 0.8292 vn -0.8080 -0.5075 0.2993 vn -0.5964 -0.7218 0.3513 vn 0.0484 -0.1246 -0.9910 vn 0.0793 -0.0929 -0.9925 vn 0.0162 -0.1417 -0.9898 vn 0.0286 -0.1366 -0.9902 vn 0.0455 -0.4240 -0.9045 vn 0.0886 -0.4131 -0.9063 vn -0.0184 -0.1299 -0.9914 vn -0.0546 -0.4140 -0.9086 vn 0.0948 -0.0360 -0.9948 vn 0.1295 -0.6218 -0.7724 vn 0.0648 -0.6459 -0.7607 vn 0.0759 -0.8061 -0.5868 vn 0.1541 -0.7836 -0.6019 vn -0.0841 -0.6490 -0.7561 vn -0.0986 -0.8202 -0.5635 vn 0.0348 -0.9725 -0.2303 vn 0.0401 -0.9888 -0.1435 vn 0.0040 -0.9715 -0.2371 vn 0.0058 -0.9872 -0.1596 vn 0.0600 -0.9971 -0.0462 vn 0.0022 -0.9984 -0.0559 vn 0.1343 -0.9849 -0.1090 vn 0.1546 -0.9880 -0.0076 vn 0.0983 -0.9725 -0.2113 vn 0.0637 -0.9959 0.0645 vn -0.0099 -0.9986 0.0519 vn -0.0259 -0.9882 0.1509 vn 0.0537 -0.9817 0.1828 vn 0.1595 -0.9614 0.2240 vn 0.1650 -0.9809 0.1031 vn 0.0179 -0.9460 0.3238 vn -0.0519 -0.9500 0.3078 vn -0.0884 -0.8540 0.5127 vn -0.0416 -0.8417 0.5384 vn 0.0712 -0.8420 0.5347 vn 0.1405 -0.9249 0.3533 vn 0.0853 -0.6731 0.7346 vn -0.0642 -0.6371 0.7681 vn 0.1947 -0.7113 0.6754 vn 0.3048 -0.8059 0.5075 vn 0.3831 -0.8693 0.3124 vn 0.4089 -0.9051 0.1162 vn 0.3833 -0.9204 -0.0771 vn 0.3221 -0.9169 -0.2358 vn 0.2473 -0.8908 -0.3812 vn 0.1827 -0.8615 -0.4738 vn 0.0806 -0.8686 -0.4888 vn -0.1285 -0.9013 -0.4137 vn -0.1943 -0.9449 -0.2635 vn -0.2945 -0.9473 -0.1261 vn -0.3132 -0.9497 0.0024 vn -0.2653 -0.9543 0.1378 vn -0.2145 -0.9225 0.3209 vn -0.1601 -0.8298 0.5345 vn -0.1010 -0.9500 0.2956 vn -0.1890 -0.9357 0.2981 vn -0.0761 -0.8641 0.4975 vn -0.1310 -0.8780 0.4604 vn -0.0822 -0.6385 0.7652 vn -0.2531 -0.9607 -0.1144 vn -0.2831 -0.9589 0.0173 vn -0.4117 -0.8519 -0.3237 vn -0.4434 -0.8927 -0.0809 vn -0.4025 -0.8749 -0.2693 vn -0.4244 -0.9036 -0.0589 vn -0.2433 -0.8657 -0.4375 vn -0.2685 -0.8461 -0.4604 vn -0.1790 -0.9413 -0.2861 vn -0.2668 -0.9546 0.1327 vn -0.2366 -0.9262 0.2936 vn -0.4502 -0.8881 0.0928 vn -0.4243 -0.8537 0.3018 vn -0.4270 -0.8972 0.1127 vn -0.3936 -0.8591 0.3273 vn 0.1545 -0.8528 -0.4989 vn 0.0620 -0.8436 -0.5333 vn 0.0272 -0.8825 -0.4695 vn 0.0459 -0.8257 -0.5622 vn 0.1177 -0.8768 -0.4662 vn 0.1498 -0.8331 -0.5324 vn 0.2170 -0.9010 -0.3756 vn 0.2452 -0.8741 -0.4194 vn 0.2366 -0.8897 -0.3904 vn -0.1321 -0.8803 -0.4556 vn -0.1214 -0.8771 -0.4647 vn -0.1457 -0.8319 -0.5354 vn 0.2809 -0.9307 -0.2342 vn 0.2747 -0.9392 -0.2061 vn 0.2927 -0.9260 -0.2386 vn 0.2902 -0.9564 -0.0335 vn 0.3085 -0.9495 -0.0574 vn 0.3115 -0.9482 -0.0625 vn 0.3230 -0.9389 0.1187 vn 0.2865 -0.9497 0.1262 vn 0.3104 -0.9425 0.1237 vn 0.2826 -0.9116 0.2984 vn 0.3026 -0.8995 0.3153 vn 0.3084 -0.8989 0.3112 vn 0.0365 -0.5810 0.8131 vn 0.1738 -0.6919 0.7008 vn -0.0081 -0.6136 0.7896 vn 0.1437 -0.7085 0.6910 vn 0.0146 -0.5591 0.8290 vn 0.1662 -0.6710 0.7226 vn -0.1071 -0.5902 0.8001 vn -0.1015 -0.5213 0.8473 vn -0.0779 -0.5804 0.8106 vn 0.2611 -0.8193 0.5105 vn 0.2441 -0.8317 0.4987 vn 0.2641 -0.8099 0.5238 vn -0.1855 -0.8529 0.4881 vn -0.1691 -0.6613 0.7308 vn -0.1479 -0.6250 0.7665 vn -0.3392 -0.7716 0.5381 vn -0.2992 -0.7674 0.5671 vn -0.0802 -0.5942 0.8003 vn -0.0749 -0.5361 0.8408 vn -0.1058 -0.5780 0.8091 vn -0.1146 -0.5180 0.8477 vn -0.2943 -0.9556 -0.0161 vn -0.2443 -0.9562 -0.1613 vn -0.1817 -0.9379 -0.2956 vn -0.2872 -0.9292 0.2325 vn -0.3043 -0.9471 0.1023 vn 0.0670 -0.9406 -0.3328 vn 0.0098 -0.9448 -0.3274 vn 0.1616 -0.9468 -0.2783 vn -0.0871 -0.9357 -0.3420 vn 0.2156 -0.9657 -0.1446 vn 0.2405 -0.9706 -0.0067 vn 0.2452 -0.9614 0.1247 vn 0.2377 -0.9347 0.2642 vn 0.1153 -0.7766 0.6194 vn -0.0364 -0.7135 0.6997 vn -0.1024 -0.7321 0.6735 vn 0.2035 -0.8758 0.4377 vn -0.2350 -0.8895 0.3919 vn -0.1278 -0.8231 0.5533 vn -0.0749 -0.7674 0.6368 vn -0.1023 -0.7495 0.6540 vn -0.0930 -0.8564 0.5078 vn -0.0772 -0.9443 0.3199 vn -0.0646 -0.9825 0.1746 vn -0.0507 -0.9967 0.0631 vn -0.0407 -0.9973 -0.0608 vn -0.0283 -0.9853 -0.1684 vn -0.0247 -0.9684 -0.2483 vn -0.0318 -0.9420 -0.3340 vn -0.0419 -0.8877 -0.4586 vn -0.0471 -0.8395 -0.5412 vn -0.0408 -0.8608 -0.5073 vn -0.0294 -0.8919 -0.4513 vn -0.0101 -0.8331 -0.5530 vn -0.0045 -0.6645 -0.7472 vn -0.0024 -0.4232 -0.9060 vn -0.0021 -0.1406 -0.9901 vn 0.5139 -0.0527 -0.8562 vn 0.7024 -0.0811 -0.7071 vn 0.3058 -0.0222 -0.9518 vn 0.8380 -0.1024 -0.5359 vn 0.9276 -0.1171 -0.3548 vn 0.9664 -0.1255 -0.2243 vn 0.9865 -0.1286 -0.1016 vn 0.9870 -0.1435 0.0730 vn 0.9564 -0.1519 0.2493 vn 0.8994 -0.1562 0.4083 vn 0.8518 -0.1522 0.5012 vn 0.7915 -0.1328 0.5965 vn 0.7023 -0.1133 0.7028 vn 0.6217 -0.1162 0.7746 vn 0.5460 -0.1224 0.8288 vn 0.5166 -0.1250 0.8471 vn 0.7600 -0.1462 0.6332 vn 0.1068 -0.0040 -0.9943 vn -0.5571 -0.1470 0.8174 vn -0.5949 -0.0504 0.8022 vn -0.9230 -0.3004 0.2406 vn -0.9824 -0.1327 0.1314 vn -0.7343 -0.5602 -0.3835 vn -0.8312 -0.3661 -0.4184 vn -0.8397 -0.1887 -0.5093 vn -0.6655 -0.2228 -0.7123 vn -0.6370 -0.3859 -0.6673 vn -0.5544 -0.5414 -0.6321 vn -0.3407 -0.8936 -0.2924 vn -0.5624 -0.7581 -0.3301 vn -0.4432 -0.6537 -0.6134 vn -0.2684 -0.7346 -0.6231 vn -0.0872 -0.9689 -0.2316 vn -0.0948 -0.7680 -0.6334 vn 0.1170 -0.7228 -0.6811 vn 0.1776 -0.9558 -0.2342 vn 0.3701 -0.6241 -0.6882 vn 0.4560 -0.8671 -0.2004 vn 0.7388 -0.6547 -0.1597 vn 0.5878 -0.4708 -0.6579 vn 0.9891 -0.1461 0.0179 vn 0.7698 -0.1780 -0.6130 vn -0.1033 -0.6387 0.7625 vn -0.1101 -0.0052 -0.9939 vn -0.3193 -0.0168 -0.9475 vn -0.4555 -0.0356 -0.8895 vn -0.5900 -0.0388 -0.8065 vn 0.5140 0.0545 -0.8560 vn 0.7096 0.0850 -0.6995 vn 0.2994 0.0236 -0.9538 vn 0.8448 0.1108 -0.5235 vn 0.9293 0.1315 -0.3450 vn 0.9636 0.1435 -0.2257 vn 0.9821 0.1537 -0.1086 vn 0.9857 0.1595 0.0545 vn 0.9595 0.1566 0.2340 vn 0.8998 0.1494 0.4100 vn 0.8348 0.1472 0.5305 vn 0.6583 0.1584 0.7359 vn 0.7428 0.1635 0.6493 vn 0.0329 -0.0145 0.9994 vn 0.0569 0.0792 0.9952 vn 0.2703 -0.0326 0.9622 vn 0.3412 0.1044 0.9342 vn -0.1312 -0.0128 0.9913 vn -0.1309 0.0290 0.9910 vn -0.2730 -0.0059 0.9620 vn -0.2726 -0.0098 0.9621 vn -0.4984 -0.0388 0.8661 vn -0.0222 -0.0333 0.9992 vn -0.0627 0.2857 0.9563 vn 0.2988 0.2685 0.9158 vn 0.3881 -0.0326 0.9210 vn -0.9548 -0.0516 0.2926 vn -0.8189 -0.0451 0.5721 vn -0.7151 -0.0345 -0.6981 vn -0.8152 0.0725 -0.5746 vn -0.8208 -0.0376 -0.5700 vn -0.9136 0.0601 -0.4021 vn -0.9123 -0.0370 -0.4078 vn 0.5102 -0.0608 0.8579 vn 0.5845 0.1137 0.8034 vn 0.7231 -0.0762 0.6865 vn 0.7251 0.1735 0.6664 vn 0.5933 0.1604 0.7888 vn 0.5118 0.1545 0.8451 vn 0.6660 -0.0467 0.7445 vn 0.6000 0.2589 0.7569 vn -0.9661 -0.0502 -0.2532 vn -0.9964 -0.0515 -0.0678 vn -0.9947 -0.0477 0.0906 vn -0.9828 -0.0465 0.1788 vn 0.5022 0.1658 0.8487 vn 0.6614 0.2247 0.7156 vn -0.6442 -0.0130 0.7648 vn -0.6798 -0.0171 0.7332 vn -0.9972 -0.0635 0.0404 vn -1.0000 -0.0079 0.0060 vn 0.1022 0.0035 -0.9948 vn -0.8281 -0.0923 -0.5530 vn -0.8261 -0.0110 -0.5634 vn -0.6800 -0.1116 -0.7247 vn -0.7073 0.0101 -0.7068 vn 0.7320 0.2584 -0.6304 vn 0.9332 0.3095 0.1826 vn -0.1909 0.3382 -0.9215 vn -0.1152 0.4106 -0.9045 vn -0.0544 0.1091 -0.9925 vn -0.0318 0.1291 -0.9911 vn -0.2787 0.2344 -0.9314 vn -0.0891 0.0799 -0.9928 vn -0.3103 0.1285 -0.9419 vn -0.1152 0.0347 -0.9927 vn -0.3107 0.0609 -0.9485 vn -0.1144 0.0176 -0.9933 vn 0.5544 0.5310 -0.6409 vn 0.3972 0.4161 -0.8180 vn 0.2994 0.6898 -0.6592 vn 0.2220 0.5378 -0.8133 vn 0.2362 0.2729 -0.9326 vn 0.1543 0.3674 -0.9172 vn -0.1815 0.6368 -0.7493 vn -0.3060 0.5267 -0.7930 vn -0.4335 0.3683 -0.8225 vn -0.5455 0.4716 -0.6928 vn -0.3951 0.6609 -0.6380 vn -0.2177 0.7894 -0.5739 vn -0.4739 0.2199 -0.8527 vn -0.4635 0.1116 -0.8791 vn -0.5944 0.1736 -0.7852 vn -0.5992 0.3061 -0.7397 vn 0.2930 0.1279 -0.9475 vn 0.5061 0.2156 -0.8351 vn 0.6875 0.2917 -0.6651 vn 0.7241 0.6092 -0.3235 vn 0.6583 0.5891 -0.4686 vn 0.4742 0.8000 -0.3676 vn 0.3780 0.7805 -0.4979 vn -0.2498 0.8687 -0.4277 vn -0.4496 0.7297 -0.5152 vn -0.6156 0.5318 -0.5816 vn -0.6701 0.5668 -0.4792 vn -0.5025 0.7645 -0.4039 vn -0.3139 0.8946 -0.3179 vn 0.8026 0.3441 -0.4873 vn 0.8717 0.3734 -0.3173 vn 0.7692 0.6115 -0.1856 vn 0.5521 0.8023 -0.2269 vn 0.7904 0.6083 -0.0719 vn 0.6009 0.7955 -0.0781 vn 0.8986 0.3947 -0.1917 vn 0.9054 0.4147 -0.0905 vn 0.5618 0.7879 0.2520 vn 0.7591 0.6149 0.2136 vn 0.6075 0.7901 0.0823 vn 0.7866 0.6156 0.0470 vn 0.9063 0.4203 0.0454 vn 0.8828 0.4177 0.2150 vn 0.3345 0.7257 0.6013 vn 0.5912 0.5817 0.5587 vn 0.4712 0.7712 0.4280 vn 0.6926 0.6059 0.3914 vn 0.8290 0.4026 0.3881 vn 0.7460 0.3894 0.5403 vn 0.1370 0.6754 0.7246 vn 0.3829 0.5886 0.7120 vn 0.2088 0.6901 0.6929 vn 0.4864 0.5731 0.6595 vn 0.6546 0.3835 0.6515 vn 0.5880 0.3797 0.7142 vn -0.1333 0.3996 0.9069 vn -0.1567 0.3756 0.9134 vn -0.0959 0.5854 0.8050 vn -0.1377 0.5292 0.8373 vn -0.1842 0.2927 0.9383 vn -0.1677 0.4321 0.8861 vn -0.1192 0.5540 0.8240 vn -0.0829 0.6200 0.7802 vn -0.0530 0.6484 0.7595 vn 0.0830 0.2570 0.9628 vn -0.1155 0.1375 0.9837 vn -0.1327 0.2602 0.9564 vn -0.1794 0.2266 0.9573 vn -0.1832 0.0896 0.9790 vn -0.1919 0.1815 0.9645 vn -0.2834 0.1090 0.9528 vn -0.2787 0.0260 0.9600 vn -0.2693 0.2230 0.9369 vn -0.4768 0.6521 0.5894 vn -0.4930 0.4881 0.7202 vn -0.0945 0.7074 0.7004 vn -0.0715 0.5560 0.8281 vn 0.3183 0.5545 0.7689 vn 0.3003 0.6668 0.6821 vn 0.2213 0.6840 0.6951 vn -0.1048 0.7535 0.6490 vn -0.4126 0.7242 0.5525 vn -0.8956 0.3527 0.2712 vn -0.9446 0.1828 0.2724 vn -0.7611 0.5019 0.4110 vn -0.8077 0.3280 0.4900 vn -0.6457 0.6353 0.4237 vn -0.8078 0.5304 0.2573 vn -0.6927 0.7073 0.1413 vn -0.5274 0.7749 0.3483 vn -0.2992 0.7989 0.5218 vn -0.2481 0.8815 0.4017 vn -0.4027 0.8966 0.1844 vn -0.5376 0.8420 -0.0438 vn -0.7044 0.6872 -0.1780 vn -0.7885 0.5566 -0.2617 vn -0.7867 0.6172 -0.0150 vn -0.8664 0.4817 -0.1316 vn -0.9222 0.3829 0.0539 vn -0.8811 0.4503 0.1448 vn -0.6789 0.3689 -0.6348 vn -0.7006 0.2178 -0.6795 vn -0.7678 0.2518 -0.5892 vn -0.7355 0.4112 -0.5385 vn -0.0849 0.9795 -0.1828 vn -0.1463 0.9744 -0.1709 vn -0.0935 0.9935 -0.0649 vn -0.1659 0.9831 -0.0769 vn -0.1711 0.9853 -0.0008 vn -0.1047 0.9944 0.0117 vn -0.5763 0.7640 -0.2902 vn -0.7246 0.5829 -0.3677 vn -0.3952 0.8992 -0.1877 vn -0.7938 0.4437 -0.4158 vn -0.8658 0.2504 -0.4332 vn -0.9430 0.2153 -0.2536 vn -0.8628 0.4190 -0.2828 vn -0.1699 0.9730 0.1564 vn -0.0910 0.9797 0.1785 vn -0.1707 0.9827 0.0720 vn -0.1064 0.9898 0.0952 vn -0.0615 0.6602 0.7485 vn -0.1077 0.7346 0.6698 vn -0.0739 0.6191 0.7818 vn -0.1035 0.6865 0.7197 vn 0.3550 0.3963 0.8467 vn -0.0271 0.3157 0.9485 vn -0.0948 0.4379 0.8940 vn -0.0157 0.5637 0.8258 vn 0.2595 0.4334 0.8630 vn 0.5970 0.4774 0.6448 vn 0.0476 0.6460 0.7618 vn 0.2236 0.5480 0.8060 vn 0.0900 0.6504 0.7542 vn 0.2860 0.5802 0.7626 vn 0.0301 0.6895 0.7236 vn 0.0024 0.6654 0.7465 vn -0.0363 0.6126 0.7895 vn 0.4954 0.3778 0.7822 vn 0.3905 0.3507 0.8512 vn -0.2287 0.3662 0.9020 vn -0.1571 0.5063 0.8479 vn 0.2318 0.5763 0.7837 vn 0.4842 0.5581 0.6738 vn 0.5940 0.5278 0.6072 vn -0.9406 0.3139 -0.1298 vn -0.9813 0.1734 -0.0834 vn -0.9846 0.1582 0.0746 vn -0.9642 0.2591 0.0571 vn -0.0352 0.7502 0.6602 vn -0.1155 0.7972 0.5925 vn -0.9446 0.2857 0.1614 vn -0.9730 0.1588 0.1675 vn 0.3805 0.3499 0.8561 vn 0.2269 0.5390 0.8112 vn 0.3457 0.7119 0.6113 vn 0.5783 0.4830 0.6575 vn -0.2301 0.5839 0.7786 vn -0.0653 0.5998 0.7975 vn -0.0891 0.6925 0.7159 vn -0.3347 0.8199 0.4645 vn -0.1135 0.8999 0.4210 vn 0.1196 0.8671 0.4836 vn 0.0774 0.6819 0.7274 vn -0.3561 0.4334 0.8278 vn -0.4463 0.2901 0.8465 vn -0.7483 0.5062 0.4288 vn -0.5559 0.6774 0.4817 vn 0.0788 0.1005 -0.9918 vn 0.0560 0.1317 -0.9897 vn 0.0129 0.1424 -0.9897 vn 0.0395 0.4351 -0.8995 vn 0.0346 0.1473 -0.9885 vn 0.1007 0.4262 -0.8990 vn -0.0555 0.4401 -0.8962 vn -0.0161 0.1419 -0.9897 vn 0.0967 0.0426 -0.9944 vn 0.0578 0.6596 -0.7494 vn 0.1403 0.6279 -0.7656 vn 0.0706 0.8050 -0.5890 vn 0.1676 0.7801 -0.6028 vn -0.0873 0.6745 -0.7331 vn -0.1149 0.8355 -0.5374 vn 0.0109 0.9718 -0.2355 vn -0.0338 0.9766 -0.2125 vn 0.0001 0.9956 -0.0934 vn -0.0446 0.9940 -0.0999 vn -0.0509 0.9987 0.0091 vn 0.0027 0.9999 0.0169 vn 0.1242 0.9922 0.0130 vn 0.1188 0.9873 -0.1059 vn 0.0974 0.9684 -0.2294 vn -0.0027 0.9908 0.1355 vn -0.0576 0.9907 0.1229 vn -0.0577 0.9699 0.2367 vn -0.0126 0.9649 0.2625 vn 0.0845 0.9496 0.3019 vn 0.1173 0.9805 0.1579 vn -0.0310 0.9277 0.3720 vn -0.0599 0.9318 0.3580 vn -0.0693 0.8751 0.4790 vn -0.0542 0.8721 0.4862 vn 0.0172 0.8735 0.4865 vn 0.0539 0.9158 0.3981 vn -0.0341 0.6443 0.7640 vn 0.0741 0.6751 0.7340 vn 0.1777 0.7466 0.6411 vn 0.2910 0.8386 0.4605 vn 0.3838 0.8789 0.2832 vn 0.4297 0.8965 0.1078 vn 0.4131 0.9083 -0.0659 vn 0.3476 0.9092 -0.2291 vn 0.2688 0.8894 -0.3697 vn 0.0777 0.8605 -0.5035 vn 0.1970 0.8611 -0.4686 vn -0.1579 0.8960 -0.4151 vn -0.2187 0.9298 -0.2960 vn -0.2790 0.9475 -0.1565 vn -0.3050 0.9520 -0.0252 vn -0.1918 0.9349 0.2986 vn -0.2523 0.9613 0.1102 vn -0.1666 0.8382 0.5194 vn -0.1474 0.9439 0.2954 vn -0.0633 0.9495 0.3073 vn -0.0497 0.8855 0.4619 vn -0.0887 0.8892 0.4489 vn -0.0637 0.6624 0.7465 vn -0.2876 0.9574 -0.0245 vn -0.2808 0.9401 -0.1930 vn -0.3076 0.9179 -0.2506 vn -0.3290 0.9082 -0.2589 vn -0.3530 0.9306 -0.0967 vn -0.3726 0.9242 -0.0836 vn -0.2467 0.8937 -0.3748 vn -0.2582 0.8781 -0.4029 vn -0.2377 0.9054 -0.3517 vn -0.2161 0.9389 0.2679 vn -0.2457 0.9631 0.1100 vn -0.3839 0.9214 0.0605 vn -0.3879 0.9177 0.0859 vn -0.4070 0.8713 0.2741 vn -0.3900 0.8700 0.3015 vn 0.1708 0.8527 -0.4937 vn 0.0579 0.8465 -0.5292 vn 0.0172 0.8657 -0.5003 vn 0.1389 0.8516 -0.5054 vn 0.0438 0.8061 -0.5901 vn 0.1741 0.8131 -0.5554 vn 0.2557 0.8778 -0.4050 vn 0.2735 0.8617 -0.4273 vn 0.2557 0.8884 -0.3813 vn -0.1612 0.8724 -0.4614 vn -0.1534 0.8833 -0.4429 vn -0.1713 0.8502 -0.4978 vn 0.3066 0.9229 -0.2329 vn 0.3208 0.9188 -0.2298 vn 0.3312 0.9092 -0.2523 vn 0.3370 0.9403 -0.0477 vn 0.3550 0.9321 -0.0711 vn 0.3474 0.9354 -0.0654 vn 0.3713 0.9209 0.1188 vn 0.3286 0.9349 0.1341 vn 0.3669 0.9218 0.1249 vn 0.2995 0.8927 0.3368 vn 0.3471 0.8736 0.3412 vn 0.3475 0.8829 0.3159 vn 0.1837 0.6704 0.7189 vn 0.0522 0.5562 0.8294 vn -0.0072 0.6117 0.7911 vn 0.0310 0.4935 0.8692 vn 0.1222 0.7044 0.6992 vn 0.1711 0.6230 0.7633 vn -0.0977 0.5999 0.7941 vn -0.0912 0.4890 0.8675 vn -0.0598 0.5843 0.8094 vn 0.2833 0.8040 0.5229 vn 0.2344 0.8114 0.5354 vn 0.2865 0.7757 0.5623 vn -0.1821 0.8637 0.4700 vn -0.1659 0.6790 0.7151 vn -0.3202 0.7842 0.5315 vn -0.1599 0.6262 0.7631 vn -0.3022 0.7758 0.5539 vn -0.0716 0.6275 0.7753 vn -0.0686 0.5506 0.8319 vn -0.0890 0.5863 0.8052 vn -0.0942 0.5020 0.8597 vn -0.2461 0.9686 -0.0342 vn -0.2315 0.9608 -0.1523 vn -0.2037 0.9400 -0.2736 vn -0.2497 0.9470 0.2019 vn -0.2558 0.9638 0.0745 vn 0.0652 0.9339 -0.3516 vn -0.0153 0.9488 -0.3155 vn 0.1827 0.9326 -0.3113 vn -0.1109 0.9420 -0.3168 vn 0.2451 0.9544 -0.1705 vn 0.2657 0.9640 -0.0083 vn 0.2513 0.9560 0.1515 vn 0.2082 0.9251 0.3176 vn 0.0673 0.8138 0.5773 vn -0.0414 0.7901 0.6116 vn -0.0804 0.8005 0.5939 vn 0.1438 0.8736 0.4650 vn -0.2067 0.9073 0.3663 vn -0.1191 0.8421 0.5260 vn -0.0590 0.8075 0.5869 vn -0.0711 0.8011 0.5943 vn -0.0605 0.8790 0.4730 vn -0.0582 0.9391 0.3386 vn -0.0674 0.9751 0.2111 vn -0.0751 0.9916 0.1057 vn -0.0751 0.9971 0.0109 vn -0.0667 0.9943 -0.0837 vn -0.0549 0.9792 -0.1955 vn -0.0558 0.9492 -0.3097 vn -0.0757 0.8339 -0.5467 vn -0.0725 0.8893 -0.4516 vn -0.0492 0.8781 -0.4760 vn -0.0630 0.8529 -0.5182 vn -0.0243 0.8285 -0.5595 vn -0.0135 0.6799 -0.7332 vn -0.0048 0.4440 -0.8960 vn -0.0039 0.1514 -0.9885 vn -0.3120 0.0198 -0.9499 vn -0.1149 0.0095 -0.9933 vn -0.4549 0.0322 -0.8900 vn -0.6012 0.0560 -0.7971 vn -0.5052 0.2501 0.8260 vn -0.9497 0.0899 0.2999 vn -0.8124 0.1610 0.5604 vn -0.7126 0.0725 -0.6978 vn -0.9639 0.0706 -0.2566 vn -0.9939 0.0760 -0.0798 vn -0.9935 0.0717 0.0887 vn -0.9791 0.0690 0.1911 vn -0.5888 0.0338 0.8076 vn -0.9852 0.0885 0.1469 vn -0.5201 0.1543 0.8401 vn -0.8970 0.3048 0.3201 vn -0.8463 0.1482 -0.5117 vn -0.7086 0.1723 -0.6843 vn -0.7646 0.5810 -0.2789 vn -0.8528 0.3409 -0.3957 vn -0.6763 0.3227 -0.6622 vn -0.5952 0.5174 -0.6149 vn -0.3785 0.9173 -0.1236 vn -0.6130 0.7711 -0.1719 vn -0.5006 0.6600 -0.5601 vn -0.3351 0.7783 -0.5310 vn -0.1318 0.9816 -0.1381 vn -0.1406 0.8239 -0.5490 vn 0.0661 0.8132 -0.5783 vn 0.1226 0.9855 -0.1178 vn 0.2498 0.7311 -0.6349 vn 0.4105 0.9093 -0.0683 vn 0.7758 0.6300 -0.0364 vn 0.4848 0.5086 -0.7115 vn -0.9516 -0.3051 0.0372 vn -0.9305 -0.3235 0.1716 vn -0.9591 -0.2803 0.0398 vn -0.9460 -0.2721 0.1761 vn -0.9438 -0.3291 0.0289 vn -0.9143 -0.3740 0.1553 vn -0.9363 -0.3509 0.0151 vn -0.8985 -0.4199 0.1280 vn -0.9291 -0.3697 -0.0037 vn -0.8839 -0.4589 0.0901 vn -0.9231 -0.3837 -0.0261 vn -0.8714 -0.4885 0.0443 vn -0.9179 -0.3935 -0.0510 vn -0.8618 -0.5071 -0.0106 vn -0.9141 -0.3981 -0.0769 vn -0.8554 -0.5136 -0.0672 vn -0.9120 -0.3964 -0.1055 vn -0.8521 -0.5098 -0.1187 vn -0.9117 -0.3891 -0.1319 vn -0.8522 -0.4942 -0.1718 vn -0.9132 -0.3757 -0.1578 vn -0.8541 -0.4681 -0.2266 vn -0.9166 -0.3566 -0.1809 vn -0.8586 -0.4330 -0.2743 vn -0.9230 -0.3299 -0.1979 vn -0.8733 -0.3784 -0.3068 vn -0.9350 -0.2900 -0.2039 vn -0.8979 -0.2920 -0.3296 vn -0.9306 -0.3047 -0.2029 vn -0.8914 -0.3206 -0.3203 vn -0.8946 -0.3371 0.2933 vn -0.9176 -0.2608 0.2999 vn -0.8705 -0.4119 0.2695 vn -0.8469 -0.4797 0.2296 vn -0.8250 -0.5371 0.1757 vn -0.8063 -0.5812 0.1099 vn -0.7916 -0.6100 0.0359 vn -0.7826 -0.6209 -0.0455 vn -0.7790 -0.6139 -0.1278 vn -0.7792 -0.5908 -0.2095 vn -0.7831 -0.5466 -0.2967 vn -0.7912 -0.4907 -0.3650 vn -0.8080 -0.4259 -0.4071 vn -0.8337 -0.3434 -0.4324 vn -0.8457 -0.3451 0.4072 vn -0.8756 -0.2460 0.4158 vn -0.8142 -0.4422 0.3762 vn -0.7833 -0.5303 0.3243 vn -0.7548 -0.6048 0.2540 vn -0.7301 -0.6622 0.1689 vn -0.7103 -0.7001 0.0730 vn -0.6965 -0.7169 -0.0293 vn -0.6902 -0.7107 -0.1358 vn -0.6924 -0.6786 -0.2454 vn -0.6972 -0.6183 -0.3629 vn -0.6985 -0.5511 -0.4565 vn -0.7186 -0.4674 -0.5149 vn -0.7599 -0.3540 -0.5452 vn -0.8206 -0.2273 0.5244 vn -0.7843 -0.3477 0.5137 vn -0.7461 -0.4656 0.4760 vn -0.7085 -0.5723 0.4128 vn -0.6739 -0.6623 0.3276 vn -0.6438 -0.7316 0.2243 vn -0.6198 -0.7773 0.1080 vn -0.6031 -0.7975 -0.0159 vn -0.5948 -0.7912 -0.1423 vn -0.5958 -0.7552 -0.2733 vn -0.6047 -0.6734 -0.4253 vn -0.6036 -0.5828 -0.5441 vn -0.6133 -0.4986 -0.6125 vn -0.6572 -0.3689 -0.6573 vn -0.7541 -0.2055 0.6238 vn -0.7120 -0.3451 0.6115 vn -0.6676 -0.4817 0.5676 vn -0.6242 -0.6052 0.4942 vn -0.5840 -0.7091 0.3951 vn -0.5491 -0.7890 0.2754 vn -0.5214 -0.8416 0.1408 vn -0.5021 -0.8648 -0.0025 vn -0.4921 -0.8578 -0.1482 vn -0.4921 -0.8174 -0.2996 vn -0.5102 -0.7191 -0.4718 vn -0.5210 -0.6011 -0.6060 vn -0.5445 -0.5056 -0.6692 vn -0.5895 -0.3859 -0.7096 vn -0.6773 -0.1810 0.7131 vn -0.6301 -0.3375 0.6993 vn -0.5804 -0.4906 0.6499 vn -0.5318 -0.6288 0.5673 vn -0.4868 -0.7450 0.4561 vn -0.4479 -0.8341 0.3218 vn -0.4170 -0.8927 0.1710 vn -0.3955 -0.9184 0.0107 vn -0.3844 -0.9105 -0.1523 vn -0.3860 -0.8669 -0.3154 vn -0.4129 -0.7642 -0.4956 vn -0.4674 -0.6222 -0.6281 vn -0.5919 -0.1542 0.7911 vn -0.5403 -0.3253 0.7761 vn -0.4860 -0.4925 0.7220 vn -0.4330 -0.6432 0.6315 vn -0.3841 -0.7698 0.5097 vn -0.3419 -0.8668 0.3630 vn -0.3084 -0.9304 0.1984 vn -0.2850 -0.9582 0.0235 vn -0.2729 -0.9496 -0.1541 vn -0.2728 -0.9037 -0.3300 vn -0.2873 -0.8097 -0.5117 vn -0.3541 -0.6843 -0.6375 vn -0.4990 -0.1256 0.8574 vn -0.4439 -0.3086 0.8413 vn -0.3859 -0.4875 0.7832 vn -0.3294 -0.6486 0.6862 vn -0.2775 -0.7837 0.5557 vn -0.2327 -0.8871 0.3986 vn -0.1971 -0.9548 0.2226 vn -0.1724 -0.9844 0.0358 vn -0.1594 -0.9752 -0.1538 vn -0.1584 -0.9258 -0.3432 vn -0.1752 -0.8326 -0.5254 vn -0.2125 -0.7250 -0.6552 vn -0.4002 -0.0955 0.9114 vn -0.3422 -0.2879 0.8944 vn -0.2815 -0.4759 0.8332 vn -0.2224 -0.6451 0.7310 vn -0.1683 -0.7870 0.5936 vn -0.1216 -0.8953 0.4285 vn -0.0846 -0.9662 0.2436 vn -0.0588 -0.9971 0.0475 vn -0.0467 -0.9874 -0.1511 vn -0.0493 -0.9365 -0.3472 vn -0.0690 -0.8413 -0.5362 vn -0.0987 -0.7325 -0.6736 vn -0.2790 -0.0590 0.9585 vn -0.2188 -0.2590 0.9408 vn -0.1559 -0.4544 0.8771 vn -0.0949 -0.6302 0.7706 vn -0.0391 -0.7775 0.6276 vn 0.0088 -0.8900 0.4559 vn 0.0479 -0.9636 0.2629 vn 0.0772 -0.9951 0.0613 vn 0.0932 -0.9850 -0.1451 vn 0.0833 -0.9343 -0.3465 vn 0.0724 -0.8366 -0.5430 vn 0.0378 -0.7010 -0.7122 vn -0.1356 -0.0164 0.9906 vn -0.0742 -0.2207 0.9725 vn -0.0102 -0.4201 0.9074 vn 0.0515 -0.5997 0.7985 vn 0.1127 -0.7504 0.6513 vn 0.1753 -0.8634 0.4731 vn 0.2262 -0.9323 0.2824 vn 0.2414 -0.9679 0.0702 vn 0.2193 -0.9688 -0.1156 vn 0.1990 -0.9164 -0.3473 vn 0.2143 -0.8107 -0.5448 vn 0.1888 -0.6381 -0.7464 vn 0.0107 0.0266 0.9996 vn 0.0719 -0.1771 0.9816 vn 0.1354 -0.3762 0.9166 vn 0.1696 -0.5549 0.8145 vn 0.2013 -0.7185 0.6658 vn 0.2777 -0.8444 0.4581 vn 0.3913 -0.8671 0.3084 vn 0.3739 -0.9172 0.1374 vn 0.3443 -0.9375 -0.0511 vn 0.3325 -0.8801 -0.3390 vn 0.3359 -0.7524 -0.5667 vn 0.3665 -0.5809 -0.7268 vn 0.1393 0.0638 0.9882 vn 0.1991 -0.1355 0.9706 vn 0.2262 -0.3201 0.9200 vn 0.2085 -0.5215 0.8274 vn 0.4962 -0.8412 0.2146 vn 0.5414 -0.8365 0.0842 vn 0.5202 -0.8120 -0.2646 vn 0.4778 -0.6664 -0.5723 vn 0.4870 -0.5067 -0.7114 vn 0.2479 0.0950 0.9641 vn 0.2917 -0.0878 0.9525 vn 0.2725 -0.3055 0.9124 vn 0.3541 0.1251 0.9268 vn 0.3943 -0.0197 0.9188 vn 0.3922 -0.1435 0.9086 vn 0.6929 -0.6868 -0.2195 vn 0.6978 -0.7103 0.0928 vn 0.6176 -0.5762 -0.5353 vn 0.5868 -0.4257 -0.6888 vn 0.4564 0.1538 0.8764 vn 0.4919 0.0293 0.8702 vn 0.5181 -0.0345 0.8546 vn 0.8218 -0.5628 0.0892 vn 0.8020 -0.5780 -0.1506 vn 0.7396 -0.4928 -0.4584 vn 0.6744 -0.3208 -0.6651 vn 0.5535 0.1806 0.8131 vn 0.5871 0.0690 0.8065 vn 0.6382 0.0215 0.7695 vn 0.8777 -0.4693 -0.0968 vn 0.8616 -0.5019 0.0756 vn 0.8784 -0.3037 -0.3690 vn 0.8099 -0.1989 -0.5518 vn 0.6662 0.2112 0.7152 vn 0.7032 0.1126 0.7021 vn 0.7688 0.0509 0.6374 vn 0.8760 -0.4740 0.0893 vn 0.9207 -0.3576 -0.1565 vn 0.9227 -0.1790 -0.3415 vn 0.8878 -0.1256 -0.4428 vn 0.7206 -0.0779 -0.6890 vn 0.8574 -0.0132 -0.5145 vn 0.6544 0.1678 -0.7373 vn 0.5109 0.1531 -0.8459 vn 0.6326 -0.1323 -0.7631 vn 0.7792 0.2410 0.5786 vn 0.8152 0.1390 0.5622 vn 0.8575 0.0613 0.5109 vn 0.8052 0.2459 0.5396 vn 0.8454 0.1115 0.5223 vn 0.8089 0.2499 0.5322 vn 0.8789 0.0569 0.4736 vn -0.9550 -0.2835 -0.0869 vn 0.4567 -0.8202 0.3445 vn 0.5454 -0.7132 0.4403 vn 0.4082 -0.7986 0.4423 vn 0.4969 -0.7014 0.5111 vn 0.4793 -0.7661 0.4282 vn 0.4872 -0.8220 0.2950 vn 0.3395 -0.7605 0.5535 vn 0.4182 -0.7351 0.5337 vn 0.4499 -0.6875 0.5700 vn 0.5695 -0.8175 0.0853 vn 0.6554 -0.7096 0.2586 vn 0.5046 -0.8323 0.2297 vn 0.5977 -0.7133 0.3659 vn 0.5214 -0.8382 0.1599 vn 0.5937 -0.8013 0.0740 vn 0.6937 -0.7007 -0.1666 vn 0.7444 -0.6664 -0.0430 vn 0.6396 -0.7671 -0.0490 vn 0.7120 -0.6948 0.1015 vn 0.6789 -0.7342 -0.0024 vn 0.7530 -0.6553 -0.0596 vn 0.8201 -0.5188 -0.2413 vn 0.7566 -0.6245 -0.1938 vn 0.8369 -0.4629 -0.2920 vn 0.7543 -0.6019 -0.2622 vn 0.8032 -0.5847 -0.1141 vn 0.8619 -0.4810 -0.1607 vn 0.9118 -0.3715 -0.1750 vn 0.9607 -0.2704 -0.0629 vn 0.9176 -0.3304 -0.2210 vn 0.9572 -0.2891 -0.0113 vn 0.8891 -0.4407 -0.1233 vn 0.8722 -0.4854 0.0605 vn 0.7799 -0.4996 0.3769 vn 0.8416 -0.4906 0.2259 vn 0.8813 -0.2826 0.3788 vn 0.9373 -0.2837 0.2022 vn 0.9653 -0.2188 0.1427 vn 0.9188 -0.1865 0.3479 vn 0.6624 -0.5190 0.5402 vn 0.7195 -0.5105 0.4709 vn 0.7356 -0.3086 0.6031 vn 0.8115 -0.2924 0.5060 vn 0.8495 -0.1628 0.5019 vn 0.7561 -0.1708 0.6318 vn 0.5551 -0.5069 0.6595 vn 0.6013 -0.5227 0.6043 vn 0.5455 -0.3293 0.7707 vn 0.6488 -0.3217 0.6896 vn 0.6482 -0.1755 0.7410 vn 0.5219 -0.2281 0.8220 vn 0.3301 -0.5464 0.7697 vn 0.4876 -0.4160 0.7676 vn 0.2062 -0.4882 0.8480 vn 0.3964 -0.3574 0.8457 vn 0.3711 -0.3245 0.8701 vn 0.2627 -0.4951 0.8282 vn 0.2522 -0.6746 0.6938 vn 0.3271 -0.6944 0.6409 vn 0.3572 -0.6498 0.6709 vn 0.8226 -0.4674 0.3238 vn 0.7821 -0.4585 0.4220 vn 0.8641 -0.3387 0.3723 vn 0.8136 -0.3821 0.4381 vn 0.5918 0.2314 0.7721 vn 0.7298 0.3197 0.6043 vn 0.7541 -0.3990 0.5216 vn 0.8576 -0.4623 0.2255 vn 0.8555 -0.4731 0.2106 vn 0.9113 -0.4111 -0.0243 vn 0.9409 -0.3384 -0.0120 vn 0.9779 0.1552 -0.1405 vn 0.9252 -0.0498 -0.3762 vn 0.9558 -0.2807 0.0879 vn 0.8620 -0.4713 0.1866 vn 0.8536 -0.4565 0.2511 vn 0.9146 -0.2960 0.2755 vn 0.8561 0.3321 0.3960 vn 0.9445 0.2966 0.1410 vn 0.7065 -0.3805 0.5968 vn 0.7180 -0.3537 0.5995 vn 0.7343 -0.4159 0.5365 vn 0.4320 -0.0094 0.9018 vn 0.5132 0.1182 0.8501 vn 0.7174 -0.3327 0.6121 vn 0.7330 -0.3442 0.5867 vn 0.7258 -0.4021 0.5582 vn 0.6923 -0.4178 0.5883 vn 0.3245 -0.2061 0.9231 vn 0.2475 -0.4636 0.8508 vn 0.6350 -0.5509 0.5415 vn 0.1963 -0.7059 0.6805 vn 0.5625 -0.6844 0.4639 vn 0.2382 -0.8295 0.5051 vn 0.5995 -0.7034 0.3818 vn 0.6847 -0.6359 0.3560 vn 0.6917 -0.5429 0.4762 vn 0.8109 -0.4680 0.3512 vn 0.6801 -0.6381 0.3610 vn 0.7641 -0.5628 0.3153 vn 0.3426 -0.8581 0.3825 vn 0.4548 -0.8313 0.3194 vn 0.7288 -0.5667 0.3844 vn 0.7915 -0.4336 0.4307 vn 0.7302 -0.5476 0.4087 vn 0.8124 -0.4189 0.4055 vn 0.5047 -0.8210 0.2669 vn 0.5170 -0.8420 0.1542 vn 0.7081 -0.5963 0.3782 vn 0.7790 -0.5497 0.3018 vn 0.7143 -0.6494 0.2610 vn 0.7678 -0.5060 0.3930 vn 0.5443 -0.8389 0.0007 vn 0.6274 -0.7526 -0.2000 vn 0.7752 -0.6229 0.1052 vn 0.8510 -0.4751 0.2236 vn 0.8517 -0.5241 0.0050 vn 0.8185 -0.5195 0.2455 vn 0.7351 -0.5487 -0.3981 vn 0.8485 -0.2791 -0.4497 vn 0.6414 -0.0415 0.7661 vn 0.5736 -0.1647 0.8024 vn 0.6991 0.0797 0.7106 vn 0.9597 0.1584 0.2323 vn 0.8936 0.1504 0.4229 vn 0.9972 -0.0200 0.0725 vn 0.8094 0.1260 0.5736 vn 0.4873 -0.3290 0.8089 vn 0.4286 -0.5204 0.7386 vn 0.9706 -0.2164 -0.1057 vn 0.8867 -0.4553 -0.0797 vn 0.4984 -0.7666 0.4048 vn 0.4357 -0.7040 0.5609 vn 0.7876 -0.6151 -0.0364 vn 0.6884 -0.7170 0.1097 vn 0.5634 -0.7501 0.3462 vn 0.5329 -0.7664 0.3586 vn 0.6368 -0.7352 0.2322 vn 0.6040 -0.7520 0.2640 vn 0.5970 -0.7305 0.3316 vn 0.8646 -0.4595 0.2035 vn 0.9455 -0.2866 0.1546 vn 0.8101 -0.4644 0.3579 vn 0.9347 -0.1948 0.2974 vn 0.8945 -0.1853 0.4069 vn 0.6704 -0.6369 0.3807 vn 0.7168 -0.6168 0.3252 vn 0.7063 -0.6474 0.2864 vn 0.8034 -0.3119 0.5071 vn 0.7535 -0.3025 0.5837 vn 0.8394 -0.3013 0.4523 vn 0.5881 -0.6709 0.4517 vn 0.5710 -0.6006 0.5597 vn 0.6223 -0.4851 0.6144 vn 0.6658 -0.6801 0.3070 vn 0.7956 -0.5052 0.3343 vn 0.7134 -0.6088 0.3471 vn 0.8210 -0.4190 0.3878 vn 0.8655 -0.2332 0.4432 vn 0.6885 -0.3725 0.6222 vn 0.9261 -0.3284 0.1857 vn 0.9735 -0.1760 0.1463 vn 0.8837 -0.4299 0.1851 vn 0.9567 -0.0770 0.2807 vn 0.9189 -0.0637 0.3893 vn 0.6033 -0.7637 0.2299 vn 0.6362 -0.7319 0.2439 vn 0.5051 -0.8089 0.3011 vn 0.7063 -0.0696 0.7045 vn 0.5893 -0.1109 0.8003 vn 0.8260 -0.0560 0.5609 vn 0.3084 -0.5991 0.7389 vn 0.3103 -0.7189 0.6220 vn 0.3659 -0.4319 0.8243 vn 0.3855 -0.8207 0.4218 vn 0.6961 -0.6916 0.1925 vn 0.7726 -0.6229 0.1224 vn 0.8410 -0.5251 0.1303 vn 0.8811 -0.0612 0.4690 vn 0.4759 -0.2497 0.8433 vn 0.9259 -0.2353 0.2955 vn 0.8791 -0.3891 0.2754 vn 0.9365 -0.0821 0.3410 vn 0.9119 -0.0048 0.4103 vn 0.5001 0.6771 -0.5398 vn 0.3197 0.9166 0.2400 vn 0.9564 0.2293 0.1810 vn 0.2835 0.2065 0.9365 vn 0.7094 0.6917 0.1353 vn 0.9886 0.1493 0.0205 vn 0.6124 0.1368 0.7786 vn 0.9711 0.2361 0.0347 vn 0.6079 0.1876 0.7716 vn 0.5381 0.0817 0.8389 vn 0.9817 0.1444 0.1243 vn 0.6798 0.1731 -0.7127 vn 0.6513 0.3884 -0.6518 vn 0.4881 0.1249 -0.8638 vn 0.4340 0.2221 -0.8731 vn 0.7557 0.1679 -0.6331 vn 0.2504 -0.6165 0.7465 vn 0.5415 -0.5452 0.6400 vn 0.1019 -0.5867 0.8034 vn 0.4909 -0.5153 0.7025 vn 0.8297 -0.4636 0.3107 vn 0.8962 -0.2400 0.3731 vn 0.8212 -0.0999 -0.5619 vn 0.6336 -0.0506 -0.7720 vn 0.1594 -0.1672 -0.9730 vn 0.1956 -0.1271 -0.9724 vn 0.1695 0.0284 -0.9851 vn 0.5702 0.1345 -0.8104 vn 0.3962 -0.5186 0.7577 vn 0.7984 -0.2291 0.5568 vn 0.2313 -0.3733 0.8984 vn 0.7125 -0.1100 0.6930 vn 0.3717 -0.3432 0.8626 vn 0.2967 -0.5726 0.7643 vn 0.1673 -0.6290 0.7592 vn 0.1605 -0.6303 0.7596 vn 0.3216 -0.6125 0.7221 vn 0.1945 -0.6004 0.7757 vn 0.1726 -0.9752 0.1383 vn 0.4440 -0.8777 0.1803 vn 0.2370 -0.7332 0.6374 vn 0.3602 -0.6906 0.6272 vn 0.6925 -0.7153 0.0934 vn 0.5806 -0.6942 0.4255 vn 0.4656 -0.8107 -0.3549 vn 0.1221 -0.9041 -0.4096 vn 0.2325 -0.1678 -0.9580 vn 0.4037 -0.3417 -0.8487 vn 0.0791 -0.5271 -0.8461 vn 0.5146 -0.3554 -0.7803 vn 0.2254 -0.1849 -0.9566 vn 0.2345 -0.1056 -0.9664 vn 0.1667 -0.1352 -0.9767 vn 0.1773 0.0287 -0.9837 vn 0.0071 -0.0214 -0.9997 vn 0.3637 -0.3236 0.8735 vn 0.2395 -0.5522 0.7985 vn 0.3176 -0.2813 0.9055 vn 0.2200 -0.5249 0.8222 vn 0.9354 -0.3515 -0.0382 vn 0.9866 -0.1321 -0.0959 vn 0.8289 -0.3698 -0.4198 vn 0.7812 -0.4653 -0.4161 vn 0.7765 -0.2570 -0.5753 vn 0.9276 -0.3517 0.1256 vn 0.9963 -0.0584 0.0635 vn 0.2645 -0.3142 -0.9118 vn 0.4137 -0.3907 -0.8223 vn 0.9873 0.1564 -0.0294 vn 0.7036 -0.0333 -0.7099 vn 0.9585 0.2767 -0.0681 vn 0.6766 0.1805 -0.7139 vn 0.1192 0.0112 -0.9928 vn 0.1256 -0.2275 -0.9656 vn 0.4083 -0.2510 0.8776 vn 0.3525 -0.5496 0.7574 vn 0.9138 -0.0845 0.3974 vn 0.9039 -0.3104 0.2943 vn 0.3660 -0.7411 0.5629 vn 0.9060 -0.4191 0.0597 vn 0.5186 0.7860 0.3365 vn 0.2906 0.6976 -0.6549 vn 0.4708 0.5354 -0.7012 vn 0.2553 0.6002 -0.7580 vn 0.2897 0.1729 -0.9414 vn 0.7588 0.4695 0.4514 vn 0.1531 0.3053 0.9399 vn 0.1418 0.2111 0.9671 vn 0.1556 0.1389 0.9780 vn 0.2710 0.1208 0.9550 vn 0.1034 0.1771 0.9788 vn 0.1991 0.1580 0.9672 vn 0.2926 0.1044 0.9505 vn 0.2036 0.0709 0.9765 vn 0.1860 -0.6650 0.7233 vn -0.0835 -0.6450 0.7596 vn -0.2985 -0.6297 0.7172 vn -0.0207 -0.7265 0.6868 vn 0.5402 0.1903 -0.8197 vn 0.3031 0.4317 -0.8496 vn 0.3616 0.1837 -0.9141 vn 0.3174 0.4516 -0.8339 vn 0.2646 0.6021 -0.7533 vn 0.3245 0.6091 -0.7236 vn 0.7130 -0.1641 -0.6817 vn 0.4621 -0.2579 -0.8485 vn 0.8118 -0.0262 0.5834 vn 0.9785 0.0409 -0.2020 vn 0.7649 -0.5002 -0.4059 vn 0.5440 -0.7077 -0.4509 vn 0.3528 0.5097 -0.7847 vn 0.2884 0.5251 -0.8007 vn 0.4004 0.5065 -0.7636 vn 0.3970 0.1793 -0.9001 vn 0.4521 0.1543 -0.8785 vn 0.3536 0.1975 -0.9143 vn -0.1376 -0.7473 0.6501 vn -0.1404 -0.7223 0.6772 vn -0.3471 -0.6350 0.6901 vn -0.3469 -0.6376 0.6878 vn -0.3345 -0.6308 0.7001 vn -0.1276 -0.6974 0.7053 vn 0.6786 0.2773 -0.6802 vn 0.5995 0.3820 -0.7033 vn 0.7691 0.2603 -0.5837 vn 0.6572 0.3865 -0.6471 vn 0.4465 0.5820 -0.6796 vn 0.4789 0.5620 -0.6744 vn 0.4159 0.5884 -0.6934 vn 0.5440 0.3977 -0.7388 vn 0.6350 0.2466 -0.7321 vn 0.7540 -0.1013 -0.6490 vn 0.8042 -0.0357 -0.5932 vn 0.7507 -0.5617 -0.3477 vn 0.8224 -0.5411 -0.1755 vn 0.9162 0.0352 -0.3991 vn -0.5554 -0.4224 0.7163 vn -0.5549 -0.4058 0.7262 vn -0.6933 -0.2000 0.6923 vn -0.6826 -0.1975 0.7036 vn -0.4311 -0.3560 0.8291 vn -0.5748 -0.2342 0.7841 vn 0.4134 0.4739 -0.7775 vn 0.4519 0.4999 -0.7389 vn 0.4378 0.4367 -0.7859 vn 0.5987 0.1875 -0.7787 vn 0.5750 0.2339 -0.7840 vn 0.6147 0.1554 -0.7733 vn -0.1366 -0.5091 0.8498 vn -0.2144 -0.2997 0.9296 vn 0.6312 0.3515 -0.6913 vn 0.7119 0.2375 -0.6609 vn 0.8413 0.2266 -0.4907 vn 0.8855 0.1788 -0.4288 vn 0.1256 -0.2030 -0.9711 vn 0.4087 -0.5232 -0.7478 vn 0.4080 -0.5494 -0.7292 vn -0.0180 -0.2565 -0.9664 vn 0.8287 -0.5170 -0.2143 vn 0.7038 -0.6966 -0.1395 vn 0.7164 -0.4378 0.5433 vn 0.6662 -0.5228 0.5319 vn 0.1583 0.0856 0.9837 vn 0.1036 0.1652 0.9808 vn 0.1008 0.1623 0.9816 vn 0.1002 0.2052 0.9736 vn 0.1681 0.1739 0.9703 vn 0.0110 -0.1969 -0.9804 vn -0.1006 -0.2167 -0.9710 vn -0.0498 -0.0377 -0.9980 vn -0.1259 -0.0608 -0.9902 vn 0.1143 0.1736 0.9782 vn 0.1596 0.0742 0.9844 vn 0.0633 0.1406 0.9880 vn 0.0724 0.0800 0.9942 vn -0.0465 -0.2706 -0.9616 vn 0.4127 -0.4807 -0.7737 vn 0.7517 -0.6099 -0.2510 vn 0.7920 -0.4778 0.3801 vn 0.2139 0.1121 0.9704 vn 0.2358 0.0986 0.9668 vn -0.1995 -0.0816 -0.9765 vn -0.1484 -0.2379 -0.9599 vn 0.2430 0.1801 0.9532 vn 0.2712 0.1188 0.9552 vn -0.0427 -0.5381 0.8418 vn -0.3510 -0.4920 0.7967 vn 0.1344 -0.2576 0.9569 vn -0.2058 -0.2229 0.9529 vn 0.5525 0.1483 -0.8202 vn 0.4358 0.5106 -0.7412 vn 0.3739 0.5978 -0.7091 vn 0.3734 -0.8909 0.2588 vn 0.4131 -0.8783 0.2405 vn 0.6834 -0.6289 -0.3707 vn 0.3765 -0.8853 0.2730 vn -0.0870 -0.7588 0.6455 vn -0.3224 -0.6282 0.7081 vn 0.5132 0.2213 -0.8293 vn 0.4711 0.4417 -0.7635 vn 0.3484 -0.8287 0.4380 vn 0.5136 -0.7783 0.3613 vn 0.7746 -0.6293 -0.0625 vn 0.4703 -0.5555 -0.6857 vn 0.5990 0.1747 0.7814 vn 0.0349 0.0678 0.9971 vn 0.3010 0.1109 0.9471 vn 0.2917 0.1146 0.9496 vn 0.3023 0.1253 0.9449 vn 0.1987 0.0929 0.9756 vn -0.0646 0.0046 0.9979 vn -0.5566 -0.2034 0.8055 vn -0.4928 -0.1236 0.8613 vn -0.6815 -0.1823 0.7087 vn -0.7118 -0.1919 0.6757 vn -0.6493 -0.1722 0.7408 vn -0.2786 -0.0588 0.9586 vn 0.3143 0.1138 0.9425 vn 0.9837 0.0933 -0.1539 vn 0.9553 0.2764 -0.1049 vn 0.8379 0.2559 0.4820 vn 0.8742 0.2453 -0.4191 vn 0.7408 0.2012 -0.6409 vn 0.6520 0.1730 -0.7382 vn 0.6537 0.1736 -0.7366 vn 0.6336 0.1673 -0.7554 vn 0.5322 0.1358 -0.8357 vn 0.4154 0.1001 -0.9041 vn 0.3919 0.0931 -0.9153 vn 0.3842 0.0909 -0.9188 vn 0.7566 0.2470 0.6054 vn 0.7346 0.0910 0.6724 vn 0.7702 0.2355 0.5928 vn 0.3094 -0.1745 0.9348 vn 0.9862 0.0112 -0.1653 vn 0.3654 -0.1260 0.9223 vn 0.1909 0.0940 0.9771 vn 0.5243 -0.0872 0.8471 vn 0.2520 0.1041 0.9621 vn 0.6332 -0.1937 -0.7494 vn 0.6953 -0.1039 0.7112 vn 0.9635 0.0787 -0.2558 vn 0.3555 0.5050 -0.7865 vn -0.4800 -0.4564 0.7492 vn 0.8882 0.2709 0.3711 vn 0.9444 0.1182 0.3068 vn 0.9517 -0.0388 0.3045 vn 0.9632 -0.1328 0.2335 vn 0.9518 -0.2695 0.1466 vn 0.9110 -0.4123 -0.0049 vn 0.9647 -0.1933 -0.1789 vn 0.9818 0.0118 -0.1895 vn 0.7355 -0.5505 -0.3949 vn 0.7645 -0.3071 -0.5668 vn 0.8504 0.2299 -0.4732 vn 0.9605 -0.0085 -0.2783 vn 0.7867 0.1783 -0.5910 vn 0.9845 -0.0158 -0.1748 vn 0.7703 -0.2517 -0.5859 vn 0.5048 -0.4255 -0.7511 vn -0.9606 -0.2206 0.1693 vn -0.9662 -0.2553 0.0366 vn -0.9739 -0.1699 0.1509 vn -0.9729 -0.2297 0.0267 vn -0.9848 -0.1240 0.1216 vn -0.9774 -0.2106 0.0155 vn -0.9929 -0.0850 0.0837 vn -0.9811 -0.1934 -0.0023 vn -0.9978 -0.0545 0.0377 vn -0.9839 -0.1759 -0.0307 vn -0.9993 -0.0352 -0.0145 vn -0.9843 -0.1673 -0.0571 vn -0.9971 -0.0280 -0.0709 vn -0.9829 -0.1643 -0.0831 vn -0.9912 -0.0328 -0.1285 vn -0.9799 -0.1663 -0.1100 vn -0.9756 -0.1732 -0.1350 vn -0.9825 -0.0489 -0.1799 vn -0.9701 -0.1853 -0.1570 vn -0.9710 -0.0745 -0.2271 vn -0.9572 -0.1088 -0.2681 vn -0.9632 -0.2034 -0.1756 vn -0.9385 -0.1680 -0.3018 vn -0.9538 -0.2324 -0.1905 vn -0.9147 -0.2474 -0.3196 vn -0.9442 -0.2641 -0.1968 vn -0.9392 -0.1846 0.2897 vn -0.9587 -0.1096 0.2623 vn -0.9748 -0.0415 0.2193 vn -0.9865 0.0162 0.1627 vn -0.9936 0.0610 0.0952 vn -0.9957 0.0907 0.0201 vn -0.9928 0.1035 -0.0596 vn -0.9852 0.0979 -0.1409 vn -0.9725 0.0739 -0.2207 vn -0.9548 0.0360 -0.2950 vn -0.9322 -0.0191 -0.3613 vn -0.9024 -0.1023 -0.4186 vn -0.8718 -0.2014 -0.4466 vn -0.9036 -0.1466 0.4025 vn -0.9289 -0.0491 0.3670 vn -0.9496 0.0394 0.3108 vn -0.9647 0.1143 0.2372 vn -0.9737 0.1723 0.1494 vn -0.9762 0.2107 0.0517 vn -0.9723 0.2279 -0.0514 vn -0.9624 0.2228 -0.1553 vn -0.9441 0.1966 -0.2646 vn -0.9194 0.1528 -0.3624 vn -0.8821 0.0808 -0.4641 vn -0.8438 -0.0292 -0.5359 vn -0.8170 -0.1398 -0.5594 vn -0.8546 -0.1068 0.5082 vn -0.8853 0.0114 0.4649 vn -0.9103 0.1187 0.3966 vn -0.9284 0.2094 0.3071 vn -0.9390 0.2795 0.2006 vn -0.9418 0.3259 0.0821 vn -0.9371 0.3465 -0.0427 vn -0.9244 0.3409 -0.1708 vn -0.9016 0.3060 -0.3056 vn -0.8679 0.2437 -0.4328 vn -0.8209 0.1545 -0.5497 vn -0.7837 0.0598 -0.6182 vn -0.7686 -0.0693 -0.6359 vn -0.7935 -0.0659 0.6050 vn -0.8290 0.0711 0.5547 vn -0.8578 0.1953 0.4755 vn -0.8786 0.3002 0.3715 vn -0.8907 0.3810 0.2480 vn -0.8939 0.4344 0.1109 vn -0.8883 0.4581 -0.0335 vn -0.8736 0.4510 -0.1829 vn -0.8508 0.4078 -0.3314 vn -0.8184 0.3241 -0.4746 vn -0.7720 0.2112 -0.5995 vn -0.7271 0.1196 -0.6760 vn -0.7007 -0.0244 -0.7131 vn -0.7215 -0.0244 0.6919 vn -0.7613 0.1291 0.6354 vn -0.7935 0.2680 0.5463 vn -0.8167 0.3852 0.4296 vn -0.8302 0.4754 0.2911 vn -0.8337 0.5348 0.1376 vn -0.8274 0.5611 -0.0240 vn -0.8117 0.5526 -0.1891 vn -0.7885 0.5053 -0.3506 vn -0.7564 0.4124 -0.5076 vn -0.7111 0.2849 -0.6428 vn -0.6688 -0.1526 -0.7276 vn -0.6402 0.0169 0.7680 vn -0.6837 0.1845 0.7061 vn -0.7188 0.3360 0.6086 vn -0.7442 0.4637 0.4808 vn -0.7589 0.5617 0.3295 vn -0.7627 0.6261 0.1619 vn -0.7558 0.6546 -0.0143 vn -0.7391 0.6457 -0.1920 vn -0.7124 0.5973 -0.3684 vn -0.6708 0.5104 -0.5381 vn -0.6325 0.3988 -0.6640 vn -0.5507 0.0575 0.8327 vn -0.5974 0.2368 0.7662 vn -0.6351 0.3986 0.6616 vn -0.6624 0.5347 0.5247 vn -0.6782 0.6391 0.3628 vn -0.6824 0.7076 0.1837 vn -0.6750 0.7378 -0.0045 vn -0.6568 0.7288 -0.1937 vn -0.6251 0.6780 -0.3867 vn -0.5711 0.5936 -0.5669 vn -0.5326 0.5207 -0.6673 vn -0.4547 0.0970 0.8854 vn -0.5037 0.2853 0.8154 vn -0.5436 0.4551 0.7052 vn -0.5725 0.5978 0.5612 vn -0.5894 0.7070 0.3909 vn -0.5939 0.7786 0.2027 vn -0.5863 0.8100 0.0051 vn -0.5673 0.8005 -0.1934 vn -0.5348 0.7450 -0.3988 vn -0.4762 0.6556 -0.5860 vn -0.4417 0.6046 -0.6629 vn -0.3356 0.1411 0.9314 vn -0.3868 0.3367 0.8585 vn -0.4286 0.5129 0.7438 vn -0.4590 0.6608 0.5939 vn -0.4769 0.7738 0.4169 vn -0.4820 0.8478 0.2214 vn -0.4744 0.8802 0.0163 vn -0.4546 0.8702 -0.1898 vn -0.4197 0.8124 -0.4048 vn -0.3596 0.7149 -0.5996 vn -0.3112 0.6797 -0.6642 vn -0.1934 0.1879 0.9630 vn -0.2459 0.3875 0.8885 vn -0.2891 0.5671 0.7712 vn -0.3167 0.7206 0.6168 vn -0.3227 0.8415 0.4333 vn -0.3192 0.9152 0.2460 vn -0.3377 0.9408 0.0284 vn -0.3176 0.9306 -0.1818 vn -0.2814 0.8709 -0.4030 vn -0.2180 0.7604 -0.6118 vn -0.1574 0.7154 -0.6808 vn -0.0469 0.2302 0.9720 vn -0.0997 0.4292 0.8977 vn -0.1656 0.5936 0.7876 vn -0.2250 0.7419 0.6316 vn -0.2262 0.8794 0.4190 vn -0.1507 0.9467 0.2847 vn -0.1922 0.9785 0.0749 vn -0.2063 0.9636 -0.1699 vn -0.1658 0.8959 -0.4121 vn -0.0682 0.7865 -0.6138 vn 0.0045 0.7160 -0.6981 vn 0.0828 0.2630 0.9612 vn 0.0069 0.4308 0.9024 vn -0.1150 0.5869 0.8015 vn -0.0567 0.9815 0.1830 vn -0.0553 0.9984 -0.0144 vn -0.0658 0.9211 -0.3838 vn 0.0156 0.7645 -0.6445 vn 0.1004 0.6746 -0.7313 vn 0.1867 0.2719 0.9440 vn 0.0539 0.4432 0.8948 vn 0.3102 0.2682 0.9120 vn 0.2420 0.3709 0.8966 vn 0.1263 0.9381 -0.3224 vn 0.0837 0.9965 0.0017 vn 0.1410 0.7784 -0.6117 vn 0.2221 0.5897 -0.7765 vn 0.4194 0.2774 0.8644 vn 0.4075 0.3445 0.8457 vn 0.3746 0.8847 -0.2775 vn 0.3203 0.9473 0.0027 vn 0.3645 0.7798 -0.5089 vn 0.3815 0.5636 -0.7327 vn 0.5220 0.2924 0.8013 vn 0.5399 0.3582 0.7617 vn 0.5848 0.7856 -0.2021 vn 0.5610 0.8277 0.0151 vn 0.5809 0.7309 -0.3583 vn 0.5672 0.5948 -0.5696 vn 0.6444 0.3136 0.6974 vn 0.6665 0.3953 0.6321 vn 0.5753 0.7887 -0.2168 vn 0.5590 0.8292 0.0016 vn 0.6363 0.7137 -0.2929 vn 0.7122 0.5954 -0.3719 vn 0.5862 0.3572 -0.7272 vn 0.7625 0.3930 -0.5140 vn 0.4493 0.3489 -0.8224 vn 0.7538 0.3429 0.5606 vn 0.7548 0.4325 0.4931 vn 0.7689 0.3931 0.5042 vn 0.7629 0.4620 0.4523 vn -0.0597 0.9503 0.3054 vn -0.0908 0.9111 0.4021 vn 0.0707 0.9128 0.4021 vn 0.0353 0.8797 0.4742 vn -0.0140 0.9209 0.3895 vn -0.0317 0.9639 0.2644 vn -0.1296 0.8468 0.5158 vn -0.0501 0.8668 0.4961 vn 0.0024 0.8454 0.5341 vn 0.0463 0.9968 0.0649 vn -0.0221 0.9795 0.2002 vn 0.1699 0.9597 0.2239 vn 0.1162 0.9376 0.3278 vn -0.0056 0.9892 0.1462 vn 0.0739 0.9963 0.0442 vn 0.2195 0.9560 -0.1948 vn 0.1366 0.9881 -0.0713 vn 0.2793 0.9579 -0.0667 vn 0.2306 0.9704 0.0721 vn 0.1840 0.9818 -0.0471 vn 0.2859 0.9514 -0.1145 vn 0.3275 0.8985 -0.2922 vn 0.3188 0.9234 -0.2136 vn 0.4701 0.8248 -0.3141 vn 0.4247 0.8665 -0.2625 vn 0.3677 0.9166 -0.1570 vn 0.4782 0.8558 -0.1971 vn 0.6522 0.7572 -0.0348 vn 0.6655 0.7415 -0.0852 vn 0.6030 0.7604 -0.2413 vn 0.5759 0.7934 -0.1972 vn 0.5189 0.8421 -0.1473 vn 0.4758 0.8790 0.0317 vn 0.6350 0.7517 0.1779 vn 0.4432 0.8750 0.1945 vn 0.5861 0.7282 0.3552 vn 0.3840 0.8564 0.3451 vn 0.6706 0.6654 0.3279 vn 0.6945 0.7093 0.1208 vn 0.5205 0.7043 0.4828 vn 0.3262 0.8370 0.4392 vn 0.4466 0.6813 0.5800 vn 0.2726 0.8164 0.5091 vn 0.5374 0.5778 0.6142 vn 0.6221 0.6157 0.4837 vn 0.3651 0.6496 0.6668 vn 0.2182 0.7894 0.5737 vn 0.2732 0.6041 0.7487 vn 0.1873 0.7538 0.6298 vn 0.3071 0.5083 0.8046 vn 0.4428 0.5281 0.7245 vn 0.1316 0.5506 0.8243 vn 0.1783 0.6454 0.7427 vn -0.0995 0.5585 0.8235 vn -0.0254 0.6714 0.7407 vn -0.0544 0.5945 0.8023 vn 0.1275 0.5107 0.8502 vn -0.1585 0.7336 0.6608 vn -0.1053 0.7884 0.6061 vn -0.0565 0.7683 0.6376 vn 0.4391 0.8428 0.3112 vn 0.5426 0.7593 0.3592 vn 0.4146 0.8096 0.4156 vn 0.4816 0.7655 0.4267 vn 0.6116 0.1653 0.7737 vn 0.4192 0.7557 0.5032 vn 0.7814 0.1411 0.6079 vn 0.4716 0.8601 0.1944 vn 0.5475 0.8351 -0.0536 vn 0.4642 0.8675 0.1790 vn 0.6114 0.7904 -0.0385 vn 0.9096 0.3890 -0.1457 vn 0.6536 0.7541 0.0637 vn 0.7580 0.5229 -0.3897 vn 0.4710 0.8684 0.1551 vn 0.4717 0.8530 0.2233 vn 0.6089 0.7517 0.2533 vn 0.8972 0.1889 0.3992 vn 0.9545 0.2624 0.1417 vn 0.3987 0.6967 0.5963 vn 0.4017 0.7460 0.5311 vn 0.4147 0.6997 0.5818 vn 0.3516 0.2731 0.8954 vn 0.4266 0.6841 0.5917 vn 0.4867 0.2130 0.8472 vn 0.4398 0.6868 0.5788 vn 0.4008 0.7437 0.5351 vn 0.3588 0.7454 0.5619 vn 0.1598 0.3879 0.9077 vn -0.0500 0.5513 0.8328 vn 0.2400 0.8254 0.5109 vn -0.2247 0.7232 0.6531 vn -0.2498 0.8486 0.4664 vn 0.0979 0.8949 0.4355 vn 0.1368 0.9296 0.3422 vn 0.2057 0.9107 0.3583 vn 0.2838 0.8535 0.4370 vn 0.4572 0.8369 0.3011 vn 0.3759 0.8797 0.2913 vn 0.2549 0.9149 0.3131 vn -0.1639 0.9268 0.3380 vn -0.0485 0.9576 0.2839 vn 0.3308 0.8758 0.3515 vn 0.4126 0.8224 0.3916 vn 0.4572 0.8138 0.3587 vn 0.3107 0.8762 0.3685 vn -0.0195 0.9735 0.2278 vn -0.0183 0.9936 0.1115 vn 0.2683 0.9002 0.3430 vn 0.3552 0.8956 0.2678 vn 0.3608 0.8601 0.3606 vn 0.2495 0.9422 0.2236 vn 0.0077 0.9991 -0.0425 vn 0.1266 0.9624 -0.2402 vn 0.3172 0.9459 0.0687 vn 0.4592 0.8673 0.1922 vn 0.4076 0.8881 0.2125 vn 0.4362 0.8994 -0.0284 vn 0.3296 0.8401 -0.4309 vn 0.5710 0.6718 -0.4718 vn 0.3878 0.4911 0.7800 vn 0.5114 0.4220 0.7486 vn 0.6275 0.3422 0.6994 vn 0.8287 0.3717 0.4184 vn 0.8903 0.3948 0.2269 vn 0.8287 0.5566 0.0594 vn 0.7471 0.3350 0.5742 vn 0.0785 0.6991 0.7107 vn 0.2296 0.5769 0.7839 vn 0.7035 0.6991 -0.1278 vn 0.5039 0.8566 -0.1112 vn 0.0004 0.9295 0.3687 vn -0.0206 0.8478 0.5300 vn 0.3335 0.9399 -0.0732 vn 0.1933 0.9786 0.0699 vn 0.0528 0.9509 0.3051 vn 0.0202 0.9473 0.3198 vn 0.1387 0.9715 0.1921 vn 0.1013 0.9694 0.2237 vn 0.1042 0.9503 0.2933 vn 0.6384 0.7500 0.1730 vn 0.4722 0.8589 0.1983 vn 0.4177 0.8322 0.3647 vn 0.6559 0.6620 0.3627 vn 0.6868 0.6795 0.2582 vn 0.2268 0.8758 0.4261 vn 0.2730 0.9016 0.3354 vn 0.2446 0.9308 0.2715 vn 0.4651 0.6852 0.5606 vn 0.5009 0.7172 0.4845 vn 0.5307 0.7286 0.4330 vn 0.1294 0.9006 0.4149 vn 0.1513 0.8368 0.5261 vn 0.2561 0.7696 0.5850 vn 0.1924 0.9430 0.2715 vn 0.4500 0.7781 0.4383 vn 0.3043 0.8395 0.4502 vn 0.4732 0.7700 0.4280 vn 0.5959 0.6853 0.4187 vn 0.3723 0.7107 0.5968 vn 0.7184 0.6780 0.1556 vn 0.6168 0.7720 0.1533 vn 0.5447 0.8074 0.2269 vn 0.7667 0.5910 0.2507 vn 0.7547 0.5509 0.3562 vn 0.0981 0.9642 0.2463 vn 0.0804 0.9683 0.2364 vn -0.0204 0.9576 0.2873 vn 0.5482 0.4685 0.6928 vn 0.4271 0.4449 0.7872 vn 0.6552 0.5133 0.5543 vn -0.0714 0.7021 0.7085 vn -0.1328 0.7990 0.5865 vn 0.0657 0.5961 0.8002 vn -0.1215 0.9156 0.3832 vn 0.3728 0.8926 0.2537 vn 0.1575 0.9564 0.2461 vn 0.4910 0.8302 0.2642 vn 0.7084 0.5422 0.4519 vn 0.2563 0.5026 0.8256 vn 0.6494 0.7060 0.2825 vn 0.4386 0.8603 0.2599 vn 0.7664 0.5154 0.3833 vn 0.7374 0.5955 0.3188 vn 0.8153 -0.4890 0.3102 vn 0.9464 0.2864 0.1491 vn 0.9767 -0.1884 0.1025 vn 0.4347 0.0232 0.9003 vn 0.9718 0.2327 -0.0377 vn 0.6660 0.1601 0.7285 vn 0.9480 0.3092 0.0759 vn 0.5816 0.1821 0.7929 vn 0.7471 0.1811 -0.6395 vn 0.8338 0.0319 -0.5512 vn 0.4787 0.0960 -0.8727 vn 0.1486 0.6686 0.7286 vn 0.1408 0.7696 0.6228 vn -0.2694 0.4832 0.8331 vn -0.2241 0.6433 0.7321 vn 0.6782 0.6708 0.3001 vn 0.4813 0.8501 0.2138 vn 0.1080 0.1680 -0.9798 vn 0.5463 0.3457 -0.7629 vn 0.0707 0.1888 -0.9795 vn 0.6799 0.5050 -0.5317 vn 0.0118 0.7308 0.6825 vn 0.5282 0.6726 0.5184 vn 0.5046 0.5553 0.6611 vn -0.0544 0.5446 0.8369 vn 0.0970 0.5661 0.8186 vn -0.1336 0.7069 0.6946 vn -0.2191 0.5770 0.7868 vn -0.3163 0.5979 0.7366 vn -0.1569 0.6754 0.7205 vn -0.3072 0.5937 0.7437 vn -0.3617 0.9254 0.1129 vn -0.2029 0.7230 0.6604 vn -0.0970 0.9877 0.1225 vn -0.0688 0.7716 0.6324 vn 0.0992 0.9106 0.4011 vn 0.1954 0.9794 0.0501 vn -0.0698 0.9116 -0.4052 vn -0.3721 0.7966 -0.4764 vn 0.1180 0.2250 -0.9672 vn 0.1612 0.4684 -0.8687 vn -0.2198 0.4553 -0.8628 vn 0.1028 0.2356 -0.9664 vn 0.2526 0.5422 -0.8014 vn 0.1531 0.1734 -0.9729 vn 0.0798 0.1613 -0.9837 vn 0.1196 0.5486 0.8275 vn 0.1002 0.4806 0.8712 vn -0.1629 0.6414 0.7497 vn -0.1723 0.5875 0.7907 vn 0.5989 0.7980 -0.0665 vn 0.7585 0.6419 -0.1124 vn 0.5023 0.7430 -0.4423 vn 0.7978 0.6011 0.0477 vn 0.5238 0.6121 -0.5924 vn 0.6025 0.7889 0.1207 vn 0.4199 0.7975 -0.4332 vn 0.0656 0.3675 -0.9277 vn 0.1490 0.5159 -0.8436 vn 0.9139 0.4052 -0.0247 vn 0.5833 0.3749 -0.7206 vn -0.0041 0.2171 -0.9761 vn 0.1982 0.4689 0.8607 vn 0.7199 0.5796 0.3818 vn -0.0079 0.6850 0.7285 vn 0.5914 0.7601 0.2693 vn 0.5377 0.8426 0.0300 vn -0.0970 0.8451 0.5257 vn 0.4700 -0.4658 -0.7498 vn 0.7100 -0.4632 -0.5304 vn 0.7260 -0.2006 -0.6578 vn 0.8670 -0.2475 0.4325 vn 0.8679 -0.2379 -0.4361 vn 0.8091 0.1131 0.5766 vn 0.3975 0.0192 -0.9174 vn 0.3444 -0.0944 0.9341 vn 0.2495 -0.0080 0.9683 vn 0.2006 -0.0429 0.9787 vn 0.2911 -0.0137 0.9566 vn 0.1868 0.0010 0.9824 vn 0.2906 0.0577 0.9551 vn -0.2463 0.6604 0.7094 vn -0.5171 0.4605 0.7215 vn -0.4210 0.6207 0.6614 vn -0.6451 0.3571 0.6755 vn 0.5888 0.0835 -0.8040 vn 0.4071 -0.0067 -0.9134 vn 0.4304 -0.2116 -0.8775 vn 0.4950 -0.1958 -0.8465 vn 0.6846 -0.3274 -0.6512 vn 0.5764 -0.3987 -0.7133 vn 0.2600 0.4315 -0.8638 vn 0.5219 0.4923 -0.6966 vn 0.6951 0.4521 0.5590 vn 0.8497 0.4821 -0.2136 vn 0.3785 0.8192 -0.4309 vn 0.0811 0.8688 -0.4885 vn 0.5047 -0.3868 -0.7718 vn 0.5695 -0.3165 -0.7587 vn 0.6029 -0.2644 -0.7527 vn 0.4738 0.0764 -0.8773 vn 0.4312 0.0372 -0.9015 vn 0.3923 0.0204 -0.9196 vn -0.6439 0.3801 0.6640 vn -0.5153 0.5619 0.6471 vn -0.6428 0.3778 0.6664 vn -0.5261 0.5833 0.6189 vn -0.4915 0.5491 0.6760 vn -0.6301 0.3815 0.6763 vn 0.7298 0.1019 -0.6760 vn 0.7957 0.1692 -0.5815 vn 0.7197 -0.0298 -0.6936 vn 0.7700 0.0004 -0.6381 vn 0.7145 -0.2446 -0.6555 vn 0.6980 -0.2790 -0.6595 vn 0.6820 -0.2978 -0.6680 vn 0.6858 -0.0672 -0.7247 vn 0.6779 0.1052 -0.7276 vn 0.5893 0.4629 -0.6622 vn 0.3352 0.8616 -0.3811 vn 0.6662 0.4371 -0.6042 vn 0.4046 0.8903 -0.2091 vn 0.7963 0.4459 -0.4087 vn -0.7044 0.0879 0.7043 vn -0.7007 -0.1746 0.6917 vn -0.6950 0.0747 0.7151 vn -0.6905 -0.1704 0.7030 vn -0.6204 -0.0780 0.7804 vn -0.5652 0.1037 0.8184 vn 0.6591 -0.2098 -0.7222 vn 0.6130 -0.2106 -0.7615 vn 0.6139 -0.1662 -0.7717 vn 0.6203 0.0783 -0.7804 vn 0.6153 0.1304 -0.7774 vn 0.6114 0.1663 -0.7736 vn -0.3995 0.3920 0.8287 vn -0.3535 0.1773 0.9185 vn 0.8481 0.3071 -0.4317 vn 0.7361 0.1542 -0.6590 vn 0.8372 0.2404 -0.4912 vn 0.7299 0.0137 -0.6834 vn 0.0088 0.1966 -0.9804 vn 0.0726 0.6280 -0.7748 vn -0.1409 0.1647 -0.9762 vn 0.0576 0.6505 -0.7574 vn 0.2851 0.7964 0.5334 vn 0.2343 0.9610 -0.1470 vn 0.4045 0.7307 0.5499 vn 0.4252 0.8715 -0.2442 vn 0.0821 0.0886 0.9927 vn 0.0872 -0.0533 0.9948 vn 0.1500 -0.0809 0.9854 vn 0.2166 -0.0538 0.9748 vn 0.2455 -0.0954 0.9647 vn -0.0841 0.1295 -0.9880 vn -0.1889 0.0863 -0.9782 vn 0.2081 -0.0422 0.9772 vn 0.1878 -0.0412 0.9813 vn -0.1727 0.1613 -0.9717 vn 0.0992 0.5933 -0.7989 vn 0.3113 0.9097 -0.2747 vn 0.3873 0.8502 0.3566 vn 0.3646 0.0763 0.9280 vn 0.2635 0.0615 0.9627 vn -0.2410 0.0787 -0.9673 vn 0.2447 0.0735 0.9668 vn -0.3359 0.1094 0.9355 vn -0.6162 0.2301 0.7532 vn -0.0933 0.3309 0.9391 vn -0.4327 0.4214 0.7970 vn 0.5559 0.1368 -0.8199 vn 0.6495 -0.2302 -0.7247 vn 0.6712 -0.3143 -0.6713 vn -0.1675 0.9621 0.2153 vn -0.1271 0.9721 0.1973 vn 0.2427 0.8811 -0.4059 vn -0.1643 0.9596 0.2283 vn -0.4896 0.6200 0.6131 vn -0.6186 0.3861 0.6843 vn 0.5646 0.0724 -0.8222 vn 0.6604 -0.1175 -0.7416 vn -0.1573 0.9040 0.3974 vn -0.0020 0.9472 0.3205 vn 0.3092 0.9451 -0.1060 vn 0.0954 0.6907 -0.7168 vn -0.5890 -0.0934 0.8027 vn 0.8814 0.4437 -0.1621 vn 0.7389 0.2498 0.6259 vn 0.6371 0.3729 0.6746 vn 0.8402 0.5122 -0.1781 vn 0.2162 0.3143 0.9244 vn 0.0970 0.1278 0.9870 vn 0.1774 0.3883 0.9043 vn 0.3928 0.4654 0.7931 vn 0.3580 0.1836 0.9155 vn 0.4394 0.4722 -0.7642 vn 0.5448 0.4693 0.6949 vn 0.8578 0.4407 -0.2644 vn 0.6181 -0.2387 -0.7489 vn -0.6597 0.1583 0.7346 vn 0.8521 0.4305 0.2977 vn 0.7798 0.5616 0.2765 vn 0.7793 0.6067 0.1567 vn 0.6084 0.7850 0.1164 vn 0.5139 0.8563 -0.0523 vn 0.4481 0.7060 -0.5484 vn 0.7931 0.5938 -0.1355 vn 0.3861 0.8053 -0.4499 vn 0.7030 0.6768 -0.2186 vn 0.8570 0.4694 -0.2129 vn 0.8666 0.4988 -0.0158 vn 0.5229 0.6000 -0.6054 vn 0.2016 0.5926 -0.7798 vn -0.0774 0.6552 0.7515 vn -0.9391 -0.2788 -0.2009 vn -0.9004 -0.2851 -0.3285 vn -0.8565 -0.2499 -0.4517 vn -0.8025 -0.2022 -0.5614 vn -0.7405 -0.1687 -0.6505 vn -0.6416 -0.2553 -0.7234 vn -0.8502 -0.2764 -0.4480 vn -0.7839 -0.2591 -0.5642 vn -0.7051 -0.2579 -0.6606 vn 0.5716 -0.2954 -0.7655 vn 0.0003 0.0072 -1.0000 vn -0.0050 0.0054 -1.0000 vn -0.0037 0.0066 -1.0000 vn -0.0017 0.0071 -1.0000 vn -0.0012 0.0079 -1.0000 vn 0.0004 0.0079 -1.0000 vn 0.0004 0.0087 -1.0000 vn -0.0036 -0.0026 -1.0000 vn 0.0030 -0.0108 -0.9999 vn 0.0017 -0.0105 -0.9999 vn 0.0106 -0.0115 -0.9999 vn 0.0053 -0.0071 -1.0000 vn -0.0051 -0.0041 -1.0000 vn -0.0031 -0.0082 -1.0000 vn -0.0058 -0.0177 -0.9998 vn 0.0003 -0.0215 -0.9998 vn 0.0025 -0.0128 -0.9999 vn -0.0056 -0.0033 -1.0000 vn 0.0003 -0.0129 -0.9999 s 1 f 23/1/40 24/2/41 25/3/42 f 24/2/41 26/4/43 25/3/42 f 26/4/43 27/5/44 25/3/42 f 28/6/45 29/7/46 25/3/42 f 27/5/44 30/8/47 25/3/42 f 30/8/47 31/9/48 25/3/42 f 31/9/48 32/10/49 25/3/42 f 33/11/50 34/12/51 35/13/52 f 35/13/52 34/12/51 36/14/53 f 34/12/51 33/11/50 37/15/54 f 37/15/54 33/11/50 38/16/55 f 39/17/56 40/18/57 38/16/55 f 38/16/55 40/18/57 37/15/54 f 40/18/57 39/17/56 41/19/58 f 41/19/58 39/17/56 42/20/59 f 43/21/60 44/22/61 45/23/62 f 45/23/62 44/22/61 46/24/63 f 44/22/61 43/21/60 47/25/64 f 47/25/64 43/21/60 48/26/65 f 49/27/66 33/11/50 50/28/67 f 50/28/67 33/11/50 35/13/52 f 33/11/50 49/27/66 38/16/55 f 38/16/55 49/27/66 51/29/68 f 49/27/66 52/30/69 51/29/68 f 51/29/68 52/30/69 53/31/70 f 52/30/69 49/27/66 54/32/71 f 54/32/71 49/27/66 50/28/67 f 55/33/72 39/17/56 51/29/68 f 51/29/68 39/17/56 38/16/55 f 39/17/56 55/33/72 42/20/59 f 42/20/59 55/33/72 56/34/73 f 55/33/72 57/35/74 56/34/73 f 56/34/73 57/35/74 58/36/75 f 57/35/74 55/33/72 53/31/70 f 53/31/70 55/33/72 51/29/68 f 59/37/76 44/22/61 60/38/77 f 60/38/77 44/22/61 47/25/64 f 44/22/61 59/37/76 46/24/63 f 46/24/63 59/37/76 61/39/78 f 62/40/79 63/41/80 64/42/81 f 64/42/81 63/41/80 65/43/82 f 63/41/80 62/40/79 46/24/63 f 46/24/63 62/40/79 45/23/62 f 66/44/83 52/30/69 67/45/84 f 67/45/84 52/30/69 54/32/71 f 52/30/69 66/44/83 53/31/70 f 53/31/70 66/44/83 68/46/85 f 66/44/83 69/47/86 68/46/85 f 68/46/85 69/47/86 70/48/87 f 69/47/86 66/44/83 71/49/88 f 71/49/88 66/44/83 67/45/84 f 72/50/89 63/41/80 61/39/78 f 61/39/78 63/41/80 46/24/63 f 63/41/80 72/50/89 65/43/82 f 65/43/82 72/50/89 73/51/90 f 74/52/91 75/53/92 65/43/82 f 65/43/82 75/53/92 64/42/81 f 75/53/92 74/52/91 76/54/93 f 76/54/93 74/52/91 77/55/94 f 78/56/95 74/52/91 73/51/90 f 73/51/90 74/52/91 65/43/82 f 74/52/91 78/56/95 77/55/94 f 77/55/94 78/56/95 79/57/96 f 80/58/97 81/59/98 82/60/99 f 82/60/99 81/59/98 83/61/100 f 83/61/100 81/59/98 77/55/94 f 77/55/94 81/59/98 76/54/93 f 84/62/101 83/61/100 79/57/96 f 79/57/96 83/61/100 77/55/94 f 84/62/101 85/63/102 83/61/100 f 83/61/100 85/63/102 82/60/99 f 86/64/103 87/65/104 88/66/105 f 88/66/105 87/65/104 89/67/106 f 87/65/104 80/58/97 89/67/106 f 89/67/106 80/58/97 82/60/99 f 85/63/102 90/68/107 82/60/99 f 82/60/99 90/68/107 89/67/106 f 90/68/107 91/69/108 89/67/106 f 89/67/106 91/69/108 88/66/105 f 92/70/109 93/71/110 94/72/111 f 94/72/111 93/71/110 95/73/112 f 93/71/110 86/64/103 95/73/112 f 95/73/112 86/64/103 88/66/105 f 91/69/108 96/74/113 88/66/105 f 88/66/105 96/74/113 95/73/112 f 96/74/113 97/75/114 95/73/112 f 95/73/112 97/75/114 94/72/111 f 98/76/115 99/77/116 100/78/117 f 100/78/117 99/77/116 101/79/118 f 100/78/117 101/79/118 102/80/119 f 102/80/119 101/79/118 103/81/120 f 101/79/118 104/82/121 103/81/120 f 103/81/120 104/82/121 105/83/122 f 104/82/121 101/79/118 106/84/123 f 106/84/123 101/79/118 99/77/116 f 107/85/124 108/86/125 109/87/126 f 109/87/126 108/86/125 110/88/127 f 108/86/125 107/85/124 111/89/128 f 111/89/128 107/85/124 112/90/129 f 107/85/124 100/78/117 112/90/129 f 112/90/129 100/78/117 102/80/119 f 100/78/117 107/85/124 98/76/115 f 98/76/115 107/85/124 109/87/126 f 112/90/129 113/91/130 111/89/128 f 111/89/128 113/91/130 114/92/131 f 112/90/129 102/80/119 113/91/130 f 113/91/130 102/80/119 115/93/132 f 116/94/133 117/95/134 118/96/135 f 118/96/135 117/95/134 119/97/136 f 119/98/136 117/99/134 120/100/137 f 120/100/137 117/99/134 121/101/138 f 117/99/134 122/102/139 121/101/138 f 121/101/138 122/102/139 123/103/140 f 117/95/134 116/94/133 122/104/139 f 122/104/139 116/94/133 124/105/141 f 125/106/142 126/107/143 127/108/144 f 127/108/144 126/107/143 128/109/145 f 126/107/143 116/94/133 128/109/145 f 128/109/145 116/94/133 118/96/135 f 116/94/133 126/107/143 124/105/141 f 124/105/141 126/107/143 129/110/146 f 126/107/143 125/106/142 129/110/146 f 129/110/146 125/106/142 130/111/147 f 131/112/148 132/113/149 130/111/147 f 130/111/147 132/113/149 129/110/146 f 132/113/149 133/114/150 129/110/146 f 129/110/146 133/114/150 124/105/141 f 133/114/150 132/113/149 134/115/151 f 134/115/151 132/113/149 135/116/152 f 132/113/149 131/112/148 135/116/152 f 135/116/152 131/112/148 136/117/153 f 137/118/154 138/119/155 139/120/156 f 139/120/156 138/119/155 140/121/157 f 140/121/157 138/119/155 141/122/158 f 141/122/158 138/119/155 142/123/159 f 138/119/155 131/112/148 142/123/159 f 142/123/159 131/112/148 130/111/147 f 138/119/155 137/118/154 131/112/148 f 131/112/148 137/118/154 136/117/153 f 143/124/160 57/35/74 68/46/85 f 68/46/85 57/35/74 53/31/70 f 57/35/74 143/124/160 58/36/75 f 58/36/75 143/124/160 144/125/161 f 143/124/160 145/126/162 144/125/161 f 144/125/161 145/126/162 146/127/163 f 145/126/162 143/124/160 70/48/87 f 70/48/87 143/124/160 68/46/85 f 147/128/164 148/129/165 149/130/166 f 149/130/166 148/129/165 150/131/167 f 148/129/165 151/132/168 150/131/167 f 150/131/167 151/132/168 152/133/169 f 69/47/86 153/134/170 70/48/87 f 70/48/87 153/134/170 154/135/171 f 153/134/170 137/136/154 154/135/171 f 154/135/171 137/136/154 139/137/156 f 137/136/154 153/134/170 136/138/153 f 136/138/153 153/134/170 155/139/172 f 153/134/170 69/47/86 155/139/172 f 155/139/172 69/47/86 71/49/88 f 145/126/162 156/140/173 146/127/163 f 146/127/163 156/140/173 157/141/174 f 156/140/173 158/142/175 157/141/174 f 157/141/174 158/142/175 159/143/176 f 158/142/175 156/140/173 139/137/156 f 139/137/156 156/140/173 154/135/171 f 156/140/173 145/126/162 154/135/171 f 154/135/171 145/126/162 70/48/87 f 160/144/177 161/145/178 162/146/179 f 162/146/179 161/145/178 163/147/180 f 151/132/168 160/144/177 152/133/169 f 152/133/169 160/144/177 162/146/179 f 164/148/181 165/149/182 166/150/183 f 166/150/183 165/149/182 167/151/184 f 168/152/185 169/153/186 110/88/127 f 110/88/127 169/153/186 109/87/126 f 169/153/186 170/154/187 109/87/126 f 109/87/126 170/154/187 98/76/115 f 170/155/187 169/156/186 171/157/188 f 171/157/188 169/156/186 172/158/189 f 169/156/186 168/159/185 172/158/189 f 172/158/189 168/159/185 173/160/190 f 174/161/191 175/162/192 176/163/193 f 176/163/193 175/162/192 177/164/194 f 175/162/192 92/70/109 177/164/194 f 177/164/194 92/70/109 94/72/111 f 178/165/195 179/166/196 106/84/123 f 106/84/123 179/166/196 104/82/121 f 179/166/196 180/167/197 104/82/121 f 104/82/121 180/167/197 105/83/122 f 180/167/197 179/166/196 174/161/191 f 174/161/191 179/166/196 175/162/192 f 179/166/196 178/165/195 175/162/192 f 175/162/192 178/165/195 92/70/109 f 97/75/114 181/168/198 94/72/111 f 94/72/111 181/168/198 177/164/194 f 181/168/198 182/169/199 177/164/194 f 177/164/194 182/169/199 176/163/193 f 183/170/200 115/93/132 103/81/120 f 103/81/120 115/93/132 102/80/119 f 103/81/120 105/83/122 183/170/200 f 183/170/200 105/83/122 184/171/201 f 185/172/202 186/173/203 123/103/140 f 123/103/140 186/173/203 121/101/138 f 186/173/203 187/174/204 121/101/138 f 121/101/138 187/174/204 120/100/137 f 187/174/204 186/173/203 173/160/190 f 173/160/190 186/173/203 172/158/189 f 186/173/203 185/172/202 172/158/189 f 172/158/189 185/172/202 171/157/188 f 158/175/175 188/176/205 159/177/176 f 159/177/176 188/176/205 189/178/206 f 188/176/205 190/179/207 189/178/206 f 189/178/206 190/179/207 191/180/208 f 190/179/207 188/176/205 141/122/158 f 141/122/158 188/176/205 140/121/157 f 188/176/205 158/175/175 140/121/157 f 140/121/157 158/175/175 139/120/156 f 192/181/209 193/182/210 185/172/202 f 185/172/202 193/182/210 171/157/188 f 193/183/210 192/184/209 134/115/151 f 134/115/151 192/184/209 133/114/150 f 192/184/209 122/104/139 133/114/150 f 133/114/150 122/104/139 124/105/141 f 122/102/139 192/181/209 123/103/140 f 123/103/140 192/181/209 185/172/202 f 142/123/159 194/185/211 141/122/158 f 141/122/158 194/185/211 190/179/207 f 190/179/207 194/185/211 191/180/208 f 191/180/208 194/185/211 195/186/212 f 194/185/211 125/106/142 195/186/212 f 195/186/212 125/106/142 127/108/144 f 125/106/142 194/185/211 130/111/147 f 130/111/147 194/185/211 142/123/159 f 182/169/199 196/187/213 176/163/193 f 176/163/193 196/187/213 197/188/214 f 196/187/213 198/189/215 197/188/214 f 197/188/214 198/189/215 199/190/216 f 200/191/217 201/192/218 202/193/219 f 202/193/219 201/192/218 184/171/201 f 201/192/218 200/191/217 203/194/220 f 203/194/220 200/191/217 204/195/221 f 200/191/217 205/196/222 204/195/221 f 204/195/221 205/196/222 206/197/223 f 205/196/222 200/191/217 174/161/191 f 174/161/191 200/191/217 202/193/219 f 206/197/223 205/196/222 199/190/216 f 199/190/216 205/196/222 197/188/214 f 197/188/214 205/196/222 176/163/193 f 176/163/193 205/196/222 174/161/191 f 201/192/218 207/198/224 184/171/201 f 184/171/201 207/198/224 183/170/200 f 207/198/224 208/199/225 183/170/200 f 183/170/200 208/199/225 115/93/132 f 208/199/225 207/198/224 209/200/226 f 209/200/226 207/198/224 210/201/227 f 207/198/224 201/192/218 210/201/227 f 210/201/227 201/192/218 203/194/220 f 211/202/228 212/203/229 48/26/65 f 48/26/65 212/203/229 47/25/64 f 213/204/230 214/205/231 215/206/232 f 215/206/232 214/205/231 216/207/233 f 214/205/231 211/202/228 216/207/233 f 216/207/233 211/202/228 48/26/65 f 217/208/234 218/209/235 36/14/53 f 36/14/53 218/209/235 35/13/52 f 212/203/229 219/210/236 47/25/64 f 47/25/64 219/210/236 60/38/77 f 220/211/237 221/212/238 216/207/233 f 216/207/233 221/212/238 215/206/232 f 221/212/238 220/211/237 222/213/239 f 222/213/239 220/211/237 223/214/240 f 220/211/237 43/21/60 223/214/240 f 223/214/240 43/21/60 45/23/62 f 43/21/60 220/211/237 48/26/65 f 48/26/65 220/211/237 216/207/233 f 218/209/235 224/215/241 35/13/52 f 35/13/52 224/215/241 50/28/67 f 224/215/241 225/216/242 50/28/67 f 50/28/67 225/216/242 54/32/71 f 226/217/243 227/218/244 228/219/245 f 228/219/245 227/218/244 229/220/246 f 227/218/244 230/221/247 229/220/246 f 229/220/246 230/221/247 231/222/248 f 227/223/244 232/224/249 230/225/247 f 230/225/247 232/224/249 233/226/250 f 232/224/249 227/223/244 234/227/251 f 234/227/251 227/223/244 226/228/243 f 230/221/247 235/229/252 231/222/248 f 231/222/248 235/229/252 236/230/253 f 236/230/253 235/229/252 237/231/254 f 237/231/254 235/229/252 238/232/255 f 238/233/255 235/234/252 239/235/256 f 239/235/256 235/234/252 240/236/257 f 235/234/252 230/225/247 240/236/257 f 240/236/257 230/225/247 233/226/250 f 241/237/258 242/238/259 238/232/255 f 238/232/255 242/238/259 237/231/254 f 242/238/259 241/237/258 243/239/260 f 243/239/260 241/237/258 244/240/261 f 244/241/261 241/242/258 245/243/262 f 245/243/262 241/242/258 246/244/263 f 241/242/258 238/233/255 246/244/263 f 246/244/263 238/233/255 239/235/256 f 247/245/264 178/165/195 248/246/265 f 248/246/265 178/165/195 106/84/123 f 178/165/195 247/245/264 92/70/109 f 92/70/109 247/245/264 93/71/110 f 247/245/264 249/247/266 93/71/110 f 93/71/110 249/247/266 86/64/103 f 180/167/197 202/193/219 105/83/122 f 105/83/122 202/193/219 184/171/201 f 202/193/219 180/167/197 174/161/191 f 249/247/266 250/248/267 86/64/103 f 86/64/103 250/248/267 87/65/104 f 250/248/267 251/249/268 87/65/104 f 87/65/104 251/249/268 80/58/97 f 251/249/268 252/250/269 80/58/97 f 80/58/97 252/250/269 81/59/98 f 81/59/98 252/250/269 76/54/93 f 76/54/93 252/250/269 253/251/270 f 254/252/271 75/53/92 253/251/270 f 253/251/270 75/53/92 76/54/93 f 75/53/92 254/252/271 64/42/81 f 64/42/81 254/252/271 255/253/272 f 256/254/273 257/255/274 223/214/240 f 223/214/240 257/255/274 222/213/239 f 256/254/273 62/40/79 255/253/272 f 255/253/272 62/40/79 64/42/81 f 62/40/79 256/254/273 45/23/62 f 45/23/62 256/254/273 223/214/240 f 225/216/242 258/256/275 54/32/71 f 54/32/71 258/256/275 67/45/84 f 258/256/275 259/257/276 67/45/84 f 67/45/84 259/257/276 71/49/88 f 259/257/276 260/258/277 71/49/88 f 71/49/88 260/258/277 155/139/172 f 260/258/277 261/259/278 155/139/172 f 155/139/172 261/259/278 136/138/153 f 262/260/279 263/261/280 135/262/152 f 135/262/152 263/261/280 134/263/151 f 261/259/278 262/260/279 136/138/153 f 136/138/153 262/260/279 135/262/152 f 264/264/281 193/265/210 263/261/280 f 263/261/280 193/265/210 134/263/151 f 193/265/210 264/264/281 171/266/188 f 171/266/188 264/264/281 164/148/181 f 265/267/282 266/268/283 161/145/178 f 161/145/178 266/268/283 163/147/180 f 267/269/284 268/270/285 265/267/282 f 265/267/282 268/270/285 266/268/283 f 166/150/183 170/154/187 164/148/181 f 164/148/181 170/154/187 171/266/188 f 269/271/286 99/77/116 166/150/183 f 99/77/116 98/76/115 166/150/183 f 166/150/183 98/76/115 170/154/187 f 270/272/287 271/273/288 260/258/277 f 260/258/277 271/273/288 261/259/278 f 272/274/289 273/275/290 274/276/291 f 274/276/291 273/275/290 275/277/292 f 276/278/293 272/274/289 277/279/294 f 277/279/294 272/274/289 274/276/291 f 278/280/295 270/272/287 259/257/276 f 259/257/276 270/272/287 260/258/277 f 279/281/296 280/282/297 262/260/279 f 262/260/279 280/282/297 263/261/280 f 281/283/298 282/284/299 283/285/300 f 283/285/300 282/284/299 284/286/301 f 273/275/290 281/283/298 275/277/292 f 275/277/292 281/283/298 283/285/300 f 271/273/288 279/281/296 261/259/278 f 261/259/278 279/281/296 262/260/279 f 256/254/273 285/287/302 257/255/274 f 257/255/274 285/287/302 286/288/303 f 287/289/304 288/290/305 289/291/306 f 289/291/306 288/290/305 290/292/307 f 289/293/306 290/294/307 291/295/308 f 291/295/308 290/294/307 292/296/309 f 285/287/302 256/254/273 293/297/310 f 293/297/310 256/254/273 255/253/272 f 294/298/311 278/280/295 258/256/275 f 258/256/275 278/280/295 259/257/276 f 295/299/312 276/278/293 296/300/313 f 296/300/313 276/278/293 277/279/294 f 254/252/271 297/301/314 255/253/272 f 255/253/272 297/301/314 293/297/310 f 291/295/308 292/296/309 298/302/315 f 298/302/315 292/296/309 299/303/316 f 298/302/315 299/303/316 300/304/317 f 300/304/317 299/303/316 301/305/318 f 297/301/314 254/252/271 302/306/319 f 302/306/319 254/252/271 253/251/270 f 252/250/269 303/307/320 253/251/270 f 253/251/270 303/307/320 302/306/319 f 300/304/317 301/305/318 304/308/321 f 304/308/321 301/305/318 305/309/322 f 306/310/323 304/308/321 307/311/324 f 307/311/324 304/308/321 305/309/322 f 308/312/325 303/307/320 251/249/268 f 251/249/268 303/307/320 252/250/269 f 309/313/326 310/314/327 247/245/264 f 247/245/264 310/314/327 249/247/266 f 311/315/328 312/316/329 313/317/330 f 313/317/330 312/316/329 314/318/331 f 315/319/332 311/320/328 316/321/333 f 316/321/333 311/320/328 313/322/330 f 317/323/334 309/313/326 248/246/265 f 248/246/265 309/313/326 247/245/264 f 318/324/335 308/312/325 250/248/267 f 250/248/267 308/312/325 251/249/268 f 319/325/336 306/310/323 320/326/337 f 320/326/337 306/310/323 307/311/324 f 312/316/329 319/325/336 314/318/331 f 314/318/331 319/325/336 320/326/337 f 310/314/327 318/324/335 249/247/266 f 249/247/266 318/324/335 250/248/267 f 264/264/281 321/327/338 164/148/181 f 164/148/181 321/327/338 165/149/182 f 322/328/339 323/329/340 324/330/341 f 324/330/341 323/329/340 325/331/342 f 324/330/341 325/331/342 282/284/299 f 282/284/299 325/331/342 284/286/301 f 321/327/338 264/264/281 280/282/297 f 280/282/297 264/264/281 263/261/280 f 326/332/343 327/333/344 322/328/339 f 322/328/339 327/333/344 323/329/340 f 328/334/345 329/335/346 326/332/343 f 326/332/343 329/335/346 327/333/344 f 330/336/347 331/337/348 152/133/169 f 152/133/169 331/337/348 150/131/167 f 149/130/166 150/131/167 332/338/349 f 332/338/349 150/131/167 331/337/348 f 333/339/350 334/340/351 163/147/180 f 163/147/180 334/340/351 162/146/179 f 152/133/169 162/146/179 330/336/347 f 330/336/347 162/146/179 334/340/351 f 335/341/352 226/217/243 336/342/353 f 336/342/353 226/217/243 228/219/245 f 226/228/243 335/343/352 234/227/251 f 234/227/251 335/343/352 337/344/354 f 332/338/349 338/345/355 149/130/166 f 149/130/166 338/345/355 147/128/164 f 234/227/251 337/344/354 232/224/249 f 232/224/249 337/344/354 339/346/356 f 232/224/249 339/346/356 233/226/250 f 233/226/250 339/346/356 340/347/357 f 233/226/250 340/347/357 240/236/257 f 240/236/257 340/347/357 341/348/358 f 239/235/256 240/236/257 342/349/359 f 342/349/359 240/236/257 341/348/358 f 343/350/360 344/351/361 245/243/262 f 245/243/262 344/351/361 244/241/261 f 243/239/260 244/240/261 345/352/362 f 345/352/362 244/240/261 344/353/361 f 342/349/359 346/354/363 239/235/256 f 239/235/256 346/354/363 246/244/263 f 245/243/262 246/244/263 343/350/360 f 343/350/360 246/244/263 346/354/363 f 347/355/364 266/268/283 348/356/365 f 348/356/365 266/268/283 268/270/285 f 333/339/350 163/147/180 347/355/364 f 347/355/364 163/147/180 266/268/283 f 348/356/365 268/270/285 349/357/366 f 349/357/366 268/270/285 267/269/284 f 349/357/366 267/269/284 350/358/367 f 350/358/367 267/269/284 351/359/368 f 351/359/368 243/239/260 350/358/367 f 350/358/367 243/239/260 345/352/362 f 265/267/282 352/360/369 267/269/284 f 267/269/284 352/360/369 351/359/368 f 242/238/259 352/360/369 237/231/254 f 237/231/254 352/360/369 353/361/370 f 354/362/371 353/361/370 160/144/177 f 160/144/177 353/361/370 161/145/178 f 354/362/371 355/363/372 236/230/253 f 236/230/253 355/363/372 231/222/248 f 356/364/373 355/363/372 148/129/165 f 148/129/165 355/363/372 151/132/168 f 356/364/373 357/365/374 229/220/246 f 229/220/246 357/365/374 228/219/245 f 357/365/374 147/128/164 358/366/375 f 358/366/375 147/128/164 338/345/355 f 357/365/374 358/366/375 228/219/245 f 228/219/245 358/366/375 336/342/353 f 359/367/376 295/299/312 360/368/377 f 360/368/377 295/299/312 296/300/313 f 361/369/378 362/370/379 286/288/303 f 286/288/303 362/370/379 257/255/274 f 362/370/379 363/371/380 257/255/274 f 257/255/274 363/371/380 222/213/239 f 364/372/381 363/371/380 224/215/241 f 224/215/241 363/371/380 225/216/242 f 221/212/238 364/372/381 215/206/232 f 215/206/232 364/372/381 365/373/382 f 366/374/383 365/373/382 217/208/234 f 217/208/234 365/373/382 218/209/235 f 281/283/298 273/275/290 334/340/351 f 334/340/351 273/275/290 330/336/347 f 282/284/299 281/283/298 333/339/350 f 333/339/350 281/283/298 334/340/351 f 276/278/293 295/299/312 332/338/349 f 332/338/349 295/299/312 338/345/355 f 273/275/290 272/274/289 330/336/347 f 330/336/347 272/274/289 331/337/348 f 272/274/289 276/278/293 331/337/348 f 331/337/348 276/278/293 332/338/349 f 347/355/364 324/330/341 333/339/350 f 333/339/350 324/330/341 282/284/299 f 324/330/341 347/355/364 322/328/339 f 322/328/339 347/355/364 348/356/365 f 295/299/312 359/367/376 338/345/355 f 338/345/355 359/367/376 358/366/375 f 288/290/305 287/289/304 360/368/377 f 360/368/377 287/289/304 359/367/376 f 339/346/356 298/302/315 340/347/357 f 340/347/357 298/302/315 300/304/317 f 298/302/315 339/346/356 291/295/308 f 291/295/308 339/346/356 337/344/354 f 289/291/306 335/341/352 287/289/304 f 287/289/304 335/341/352 336/342/353 f 335/343/352 289/293/306 337/344/354 f 337/344/354 289/293/306 291/295/308 f 304/308/321 341/348/358 300/304/317 f 300/304/317 341/348/358 340/347/357 f 304/308/321 306/310/323 341/348/358 f 341/348/358 306/310/323 342/349/359 f 306/310/323 319/325/336 342/349/359 f 342/349/359 319/325/336 346/354/363 f 319/325/336 312/316/329 346/354/363 f 346/354/363 312/316/329 343/350/360 f 328/334/345 315/319/332 329/335/346 f 329/335/346 315/319/332 316/321/333 f 315/319/332 328/334/345 345/352/362 f 345/352/362 328/334/345 350/358/367 f 312/316/329 311/315/328 343/350/360 f 343/350/360 311/315/328 344/351/361 f 311/320/328 315/319/332 344/353/361 f 344/353/361 315/319/332 345/352/362 f 349/357/366 326/332/343 348/356/365 f 348/356/365 326/332/343 322/328/339 f 59/37/76 367/375/384 61/39/78 f 61/39/78 367/375/384 368/376/385 f 367/375/384 59/37/76 369/377/386 f 369/377/386 59/37/76 60/38/77 f 72/50/89 370/378/387 73/51/90 f 73/51/90 370/378/387 371/379/388 f 370/378/387 72/50/89 368/376/385 f 368/376/385 72/50/89 61/39/78 f 78/56/95 372/380/389 79/57/96 f 79/57/96 372/380/389 373/381/390 f 372/380/389 78/56/95 371/379/388 f 371/379/388 78/56/95 73/51/90 f 374/382/391 375/383/392 84/62/101 f 84/62/101 375/383/392 85/63/102 f 373/381/390 374/382/391 79/57/96 f 79/57/96 374/382/391 84/62/101 f 376/384/393 377/385/394 90/68/107 f 90/68/107 377/385/394 91/69/108 f 375/383/392 376/384/393 85/63/102 f 85/63/102 376/384/393 90/68/107 f 378/386/395 379/387/396 96/74/113 f 96/74/113 379/387/396 97/75/114 f 377/385/394 378/386/395 91/69/108 f 91/69/108 378/386/395 96/74/113 f 380/388/397 381/389/398 181/168/198 f 181/168/198 381/389/398 182/169/199 f 181/168/198 97/75/114 380/388/397 f 380/388/397 97/75/114 379/387/396 f 382/390/399 383/391/400 196/187/213 f 196/187/213 383/391/400 198/189/215 f 196/187/213 182/169/199 382/390/399 f 382/390/399 182/169/199 381/389/398 f 219/210/236 384/392/401 60/38/77 f 60/38/77 384/392/401 369/377/386 f 385/393/402 113/91/130 208/199/225 f 208/199/225 113/91/130 115/93/132 f 113/91/130 385/393/402 114/92/131 f 114/92/131 385/393/402 386/394/403 f 385/393/402 387/395/404 386/394/403 f 386/394/403 387/395/404 388/396/405 f 385/393/402 208/199/225 387/395/404 f 387/395/404 208/199/225 209/200/226 f 389/397/406 390/398/407 209/200/226 f 209/200/226 390/398/407 387/395/404 f 390/398/407 391/399/408 387/395/404 f 387/395/404 391/399/408 388/396/405 f 391/399/408 390/398/407 392/400/409 f 392/400/409 390/398/407 393/401/410 f 390/398/407 389/397/406 393/401/410 f 393/401/410 389/397/406 394/402/411 f 395/403/412 396/404/413 203/194/220 f 203/194/220 396/404/413 210/201/227 f 396/404/413 389/397/406 210/201/227 f 210/201/227 389/397/406 209/200/226 f 389/397/406 396/404/413 394/402/411 f 394/402/411 396/404/413 397/405/414 f 396/404/413 395/403/412 397/405/414 f 397/405/414 395/403/412 398/406/415 f 399/407/416 395/403/412 204/195/221 f 204/195/221 395/403/412 203/194/220 f 395/403/412 399/407/416 398/406/415 f 398/406/415 399/407/416 400/408/417 f 400/408/417 399/407/416 401/409/418 f 401/409/418 399/407/416 402/410/419 f 399/407/416 204/195/221 402/410/419 f 402/410/419 204/195/221 206/197/223 f 401/409/418 402/410/419 403/411/420 f 403/411/420 402/410/419 404/412/421 f 402/410/419 206/197/223 404/412/421 f 404/412/421 206/197/223 199/190/216 f 198/189/215 405/413/422 199/190/216 f 199/190/216 405/413/422 404/412/421 f 404/412/421 405/413/422 403/411/420 f 403/411/420 405/413/422 406/414/423 f 383/391/400 407/415/424 198/189/215 f 198/189/215 407/415/424 405/413/422 f 407/415/424 408/416/425 405/413/422 f 405/413/422 408/416/425 406/414/423 f 166/150/183 167/151/184 269/271/286 f 269/271/286 167/151/184 409/417/426 f 269/271/286 248/246/265 99/77/116 f 99/77/116 248/246/265 106/84/123 f 409/417/426 317/323/334 269/271/286 f 269/271/286 317/323/334 248/246/265 f 365/373/382 366/374/383 215/206/232 f 215/206/232 366/374/383 213/204/230 f 365/373/382 364/372/381 218/209/235 f 218/209/235 364/372/381 224/215/241 f 222/213/239 363/371/380 221/212/238 f 221/212/238 363/371/380 364/372/381 f 363/371/380 362/370/379 225/216/242 f 225/216/242 362/370/379 258/256/275 f 258/256/275 362/370/379 294/298/311 f 294/298/311 362/370/379 361/369/378 f 358/366/375 359/367/376 336/342/353 f 336/342/353 359/367/376 287/289/304 f 357/365/374 356/364/373 147/128/164 f 147/128/164 356/364/373 148/129/165 f 355/363/372 356/364/373 231/222/248 f 231/222/248 356/364/373 229/220/246 f 355/363/372 354/362/371 151/132/168 f 151/132/168 354/362/371 160/144/177 f 237/231/254 353/361/370 236/230/253 f 236/230/253 353/361/370 354/362/371 f 161/145/178 353/361/370 265/267/282 f 265/267/282 353/361/370 352/360/369 f 243/239/260 351/359/368 242/238/259 f 242/238/259 351/359/368 352/360/369 f 350/358/367 328/334/345 349/357/366 f 349/357/366 328/334/345 326/332/343 f 41/19/58 42/20/59 410/418/427 f 410/418/427 42/20/59 411/419/428 f 42/20/59 56/34/73 411/419/428 f 411/419/428 56/34/73 412/420/429 f 56/34/73 58/36/75 412/420/429 f 412/420/429 58/36/75 413/421/430 f 367/375/384 414/422/431 368/376/385 f 368/376/385 414/422/431 415/423/432 f 369/377/386 416/424/433 367/375/384 f 367/375/384 416/424/433 414/422/431 f 370/378/387 417/425/434 371/379/388 f 371/379/388 417/425/434 418/426/435 f 370/378/387 368/376/385 417/425/434 f 417/425/434 368/376/385 415/423/432 f 372/380/389 419/427/436 373/381/390 f 373/381/390 419/427/436 420/428/437 f 371/379/388 418/426/435 372/380/389 f 372/380/389 418/426/435 419/427/436 f 374/382/391 421/429/438 375/383/392 f 375/383/392 421/429/438 422/430/439 f 373/381/390 420/428/437 374/382/391 f 374/382/391 420/428/437 421/429/438 f 376/384/393 423/431/440 377/385/394 f 377/385/394 423/431/440 424/432/441 f 375/383/392 422/430/439 376/384/393 f 376/384/393 422/430/439 423/431/440 f 379/387/396 378/386/395 425/433/442 f 425/433/442 378/386/395 426/434/443 f 377/385/394 424/432/441 378/386/395 f 378/386/395 424/432/441 426/434/443 f 427/435/444 428/436/445 429/437/446 f 429/437/446 428/436/445 430/438/447 f 431/439/448 432/440/449 427/435/444 f 427/435/444 432/440/449 428/436/445 f 433/441/450 434/442/451 431/439/448 f 431/439/448 434/442/451 432/440/449 f 118/96/135 119/97/136 435/443/452 f 435/443/452 119/97/136 436/444/453 f 437/445/454 436/446/453 438/447/455 f 438/447/455 436/446/453 439/448/456 f 127/108/144 128/109/145 440/449/457 f 440/449/457 128/109/145 441/450/458 f 128/109/145 118/96/135 441/450/458 f 441/450/458 118/96/135 435/443/452 f 58/36/75 144/125/161 413/421/430 f 413/421/430 144/125/161 442/451/459 f 443/452/460 444/453/461 445/454/462 f 445/454/462 444/453/461 446/455/463 f 447/456/464 448/457/465 449/458/466 f 449/458/466 448/457/465 450/459/467 f 429/437/446 430/438/447 447/460/464 f 447/460/464 430/438/447 448/461/465 f 380/388/397 451/462/468 381/389/398 f 381/389/398 451/462/468 452/463/469 f 379/387/396 425/433/442 380/388/397 f 380/388/397 425/433/442 451/462/468 f 453/464/470 454/465/471 439/448/456 f 439/448/456 454/465/471 438/447/455 f 449/458/466 450/459/467 453/464/470 f 453/464/470 450/459/467 454/465/471 f 159/177/176 189/178/206 455/466/472 f 455/466/472 189/178/206 456/467/473 f 189/178/206 191/180/208 456/467/473 f 456/467/473 191/180/208 457/468/474 f 191/180/208 195/186/212 457/468/474 f 457/468/474 195/186/212 458/469/475 f 195/186/212 127/108/144 458/469/475 f 458/469/475 127/108/144 440/449/457 f 382/390/399 459/470/476 383/391/400 f 383/391/400 459/470/476 460/471/477 f 381/389/398 452/463/469 382/390/399 f 382/390/399 452/463/469 459/470/476 f 461/472/478 462/473/479 433/441/450 f 433/441/450 462/473/479 434/442/451 f 463/474/480 464/475/481 461/472/478 f 461/472/478 464/475/481 462/473/479 f 384/392/401 465/476/482 369/377/386 f 369/377/386 465/476/482 416/424/433 f 466/477/483 467/478/484 463/474/480 f 463/474/480 467/478/484 464/475/481 f 468/479/485 469/480/486 466/477/483 f 466/477/483 469/480/486 467/478/484 f 408/416/425 407/415/424 470/481/487 f 470/481/487 407/415/424 471/482/488 f 407/415/424 383/391/400 471/482/488 f 471/482/488 383/391/400 460/471/477 f 472/483/489 473/484/490 474/485/491 f 474/485/491 473/484/490 475/486/492 f 476/487/493 472/483/489 477/488/494 f 477/488/494 472/483/489 474/485/491 f 478/489/495 476/487/493 479/490/496 f 479/490/496 476/487/493 477/488/494 f 480/491/497 478/489/495 481/492/498 f 481/492/498 478/489/495 479/490/496 f 482/493/499 483/494/500 484/495/501 f 484/495/501 483/494/500 485/496/502 f 483/494/500 486/497/503 485/496/502 f 485/496/502 486/497/503 487/498/504 f 473/484/490 472/483/489 488/499/505 f 488/499/505 472/483/489 489/500/506 f 472/483/489 476/487/493 489/500/506 f 489/500/506 476/487/493 490/501/507 f 491/502/508 492/503/509 490/501/507 f 490/501/507 492/503/509 489/500/506 f 492/503/509 493/504/510 489/500/506 f 489/500/506 493/504/510 488/499/505 f 476/487/493 478/489/495 490/501/507 f 490/501/507 478/489/495 494/505/511 f 478/489/495 480/491/497 494/505/511 f 494/505/511 480/491/497 495/506/512 f 496/507/513 497/508/514 495/506/512 f 495/506/512 497/508/514 494/505/511 f 497/508/514 491/502/508 494/505/511 f 494/505/511 491/502/508 490/501/507 f 486/497/503 483/494/500 498/509/515 f 498/509/515 483/494/500 499/510/516 f 483/494/500 482/493/499 499/510/516 f 499/510/516 482/493/499 500/511/517 f 501/512/518 502/513/519 503/514/520 f 503/514/520 502/513/519 504/515/521 f 502/513/519 482/493/499 504/515/521 f 504/515/521 482/493/499 484/495/501 f 493/504/510 492/503/509 505/516/522 f 505/516/522 492/503/509 506/517/523 f 492/503/509 491/502/508 506/517/523 f 506/517/523 491/502/508 507/518/524 f 508/519/525 509/520/526 507/518/524 f 507/518/524 509/520/526 506/517/523 f 509/520/526 510/521/527 506/517/523 f 506/517/523 510/521/527 505/516/522 f 482/493/499 502/513/519 500/511/517 f 500/511/517 502/513/519 511/522/528 f 502/513/519 501/512/518 511/522/528 f 511/522/528 501/512/518 512/523/529 f 513/524/530 501/512/518 514/525/531 f 514/525/531 501/512/518 503/514/520 f 515/526/532 513/524/530 516/527/533 f 516/527/533 513/524/530 514/525/531 f 501/512/518 513/524/530 512/523/529 f 512/523/529 513/524/530 517/528/534 f 513/524/530 515/526/532 517/528/534 f 517/528/534 515/526/532 518/529/535 f 519/530/536 520/531/537 521/532/538 f 521/532/538 520/531/537 522/533/539 f 522/533/539 515/526/532 521/532/538 f 521/532/538 515/526/532 516/527/533 f 515/526/532 522/533/539 518/529/535 f 518/529/535 522/533/539 523/534/540 f 520/531/537 524/535/541 522/533/539 f 522/533/539 524/535/541 523/534/540 f 525/536/542 526/537/543 527/538/544 f 527/538/544 526/537/543 528/539/545 f 520/531/537 519/530/536 528/539/545 f 528/539/545 519/530/536 527/538/544 f 524/535/541 520/531/537 529/540/546 f 529/540/546 520/531/537 528/539/545 f 526/537/543 530/541/547 528/539/545 f 528/539/545 530/541/547 529/540/546 f 531/542/548 532/543/549 533/544/550 f 533/544/550 532/543/549 534/545/551 f 526/537/543 525/536/542 534/545/551 f 534/545/551 525/536/542 533/544/550 f 530/541/547 526/537/543 535/546/552 f 535/546/552 526/537/543 534/545/551 f 532/543/549 536/547/553 534/545/551 f 534/545/551 536/547/553 535/546/552 f 537/548/554 538/549/555 539/550/556 f 539/550/556 538/549/555 540/551/557 f 538/549/555 541/552/558 540/551/557 f 540/551/557 541/552/558 542/553/559 f 543/554/560 544/555/561 542/553/559 f 542/553/559 544/555/561 540/551/557 f 544/555/561 545/556/562 540/551/557 f 540/551/557 545/556/562 539/550/556 f 546/557/563 547/558/564 548/559/565 f 548/559/565 547/558/564 549/560/566 f 547/558/564 550/561/567 549/560/566 f 549/560/566 550/561/567 551/562/568 f 541/552/558 538/549/555 551/562/568 f 551/562/568 538/549/555 549/560/566 f 538/549/555 537/548/554 549/560/566 f 549/560/566 537/548/554 548/559/565 f 551/562/568 550/561/567 552/563/569 f 552/563/569 550/561/567 553/564/570 f 551/562/568 552/563/569 541/552/558 f 541/552/558 552/563/569 554/565/571 f 555/566/572 556/567/573 557/568/574 f 557/568/574 556/567/573 558/569/575 f 558/570/575 559/571/576 557/572/574 f 557/572/574 559/571/576 560/573/577 f 561/574/578 562/575/579 560/573/577 f 560/573/577 562/575/579 557/572/574 f 563/576/580 555/566/572 562/577/579 f 562/577/579 555/566/572 557/568/574 f 564/578/581 565/579/582 566/580/583 f 566/580/583 565/579/582 567/581/584 f 556/567/573 555/566/572 567/581/584 f 567/581/584 555/566/572 566/580/583 f 555/566/572 563/576/580 566/580/583 f 566/580/583 563/576/580 568/582/585 f 569/583/586 564/578/581 568/582/585 f 568/582/585 564/578/581 566/580/583 f 570/584/587 569/583/586 571/585/588 f 571/585/588 569/583/586 568/582/585 f 563/576/580 572/586/589 568/582/585 f 568/582/585 572/586/589 571/585/588 f 572/586/589 573/587/590 571/585/588 f 571/585/588 573/587/590 574/588/591 f 575/589/592 570/584/587 574/588/591 f 574/588/591 570/584/587 571/585/588 f 576/590/593 577/591/594 578/592/595 f 578/592/595 577/591/594 579/593/596 f 579/593/596 580/594/597 578/592/595 f 578/592/595 580/594/597 581/595/598 f 569/583/586 570/584/587 581/595/598 f 581/595/598 570/584/587 578/592/595 f 575/589/592 576/590/593 570/584/587 f 570/584/587 576/590/593 578/592/595 f 491/502/508 497/508/514 507/518/524 f 507/518/524 497/508/514 582/596/599 f 497/508/514 496/507/513 582/596/599 f 582/596/599 496/507/513 583/597/600 f 584/598/601 585/599/602 583/597/600 f 583/597/600 585/599/602 582/596/599 f 585/599/602 508/519/525 582/596/599 f 582/596/599 508/519/525 507/518/524 f 586/600/603 587/601/604 588/602/605 f 588/602/605 587/601/604 589/603/606 f 590/604/607 591/605/608 589/603/606 f 589/603/606 591/605/608 588/602/605 f 509/520/526 508/519/525 592/606/609 f 592/606/609 508/519/525 593/607/610 f 577/608/594 576/609/593 593/607/610 f 593/607/610 576/609/593 592/606/609 f 576/609/593 575/610/592 592/606/609 f 592/606/609 575/610/592 594/611/611 f 510/521/527 509/520/526 594/611/611 f 594/611/611 509/520/526 592/606/609 f 585/599/602 584/598/601 595/612/612 f 595/612/612 584/598/601 596/613/613 f 597/614/614 598/615/615 596/613/613 f 596/613/613 598/615/615 595/612/612 f 598/615/615 577/608/594 595/612/612 f 595/612/612 577/608/594 593/607/610 f 508/519/525 585/599/602 593/607/610 f 593/607/610 585/599/602 595/612/612 f 599/616/616 600/617/617 601/618/618 f 601/618/618 600/617/617 602/619/619 f 591/605/608 590/604/607 602/619/619 f 602/619/619 590/604/607 601/618/618 f 603/620/620 604/621/621 605/622/622 f 605/622/622 604/621/621 606/623/623 f 607/624/624 546/557/563 608/625/625 f 608/625/625 546/557/563 548/559/565 f 537/548/554 609/626/626 548/559/565 f 548/559/565 609/626/626 608/625/625 f 609/627/626 610/628/627 608/629/625 f 608/629/625 610/628/627 611/630/628 f 612/631/629 607/632/624 611/630/628 f 611/630/628 607/632/624 608/629/625 f 613/633/630 614/634/631 615/635/632 f 615/635/632 614/634/631 616/636/633 f 532/543/549 531/542/548 616/636/633 f 616/636/633 531/542/548 615/635/632 f 617/637/634 545/556/562 618/638/635 f 618/638/635 545/556/562 544/555/561 f 543/554/560 619/639/636 544/555/561 f 544/555/561 619/639/636 618/638/635 f 619/639/636 613/633/630 618/638/635 f 618/638/635 613/633/630 615/635/632 f 531/542/548 617/637/634 615/635/632 f 615/635/632 617/637/634 618/638/635 f 536/547/553 532/543/549 620/640/637 f 620/640/637 532/543/549 616/636/633 f 614/634/631 621/641/638 616/636/633 f 616/636/633 621/641/638 620/640/637 f 622/642/639 542/553/559 554/565/571 f 554/565/571 542/553/559 541/552/558 f 623/643/640 543/554/560 622/642/639 f 622/642/639 543/554/560 542/553/559 f 624/644/641 561/574/578 625/645/642 f 625/645/642 561/574/578 560/573/577 f 559/571/576 626/646/643 560/573/577 f 560/573/577 626/646/643 625/645/642 f 626/646/643 612/631/629 625/645/642 f 625/645/642 612/631/629 611/630/628 f 610/628/627 624/644/641 611/630/628 f 611/630/628 624/644/641 625/645/642 f 598/647/615 597/648/614 627/649/644 f 627/649/644 597/648/614 628/650/645 f 629/651/646 630/652/647 628/650/645 f 628/650/645 630/652/647 627/649/644 f 630/652/647 580/594/597 627/649/644 f 627/649/644 580/594/597 579/593/596 f 577/591/594 598/647/615 579/593/596 f 579/593/596 598/647/615 627/649/644 f 624/644/641 610/628/627 631/653/648 f 631/653/648 610/628/627 632/654/649 f 573/587/590 572/586/589 632/655/649 f 632/655/649 572/586/589 631/656/648 f 563/576/580 562/577/579 572/586/589 f 572/586/589 562/577/579 631/656/648 f 562/575/579 561/574/578 631/653/648 f 631/653/648 561/574/578 624/644/641 f 580/594/597 630/652/647 581/595/598 f 581/595/598 630/652/647 633/657/650 f 630/652/647 629/651/646 633/657/650 f 633/657/650 629/651/646 634/658/651 f 565/579/582 564/578/581 634/658/651 f 634/658/651 564/578/581 633/657/650 f 564/578/581 569/583/586 633/657/650 f 633/657/650 569/583/586 581/595/598 f 621/641/638 614/634/631 635/659/652 f 635/659/652 614/634/631 636/660/653 f 637/661/654 638/662/655 636/660/653 f 636/660/653 638/662/655 635/659/652 f 623/643/640 639/663/656 640/664/657 f 640/664/657 639/663/656 641/665/658 f 639/663/656 642/666/659 641/665/658 f 641/665/658 642/666/659 643/667/660 f 643/667/660 644/668/661 641/665/658 f 641/665/658 644/668/661 645/669/662 f 645/669/662 613/633/630 641/665/658 f 641/665/658 613/633/630 640/664/657 f 644/668/661 637/661/654 645/669/662 f 645/669/662 637/661/654 636/660/653 f 614/634/631 613/633/630 636/660/653 f 636/660/653 613/633/630 645/669/662 f 639/663/656 623/643/640 646/670/663 f 646/670/663 623/643/640 622/642/639 f 554/565/571 647/671/664 622/642/639 f 622/642/639 647/671/664 646/670/663 f 647/671/664 648/672/665 646/670/663 f 646/670/663 648/672/665 649/673/666 f 642/666/659 639/663/656 649/673/666 f 649/673/666 639/663/656 646/670/663 f 486/497/503 650/674/667 487/498/504 f 487/498/504 650/674/667 651/675/668 f 652/676/669 653/677/670 654/678/671 f 654/678/671 653/677/670 655/679/672 f 487/498/504 651/675/668 655/679/672 f 655/679/672 651/675/668 654/678/671 f 473/484/490 656/680/673 475/486/492 f 475/486/492 656/680/673 657/681/674 f 650/674/667 486/497/503 658/682/675 f 658/682/675 486/497/503 498/509/515 f 653/677/670 659/683/676 655/679/672 f 655/679/672 659/683/676 660/684/677 f 659/683/676 661/685/678 660/684/677 f 660/684/677 661/685/678 662/686/679 f 484/495/501 485/496/502 662/686/679 f 662/686/679 485/496/502 660/684/677 f 485/496/502 487/498/504 660/684/677 f 660/684/677 487/498/504 655/679/672 f 656/680/673 473/484/490 663/687/680 f 663/687/680 473/484/490 488/499/505 f 493/504/510 664/688/681 488/499/505 f 488/499/505 664/688/681 663/687/680 f 665/689/682 666/690/683 667/691/684 f 667/691/684 666/690/683 668/692/685 f 669/693/686 670/694/687 668/692/685 f 668/692/685 670/694/687 667/691/684 f 671/695/688 672/696/689 670/697/687 f 670/697/687 672/696/689 667/698/684 f 672/696/689 673/699/690 667/698/684 f 667/698/684 673/699/690 665/700/682 f 670/694/687 669/693/686 674/701/691 f 674/701/691 669/693/686 675/702/692 f 676/703/693 677/704/694 675/702/692 f 675/702/692 677/704/694 674/701/691 f 677/705/694 678/706/695 674/707/691 f 674/707/691 678/706/695 679/708/696 f 679/708/696 671/695/688 674/707/691 f 674/707/691 671/695/688 670/697/687 f 677/704/694 676/703/693 680/709/697 f 680/709/697 676/703/693 681/710/698 f 681/710/698 682/711/699 680/709/697 f 680/709/697 682/711/699 683/712/700 f 683/713/700 684/714/701 680/715/697 f 680/715/697 684/714/701 685/716/702 f 678/706/695 677/705/694 685/716/702 f 685/716/702 677/705/694 680/715/697 f 545/556/562 617/637/634 686/717/703 f 686/717/703 617/637/634 687/718/704 f 617/637/634 531/542/548 687/718/704 f 687/718/704 531/542/548 533/544/550 f 525/536/542 688/719/705 533/544/550 f 533/544/550 688/719/705 687/718/704 f 543/554/560 623/643/640 619/639/636 f 619/639/636 623/643/640 640/664/657 f 640/664/657 613/633/630 619/639/636 f 688/719/705 525/536/542 689/720/706 f 689/720/706 525/536/542 527/538/544 f 519/530/536 690/721/707 527/538/544 f 527/538/544 690/721/707 689/720/706 f 690/721/707 519/530/536 691/722/708 f 691/722/708 519/530/536 521/532/538 f 521/532/538 516/527/533 691/722/708 f 691/722/708 516/527/533 692/723/709 f 516/527/533 514/525/531 692/723/709 f 692/723/709 514/525/531 693/724/710 f 514/525/531 503/514/520 693/724/710 f 693/724/710 503/514/520 694/725/711 f 661/685/678 695/726/712 662/686/679 f 662/686/679 695/726/712 696/727/713 f 503/514/520 504/515/521 694/725/711 f 694/725/711 504/515/521 696/727/713 f 504/515/521 484/495/501 696/727/713 f 696/727/713 484/495/501 662/686/679 f 664/688/681 493/504/510 697/728/714 f 697/728/714 493/504/510 505/516/522 f 510/521/527 698/729/715 505/516/522 f 505/516/522 698/729/715 697/728/714 f 698/729/715 510/521/527 699/730/716 f 699/730/716 510/521/527 594/611/611 f 575/610/592 700/731/717 594/611/611 f 594/611/611 700/731/717 699/730/716 f 573/732/590 701/733/718 574/734/591 f 574/734/591 701/733/718 702/735/719 f 700/731/717 575/610/592 702/735/719 f 702/735/719 575/610/592 574/734/591 f 573/732/590 632/736/649 701/733/718 f 701/733/718 632/736/649 703/737/720 f 632/736/649 610/738/627 703/737/720 f 703/737/720 610/738/627 606/623/623 f 599/616/616 704/739/721 600/617/617 f 600/617/617 704/739/721 705/740/722 f 706/741/723 705/740/722 707/742/724 f 707/742/724 705/740/722 704/739/721 f 610/738/627 609/626/626 606/623/623 f 606/623/623 609/626/626 605/622/622 f 708/743/725 605/622/622 539/550/556 f 605/622/622 609/626/626 539/550/556 f 609/626/626 537/548/554 539/550/556 f 700/731/717 709/744/726 699/730/716 f 699/730/716 709/744/726 710/745/727 f 711/746/728 712/747/729 713/748/730 f 713/748/730 712/747/729 714/749/731 f 715/750/732 716/751/733 711/746/728 f 711/746/728 716/751/733 712/747/729 f 717/752/734 698/729/715 710/745/727 f 710/745/727 698/729/715 699/730/716 f 701/733/718 718/753/735 702/735/719 f 702/735/719 718/753/735 719/754/736 f 720/755/737 721/756/738 722/757/739 f 722/757/739 721/756/738 723/758/740 f 713/748/730 714/749/731 720/755/737 f 720/755/737 714/749/731 721/756/738 f 709/744/726 700/731/717 719/754/736 f 719/754/736 700/731/717 702/735/719 f 696/727/713 695/726/712 724/759/741 f 724/759/741 695/726/712 725/760/742 f 726/761/743 727/762/744 728/763/745 f 728/763/745 727/762/744 729/764/746 f 727/765/744 730/766/747 729/767/746 f 729/767/746 730/766/747 731/768/748 f 694/725/711 696/727/713 732/769/749 f 732/769/749 696/727/713 724/759/741 f 698/729/715 717/752/734 697/728/714 f 697/728/714 717/752/734 733/770/750 f 734/771/751 735/772/752 715/750/732 f 715/750/732 735/772/752 716/751/733 f 693/724/710 694/725/711 736/773/753 f 736/773/753 694/725/711 732/769/749 f 730/766/747 737/774/754 731/768/748 f 731/768/748 737/774/754 738/775/755 f 737/774/754 739/776/756 738/775/755 f 738/775/755 739/776/756 740/777/757 f 692/723/709 693/724/710 741/778/758 f 741/778/758 693/724/710 736/773/753 f 691/722/708 692/723/709 742/779/759 f 742/779/759 692/723/709 741/778/758 f 739/776/756 743/780/760 740/777/757 f 740/777/757 743/780/760 744/781/761 f 745/782/762 746/783/763 743/780/760 f 743/780/760 746/783/763 744/781/761 f 747/784/764 690/721/707 742/779/759 f 742/779/759 690/721/707 691/722/708 f 688/719/705 748/785/765 687/718/704 f 687/718/704 748/785/765 749/786/766 f 750/787/767 751/788/768 752/789/769 f 752/789/769 751/788/768 753/790/770 f 754/791/771 755/792/772 750/793/767 f 750/793/767 755/792/772 751/794/768 f 756/795/773 686/717/703 749/786/766 f 749/786/766 686/717/703 687/718/704 f 690/721/707 747/784/764 689/720/706 f 689/720/706 747/784/764 757/796/774 f 758/797/775 759/798/776 745/782/762 f 745/782/762 759/798/776 746/783/763 f 752/789/769 753/790/770 758/797/775 f 758/797/775 753/790/770 759/798/776 f 748/785/765 688/719/705 757/796/774 f 757/796/774 688/719/705 689/720/706 f 703/737/720 606/623/623 760/799/777 f 760/799/777 606/623/623 604/621/621 f 761/800/778 762/801/779 763/802/780 f 763/802/780 762/801/779 764/803/781 f 762/801/779 722/757/739 764/803/781 f 764/803/781 722/757/739 723/758/740 f 701/733/718 703/737/720 718/753/735 f 718/753/735 703/737/720 760/799/777 f 765/804/782 761/800/778 766/805/783 f 766/805/783 761/800/778 763/802/780 f 767/806/784 765/804/782 768/807/785 f 768/807/785 765/804/782 766/805/783 f 769/808/786 590/604/607 770/809/787 f 770/809/787 590/604/607 589/603/606 f 770/809/787 589/603/606 771/810/788 f 771/810/788 589/603/606 587/601/604 f 772/811/789 599/616/616 773/812/790 f 773/812/790 599/616/616 601/618/618 f 773/812/790 601/618/618 769/808/786 f 769/808/786 601/618/618 590/604/607 f 774/813/791 775/814/792 665/689/682 f 665/689/682 775/814/792 666/690/683 f 665/700/682 673/699/690 774/815/791 f 774/815/791 673/699/690 776/816/793 f 771/810/788 587/601/604 777/817/794 f 777/817/794 587/601/604 586/600/603 f 778/818/795 776/816/793 672/696/689 f 672/696/689 776/816/793 673/699/690 f 672/696/689 671/695/688 778/818/795 f 778/818/795 671/695/688 779/819/796 f 780/820/797 779/819/796 679/708/696 f 679/708/696 779/819/796 671/695/688 f 780/820/797 679/708/696 781/821/798 f 781/821/798 679/708/696 678/706/695 f 782/822/799 684/714/701 783/823/800 f 783/823/800 684/714/701 683/713/700 f 783/824/800 683/712/700 784/825/801 f 784/825/801 683/712/700 682/711/699 f 781/821/798 678/706/695 785/826/802 f 785/826/802 678/706/695 685/716/702 f 785/826/802 685/716/702 782/822/799 f 782/822/799 685/716/702 684/714/701 f 786/827/803 787/828/804 704/739/721 f 704/739/721 787/828/804 707/742/724 f 704/739/721 599/616/616 786/827/803 f 786/827/803 599/616/616 772/811/789 f 787/828/804 788/829/805 707/742/724 f 707/742/724 788/829/805 706/741/723 f 788/829/805 789/830/806 706/741/723 f 706/741/723 789/830/806 790/831/807 f 784/825/801 682/711/699 789/830/806 f 789/830/806 682/711/699 790/831/807 f 705/740/722 706/741/723 791/832/808 f 791/832/808 706/741/723 790/831/807 f 676/703/693 792/833/809 681/710/698 f 681/710/698 792/833/809 791/832/808 f 600/617/617 792/833/809 602/619/619 f 602/619/619 792/833/809 793/834/810 f 669/693/686 794/835/811 675/702/692 f 675/702/692 794/835/811 793/834/810 f 591/605/608 794/835/811 588/602/605 f 588/602/605 794/835/811 795/836/812 f 666/690/683 796/837/813 668/692/685 f 668/692/685 796/837/813 795/836/812 f 777/817/794 586/600/603 797/838/814 f 797/838/814 586/600/603 796/837/813 f 775/814/792 797/838/814 666/690/683 f 666/690/683 797/838/814 796/837/813 f 735/772/752 734/771/751 798/839/815 f 798/839/815 734/771/751 799/840/816 f 695/726/712 800/841/817 725/760/742 f 725/760/742 800/841/817 801/842/818 f 661/685/678 802/843/819 695/726/712 f 695/726/712 802/843/819 800/841/817 f 664/688/681 802/843/819 663/687/680 f 663/687/680 802/843/819 803/844/820 f 659/683/676 653/677/670 803/844/820 f 803/844/820 653/677/670 804/845/821 f 656/680/673 804/845/821 657/681/674 f 657/681/674 804/845/821 805/846/822 f 769/808/786 713/748/730 773/812/790 f 773/812/790 713/748/730 720/755/737 f 722/757/739 772/811/789 720/755/737 f 720/755/737 772/811/789 773/812/790 f 715/750/732 771/810/788 734/771/751 f 734/771/751 771/810/788 777/817/794 f 713/748/730 769/808/786 711/746/728 f 711/746/728 769/808/786 770/809/787 f 771/810/788 715/750/732 770/809/787 f 770/809/787 715/750/732 711/746/728 f 786/827/803 772/811/789 762/801/779 f 762/801/779 772/811/789 722/757/739 f 787/828/804 786/827/803 761/800/778 f 761/800/778 786/827/803 762/801/779 f 734/771/751 777/817/794 799/840/816 f 799/840/816 777/817/794 797/838/814 f 799/840/816 726/761/743 798/839/815 f 798/839/815 726/761/743 728/763/745 f 778/818/795 779/819/796 737/774/754 f 737/774/754 779/819/796 739/776/756 f 776/816/793 778/818/795 730/766/747 f 730/766/747 778/818/795 737/774/754 f 775/814/792 774/813/791 726/761/743 f 726/761/743 774/813/791 727/762/744 f 774/815/791 776/816/793 727/765/744 f 727/765/744 776/816/793 730/766/747 f 779/819/796 780/820/797 739/776/756 f 739/776/756 780/820/797 743/780/760 f 781/821/798 745/782/762 780/820/797 f 780/820/797 745/782/762 743/780/760 f 745/782/762 781/821/798 758/797/775 f 758/797/775 781/821/798 785/826/802 f 782/822/799 752/789/769 785/826/802 f 785/826/802 752/789/769 758/797/775 f 767/806/784 768/807/785 754/791/771 f 754/791/771 768/807/785 755/792/772 f 754/791/771 784/825/801 767/806/784 f 767/806/784 784/825/801 789/830/806 f 752/789/769 782/822/799 750/787/767 f 750/787/767 782/822/799 783/823/800 f 784/825/801 754/791/771 783/824/800 f 783/824/800 754/791/771 750/793/767 f 788/829/805 787/828/804 765/804/782 f 765/804/782 787/828/804 761/800/778 f 806/847/823 480/491/497 807/848/824 f 807/848/824 480/491/497 481/492/498 f 480/491/497 806/847/823 495/506/512 f 495/506/512 806/847/823 808/849/825 f 809/850/826 496/507/513 808/849/825 f 808/849/825 496/507/513 495/506/512 f 499/510/516 500/511/517 414/422/431 f 414/422/431 500/511/517 415/423/432 f 498/509/515 499/510/516 416/424/433 f 416/424/433 499/510/516 414/422/431 f 511/522/528 512/523/529 417/425/434 f 417/425/434 512/523/529 418/426/435 f 500/511/517 511/522/528 415/423/432 f 415/423/432 511/522/528 417/425/434 f 518/529/535 420/428/437 517/528/534 f 517/528/534 420/428/437 419/427/436 f 512/523/529 517/528/534 418/426/435 f 418/426/435 517/528/534 419/427/436 f 524/535/541 422/430/439 523/534/540 f 523/534/540 422/430/439 421/429/438 f 420/428/437 518/529/535 421/429/438 f 421/429/438 518/529/535 523/534/540 f 530/541/547 424/432/441 529/540/546 f 529/540/546 424/432/441 423/431/440 f 422/430/439 524/535/541 423/431/440 f 423/431/440 524/535/541 529/540/546 f 536/547/553 425/433/442 535/546/552 f 535/546/552 425/433/442 426/434/443 f 424/432/441 530/541/547 426/434/443 f 426/434/443 530/541/547 535/546/552 f 547/558/564 546/557/563 428/436/445 f 428/436/445 546/557/563 430/438/447 f 550/561/567 547/558/564 432/440/449 f 432/440/449 547/558/564 428/436/445 f 550/561/567 432/440/449 553/564/570 f 553/564/570 432/440/449 434/442/451 f 556/567/573 810/851/827 558/569/575 f 558/569/575 810/851/827 437/852/454 f 438/447/455 559/571/576 437/445/454 f 437/445/454 559/571/576 558/570/575 f 565/579/582 811/853/828 567/581/584 f 567/581/584 811/853/828 812/854/829 f 810/851/827 556/567/573 812/854/829 f 812/854/829 556/567/573 567/581/584 f 496/507/513 809/850/826 583/597/600 f 583/597/600 809/850/826 813/855/830 f 443/452/460 584/598/601 813/855/830 f 813/855/830 584/598/601 583/597/600 f 584/598/601 443/452/460 596/613/613 f 596/613/613 443/452/460 445/454/462 f 814/856/831 597/614/614 445/454/462 f 445/454/462 597/614/614 596/613/613 f 612/631/629 450/459/467 607/632/624 f 607/632/624 450/459/467 448/457/465 f 430/438/447 546/557/563 448/461/465 f 448/461/465 546/557/563 607/624/624 f 621/641/638 452/463/469 620/640/637 f 620/640/637 452/463/469 451/462/468 f 620/640/637 451/462/468 536/547/553 f 536/547/553 451/462/468 425/433/442 f 626/646/643 559/571/576 454/465/471 f 454/465/471 559/571/576 438/447/455 f 612/631/629 626/646/643 450/459/467 f 450/459/467 626/646/643 454/465/471 f 597/648/614 814/857/831 628/650/645 f 628/650/645 814/857/831 815/858/832 f 816/859/833 629/651/646 815/858/832 f 815/858/832 629/651/646 628/650/645 f 629/651/646 816/859/833 634/658/651 f 634/658/651 816/859/833 817/860/834 f 811/853/828 565/579/582 817/860/834 f 817/860/834 565/579/582 634/658/651 f 638/662/655 460/471/477 635/659/652 f 635/659/652 460/471/477 459/470/476 f 635/659/652 459/470/476 621/641/638 f 621/641/638 459/470/476 452/463/469 f 553/564/570 434/442/451 818/861/835 f 818/861/835 434/442/451 462/473/479 f 464/475/481 819/862/836 462/473/479 f 462/473/479 819/862/836 818/861/835 f 658/682/675 498/509/515 465/476/482 f 465/476/482 498/509/515 416/424/433 f 554/565/571 552/563/569 647/671/664 f 647/671/664 552/563/569 820/863/837 f 552/563/569 553/564/570 820/863/837 f 820/863/837 553/564/570 818/861/835 f 819/862/836 821/864/838 818/861/835 f 818/861/835 821/864/838 820/863/837 f 821/864/838 648/672/665 820/863/837 f 820/863/837 648/672/665 647/671/664 f 819/862/836 464/475/481 822/865/839 f 822/865/839 464/475/481 467/478/484 f 823/866/840 822/865/839 469/480/486 f 469/480/486 822/865/839 467/478/484 f 648/672/665 821/864/838 824/867/841 f 824/867/841 821/864/838 825/868/842 f 821/864/838 819/862/836 825/868/842 f 825/868/842 819/862/836 822/865/839 f 823/866/840 826/869/843 822/865/839 f 822/865/839 826/869/843 825/868/842 f 827/870/844 824/867/841 826/869/843 f 826/869/843 824/867/841 825/868/842 f 828/871/845 642/666/659 829/872/846 f 829/872/846 642/666/659 649/673/666 f 649/673/666 648/672/665 829/872/846 f 829/872/846 648/672/665 824/867/841 f 827/870/844 830/873/847 824/867/841 f 824/867/841 830/873/847 829/872/846 f 830/873/847 831/874/848 829/872/846 f 829/872/846 831/874/848 828/871/845 f 643/667/660 642/666/659 832/875/849 f 832/875/849 642/666/659 828/871/845 f 831/874/848 833/876/850 828/871/845 f 828/871/845 833/876/850 832/875/849 f 833/876/850 834/877/851 832/875/849 f 832/875/849 834/877/851 835/878/852 f 644/668/661 643/667/660 835/878/852 f 835/878/852 643/667/660 832/875/849 f 834/877/851 836/879/853 835/878/852 f 835/878/852 836/879/853 837/880/854 f 637/661/654 644/668/661 837/880/854 f 837/880/854 644/668/661 835/878/852 f 638/662/655 637/661/654 838/881/855 f 838/881/855 637/661/654 837/880/854 f 836/879/853 839/882/856 837/880/854 f 837/880/854 839/882/856 838/881/855 f 460/471/477 638/662/655 471/482/488 f 471/482/488 638/662/655 838/881/855 f 839/882/856 470/481/487 838/881/855 f 838/881/855 470/481/487 471/482/488 f 840/883/857 841/884/858 842/885/859 f 842/885/859 841/884/858 843/886/860 f 844/887/861 845/888/862 840/883/857 f 840/883/857 845/888/862 841/884/858 f 846/889/863 847/890/864 844/887/861 f 844/887/861 847/890/864 845/888/862 f 848/891/865 849/892/866 846/889/863 f 846/889/863 849/892/866 847/890/864 f 850/893/867 851/894/868 848/891/865 f 848/891/865 851/894/868 849/892/866 f 852/895/869 853/896/870 850/893/867 f 850/893/867 853/896/870 851/894/868 f 854/897/871 855/898/872 852/895/869 f 852/895/869 855/898/872 853/896/870 f 856/899/873 857/900/874 854/897/871 f 854/897/871 857/900/874 855/898/872 f 856/899/873 858/901/875 857/900/874 f 857/900/874 858/901/875 859/902/876 f 858/901/875 860/903/877 859/902/876 f 859/902/876 860/903/877 861/904/878 f 862/905/879 863/906/880 860/903/877 f 860/903/877 863/906/880 861/904/878 f 864/907/881 865/908/882 862/905/879 f 862/905/879 865/908/882 863/906/880 f 866/909/883 867/910/884 868/911/885 f 868/911/885 867/910/884 869/912/886 f 841/884/858 870/913/887 843/886/860 f 843/886/860 870/913/887 871/914/888 f 845/888/862 872/915/889 841/884/858 f 841/884/858 872/915/889 870/913/887 f 847/890/864 873/916/890 845/888/862 f 845/888/862 873/916/890 872/915/889 f 849/892/866 874/917/891 847/890/864 f 847/890/864 874/917/891 873/916/890 f 851/894/868 875/918/892 849/892/866 f 849/892/866 875/918/892 874/917/891 f 853/896/870 876/919/893 851/894/868 f 851/894/868 876/919/893 875/918/892 f 855/898/872 877/920/894 853/896/870 f 853/896/870 877/920/894 876/919/893 f 857/900/874 878/921/895 855/898/872 f 855/898/872 878/921/895 877/920/894 f 857/900/874 859/902/876 878/921/895 f 878/921/895 859/902/876 879/922/896 f 859/902/876 861/904/878 879/922/896 f 879/922/896 861/904/878 880/923/897 f 863/906/880 881/924/898 861/904/878 f 861/904/878 881/924/898 880/923/897 f 882/925/899 865/908/882 883/926/900 f 883/926/900 865/908/882 869/912/886 f 870/913/887 884/927/901 871/914/888 f 871/914/888 884/927/901 885/928/902 f 872/915/889 886/929/903 870/913/887 f 870/913/887 886/929/903 884/927/901 f 873/916/890 887/930/904 872/915/889 f 872/915/889 887/930/904 886/929/903 f 874/917/891 888/931/905 873/916/890 f 873/916/890 888/931/905 887/930/904 f 875/918/892 889/932/906 874/917/891 f 874/917/891 889/932/906 888/931/905 f 876/919/893 890/933/907 875/918/892 f 875/918/892 890/933/907 889/932/906 f 877/920/894 891/934/908 876/919/893 f 876/919/893 891/934/908 890/933/907 f 878/921/895 892/935/909 877/920/894 f 877/920/894 892/935/909 891/934/908 f 878/921/895 879/922/896 892/935/909 f 892/935/909 879/922/896 893/936/910 f 880/923/897 894/937/911 879/922/896 f 879/922/896 894/937/911 893/936/910 f 881/924/898 895/938/912 880/923/897 f 880/923/897 895/938/912 894/937/911 f 896/939/913 882/925/899 897/940/914 f 897/940/914 882/925/899 883/926/900 f 885/928/902 884/927/901 898/941/915 f 898/941/915 884/927/901 899/942/916 f 886/929/903 900/943/917 884/927/901 f 884/927/901 900/943/917 899/942/916 f 887/930/904 901/944/918 886/929/903 f 886/929/903 901/944/918 900/943/917 f 888/931/905 902/945/919 887/930/904 f 887/930/904 902/945/919 901/944/918 f 889/932/906 903/946/920 888/931/905 f 888/931/905 903/946/920 902/945/919 f 890/933/907 904/947/921 889/932/906 f 889/932/906 904/947/921 903/946/920 f 891/934/908 905/948/922 890/933/907 f 890/933/907 905/948/922 904/947/921 f 892/935/909 906/949/923 891/934/908 f 891/934/908 906/949/923 905/948/922 f 892/935/909 893/936/910 906/949/923 f 906/949/923 893/936/910 907/950/924 f 894/937/911 908/951/925 893/936/910 f 893/936/910 908/951/925 907/950/924 f 895/938/912 909/952/926 894/937/911 f 894/937/911 909/952/926 908/951/925 f 910/953/927 896/939/913 911/954/928 f 911/954/928 896/939/913 897/940/914 f 898/941/915 899/942/916 912/955/929 f 912/955/929 899/942/916 913/956/930 f 900/943/917 914/957/931 899/942/916 f 899/942/916 914/957/931 913/956/930 f 901/944/918 915/958/932 900/943/917 f 900/943/917 915/958/932 914/957/931 f 902/945/919 916/959/933 901/944/918 f 901/944/918 916/959/933 915/958/932 f 903/946/920 917/960/934 902/945/919 f 902/945/919 917/960/934 916/959/933 f 904/947/921 918/961/935 903/946/920 f 903/946/920 918/961/935 917/960/934 f 905/948/922 919/962/936 904/947/921 f 904/947/921 919/962/936 918/961/935 f 906/949/923 920/963/937 905/948/922 f 905/948/922 920/963/937 919/962/936 f 906/949/923 907/950/924 920/963/937 f 920/963/937 907/950/924 921/964/938 f 908/951/925 922/965/939 907/950/924 f 907/950/924 922/965/939 921/964/938 f 909/952/926 923/966/940 908/951/925 f 908/951/925 923/966/940 922/965/939 f 924/967/941 910/953/927 925/968/942 f 925/968/942 910/953/927 911/954/928 f 912/955/929 913/956/930 926/969/943 f 926/969/943 913/956/930 927/970/944 f 914/957/931 928/971/945 913/956/930 f 913/956/930 928/971/945 927/970/944 f 915/958/932 929/972/946 914/957/931 f 914/957/931 929/972/946 928/971/945 f 916/959/933 930/973/947 915/958/932 f 915/958/932 930/973/947 929/972/946 f 917/960/934 931/974/948 916/959/933 f 916/959/933 931/974/948 930/973/947 f 918/961/935 932/975/949 917/960/934 f 917/960/934 932/975/949 931/974/948 f 919/962/936 933/976/950 918/961/935 f 918/961/935 933/976/950 932/975/949 f 920/963/937 934/977/951 919/962/936 f 919/962/936 934/977/951 933/976/950 f 920/963/937 921/964/938 934/977/951 f 934/977/951 921/964/938 935/978/952 f 921/964/938 922/965/939 935/978/952 f 935/978/952 922/965/939 936/979/953 f 923/966/940 937/980/954 922/965/939 f 922/965/939 937/980/954 936/979/953 f 394/402/411 924/967/941 393/401/410 f 393/401/410 924/967/941 925/968/942 f 926/969/943 927/970/944 938/981/955 f 938/981/955 927/970/944 939/982/956 f 928/971/945 940/983/957 927/970/944 f 927/970/944 940/983/957 939/982/956 f 929/972/946 941/984/958 928/971/945 f 928/971/945 941/984/958 940/983/957 f 930/973/947 942/985/959 929/972/946 f 929/972/946 942/985/959 941/984/958 f 931/974/948 943/986/960 930/973/947 f 930/973/947 943/986/960 942/985/959 f 932/975/949 944/987/961 931/974/948 f 931/974/948 944/987/961 943/986/960 f 933/976/950 945/988/962 932/975/949 f 932/975/949 945/988/962 944/987/961 f 934/977/951 946/989/963 933/976/950 f 933/976/950 946/989/963 945/988/962 f 934/977/951 935/978/952 946/989/963 f 946/989/963 935/978/952 947/990/964 f 935/978/952 936/979/953 947/990/964 f 947/990/964 936/979/953 948/991/965 f 937/980/954 949/992/966 936/979/953 f 936/979/953 949/992/966 948/991/965 f 938/981/955 939/982/956 950/993/967 f 950/993/967 939/982/956 951/994/968 f 940/983/957 952/995/969 939/982/956 f 939/982/956 952/995/969 951/994/968 f 941/984/958 953/996/970 940/983/957 f 940/983/957 953/996/970 952/995/969 f 942/985/959 954/997/971 941/984/958 f 941/984/958 954/997/971 953/996/970 f 943/986/960 955/998/972 942/985/959 f 942/985/959 955/998/972 954/997/971 f 944/987/961 956/999/973 943/986/960 f 943/986/960 956/999/973 955/998/972 f 945/988/962 957/1000/974 944/987/961 f 944/987/961 957/1000/974 956/999/973 f 946/989/963 958/1001/975 945/988/962 f 945/988/962 958/1001/975 957/1000/974 f 946/989/963 947/990/964 958/1001/975 f 958/1001/975 947/990/964 959/1002/976 f 947/990/964 948/991/965 959/1002/976 f 959/1002/976 948/991/965 960/1003/977 f 949/992/966 961/1004/978 948/991/965 f 948/991/965 961/1004/978 960/1003/977 f 950/993/967 951/994/968 962/1005/979 f 962/1005/979 951/994/968 963/1006/980 f 952/995/969 964/1007/981 951/994/968 f 951/994/968 964/1007/981 963/1006/980 f 953/996/970 965/1008/982 952/995/969 f 952/995/969 965/1008/982 964/1007/981 f 954/997/971 966/1009/983 953/996/970 f 953/996/970 966/1009/983 965/1008/982 f 955/998/972 967/1010/984 954/997/971 f 954/997/971 967/1010/984 966/1009/983 f 956/999/973 968/1011/985 955/998/972 f 955/998/972 968/1011/985 967/1010/984 f 957/1000/974 969/1012/986 956/999/973 f 956/999/973 969/1012/986 968/1011/985 f 958/1001/975 970/1013/987 957/1000/974 f 957/1000/974 970/1013/987 969/1012/986 f 958/1001/975 959/1002/976 970/1013/987 f 970/1013/987 959/1002/976 971/1014/988 f 959/1002/976 960/1003/977 971/1014/988 f 971/1014/988 960/1003/977 972/1015/989 f 961/1004/978 973/1016/990 960/1003/977 f 960/1003/977 973/1016/990 972/1015/989 f 962/1005/979 963/1006/980 974/1017/991 f 974/1017/991 963/1006/980 975/1018/992 f 964/1007/981 976/1019/993 963/1006/980 f 963/1006/980 976/1019/993 975/1018/992 f 965/1008/982 977/1020/994 964/1007/981 f 964/1007/981 977/1020/994 976/1019/993 f 966/1009/983 978/1021/995 965/1008/982 f 965/1008/982 978/1021/995 977/1020/994 f 967/1010/984 979/1022/996 966/1009/983 f 966/1009/983 979/1022/996 978/1021/995 f 968/1011/985 980/1023/997 967/1010/984 f 967/1010/984 980/1023/997 979/1022/996 f 969/1012/986 981/1024/998 968/1011/985 f 968/1011/985 981/1024/998 980/1023/997 f 969/1012/986 970/1013/987 981/1024/998 f 981/1024/998 970/1013/987 982/1025/999 f 970/1013/987 971/1014/988 982/1025/999 f 982/1025/999 971/1014/988 983/1026/1000 f 971/1014/988 972/1015/989 983/1026/1000 f 983/1026/1000 972/1015/989 984/1027/1001 f 972/1015/989 973/1016/990 984/1027/1001 f 984/1027/1001 973/1016/990 985/1028/1002 f 974/1017/991 975/1018/992 986/1029/1003 f 986/1029/1003 975/1018/992 987/1030/1004 f 976/1019/993 988/1031/1005 975/1018/992 f 975/1018/992 988/1031/1005 987/1030/1004 f 977/1020/994 989/1032/1006 976/1019/993 f 976/1019/993 989/1032/1006 988/1031/1005 f 978/1021/995 990/1033/1007 977/1020/994 f 977/1020/994 990/1033/1007 989/1032/1006 f 979/1022/996 991/1034/1008 978/1021/995 f 978/1021/995 991/1034/1008 990/1033/1007 f 980/1023/997 992/1035/1009 979/1022/996 f 979/1022/996 992/1035/1009 991/1034/1008 f 981/1024/998 993/1036/1010 980/1023/997 f 980/1023/997 993/1036/1010 992/1035/1009 f 981/1024/998 982/1025/999 993/1036/1010 f 993/1036/1010 982/1025/999 994/1037/1011 f 982/1025/999 983/1026/1000 994/1037/1011 f 994/1037/1011 983/1026/1000 995/1038/1012 f 984/1027/1001 996/1039/1013 983/1026/1000 f 983/1026/1000 996/1039/1013 995/1038/1012 f 984/1027/1001 985/1028/1002 996/1039/1013 f 996/1039/1013 985/1028/1002 997/1040/1014 f 986/1029/1003 987/1030/1004 998/1041/1015 f 998/1041/1015 987/1030/1004 999/1042/1016 f 987/1030/1004 988/1031/1005 999/1042/1016 f 999/1042/1016 988/1031/1005 1000/1043/1017 f 988/1031/1005 989/1032/1006 1000/1043/1017 f 1000/1043/1017 989/1032/1006 1001/1044/1018 f 989/1032/1006 990/1033/1007 1001/1044/1018 f 1001/1044/1018 990/1033/1007 1002/1045/1019 f 991/1034/1008 1003/1046/1020 990/1033/1007 f 990/1033/1007 1003/1046/1020 1002/1045/1019 f 992/1035/1009 1004/1047/1021 991/1034/1008 f 991/1034/1008 1004/1047/1021 1003/1046/1020 f 993/1036/1010 1005/1048/1022 992/1035/1009 f 992/1035/1009 1005/1048/1022 1004/1047/1021 f 993/1036/1010 994/1037/1011 1005/1048/1022 f 1005/1048/1022 994/1037/1011 1006/1049/1023 f 995/1038/1012 1007/1050/1024 994/1037/1011 f 994/1037/1011 1007/1050/1024 1006/1049/1023 f 996/1039/1013 1008/1051/1025 995/1038/1012 f 995/1038/1012 1008/1051/1025 1007/1050/1024 f 997/1040/1014 1009/1052/1026 996/1039/1013 f 996/1039/1013 1009/1052/1026 1008/1051/1025 f 998/1041/1015 999/1042/1016 1010/1053/1027 f 1010/1053/1027 999/1042/1016 1011/1054/1028 f 999/1042/1016 1000/1043/1017 1011/1054/1028 f 1011/1054/1028 1000/1043/1017 1012/1055/1029 f 1000/1043/1017 1001/1044/1018 1012/1055/1029 f 1012/1055/1029 1001/1044/1018 1013/1056/1030 f 1004/1047/1021 1005/1048/1022 1014/1057/1031 f 1006/1049/1023 1015/1058/1032 1005/1048/1022 f 1005/1048/1022 1015/1058/1032 1014/1057/1031 f 1007/1050/1024 1016/1059/1033 1006/1049/1023 f 1006/1049/1023 1016/1059/1033 1015/1058/1032 f 1008/1051/1025 1017/1060/1034 1007/1050/1024 f 1007/1050/1024 1017/1060/1034 1016/1059/1033 f 1009/1052/1026 1018/1061/1035 1008/1051/1025 f 1008/1051/1025 1018/1061/1035 1017/1060/1034 f 1010/1053/1027 1011/1054/1028 1019/1062/1036 f 1019/1062/1036 1011/1054/1028 1020/1063/1037 f 1011/1054/1028 1012/1055/1029 1020/1063/1037 f 1020/1063/1037 1012/1055/1029 1021/1064/1038 f 1019/1062/1036 1020/1063/1037 1022/1065/1039 f 1022/1065/1039 1020/1063/1037 1023/1066/1040 f 1021/1064/1038 1024/1067/1041 1020/1063/1037 f 1020/1063/1037 1024/1067/1041 1023/1066/1040 f 1025/1068/1042 1026/1069/1043 1016/1059/1033 f 1016/1059/1033 1026/1069/1043 1015/1058/1032 f 1017/1060/1034 1027/1070/1044 1016/1059/1033 f 1016/1059/1033 1027/1070/1044 1025/1068/1042 f 1018/1061/1035 1028/1071/1045 1017/1060/1034 f 1017/1060/1034 1028/1071/1045 1027/1070/1044 f 1022/1065/1039 1023/1066/1040 1029/1072/1046 f 1029/1072/1046 1023/1066/1040 1030/1073/1047 f 1024/1067/1041 1031/1074/1048 1023/1066/1040 f 1023/1066/1040 1031/1074/1048 1030/1073/1047 f 1032/1075/1049 1026/1069/1043 1033/1076/1050 f 1033/1076/1050 1026/1069/1043 1025/1068/1042 f 1025/1068/1042 1027/1070/1044 1033/1076/1050 f 1033/1076/1050 1027/1070/1044 1034/1077/1051 f 1028/1071/1045 1035/1078/1052 1027/1070/1044 f 1027/1070/1044 1035/1078/1052 1034/1077/1051 f 1029/1072/1046 1030/1073/1047 1036/1079/1053 f 1036/1079/1053 1030/1073/1047 1037/1080/1054 f 1030/1073/1047 1031/1074/1048 1037/1080/1054 f 1037/1080/1054 1031/1074/1048 1038/1081/1055 f 1039/1082/1056 1040/1083/1057 1033/1076/1050 f 1033/1076/1050 1040/1083/1057 1032/1075/1049 f 1033/1076/1050 1034/1077/1051 1039/1082/1056 f 1039/1082/1056 1034/1077/1051 1041/1084/1058 f 1035/1078/1052 1042/1085/1059 1034/1077/1051 f 1034/1077/1051 1042/1085/1059 1041/1084/1058 f 1036/1079/1053 1037/1080/1054 1043/1086/1060 f 1043/1086/1060 1037/1080/1054 1044/1087/1061 f 1037/1080/1054 1038/1081/1055 1044/1087/1061 f 1044/1087/1061 1038/1081/1055 1045/1088/1062 f 1040/1083/1057 1039/1082/1056 1046/1089/1063 f 1046/1089/1063 1039/1082/1056 1047/1090/1064 f 1039/1082/1056 1041/1084/1058 1047/1090/1064 f 1047/1090/1064 1041/1084/1058 1048/1091/1065 f 1041/1084/1058 1042/1085/1059 1048/1091/1065 f 1048/1091/1065 1042/1085/1059 1049/1092/1066 f 1042/1085/1059 1050/1093/1067 1049/1092/1066 f 1049/1092/1066 1050/1093/1067 1051/1094/1068 f 1052/1095/1069 1050/1093/1067 1053/1096/1070 f 1053/1096/1070 1050/1093/1067 1054/1097/1071 f 1043/1086/1060 1044/1087/1061 1055/1098/1072 f 1055/1098/1072 1044/1087/1061 1056/1099/1073 f 1044/1087/1061 1045/1088/1062 1056/1099/1073 f 1056/1099/1073 1045/1088/1062 1057/1100/1074 f 1058/1101/1075 1059/1102/1076 1060/1103/1077 f 1060/1103/1077 1059/1102/1076 1061/1104/1078 f 840/883/857 842/885/859 1062/1105/1079 f 844/887/861 840/883/857 1062/1105/1079 f 846/889/863 844/887/861 1062/1105/1079 f 848/891/865 846/889/863 1062/1105/1079 f 850/893/867 848/891/865 1062/1105/1079 f 852/895/869 850/893/867 1062/1105/1079 f 854/897/871 852/895/869 1062/1105/1079 f 856/899/873 854/897/871 1062/1105/1079 f 858/901/875 856/899/873 1062/1105/1079 f 860/903/877 858/901/875 1062/1105/1079 f 862/905/879 860/903/877 1062/1105/1079 f 862/905/879 1062/1105/1079 864/907/881 f 866/909/883 868/911/885 1062/1105/1079 f 1063/1106/1080 1064/1107/1081 1065/1108/1082 f 1065/1108/1082 1064/1107/1081 1066/1109/1083 f 1065/1108/1082 1067/1110/1084 1063/1106/1080 f 1063/1106/1080 1067/1110/1084 1068/1111/1085 f 1069/1112/1086 1070/1113/1087 1065/1108/1082 f 1065/1108/1082 1070/1113/1087 1067/1110/1084 f 1065/1108/1082 1066/1109/1083 1069/1112/1086 f 1069/1112/1086 1066/1109/1083 1071/1114/1088 f 1072/1115/1089 1073/1116/1090 1074/1117/1091 f 1074/1117/1091 1073/1116/1090 1075/1118/1092 f 1074/1117/1091 1076/1119/1093 1072/1115/1089 f 1072/1115/1089 1076/1119/1093 1077/1120/1094 f 1063/1106/1080 1068/1111/1085 1074/1117/1091 f 1074/1117/1091 1068/1111/1085 1076/1119/1093 f 1074/1117/1091 1075/1118/1092 1063/1106/1080 f 1063/1106/1080 1075/1118/1092 1064/1107/1081 f 1078/1121/1095 1079/1122/1096 1080/1123/1097 f 1080/1123/1097 1079/1122/1096 1081/1124/1098 f 1080/1123/1097 1082/1125/1099 1078/1121/1095 f 1078/1121/1095 1082/1125/1099 1083/1126/1100 f 1072/1115/1089 1077/1120/1094 1080/1123/1097 f 1080/1123/1097 1077/1120/1094 1082/1125/1099 f 1080/1123/1097 1081/1124/1098 1072/1115/1089 f 1072/1115/1089 1081/1124/1098 1073/1116/1090 f 1084/1127/1101 1085/1128/1102 1086/1129/1103 f 1086/1129/1103 1085/1128/1102 1087/1130/1104 f 1087/1130/1104 1088/1131/1105 1086/1129/1103 f 1086/1129/1103 1088/1131/1105 1089/1132/1106 f 1078/1121/1095 1083/1126/1100 1087/1130/1104 f 1087/1130/1104 1083/1126/1100 1088/1131/1105 f 1087/1130/1104 1085/1128/1102 1078/1121/1095 f 1078/1121/1095 1085/1128/1102 1079/1122/1096 f 1090/1133/1107 1091/1134/1108 1092/1135/1109 f 1092/1135/1109 1091/1134/1108 1093/1136/1110 f 1089/1132/1106 1090/1133/1107 1086/1129/1103 f 1086/1129/1103 1090/1133/1107 1092/1135/1109 f 1092/1135/1109 1093/1136/1110 1094/1137/1111 f 1094/1137/1111 1093/1136/1110 1095/1138/1112 f 1096/1139/1113 1097/1140/1114 1098/1141/1115 f 1098/1141/1115 1097/1140/1114 1099/1142/1116 f 1100/1143/1117 1101/1144/1118 1099/1142/1116 f 1099/1142/1116 1101/1144/1118 1098/1141/1115 f 1091/1134/1108 1100/1143/1117 1093/1136/1110 f 1093/1136/1110 1100/1143/1117 1099/1142/1116 f 1097/1140/1114 1095/1138/1112 1099/1142/1116 f 1099/1142/1116 1095/1138/1112 1093/1136/1110 f 1102/1145/1119 1103/1146/1120 1104/1147/1121 f 1104/1147/1121 1103/1146/1120 1105/1148/1122 f 1106/1149/1123 1107/1150/1124 1105/1148/1122 f 1105/1148/1122 1107/1150/1124 1104/1147/1121 f 1101/1144/1118 1106/1149/1123 1098/1141/1115 f 1098/1141/1115 1106/1149/1123 1105/1148/1122 f 1103/1146/1120 1096/1139/1113 1105/1148/1122 f 1105/1148/1122 1096/1139/1113 1098/1141/1115 f 1108/1151/1125 1109/1152/1126 1110/1153/1127 f 1110/1153/1127 1109/1152/1126 1111/1154/1128 f 1112/1155/1129 1113/1156/1130 1111/1154/1128 f 1111/1154/1128 1113/1156/1130 1110/1153/1127 f 1107/1150/1124 1112/1155/1129 1104/1147/1121 f 1104/1147/1121 1112/1155/1129 1111/1154/1128 f 1109/1152/1126 1102/1145/1119 1111/1154/1128 f 1111/1154/1128 1102/1145/1119 1104/1147/1121 f 1114/1157/1131 1115/1158/1132 1116/1159/1133 f 1116/1159/1133 1115/1158/1132 1117/1160/1134 f 1118/1161/1135 1119/1162/1136 1117/1160/1134 f 1117/1160/1134 1119/1162/1136 1116/1159/1133 f 1113/1156/1130 1118/1161/1135 1110/1153/1127 f 1110/1153/1127 1118/1161/1135 1117/1160/1134 f 1115/1158/1132 1108/1151/1125 1117/1160/1134 f 1117/1160/1134 1108/1151/1125 1110/1153/1127 f 1069/1112/1086 1071/1114/1088 1120/1163/1137 f 1120/1163/1137 1071/1114/1088 1121/1164/1138 f 1120/1163/1137 1122/1165/1139 1069/1112/1086 f 1069/1112/1086 1122/1165/1139 1070/1113/1087 f 1116/1159/1133 1119/1162/1136 1120/1163/1137 f 1120/1163/1137 1119/1162/1136 1122/1165/1139 f 1120/1163/1137 1121/1164/1138 1116/1159/1133 f 1116/1159/1133 1121/1164/1138 1114/1157/1131 f 1109/1152/1126 1108/1151/1125 1066/1109/1083 f 1066/1109/1083 1108/1151/1125 1071/1114/1088 f 1123/1166/1140 1124/1167/1141 1125/1168/1142 f 1125/1168/1142 1124/1167/1141 1126/1169/1143 f 1127/1170/1144 1128/1171/1145 1129/1172/1146 f 1129/1172/1146 1128/1171/1145 1126/1169/1143 f 1130/1173/1147 1131/1174/1148 1132/1175/1149 f 1132/1175/1149 1131/1174/1148 1133/1176/1150 f 1134/1177/1151 1135/1178/1152 1136/1179/1153 f 1136/1179/1153 1135/1178/1152 1133/1176/1150 f 1137/1180/1154 1138/1181/1155 1136/1179/1153 f 1136/1179/1153 1138/1181/1155 1139/1182/1156 f 1140/1183/1157 1141/1184/1158 1125/1168/1142 f 1125/1168/1142 1141/1184/1158 1139/1182/1156 f 1142/1185/1159 1143/1186/1160 1144/1187/1161 f 1144/1187/1161 1143/1186/1160 1129/1172/1146 f 1145/1188/1162 1146/1189/1163 1147/1190/1164 f 1147/1190/1164 1146/1189/1163 1143/1186/1160 f 1148/1191/1165 1149/1192/1166 1147/1190/1164 f 1147/1190/1164 1149/1192/1166 1150/1193/1167 f 1151/1194/1168 1150/1193/1167 1152/1195/1169 f 1152/1195/1169 1150/1193/1167 1153/1196/1170 f 1154/1197/1171 1155/1198/1172 1156/1199/1173 f 1156/1199/1173 1155/1198/1172 1157/1200/1174 f 1158/1201/1175 1155/1198/1172 1159/1202/1176 f 1159/1202/1176 1155/1198/1172 1153/1196/1170 f 1160/1203/1177 1161/1204/1178 1162/1205/1179 f 1162/1205/1179 1161/1204/1178 1157/1200/1174 f 1163/1206/1180 1161/1204/1178 1164/1207/1181 f 1164/1207/1181 1161/1204/1178 1165/1208/1182 f 1166/1209/1183 1167/1210/1184 1168/1211/1185 f 1168/1211/1185 1167/1210/1184 1165/1208/1182 f 1169/1212/1186 1167/1210/1184 1170/1213/1187 f 1170/1213/1187 1167/1210/1184 1171/1214/1188 f 1172/1215/1189 1173/1216/1190 1174/1217/1191 f 1174/1217/1191 1173/1216/1190 1171/1214/1188 f 1175/1218/1192 1173/1216/1190 1176/1219/1193 f 1176/1219/1193 1173/1216/1190 1177/1220/1194 f 1178/1221/1195 1179/1222/1196 1180/1223/1197 f 1180/1223/1197 1179/1222/1196 1177/1220/1194 f 1181/1224/1198 1179/1222/1196 1182/1225/1199 f 1182/1225/1199 1179/1222/1196 1132/1175/1149 f 1082/1125/1099 1183/1226/1200 1083/1126/1100 f 1083/1126/1100 1183/1226/1200 1184/1227/1201 f 1183/1226/1200 1082/1125/1099 1185/1228/1202 f 1185/1228/1202 1082/1125/1099 1077/1120/1094 f 1067/1110/1084 1186/1229/1203 1068/1111/1085 f 1068/1111/1085 1186/1229/1203 1187/1230/1204 f 1186/1229/1203 1067/1110/1084 1188/1231/1205 f 1188/1231/1205 1067/1110/1084 1070/1113/1087 f 1076/1119/1093 1189/1232/1206 1077/1120/1094 f 1077/1120/1094 1189/1232/1206 1185/1228/1202 f 1189/1232/1206 1076/1119/1093 1187/1230/1204 f 1187/1230/1204 1076/1119/1093 1068/1111/1085 f 1088/1131/1105 1190/1233/1207 1089/1132/1106 f 1089/1132/1106 1190/1233/1207 1191/1234/1208 f 1190/1233/1207 1088/1131/1105 1184/1227/1201 f 1184/1227/1201 1088/1131/1105 1083/1126/1100 f 1122/1165/1139 1192/1235/1209 1070/1113/1087 f 1070/1113/1087 1192/1235/1209 1188/1231/1205 f 1192/1235/1209 1122/1165/1139 1193/1236/1210 f 1193/1236/1210 1122/1165/1139 1119/1162/1136 f 1091/1134/1108 1090/1133/1107 1194/1237/1211 f 1194/1237/1211 1090/1133/1107 1195/1238/1212 f 1090/1133/1107 1089/1132/1106 1195/1238/1212 f 1195/1238/1212 1089/1132/1106 1191/1234/1208 f 1119/1162/1136 1118/1161/1135 1193/1236/1210 f 1193/1236/1210 1118/1161/1135 1196/1239/1213 f 1118/1161/1135 1113/1156/1130 1196/1239/1213 f 1196/1239/1213 1113/1156/1130 1197/1240/1214 f 1101/1144/1118 1100/1143/1117 1198/1241/1215 f 1198/1241/1215 1100/1143/1117 1199/1242/1216 f 1100/1143/1117 1091/1134/1108 1199/1242/1216 f 1199/1242/1216 1091/1134/1108 1194/1237/1211 f 1113/1156/1130 1112/1155/1129 1197/1240/1214 f 1197/1240/1214 1112/1155/1129 1200/1243/1217 f 1112/1155/1129 1107/1150/1124 1200/1243/1217 f 1200/1243/1217 1107/1150/1124 1201/1244/1218 f 1107/1150/1124 1106/1149/1123 1201/1244/1218 f 1201/1244/1218 1106/1149/1123 1202/1245/1219 f 1106/1149/1123 1101/1144/1118 1202/1245/1219 f 1202/1245/1219 1101/1144/1118 1198/1241/1215 f 1183/1246/1200 1146/1189/1163 1184/1247/1201 f 1184/1247/1201 1146/1189/1163 1145/1188/1162 f 1146/1189/1163 1183/1246/1200 1127/1170/1144 f 1127/1170/1144 1183/1246/1200 1185/1248/1202 f 1186/1249/1203 1141/1184/1158 1187/1250/1204 f 1187/1250/1204 1141/1184/1158 1140/1183/1157 f 1134/1177/1151 1141/1184/1158 1188/1251/1205 f 1188/1251/1205 1141/1184/1158 1186/1249/1203 f 1127/1170/1144 1185/1248/1202 1128/1171/1145 f 1128/1171/1145 1185/1248/1202 1189/1252/1206 f 1187/1250/1204 1140/1183/1157 1189/1252/1206 f 1189/1252/1206 1140/1183/1157 1128/1171/1145 f 1190/1253/1207 1151/1194/1168 1191/1254/1208 f 1191/1254/1208 1151/1194/1168 1152/1195/1169 f 1184/1247/1201 1145/1188/1162 1190/1253/1207 f 1190/1253/1207 1145/1188/1162 1151/1194/1168 f 1188/1251/1205 1192/1255/1209 1134/1177/1151 f 1134/1177/1151 1192/1255/1209 1135/1178/1152 f 1182/1225/1199 1135/1178/1152 1193/1256/1210 f 1193/1256/1210 1135/1178/1152 1192/1255/1209 f 1195/1257/1212 1154/1197/1171 1194/1258/1211 f 1194/1258/1211 1154/1197/1171 1156/1199/1173 f 1191/1254/1208 1152/1195/1169 1195/1257/1212 f 1195/1257/1212 1152/1195/1169 1154/1197/1171 f 1193/1256/1210 1196/1259/1213 1182/1225/1199 f 1182/1225/1199 1196/1259/1213 1181/1224/1198 f 1176/1219/1193 1181/1224/1198 1197/1260/1214 f 1197/1260/1214 1181/1224/1198 1196/1259/1213 f 1199/1261/1216 1163/1206/1180 1198/1262/1215 f 1198/1262/1215 1163/1206/1180 1164/1207/1181 f 1194/1258/1211 1156/1199/1173 1199/1261/1216 f 1199/1261/1216 1156/1199/1173 1163/1206/1180 f 1197/1260/1214 1200/1263/1217 1176/1219/1193 f 1176/1219/1193 1200/1263/1217 1175/1218/1192 f 1170/1213/1187 1175/1218/1192 1201/1264/1218 f 1201/1264/1218 1175/1218/1192 1200/1263/1217 f 1170/1213/1187 1201/1264/1218 1169/1212/1186 f 1169/1212/1186 1201/1264/1218 1202/1265/1219 f 1198/1262/1215 1164/1207/1181 1202/1265/1219 f 1202/1265/1219 1164/1207/1181 1169/1212/1186 f 1158/1201/1175 1203/1266/1220 1162/1205/1179 f 1162/1205/1179 1203/1266/1220 1204/1267/1221 f 1158/1201/1175 1159/1202/1176 1203/1266/1220 f 1203/1266/1220 1159/1202/1176 1205/1268/1222 f 1160/1203/1177 1206/1269/1223 1168/1211/1185 f 1168/1211/1185 1206/1269/1223 1207/1270/1224 f 1206/1269/1223 1160/1203/1177 1204/1267/1221 f 1204/1267/1221 1160/1203/1177 1162/1205/1179 f 1144/1187/1161 1124/1167/1141 1208/1271/1225 f 1208/1271/1225 1124/1167/1141 1209/1272/1226 f 1124/1167/1141 1123/1166/1140 1209/1272/1226 f 1209/1272/1226 1123/1166/1140 1210/1273/1227 f 1172/1215/1189 1211/1274/1228 1180/1223/1197 f 1180/1223/1197 1211/1274/1228 1212/1275/1229 f 1211/1274/1228 1172/1215/1189 1213/1276/1230 f 1213/1276/1230 1172/1215/1189 1174/1217/1191 f 1137/1180/1154 1131/1174/1148 1214/1277/1231 f 1214/1277/1231 1131/1174/1148 1215/1278/1232 f 1215/1278/1232 1131/1174/1148 1216/1279/1233 f 1216/1279/1233 1131/1174/1148 1130/1173/1147 f 1123/1166/1140 1138/1181/1155 1210/1273/1227 f 1210/1273/1227 1138/1181/1155 1217/1280/1234 f 1138/1181/1155 1137/1180/1154 1217/1280/1234 f 1217/1280/1234 1137/1180/1154 1214/1277/1231 f 1148/1191/1165 1142/1185/1159 1218/1281/1235 f 1218/1281/1235 1142/1185/1159 1219/1282/1236 f 1142/1185/1159 1144/1187/1161 1219/1282/1236 f 1219/1282/1236 1144/1187/1161 1208/1271/1225 f 1159/1202/1176 1149/1192/1166 1205/1268/1222 f 1205/1268/1222 1149/1192/1166 1220/1283/1237 f 1149/1192/1166 1148/1191/1165 1220/1283/1237 f 1220/1283/1237 1148/1191/1165 1218/1281/1235 f 1166/1209/1183 1221/1284/1238 1174/1217/1191 f 1174/1217/1191 1221/1284/1238 1213/1276/1230 f 1221/1284/1238 1166/1209/1183 1207/1270/1224 f 1207/1270/1224 1166/1209/1183 1168/1211/1185 f 1178/1221/1195 1222/1285/1239 1130/1173/1147 f 1130/1173/1147 1222/1285/1239 1216/1279/1233 f 1222/1285/1239 1178/1221/1195 1212/1275/1229 f 1212/1275/1229 1178/1221/1195 1180/1223/1197 f 1223/1286/1240 1224/1287/1241 1203/1266/1220 f 1203/1266/1220 1224/1287/1241 1204/1267/1221 f 1225/1288/1242 1223/1286/1240 1205/1268/1222 f 1205/1268/1222 1223/1286/1240 1203/1266/1220 f 1206/1269/1223 1226/1289/1243 1207/1270/1224 f 1207/1270/1224 1226/1289/1243 1227/1290/1244 f 1226/1289/1243 1206/1269/1223 1224/1287/1241 f 1224/1287/1241 1206/1269/1223 1204/1267/1221 f 1228/1291/1245 1229/1292/1246 1209/1272/1226 f 1209/1272/1226 1229/1292/1246 1208/1271/1225 f 1230/1293/1247 1228/1291/1245 1210/1273/1227 f 1210/1273/1227 1228/1291/1245 1209/1272/1226 f 1211/1274/1228 1231/1294/1248 1212/1275/1229 f 1212/1275/1229 1231/1294/1248 1232/1295/1249 f 1231/1294/1248 1211/1274/1228 1233/1296/1250 f 1233/1296/1250 1211/1274/1228 1213/1276/1230 f 1215/1278/1232 1234/1297/1251 1214/1277/1231 f 1214/1277/1231 1234/1297/1251 1235/1298/1252 f 1234/1297/1251 1215/1278/1232 1236/1299/1253 f 1236/1299/1253 1215/1278/1232 1216/1279/1233 f 1237/1300/1254 1230/1293/1247 1217/1280/1234 f 1217/1280/1234 1230/1293/1247 1210/1273/1227 f 1235/1298/1252 1237/1300/1254 1214/1277/1231 f 1214/1277/1231 1237/1300/1254 1217/1280/1234 f 1238/1301/1255 1239/1302/1256 1219/1282/1236 f 1219/1282/1236 1239/1302/1256 1218/1281/1235 f 1229/1292/1246 1238/1301/1255 1208/1271/1225 f 1208/1271/1225 1238/1301/1255 1219/1282/1236 f 1240/1303/1257 1225/1288/1242 1220/1283/1237 f 1220/1283/1237 1225/1288/1242 1205/1268/1222 f 1239/1302/1256 1240/1303/1257 1218/1281/1235 f 1218/1281/1235 1240/1303/1257 1220/1283/1237 f 1221/1284/1238 1241/1304/1258 1213/1276/1230 f 1213/1276/1230 1241/1304/1258 1233/1296/1250 f 1241/1304/1258 1221/1284/1238 1227/1290/1244 f 1227/1290/1244 1221/1284/1238 1207/1270/1224 f 1222/1285/1239 1242/1305/1259 1216/1279/1233 f 1216/1279/1233 1242/1305/1259 1236/1299/1253 f 1242/1305/1259 1222/1285/1239 1232/1295/1249 f 1232/1295/1249 1222/1285/1239 1212/1275/1229 f 1115/1158/1132 1114/1157/1131 1108/1151/1125 f 1108/1151/1125 1114/1157/1131 1071/1114/1088 f 1114/1157/1131 1121/1164/1138 1071/1114/1088 f 1064/1107/1081 1075/1118/1092 1102/1145/1119 f 1102/1145/1119 1075/1118/1092 1103/1146/1120 f 1066/1109/1083 1064/1107/1081 1109/1152/1126 f 1109/1152/1126 1064/1107/1081 1102/1145/1119 f 1075/1118/1092 1073/1116/1090 1103/1146/1120 f 1103/1146/1120 1073/1116/1090 1096/1139/1113 f 1085/1128/1102 1084/1127/1101 1079/1122/1096 f 1095/1138/1112 1079/1122/1096 1094/1137/1111 f 1079/1122/1096 1084/1127/1101 1094/1137/1111 f 1081/1124/1098 1097/1140/1114 1073/1116/1090 f 1073/1116/1090 1097/1140/1114 1096/1139/1113 f 1094/1137/1111 1084/1127/1101 1092/1135/1109 f 1092/1135/1109 1084/1127/1101 1086/1129/1103 f 1079/1122/1096 1095/1138/1112 1081/1124/1098 f 1081/1124/1098 1095/1138/1112 1097/1140/1114 f 1224/1287/1241 1223/1286/1240 1243/1306/1260 f 1243/1306/1260 1223/1286/1240 1244/1307/1261 f 1223/1286/1240 1225/1288/1242 1244/1307/1261 f 1244/1307/1261 1225/1288/1242 1046/1089/1063 f 1226/1289/1243 1245/1308/1262 1227/1290/1244 f 1227/1290/1244 1245/1308/1262 1246/1309/1263 f 1228/1291/1245 1014/1057/1031 1229/1292/1246 f 1229/1292/1246 1014/1057/1031 1015/1058/1032 f 1228/1291/1245 1230/1293/1247 1014/1057/1031 f 1014/1057/1031 1230/1293/1247 1004/1047/1021 f 1231/1294/1248 1038/1081/1055 1232/1295/1249 f 1232/1295/1249 1038/1081/1055 1031/1074/1048 f 1233/1296/1250 1045/1088/1062 1231/1294/1248 f 1231/1294/1248 1045/1088/1062 1038/1081/1055 f 1235/1298/1252 1234/1297/1251 1002/1045/1019 f 1002/1045/1019 1234/1297/1251 1013/1056/1030 f 1234/1297/1251 1236/1299/1253 1013/1056/1030 f 1013/1056/1030 1236/1299/1253 1021/1064/1038 f 1237/1300/1254 1003/1046/1020 1230/1293/1247 f 1230/1293/1247 1003/1046/1020 1004/1047/1021 f 1237/1300/1254 1235/1298/1252 1003/1046/1020 f 1003/1046/1020 1235/1298/1252 1002/1045/1019 f 1239/1302/1256 1238/1301/1255 1032/1075/1049 f 1032/1075/1049 1238/1301/1255 1026/1069/1043 f 1238/1301/1255 1229/1292/1246 1026/1069/1043 f 1026/1069/1043 1229/1292/1246 1015/1058/1032 f 1240/1303/1257 1239/1302/1256 1040/1083/1057 f 1040/1083/1057 1239/1302/1256 1032/1075/1049 f 1233/1296/1250 1241/1304/1258 1045/1088/1062 f 1045/1088/1062 1241/1304/1258 1057/1100/1074 f 1227/1290/1244 1246/1309/1263 1241/1304/1258 f 1241/1304/1258 1246/1309/1263 1057/1100/1074 f 1236/1299/1253 1242/1305/1259 1021/1064/1038 f 1021/1064/1038 1242/1305/1259 1024/1067/1041 f 1242/1305/1259 1232/1295/1249 1024/1067/1041 f 1024/1067/1041 1232/1295/1249 1031/1074/1048 f 1224/1287/1241 1243/1306/1260 1226/1289/1243 f 1226/1289/1243 1243/1306/1260 1245/1308/1262 f 1021/1064/1038 1012/1055/1029 1013/1056/1030 f 1013/1056/1030 1001/1044/1018 1002/1045/1019 f 1240/1303/1257 1040/1083/1057 1225/1288/1242 f 1225/1288/1242 1040/1083/1057 1046/1089/1063 f 1247/1310/1264 1248/1311/1265 1249/1312/1266 f 1248/1311/1265 1250/1313/1267 1251/1314/1268 f 1251/1314/1268 1250/1313/1267 1249/1312/1266 f 1252/1315/1269 1253/1316/1270 1254/1317/1271 f 1254/1317/1271 1253/1316/1270 1255/1318/1272 f 1253/1316/1270 1252/1315/1269 1256/1319/1273 f 1256/1319/1273 1252/1315/1269 1257/1320/1274 f 1258/1321/1275 1259/1322/1276 1252/1315/1269 f 1252/1315/1269 1259/1322/1276 1257/1320/1274 f 1260/1323/1277 1261/1324/1278 1262/1325/1279 f 1262/1325/1279 1261/1324/1278 1258/1321/1275 f 1258/1321/1275 1252/1315/1269 1262/1325/1279 f 1262/1325/1279 1252/1315/1269 1254/1317/1271 f 1263/1326/1280 1264/1327/1281 1265/1328/1282 f 1265/1328/1282 1264/1327/1281 1266/1329/1283 f 1267/1330/1284 1266/1329/1283 1268/1331/1285 f 1268/1331/1285 1266/1329/1283 1264/1327/1281 f 1269/1332/1286 1270/1333/1287 1271/1334/1288 f 1271/1334/1288 1270/1333/1287 1272/1335/1289 f 1273/1336/1290 1272/1335/1289 1274/1337/1291 f 1274/1337/1291 1272/1335/1289 1270/1333/1287 f 1264/1327/1281 1275/1338/1292 1268/1331/1285 f 1268/1331/1285 1275/1338/1292 1276/1339/1293 f 1275/1338/1292 1277/1340/1294 1276/1339/1293 f 1276/1339/1293 1277/1340/1294 1278/1341/1295 f 1277/1340/1294 1275/1338/1292 1279/1342/1296 f 1279/1342/1296 1275/1338/1292 1280/1343/1297 f 1275/1338/1292 1264/1327/1281 1280/1343/1297 f 1280/1343/1297 1264/1327/1281 1263/1326/1280 f 1281/1344/1298 1282/1345/1299 1283/1346/1300 f 1283/1346/1300 1282/1345/1299 1284/1347/1301 f 1265/1328/1282 1284/1347/1301 1263/1326/1280 f 1263/1326/1280 1284/1347/1301 1282/1345/1299 f 1285/1348/1302 1286/1349/1303 1287/1350/1304 f 1287/1350/1304 1286/1349/1303 1288/1351/1305 f 1286/1349/1303 1289/1352/1306 1288/1351/1305 f 1288/1351/1305 1289/1352/1306 1290/1353/1307 f 1289/1352/1306 1286/1349/1303 1291/1354/1308 f 1291/1354/1308 1286/1349/1303 1292/1355/1309 f 1292/1355/1309 1286/1349/1303 1285/1348/1302 f 1293/1356/1310 1294/1357/1311 1271/1334/1288 f 1271/1334/1288 1294/1357/1311 1295/1358/1312 f 1294/1357/1311 1293/1356/1310 1296/1359/1313 f 1296/1359/1313 1293/1356/1310 1297/1360/1314 f 1293/1356/1310 1298/1361/1315 1297/1360/1314 f 1297/1360/1314 1298/1361/1315 1299/1362/1316 f 1298/1361/1315 1300/1363/1317 1299/1362/1316 f 1299/1362/1316 1300/1363/1317 1301/1364/1318 f 1300/1363/1317 1298/1361/1315 1273/1336/1290 f 1273/1336/1290 1298/1361/1315 1272/1335/1289 f 1298/1361/1315 1293/1356/1310 1272/1335/1289 f 1272/1335/1289 1293/1356/1310 1271/1334/1288 f 1302/1365/1319 1303/1366/1320 1304/1367/1321 f 1304/1367/1321 1303/1366/1320 1305/1368/1322 f 1305/1368/1322 1303/1366/1320 1281/1344/1298 f 1281/1344/1298 1303/1366/1320 1282/1345/1299 f 1303/1366/1320 1280/1343/1297 1282/1345/1299 f 1282/1345/1299 1280/1343/1297 1263/1326/1280 f 1303/1366/1320 1302/1365/1319 1280/1343/1297 f 1280/1343/1297 1302/1365/1319 1279/1342/1296 f 1290/1353/1307 1289/1352/1306 1306/1369/1323 f 1306/1369/1323 1289/1352/1306 1307/1370/1324 f 1307/1370/1324 1289/1352/1306 1308/1371/1325 f 1309/1372/1326 1310/1373/1327 1311/1374/1328 f 1311/1374/1328 1310/1373/1327 1312/1375/1329 f 1310/1373/1327 1309/1372/1326 1313/1376/1330 f 1313/1376/1330 1309/1372/1326 1314/1377/1331 f 1315/1378/1332 1316/1379/1333 1317/1380/1334 f 1317/1380/1334 1316/1379/1333 1318/1381/1335 f 1318/1381/1335 1316/1379/1333 1319/1382/1336 f 1319/1382/1336 1316/1379/1333 1320/1383/1337 f 1316/1379/1333 1310/1373/1327 1320/1383/1337 f 1320/1383/1337 1310/1373/1327 1313/1376/1330 f 1316/1379/1333 1315/1378/1332 1310/1373/1327 f 1310/1373/1327 1315/1378/1332 1312/1375/1329 f 1321/1384/1338 1322/1385/1339 1323/1386/1340 f 1323/1386/1340 1322/1385/1339 1324/1387/1341 f 1322/1385/1339 1325/1388/1342 1324/1387/1341 f 1324/1387/1341 1325/1388/1342 1326/1389/1343 f 1327/1390/1344 1328/1391/1345 1329/1392/1346 f 1329/1392/1346 1328/1391/1345 1330/1393/1347 f 1327/1390/1344 1248/1311/1265 1328/1391/1345 f 1328/1391/1345 1248/1311/1265 1247/1310/1264 f 1331/1394/1348 1332/1395/1349 1329/1392/1346 f 1329/1392/1346 1332/1395/1349 1327/1390/1344 f 1327/1390/1344 1333/1396/1350 1248/1311/1265 f 1248/1311/1265 1333/1396/1350 1250/1313/1267 f 1333/1396/1350 1327/1390/1344 1334/1397/1351 f 1334/1397/1351 1327/1390/1344 1332/1395/1349 f 1335/1398/1352 1336/1399/1353 1337/1400/1354 f 1337/1400/1354 1336/1399/1353 1338/1401/1355 f 1336/1399/1353 1253/1316/1270 1338/1401/1355 f 1338/1401/1355 1253/1316/1270 1256/1319/1273 f 1253/1316/1270 1336/1399/1353 1255/1318/1272 f 1255/1318/1272 1336/1399/1353 1339/1402/1356 f 1336/1399/1353 1335/1398/1352 1339/1402/1356 f 1339/1402/1356 1335/1398/1352 1340/1403/1357 f 1283/1346/1300 1341/1404/1358 1281/1344/1298 f 1281/1344/1298 1341/1404/1358 1342/1405/1359 f 1343/1406/1360 1342/1405/1359 1344/1407/1361 f 1344/1407/1361 1342/1405/1359 1341/1404/1358 f 1345/1408/1362 1346/1409/1363 1347/1410/1364 f 1347/1410/1364 1346/1409/1363 1348/1411/1365 f 1346/1409/1363 1349/1412/1366 1348/1411/1365 f 1348/1411/1365 1349/1412/1366 1350/1413/1367 f 1349/1412/1366 1346/1409/1363 1330/1393/1347 f 1330/1393/1347 1346/1409/1363 1329/1392/1346 f 1351/1414/1368 1345/1408/1362 1352/1415/1369 f 1352/1415/1369 1345/1408/1362 1347/1410/1364 f 1332/1395/1349 1331/1394/1348 1353/1416/1370 f 1354/1417/1371 1353/1416/1370 1331/1394/1348 f 1354/1417/1371 1351/1414/1368 1306/1369/1323 f 1306/1369/1323 1351/1414/1368 1355/1418/1372 f 1351/1414/1368 1352/1415/1369 1355/1418/1372 f 1355/1418/1372 1352/1415/1369 1356/1419/1373 f 1357/1420/1374 1349/1412/1366 1358/1421/1375 f 1358/1421/1375 1349/1412/1366 1330/1393/1347 f 1349/1412/1366 1357/1420/1374 1350/1413/1367 f 1350/1413/1367 1357/1420/1374 1359/1422/1376 f 1357/1420/1374 1360/1423/1377 1359/1422/1376 f 1359/1422/1376 1360/1423/1377 1361/1424/1378 f 1360/1423/1377 1357/1420/1374 1362/1425/1379 f 1362/1425/1379 1357/1420/1374 1358/1421/1375 f 1363/1426/1380 1364/1427/1381 1365/1428/1382 f 1365/1428/1382 1364/1427/1381 1366/1429/1383 f 1367/1430/1384 1366/1429/1383 1368/1431/1385 f 1368/1431/1385 1366/1429/1383 1364/1427/1381 f 1369/1432/1386 1370/1433/1387 1371/1434/1388 f 1371/1434/1388 1370/1433/1387 1372/1435/1389 f 1370/1433/1387 1373/1436/1390 1372/1435/1389 f 1372/1435/1389 1373/1436/1390 1374/1437/1391 f 1373/1436/1390 1370/1433/1387 1375/1438/1392 f 1375/1438/1392 1370/1433/1387 1376/1439/1393 f 1370/1433/1387 1369/1432/1386 1376/1439/1393 f 1376/1439/1393 1369/1432/1386 1377/1440/1394 f 1378/1441/1395 1379/1442/1396 1380/1443/1397 f 1380/1443/1397 1379/1442/1396 1381/1444/1398 f 1379/1442/1396 1382/1445/1399 1381/1444/1398 f 1381/1444/1398 1382/1445/1399 1326/1389/1343 f 1379/1442/1396 1369/1432/1386 1382/1445/1399 f 1382/1445/1399 1369/1432/1386 1371/1434/1388 f 1369/1432/1386 1379/1442/1396 1377/1440/1394 f 1377/1440/1394 1379/1442/1396 1378/1441/1395 f 1383/1446/1400 1384/1447/1401 1385/1448/1402 f 1385/1448/1402 1384/1447/1401 1386/1449/1403 f 1384/1447/1401 1387/1450/1404 1386/1449/1403 f 1386/1449/1403 1387/1450/1404 1388/1451/1405 f 1387/1450/1404 1384/1447/1401 1367/1430/1384 f 1367/1430/1384 1384/1447/1401 1366/1429/1383 f 1384/1447/1401 1383/1446/1400 1366/1429/1383 f 1366/1429/1383 1383/1446/1400 1365/1428/1382 f 1389/1452/1406 1373/1436/1390 1390/1453/1407 f 1390/1453/1407 1373/1436/1390 1375/1438/1392 f 1373/1436/1390 1389/1452/1406 1374/1437/1391 f 1374/1437/1391 1389/1452/1406 1391/1454/1408 f 1389/1452/1406 1392/1455/1409 1391/1454/1408 f 1391/1454/1408 1392/1455/1409 1393/1456/1410 f 1392/1455/1409 1389/1452/1406 1394/1457/1411 f 1394/1457/1411 1389/1452/1406 1390/1453/1407 f 1368/1431/1385 1395/1458/1412 1367/1430/1384 f 1367/1430/1384 1395/1458/1412 1387/1450/1404 f 1396/1459/1413 1388/1451/1405 1395/1458/1412 f 1395/1458/1412 1388/1451/1405 1387/1450/1404 f 1397/1460/1414 1398/1461/1415 1399/1462/1416 f 1399/1462/1416 1398/1461/1415 1400/1463/1417 f 1398/1461/1415 1397/1460/1414 1393/1456/1410 f 1393/1456/1410 1397/1460/1414 1391/1454/1408 f 1391/1454/1408 1397/1460/1414 1374/1437/1391 f 1374/1437/1391 1397/1460/1414 1372/1435/1389 f 1397/1460/1414 1399/1462/1416 1372/1435/1389 f 1372/1435/1389 1399/1462/1416 1371/1434/1388 f 1401/1464/1418 1402/1465/1419 1297/1360/1314 f 1297/1360/1314 1402/1465/1419 1296/1359/1313 f 1402/1465/1419 1401/1464/1418 1403/1466/1420 f 1403/1466/1420 1401/1464/1418 1404/1467/1421 f 1405/1468/1422 1406/1469/1423 1407/1470/1424 f 1407/1470/1424 1406/1469/1423 1408/1471/1425 f 1332/1395/1349 1409/1472/1426 1334/1397/1351 f 1334/1397/1351 1409/1472/1426 1410/1473/1427 f 1411/1474/1428 1412/1475/1429 1413/1476/1430 f 1413/1476/1430 1412/1475/1429 1410/1473/1427 f 1401/1464/1418 1414/1477/1431 1404/1467/1421 f 1404/1467/1421 1414/1477/1431 1415/1478/1432 f 1414/1477/1431 1416/1479/1433 1415/1478/1432 f 1415/1478/1432 1416/1479/1433 1417/1480/1434 f 1416/1479/1433 1414/1477/1431 1301/1364/1318 f 1301/1364/1318 1414/1477/1431 1299/1362/1316 f 1414/1477/1431 1401/1464/1418 1299/1362/1316 f 1299/1362/1316 1401/1464/1418 1297/1360/1314 f 1418/1481/1435 1335/1398/1352 1412/1475/1429 f 1412/1475/1429 1335/1398/1352 1337/1400/1354 f 1335/1398/1352 1418/1481/1435 1340/1403/1357 f 1340/1403/1357 1418/1481/1435 1419/1482/1436 f 1418/1481/1435 1420/1483/1437 1419/1482/1436 f 1419/1482/1436 1420/1483/1437 1421/1484/1438 f 1420/1483/1437 1418/1481/1435 1411/1474/1428 f 1411/1474/1428 1418/1481/1435 1412/1475/1429 f 1422/1485/1439 1423/1486/1440 1404/1467/1421 f 1404/1467/1421 1423/1486/1440 1403/1466/1420 f 1406/1469/1423 1424/1487/1441 1408/1471/1425 f 1408/1471/1425 1424/1487/1441 1425/1488/1442 f 1426/1489/1443 1411/1474/1428 1427/1490/1444 f 1427/1490/1444 1411/1474/1428 1413/1476/1430 f 1428/1491/1445 1429/1492/1446 1417/1480/1434 f 1417/1480/1434 1429/1492/1446 1415/1478/1432 f 1429/1492/1446 1422/1485/1439 1415/1478/1432 f 1415/1478/1432 1422/1485/1439 1404/1467/1421 f 1420/1483/1437 1430/1493/1447 1421/1484/1438 f 1421/1484/1438 1430/1493/1447 1431/1494/1448 f 1430/1493/1447 1420/1483/1437 1426/1489/1443 f 1426/1489/1443 1420/1483/1437 1411/1474/1428 f 1424/1487/1441 1309/1372/1326 1425/1488/1442 f 1425/1488/1442 1309/1372/1326 1311/1374/1328 f 1319/1382/1336 1320/1383/1337 1428/1491/1445 f 1428/1491/1445 1320/1383/1337 1429/1492/1446 f 1320/1383/1337 1313/1376/1330 1429/1492/1446 f 1429/1492/1446 1313/1376/1330 1422/1485/1439 f 1432/1495/1449 1433/1496/1450 1434/1497/1451 f 1434/1497/1451 1433/1496/1450 1435/1498/1452 f 1433/1496/1450 1432/1495/1449 1343/1406/1360 f 1343/1406/1360 1432/1495/1449 1342/1405/1359 f 1432/1495/1449 1305/1368/1322 1342/1405/1359 f 1342/1405/1359 1305/1368/1322 1281/1344/1298 f 1305/1368/1322 1432/1495/1449 1304/1367/1321 f 1304/1367/1321 1432/1495/1449 1434/1497/1451 f 1436/1499/1453 1437/1500/1454 1361/1424/1378 f 1361/1424/1378 1437/1500/1454 1359/1422/1376 f 1437/1500/1454 1438/1501/1455 1359/1422/1376 f 1359/1422/1376 1438/1501/1455 1350/1413/1367 f 1438/1501/1455 1437/1500/1454 1375/1438/1392 f 1375/1438/1392 1437/1500/1454 1390/1453/1407 f 1437/1500/1454 1436/1499/1453 1390/1453/1407 f 1390/1453/1407 1436/1499/1453 1394/1457/1411 f 1439/1502/1456 1440/1503/1457 1380/1443/1397 f 1380/1443/1397 1440/1503/1457 1441/1504/1458 f 1440/1503/1457 1442/1505/1459 1441/1504/1458 f 1441/1504/1458 1442/1505/1459 1356/1419/1373 f 1344/1407/1361 1443/1506/1460 1343/1406/1360 f 1343/1406/1360 1443/1506/1460 1444/1507/1461 f 1365/1428/1382 1444/1507/1461 1363/1426/1380 f 1363/1426/1380 1444/1507/1461 1443/1506/1460 f 1445/1508/1462 1446/1509/1463 1377/1440/1394 f 1377/1440/1394 1446/1509/1463 1376/1439/1393 f 1446/1509/1463 1438/1501/1455 1376/1439/1393 f 1376/1439/1393 1438/1501/1455 1375/1438/1392 f 1438/1501/1455 1446/1509/1463 1350/1413/1367 f 1350/1413/1367 1446/1509/1463 1348/1411/1365 f 1446/1509/1463 1445/1508/1462 1348/1411/1365 f 1348/1411/1365 1445/1508/1462 1347/1410/1364 f 1396/1459/1413 1395/1458/1412 1321/1384/1338 f 1321/1384/1338 1395/1458/1412 1322/1385/1339 f 1395/1458/1412 1368/1431/1385 1322/1385/1339 f 1322/1385/1339 1368/1431/1385 1325/1388/1342 f 1368/1431/1385 1364/1427/1381 1325/1388/1342 f 1325/1388/1342 1364/1427/1381 1447/1510/1464 f 1364/1427/1381 1363/1426/1380 1447/1510/1464 f 1447/1510/1464 1363/1426/1380 1439/1502/1456 f 1344/1407/1361 1341/1404/1358 1442/1505/1459 f 1442/1505/1459 1341/1404/1358 1448/1511/1465 f 1341/1404/1358 1283/1346/1300 1448/1511/1465 f 1448/1511/1465 1283/1346/1300 1290/1353/1307 f 1283/1346/1300 1284/1347/1301 1290/1353/1307 f 1290/1353/1307 1284/1347/1301 1288/1351/1305 f 1284/1347/1301 1265/1328/1282 1288/1351/1305 f 1288/1351/1305 1265/1328/1282 1287/1350/1304 f 1449/1512/1466 1285/1348/1302 1267/1330/1284 f 1267/1330/1284 1285/1348/1302 1287/1350/1304 f 1450/1513/1467 1291/1354/1308 1292/1355/1309 f 1405/1468/1422 1402/1465/1419 1406/1469/1423 f 1406/1469/1423 1402/1465/1419 1403/1466/1420 f 1423/1486/1440 1424/1487/1441 1403/1466/1420 f 1403/1466/1420 1424/1487/1441 1406/1469/1423 f 1314/1377/1331 1309/1372/1326 1423/1486/1440 f 1423/1486/1440 1309/1372/1326 1424/1487/1441 f 1278/1341/1295 1277/1340/1294 1451/1514/1468 f 1451/1514/1468 1277/1340/1294 1452/1515/1469 f 1452/1515/1469 1277/1340/1294 1453/1516/1470 f 1453/1516/1470 1277/1340/1294 1279/1342/1296 f 1302/1365/1319 1454/1517/1471 1279/1342/1296 f 1279/1342/1296 1454/1517/1471 1453/1516/1470 f 1454/1517/1471 1302/1365/1319 1455/1518/1472 f 1455/1518/1472 1302/1365/1319 1304/1367/1321 f 1434/1497/1451 1456/1519/1473 1304/1367/1321 f 1304/1367/1321 1456/1519/1473 1455/1518/1472 f 1434/1497/1451 1435/1498/1452 1456/1519/1473 f 1456/1519/1473 1435/1498/1452 1457/1520/1474 f 1435/1498/1452 1458/1521/1475 1457/1520/1474 f 1457/1520/1474 1458/1521/1475 1459/1522/1476 f 1458/1521/1475 1385/1448/1402 1459/1522/1476 f 1459/1522/1476 1385/1448/1402 1460/1523/1477 f 1385/1448/1402 1386/1449/1403 1460/1523/1477 f 1460/1523/1477 1386/1449/1403 1461/1524/1478 f 1386/1449/1403 1388/1451/1405 1461/1524/1478 f 1461/1524/1478 1388/1451/1405 1462/1525/1479 f 1388/1451/1405 1396/1459/1413 1462/1525/1479 f 1462/1525/1479 1396/1459/1413 1463/1526/1480 f 1396/1459/1413 1321/1384/1338 1463/1526/1480 f 1463/1526/1480 1321/1384/1338 1464/1527/1481 f 1465/1528/1482 1466/1529/1483 1323/1386/1340 f 1323/1386/1340 1466/1529/1483 1467/1530/1484 f 1466/1529/1483 1465/1528/1482 1468/1531/1485 f 1468/1531/1485 1465/1528/1482 1400/1463/1417 f 1469/1532/1486 1398/1461/1415 1470/1533/1487 f 1470/1533/1487 1398/1461/1415 1393/1456/1410 f 1398/1461/1415 1469/1532/1486 1400/1463/1417 f 1400/1463/1417 1469/1532/1486 1468/1531/1485 f 1392/1455/1409 1471/1534/1488 1393/1456/1410 f 1393/1456/1410 1471/1534/1488 1470/1533/1487 f 1471/1534/1488 1392/1455/1409 1472/1535/1489 f 1472/1535/1489 1392/1455/1409 1394/1457/1411 f 1473/1536/1490 1436/1499/1453 1474/1537/1491 f 1474/1537/1491 1436/1499/1453 1361/1424/1378 f 1436/1499/1453 1473/1536/1490 1394/1457/1411 f 1394/1457/1411 1473/1536/1490 1472/1535/1489 f 1360/1423/1377 1475/1538/1492 1361/1424/1378 f 1361/1424/1378 1475/1538/1492 1474/1537/1491 f 1475/1538/1492 1360/1423/1377 1476/1539/1493 f 1476/1539/1493 1360/1423/1377 1362/1425/1379 f 1261/1324/1278 1260/1323/1277 1362/1425/1379 f 1362/1425/1379 1260/1323/1277 1476/1539/1493 f 1315/1378/1332 1477/1540/1494 1312/1375/1329 f 1312/1375/1329 1477/1540/1494 1478/1541/1495 f 1477/1540/1494 1430/1493/1447 1478/1541/1495 f 1478/1541/1495 1430/1493/1447 1426/1489/1443 f 1430/1493/1447 1477/1540/1494 1431/1494/1448 f 1431/1494/1448 1477/1540/1494 1479/1542/1496 f 1477/1540/1494 1315/1378/1332 1479/1542/1496 f 1479/1542/1496 1315/1378/1332 1317/1380/1334 f 1464/1527/1481 1321/1384/1338 1467/1530/1484 f 1467/1530/1484 1321/1384/1338 1323/1386/1340 f 1480/1543/1497 1353/1416/1370 1407/1470/1424 f 1407/1470/1424 1353/1416/1370 1481/1544/1498 f 1353/1416/1370 1480/1543/1497 1332/1395/1349 f 1332/1395/1349 1480/1543/1497 1409/1472/1426 f 1480/1543/1497 1482/1545/1499 1409/1472/1426 f 1409/1472/1426 1482/1545/1499 1483/1546/1500 f 1482/1545/1499 1480/1543/1497 1408/1471/1425 f 1408/1471/1425 1480/1543/1497 1407/1470/1424 f 1442/1505/1459 1448/1511/1465 1356/1419/1373 f 1356/1419/1373 1448/1511/1465 1355/1418/1372 f 1355/1418/1372 1448/1511/1465 1306/1369/1323 f 1306/1369/1323 1448/1511/1465 1290/1353/1307 f 1484/1547/1501 1482/1545/1499 1425/1488/1442 f 1425/1488/1442 1482/1545/1499 1408/1471/1425 f 1482/1545/1499 1484/1547/1501 1483/1546/1500 f 1483/1546/1500 1484/1547/1501 1485/1548/1502 f 1445/1508/1462 1486/1549/1503 1347/1410/1364 f 1347/1410/1364 1486/1549/1503 1352/1415/1369 f 1352/1415/1369 1486/1549/1503 1356/1419/1373 f 1356/1419/1373 1486/1549/1503 1441/1504/1458 f 1486/1549/1503 1378/1441/1395 1441/1504/1458 f 1441/1504/1458 1378/1441/1395 1380/1443/1397 f 1486/1549/1503 1445/1508/1462 1378/1441/1395 f 1378/1441/1395 1445/1508/1462 1377/1440/1394 f 1487/1550/1504 1484/1547/1501 1311/1374/1328 f 1311/1374/1328 1484/1547/1501 1425/1488/1442 f 1484/1547/1501 1487/1550/1504 1485/1548/1502 f 1485/1548/1502 1487/1550/1504 1427/1490/1444 f 1325/1388/1342 1447/1510/1464 1326/1389/1343 f 1326/1389/1343 1447/1510/1464 1381/1444/1398 f 1447/1510/1464 1439/1502/1456 1381/1444/1398 f 1381/1444/1398 1439/1502/1456 1380/1443/1397 f 1478/1541/1495 1487/1550/1504 1312/1375/1329 f 1312/1375/1329 1487/1550/1504 1311/1374/1328 f 1465/1528/1482 1488/1551/1505 1400/1463/1417 f 1400/1463/1417 1488/1551/1505 1399/1462/1416 f 1488/1551/1505 1382/1445/1399 1399/1462/1416 f 1399/1462/1416 1382/1445/1399 1371/1434/1388 f 1382/1445/1399 1488/1551/1505 1326/1389/1343 f 1326/1389/1343 1488/1551/1505 1324/1387/1341 f 1488/1551/1505 1465/1528/1482 1324/1387/1341 f 1324/1387/1341 1465/1528/1482 1323/1386/1340 f 1313/1376/1330 1314/1377/1331 1422/1485/1439 f 1422/1485/1439 1314/1377/1331 1423/1486/1440 f 1265/1328/1282 1266/1329/1283 1287/1350/1304 f 1287/1350/1304 1266/1329/1283 1267/1330/1284 f 1306/1369/1323 1307/1370/1324 1354/1417/1371 f 1307/1370/1324 1481/1544/1498 1354/1417/1371 f 1353/1416/1370 1354/1417/1371 1481/1544/1498 f 1489/1552/1506 1261/1324/1278 1358/1421/1375 f 1358/1421/1375 1261/1324/1278 1362/1425/1379 f 1261/1324/1278 1489/1552/1506 1258/1321/1275 f 1258/1321/1275 1489/1552/1506 1259/1322/1276 f 1328/1391/1345 1489/1552/1506 1330/1393/1347 f 1330/1393/1347 1489/1552/1506 1358/1421/1375 f 1433/1496/1450 1490/1553/1507 1435/1498/1452 f 1435/1498/1452 1490/1553/1507 1458/1521/1475 f 1490/1553/1507 1383/1446/1400 1458/1521/1475 f 1458/1521/1475 1383/1446/1400 1385/1448/1402 f 1383/1446/1400 1490/1553/1507 1365/1428/1382 f 1365/1428/1382 1490/1553/1507 1444/1507/1461 f 1490/1553/1507 1433/1496/1450 1444/1507/1461 f 1444/1507/1461 1433/1496/1450 1343/1406/1360 f 1363/1426/1380 1443/1506/1460 1439/1502/1456 f 1439/1502/1456 1443/1506/1460 1440/1503/1457 f 1443/1506/1460 1344/1407/1361 1440/1503/1457 f 1440/1503/1457 1344/1407/1361 1442/1505/1459 f 1331/1394/1348 1345/1408/1362 1354/1417/1371 f 1354/1417/1371 1345/1408/1362 1351/1414/1368 f 1346/1409/1363 1345/1408/1362 1329/1392/1346 f 1329/1392/1346 1345/1408/1362 1331/1394/1348 f 1483/1546/1500 1485/1548/1502 1413/1476/1430 f 1413/1476/1430 1485/1548/1502 1427/1490/1444 f 1409/1472/1426 1483/1546/1500 1410/1473/1427 f 1410/1473/1427 1483/1546/1500 1413/1476/1430 f 1427/1490/1444 1487/1550/1504 1426/1489/1443 f 1426/1489/1443 1487/1550/1504 1478/1541/1495 f 1412/1475/1429 1337/1400/1354 1410/1473/1427 f 1410/1473/1427 1337/1400/1354 1334/1397/1351 f 1338/1401/1355 1333/1396/1350 1337/1400/1354 f 1337/1400/1354 1333/1396/1350 1334/1397/1351 f 1333/1396/1350 1338/1401/1355 1250/1313/1267 f 1250/1313/1267 1338/1401/1355 1256/1319/1273 f 1256/1319/1273 1257/1320/1274 1250/1313/1267 f 1250/1313/1267 1257/1320/1274 1249/1312/1266 f 1259/1322/1276 1247/1310/1264 1257/1320/1274 f 1257/1320/1274 1247/1310/1264 1249/1312/1266 f 1489/1552/1506 1328/1391/1345 1259/1322/1276 f 1259/1322/1276 1328/1391/1345 1247/1310/1264 f 1491/1554/1508 1492/1555/1509 1451/1556/1468 f 1451/1556/1468 1492/1555/1509 1278/1557/1295 f 1493/1558/1510 1492/1555/1509 1245/1308/1262 f 1245/1308/1262 1492/1555/1509 1061/1104/1078 f 1245/1308/1262 1061/1104/1078 1246/1309/1263 f 1246/1309/1263 1061/1104/1078 1059/1102/1076 f 1056/1099/1073 1057/1100/1074 1059/1102/1076 f 1059/1102/1076 1057/1100/1074 1246/1309/1263 f 1055/1098/1072 1056/1099/1073 1058/1101/1075 f 1058/1101/1075 1056/1099/1073 1059/1102/1076 f 1494/1559/1511 1495/1560/1512 1268/1561/1285 f 1268/1561/1285 1495/1560/1512 1267/1562/1284 f 1496/1563/1513 1495/1560/1512 1046/1089/1063 f 1046/1089/1063 1495/1560/1512 1244/1307/1261 f 1497/1564/1514 1498/1565/1515 1499/1566/1516 f 1499/1566/1516 1498/1565/1515 1500/1567/1517 f 1501/1568/1518 1502/1569/1519 1503/1570/1520 f 1503/1570/1520 1502/1569/1519 1051/1094/1068 f 1494/1559/1511 1493/1558/1510 1243/1306/1260 f 1243/1306/1260 1493/1558/1510 1245/1308/1262 f 1497/1564/1514 1496/1563/1513 1047/1090/1064 f 1047/1090/1064 1496/1563/1513 1046/1089/1063 f 1449/1512/1466 1499/1571/1516 1285/1348/1302 f 1285/1348/1302 1499/1571/1516 1292/1355/1309 f 1271/1334/1288 1295/1358/1312 1269/1332/1286 f 1269/1332/1286 1295/1358/1312 1500/1572/1517 f 1504/1573/1521 1498/1565/1515 1049/1092/1066 f 1049/1092/1066 1498/1565/1515 1048/1091/1065 f 1295/1358/1312 1292/1355/1309 1500/1572/1517 f 1500/1572/1517 1292/1355/1309 1499/1571/1516 f 1492/1555/1509 1491/1554/1508 1061/1104/1078 f 1061/1104/1078 1491/1554/1508 1060/1103/1077 f 1502/1569/1519 1501/1568/1518 1270/1574/1287 f 1270/1574/1287 1501/1568/1518 1274/1575/1291 f 1278/1557/1295 1492/1555/1509 1276/1576/1293 f 1276/1576/1293 1492/1555/1509 1493/1558/1510 f 1276/1576/1293 1493/1558/1510 1268/1561/1285 f 1268/1561/1285 1493/1558/1510 1494/1559/1511 f 1244/1307/1261 1495/1560/1512 1243/1306/1260 f 1243/1306/1260 1495/1560/1512 1494/1559/1511 f 1495/1560/1512 1496/1563/1513 1267/1562/1284 f 1267/1562/1284 1496/1563/1513 1449/1577/1466 f 1496/1563/1513 1497/1564/1514 1449/1577/1466 f 1449/1577/1466 1497/1564/1514 1499/1566/1516 f 1498/1565/1515 1497/1564/1514 1048/1091/1065 f 1048/1091/1065 1497/1564/1514 1047/1090/1064 f 1498/1565/1515 1504/1573/1521 1500/1567/1517 f 1500/1567/1517 1504/1573/1521 1269/1578/1286 f 1504/1573/1521 1502/1569/1519 1269/1578/1286 f 1269/1578/1286 1502/1569/1519 1270/1574/1287 f 1505/1579/1522 1405/1468/1422 1481/1544/1498 f 1481/1544/1498 1405/1468/1422 1407/1470/1424 f 1402/1465/1419 1405/1468/1422 1296/1359/1313 f 1296/1359/1313 1405/1468/1422 1505/1579/1522 f 1294/1357/1311 1296/1359/1313 1506/1580/1523 f 1506/1580/1523 1296/1359/1313 1505/1579/1522 f 1505/1579/1522 1481/1544/1498 1308/1371/1325 f 1308/1371/1325 1481/1544/1498 1307/1370/1324 f 1506/1580/1523 1505/1579/1522 1450/1513/1467 f 1450/1513/1467 1505/1579/1522 1308/1371/1325 f 1506/1580/1523 1450/1513/1467 1295/1358/1312 f 1295/1358/1312 1450/1513/1467 1292/1355/1309 f 1295/1358/1312 1294/1357/1311 1506/1580/1523 f 1450/1513/1467 1308/1371/1325 1291/1354/1308 f 1291/1354/1308 1308/1371/1325 1289/1352/1306 f 1049/1092/1066 1051/1094/1068 1504/1573/1521 f 1504/1573/1521 1051/1094/1068 1502/1569/1519 f 865/908/882 882/925/899 863/906/880 f 863/906/880 882/925/899 881/924/898 f 882/925/899 896/939/913 881/924/898 f 881/924/898 896/939/913 895/938/912 f 896/939/913 910/953/927 895/938/912 f 895/938/912 910/953/927 909/952/926 f 910/953/927 924/967/941 909/952/926 f 909/952/926 924/967/941 923/966/940 f 923/966/940 924/967/941 937/980/954 f 937/980/954 924/967/941 394/402/411 f 394/402/411 397/405/414 937/980/954 f 937/980/954 397/405/414 949/992/966 f 1035/1078/1052 1054/1097/1071 1042/1085/1059 f 1042/1085/1059 1054/1097/1071 1050/1093/1067 f 843/886/860 1507/1581/1524 842/885/859 f 842/885/859 1507/1581/1524 1508/1582/1525 f 1507/1581/1524 1509/1583/1526 1508/1582/1525 f 1508/1582/1525 1509/1583/1526 1510/1584/1527 f 1509/1583/1526 1511/1585/1528 1510/1584/1527 f 1510/1584/1527 1511/1585/1528 1512/1586/1529 f 1511/1585/1528 1513/1587/1530 1512/1586/1529 f 1512/1586/1529 1513/1587/1530 1514/1588/1531 f 1513/1587/1530 1515/1589/1532 1514/1588/1531 f 1514/1588/1531 1515/1589/1532 1516/1590/1533 f 1515/1589/1532 1517/1591/1534 1516/1590/1533 f 1516/1590/1533 1517/1591/1534 1518/1592/1535 f 1517/1591/1534 1519/1593/1536 1518/1592/1535 f 1518/1592/1535 1519/1593/1536 1520/1594/1537 f 1519/1593/1536 1521/1595/1538 1520/1594/1537 f 1520/1594/1537 1521/1595/1538 1522/1596/1539 f 1522/1596/1539 1521/1595/1538 1523/1597/1540 f 1523/1597/1540 1521/1595/1538 1524/1598/1541 f 1523/1597/1540 1524/1598/1541 1525/1599/1542 f 1525/1599/1542 1524/1598/1541 1526/1600/1543 f 1526/1600/1543 1527/1601/1544 1525/1599/1542 f 1525/1599/1542 1527/1601/1544 1528/1602/1545 f 1527/1601/1544 1529/1603/1546 1528/1602/1545 f 1528/1602/1545 1529/1603/1546 1530/1604/1547 f 1529/1603/1546 1531/1605/1548 1530/1604/1547 f 1530/1604/1547 1531/1605/1548 1532/1606/1549 f 871/914/888 1533/1607/1550 843/886/860 f 843/886/860 1533/1607/1550 1507/1581/1524 f 1533/1607/1550 1534/1608/1551 1507/1581/1524 f 1507/1581/1524 1534/1608/1551 1509/1583/1526 f 1534/1608/1551 1535/1609/1552 1509/1583/1526 f 1509/1583/1526 1535/1609/1552 1511/1585/1528 f 1535/1609/1552 1536/1610/1553 1511/1585/1528 f 1511/1585/1528 1536/1610/1553 1513/1587/1530 f 1536/1610/1553 1537/1611/1554 1513/1587/1530 f 1513/1587/1530 1537/1611/1554 1515/1589/1532 f 1537/1611/1554 1538/1612/1555 1515/1589/1532 f 1515/1589/1532 1538/1612/1555 1517/1591/1534 f 1538/1612/1555 1539/1613/1556 1517/1591/1534 f 1517/1591/1534 1539/1613/1556 1519/1593/1536 f 1539/1613/1556 1540/1614/1557 1519/1593/1536 f 1519/1593/1536 1540/1614/1557 1521/1595/1538 f 1521/1595/1538 1540/1614/1557 1524/1598/1541 f 1524/1598/1541 1540/1614/1557 1541/1615/1558 f 1524/1598/1541 1541/1615/1558 1526/1600/1543 f 1526/1600/1543 1541/1615/1558 1542/1616/1559 f 1542/1616/1559 1543/1617/1560 1526/1600/1543 f 1526/1600/1543 1543/1617/1560 1527/1601/1544 f 1544/1618/1561 1545/1619/1562 1529/1603/1546 f 1529/1603/1546 1545/1619/1562 1531/1605/1548 f 885/928/902 1546/1620/1563 871/914/888 f 871/914/888 1546/1620/1563 1533/1607/1550 f 1546/1620/1563 1547/1621/1564 1533/1607/1550 f 1533/1607/1550 1547/1621/1564 1534/1608/1551 f 1547/1621/1564 1548/1622/1565 1534/1608/1551 f 1534/1608/1551 1548/1622/1565 1535/1609/1552 f 1548/1622/1565 1549/1623/1566 1535/1609/1552 f 1535/1609/1552 1549/1623/1566 1536/1610/1553 f 1549/1623/1566 1550/1624/1567 1536/1610/1553 f 1536/1610/1553 1550/1624/1567 1537/1611/1554 f 1550/1624/1567 1551/1625/1568 1537/1611/1554 f 1537/1611/1554 1551/1625/1568 1538/1612/1555 f 1551/1625/1568 1552/1626/1569 1538/1612/1555 f 1538/1612/1555 1552/1626/1569 1539/1613/1556 f 1552/1626/1569 1553/1627/1570 1539/1613/1556 f 1539/1613/1556 1553/1627/1570 1540/1614/1557 f 1540/1614/1557 1553/1627/1570 1541/1615/1558 f 1541/1615/1558 1553/1627/1570 1554/1628/1571 f 1541/1615/1558 1554/1628/1571 1542/1616/1559 f 1542/1616/1559 1554/1628/1571 1555/1629/1572 f 1542/1616/1559 1555/1629/1572 1543/1617/1560 f 1543/1617/1560 1555/1629/1572 1556/1630/1573 f 1557/1631/1574 1558/1632/1575 1544/1618/1561 f 1544/1618/1561 1558/1632/1575 1545/1619/1562 f 885/928/902 898/941/915 1546/1620/1563 f 1546/1620/1563 898/941/915 1559/1633/1576 f 1559/1633/1576 1560/1634/1577 1546/1620/1563 f 1546/1620/1563 1560/1634/1577 1547/1621/1564 f 1560/1634/1577 1561/1635/1578 1547/1621/1564 f 1547/1621/1564 1561/1635/1578 1548/1622/1565 f 1561/1635/1578 1562/1636/1579 1548/1622/1565 f 1548/1622/1565 1562/1636/1579 1549/1623/1566 f 1562/1636/1579 1563/1637/1580 1549/1623/1566 f 1549/1623/1566 1563/1637/1580 1550/1624/1567 f 1563/1637/1580 1564/1638/1581 1550/1624/1567 f 1550/1624/1567 1564/1638/1581 1551/1625/1568 f 1564/1638/1581 1565/1639/1582 1551/1625/1568 f 1551/1625/1568 1565/1639/1582 1552/1626/1569 f 1565/1639/1582 1566/1640/1583 1552/1626/1569 f 1552/1626/1569 1566/1640/1583 1553/1627/1570 f 1553/1627/1570 1566/1640/1583 1554/1628/1571 f 1554/1628/1571 1566/1640/1583 1567/1641/1584 f 1567/1641/1584 1568/1642/1585 1554/1628/1571 f 1554/1628/1571 1568/1642/1585 1555/1629/1572 f 1555/1629/1572 1568/1642/1585 1556/1630/1573 f 1556/1630/1573 1568/1642/1585 1569/1643/1586 f 1570/1644/1587 1571/1645/1588 1557/1631/1574 f 1557/1631/1574 1571/1645/1588 1558/1632/1575 f 898/941/915 912/955/929 1559/1633/1576 f 1559/1633/1576 912/955/929 1572/1646/1589 f 1572/1646/1589 1573/1647/1590 1559/1633/1576 f 1559/1633/1576 1573/1647/1590 1560/1634/1577 f 1573/1647/1590 1574/1648/1591 1560/1634/1577 f 1560/1634/1577 1574/1648/1591 1561/1635/1578 f 1574/1648/1591 1575/1649/1592 1561/1635/1578 f 1561/1635/1578 1575/1649/1592 1562/1636/1579 f 1575/1649/1592 1576/1650/1593 1562/1636/1579 f 1562/1636/1579 1576/1650/1593 1563/1637/1580 f 1576/1650/1593 1577/1651/1594 1563/1637/1580 f 1563/1637/1580 1577/1651/1594 1564/1638/1581 f 1577/1651/1594 1578/1652/1595 1564/1638/1581 f 1564/1638/1581 1578/1652/1595 1565/1639/1582 f 1578/1652/1595 1579/1653/1596 1565/1639/1582 f 1565/1639/1582 1579/1653/1596 1566/1640/1583 f 1566/1640/1583 1579/1653/1596 1567/1641/1584 f 1567/1641/1584 1579/1653/1596 1580/1654/1597 f 1580/1654/1597 1581/1655/1598 1567/1641/1584 f 1567/1641/1584 1581/1655/1598 1568/1642/1585 f 1581/1655/1598 1582/1656/1599 1568/1642/1585 f 1568/1642/1585 1582/1656/1599 1569/1643/1586 f 1583/1657/1600 1584/1658/1601 1570/1644/1587 f 1570/1644/1587 1584/1658/1601 1571/1645/1588 f 912/955/929 926/969/943 1572/1646/1589 f 1572/1646/1589 926/969/943 1585/1659/1602 f 1585/1659/1602 1586/1660/1603 1572/1646/1589 f 1572/1646/1589 1586/1660/1603 1573/1647/1590 f 1586/1660/1603 1587/1661/1604 1573/1647/1590 f 1573/1647/1590 1587/1661/1604 1574/1648/1591 f 1587/1661/1604 1588/1662/1605 1574/1648/1591 f 1574/1648/1591 1588/1662/1605 1575/1649/1592 f 1588/1662/1605 1589/1663/1606 1575/1649/1592 f 1575/1649/1592 1589/1663/1606 1576/1650/1593 f 1589/1663/1606 1590/1664/1607 1576/1650/1593 f 1576/1650/1593 1590/1664/1607 1577/1651/1594 f 1590/1664/1607 1591/1665/1608 1577/1651/1594 f 1577/1651/1594 1591/1665/1608 1578/1652/1595 f 1591/1665/1608 1592/1666/1609 1578/1652/1595 f 1578/1652/1595 1592/1666/1609 1579/1653/1596 f 1579/1653/1596 1592/1666/1609 1580/1654/1597 f 1580/1654/1597 1592/1666/1609 1593/1667/1610 f 1593/1667/1610 1594/1668/1611 1580/1654/1597 f 1580/1654/1597 1594/1668/1611 1581/1655/1598 f 1594/1668/1611 1595/1669/1612 1581/1655/1598 f 1581/1655/1598 1595/1669/1612 1582/1656/1599 f 1596/1670/1613 1584/1658/1601 468/479/485 f 468/479/485 1584/1658/1601 469/480/486 f 926/969/943 938/981/955 1585/1659/1602 f 1585/1659/1602 938/981/955 1597/1671/1614 f 1597/1671/1614 1598/1672/1615 1585/1659/1602 f 1585/1659/1602 1598/1672/1615 1586/1660/1603 f 1598/1672/1615 1599/1673/1616 1586/1660/1603 f 1586/1660/1603 1599/1673/1616 1587/1661/1604 f 1599/1673/1616 1600/1674/1617 1587/1661/1604 f 1587/1661/1604 1600/1674/1617 1588/1662/1605 f 1600/1674/1617 1601/1675/1618 1588/1662/1605 f 1588/1662/1605 1601/1675/1618 1589/1663/1606 f 1601/1675/1618 1602/1676/1619 1589/1663/1606 f 1589/1663/1606 1602/1676/1619 1590/1664/1607 f 1602/1676/1619 1603/1677/1620 1590/1664/1607 f 1590/1664/1607 1603/1677/1620 1591/1665/1608 f 1603/1677/1620 1604/1678/1621 1591/1665/1608 f 1591/1665/1608 1604/1678/1621 1592/1666/1609 f 1592/1666/1609 1604/1678/1621 1593/1667/1610 f 1593/1667/1610 1604/1678/1621 1605/1679/1622 f 1593/1667/1610 1605/1679/1622 1594/1668/1611 f 1594/1668/1611 1605/1679/1622 1606/1680/1623 f 1606/1680/1623 1607/1681/1624 1594/1668/1611 f 1594/1668/1611 1607/1681/1624 1595/1669/1612 f 938/981/955 950/993/967 1597/1671/1614 f 1597/1671/1614 950/993/967 1608/1682/1625 f 1608/1682/1625 1609/1683/1626 1597/1671/1614 f 1597/1671/1614 1609/1683/1626 1598/1672/1615 f 1609/1683/1626 1610/1684/1627 1598/1672/1615 f 1598/1672/1615 1610/1684/1627 1599/1673/1616 f 1610/1684/1627 1611/1685/1628 1599/1673/1616 f 1599/1673/1616 1611/1685/1628 1600/1674/1617 f 1611/1685/1628 1612/1686/1629 1600/1674/1617 f 1600/1674/1617 1612/1686/1629 1601/1675/1618 f 1612/1686/1629 1613/1687/1630 1601/1675/1618 f 1601/1675/1618 1613/1687/1630 1602/1676/1619 f 1613/1687/1630 1614/1688/1631 1602/1676/1619 f 1602/1676/1619 1614/1688/1631 1603/1677/1620 f 1614/1688/1631 1615/1689/1632 1603/1677/1620 f 1603/1677/1620 1615/1689/1632 1604/1678/1621 f 1604/1678/1621 1615/1689/1632 1605/1679/1622 f 1605/1679/1622 1615/1689/1632 1616/1690/1633 f 1616/1690/1633 1617/1691/1634 1605/1679/1622 f 1605/1679/1622 1617/1691/1634 1606/1680/1623 f 1617/1691/1634 1618/1692/1635 1606/1680/1623 f 1606/1680/1623 1618/1692/1635 1607/1681/1624 f 950/993/967 962/1005/979 1608/1682/1625 f 1608/1682/1625 962/1005/979 1619/1693/1636 f 1619/1693/1636 1620/1694/1637 1608/1682/1625 f 1608/1682/1625 1620/1694/1637 1609/1683/1626 f 1620/1694/1637 1621/1695/1638 1609/1683/1626 f 1609/1683/1626 1621/1695/1638 1610/1684/1627 f 1621/1695/1638 1622/1696/1639 1610/1684/1627 f 1610/1684/1627 1622/1696/1639 1611/1685/1628 f 1622/1696/1639 1623/1697/1640 1611/1685/1628 f 1611/1685/1628 1623/1697/1640 1612/1686/1629 f 1623/1697/1640 1624/1698/1641 1612/1686/1629 f 1612/1686/1629 1624/1698/1641 1613/1687/1630 f 1624/1698/1641 1625/1699/1642 1613/1687/1630 f 1613/1687/1630 1625/1699/1642 1614/1688/1631 f 1625/1699/1642 1626/1700/1643 1614/1688/1631 f 1614/1688/1631 1626/1700/1643 1615/1689/1632 f 1615/1689/1632 1626/1700/1643 1616/1690/1633 f 1616/1690/1633 1626/1700/1643 1627/1701/1644 f 1627/1701/1644 1628/1702/1645 1616/1690/1633 f 1616/1690/1633 1628/1702/1645 1617/1691/1634 f 1628/1702/1645 1629/1703/1646 1617/1691/1634 f 1617/1691/1634 1629/1703/1646 1618/1692/1635 f 962/1005/979 974/1017/991 1619/1693/1636 f 1619/1693/1636 974/1017/991 1630/1704/1647 f 1630/1704/1647 1631/1705/1648 1619/1693/1636 f 1619/1693/1636 1631/1705/1648 1620/1694/1637 f 1631/1705/1648 1632/1706/1649 1620/1694/1637 f 1620/1694/1637 1632/1706/1649 1621/1695/1638 f 1632/1706/1649 1633/1707/1650 1621/1695/1638 f 1621/1695/1638 1633/1707/1650 1622/1696/1639 f 1633/1707/1650 1634/1708/1651 1622/1696/1639 f 1622/1696/1639 1634/1708/1651 1623/1697/1640 f 1634/1708/1651 1635/1709/1652 1623/1697/1640 f 1623/1697/1640 1635/1709/1652 1624/1698/1641 f 1635/1709/1652 1636/1710/1653 1624/1698/1641 f 1624/1698/1641 1636/1710/1653 1625/1699/1642 f 1636/1710/1653 1637/1711/1654 1625/1699/1642 f 1625/1699/1642 1637/1711/1654 1626/1700/1643 f 1626/1700/1643 1637/1711/1654 1627/1701/1644 f 1627/1701/1644 1637/1711/1654 1638/1712/1655 f 1627/1701/1644 1638/1712/1655 1628/1702/1645 f 1628/1702/1645 1638/1712/1655 1639/1713/1656 f 1628/1702/1645 1639/1713/1656 1629/1703/1646 f 1629/1703/1646 1639/1713/1656 1640/1714/1657 f 974/1017/991 986/1029/1003 1630/1704/1647 f 1630/1704/1647 986/1029/1003 1641/1715/1658 f 1641/1715/1658 1642/1716/1659 1630/1704/1647 f 1630/1704/1647 1642/1716/1659 1631/1705/1648 f 1642/1716/1659 1643/1717/1660 1631/1705/1648 f 1631/1705/1648 1643/1717/1660 1632/1706/1649 f 1643/1717/1660 1644/1718/1661 1632/1706/1649 f 1632/1706/1649 1644/1718/1661 1633/1707/1650 f 1644/1718/1661 1645/1719/1662 1633/1707/1650 f 1633/1707/1650 1645/1719/1662 1634/1708/1651 f 1645/1719/1662 1646/1720/1663 1634/1708/1651 f 1634/1708/1651 1646/1720/1663 1635/1709/1652 f 1646/1720/1663 1647/1721/1664 1635/1709/1652 f 1635/1709/1652 1647/1721/1664 1636/1710/1653 f 1636/1710/1653 1647/1721/1664 1637/1711/1654 f 1637/1711/1654 1647/1721/1664 1648/1722/1665 f 1637/1711/1654 1648/1722/1665 1638/1712/1655 f 1638/1712/1655 1648/1722/1665 1649/1723/1666 f 1638/1712/1655 1649/1723/1666 1639/1713/1656 f 1639/1713/1656 1649/1723/1666 1650/1724/1667 f 1639/1713/1656 1650/1724/1667 1640/1714/1657 f 1640/1714/1657 1650/1724/1667 1651/1725/1668 f 986/1029/1003 998/1041/1015 1641/1715/1658 f 1641/1715/1658 998/1041/1015 1652/1726/1669 f 1641/1715/1658 1652/1726/1669 1642/1716/1659 f 1642/1716/1659 1652/1726/1669 1653/1727/1670 f 1642/1716/1659 1653/1727/1670 1643/1717/1660 f 1643/1717/1660 1653/1727/1670 1654/1728/1671 f 1643/1717/1660 1654/1728/1671 1644/1718/1661 f 1644/1718/1661 1654/1728/1671 1655/1729/1672 f 1655/1729/1672 1656/1730/1673 1644/1718/1661 f 1644/1718/1661 1656/1730/1673 1645/1719/1662 f 1656/1730/1673 1657/1731/1674 1645/1719/1662 f 1645/1719/1662 1657/1731/1674 1646/1720/1663 f 1657/1731/1674 1658/1732/1675 1646/1720/1663 f 1646/1720/1663 1658/1732/1675 1647/1721/1664 f 1647/1721/1664 1658/1732/1675 1648/1722/1665 f 1648/1722/1665 1658/1732/1675 1659/1733/1676 f 1648/1722/1665 1659/1733/1676 1649/1723/1666 f 1649/1723/1666 1659/1733/1676 1660/1734/1677 f 1649/1723/1666 1660/1734/1677 1650/1724/1667 f 1650/1724/1667 1660/1734/1677 1661/1735/1678 f 1650/1724/1667 1661/1735/1678 1651/1725/1668 f 1651/1725/1668 1661/1735/1678 1662/1736/1679 f 998/1041/1015 1010/1053/1027 1652/1726/1669 f 1652/1726/1669 1010/1053/1027 1663/1737/1680 f 1652/1726/1669 1663/1737/1680 1653/1727/1670 f 1653/1727/1670 1663/1737/1680 1664/1738/1681 f 1653/1727/1670 1664/1738/1681 1654/1728/1671 f 1654/1728/1671 1664/1738/1681 1665/1739/1682 f 1657/1731/1674 1666/1740/1683 1658/1732/1675 f 1666/1740/1683 1667/1741/1684 1658/1732/1675 f 1658/1732/1675 1667/1741/1684 1659/1733/1676 f 1667/1741/1684 1668/1742/1685 1659/1733/1676 f 1659/1733/1676 1668/1742/1685 1660/1734/1677 f 1668/1742/1685 1669/1743/1686 1660/1734/1677 f 1660/1734/1677 1669/1743/1686 1661/1735/1678 f 1661/1735/1678 1669/1743/1686 1662/1736/1679 f 1662/1736/1679 1669/1743/1686 1670/1744/1687 f 1010/1053/1027 1019/1062/1036 1663/1737/1680 f 1663/1737/1680 1019/1062/1036 1671/1745/1688 f 1663/1737/1680 1671/1745/1688 1664/1738/1681 f 1664/1738/1681 1671/1745/1688 1672/1746/1689 f 1019/1062/1036 1022/1065/1039 1671/1745/1688 f 1671/1745/1688 1022/1065/1039 1673/1747/1690 f 1673/1747/1690 1674/1748/1691 1671/1745/1688 f 1671/1745/1688 1674/1748/1691 1672/1746/1689 f 1675/1749/1692 1668/1742/1685 1676/1750/1693 f 1676/1750/1693 1668/1742/1685 1667/1741/1684 f 1675/1749/1692 1677/1751/1694 1668/1742/1685 f 1668/1742/1685 1677/1751/1694 1669/1743/1686 f 1669/1743/1686 1677/1751/1694 1670/1744/1687 f 1670/1744/1687 1677/1751/1694 1678/1752/1695 f 1022/1065/1039 1029/1072/1046 1673/1747/1690 f 1673/1747/1690 1029/1072/1046 1679/1753/1696 f 1679/1753/1696 1680/1754/1697 1673/1747/1690 f 1673/1747/1690 1680/1754/1697 1674/1748/1691 f 1675/1749/1692 1676/1750/1693 1681/1755/1698 f 1681/1755/1698 1676/1750/1693 1682/1756/1699 f 1675/1749/1692 1681/1755/1698 1677/1751/1694 f 1677/1751/1694 1681/1755/1698 1683/1757/1700 f 1677/1751/1694 1683/1757/1700 1678/1752/1695 f 1678/1752/1695 1683/1757/1700 1684/1758/1701 f 1029/1072/1046 1036/1079/1053 1679/1753/1696 f 1679/1753/1696 1036/1079/1053 1685/1759/1702 f 1679/1753/1696 1685/1759/1702 1680/1754/1697 f 1680/1754/1697 1685/1759/1702 1686/1760/1703 f 1687/1761/1704 1681/1755/1698 1688/1762/1705 f 1688/1762/1705 1681/1755/1698 1682/1756/1699 f 1681/1755/1698 1687/1761/1704 1683/1757/1700 f 1683/1757/1700 1687/1761/1704 1689/1763/1706 f 1683/1757/1700 1689/1763/1706 1684/1758/1701 f 1684/1758/1701 1689/1763/1706 1690/1764/1707 f 1036/1079/1053 1043/1086/1060 1685/1759/1702 f 1685/1759/1702 1043/1086/1060 1691/1765/1708 f 1685/1759/1702 1691/1765/1708 1686/1760/1703 f 1686/1760/1703 1691/1765/1708 1692/1766/1709 f 1693/1767/1710 1687/1761/1704 1694/1768/1711 f 1694/1768/1711 1687/1761/1704 1688/1762/1705 f 1687/1761/1704 1693/1767/1710 1689/1763/1706 f 1689/1763/1706 1693/1767/1710 1695/1769/1712 f 1689/1763/1706 1695/1769/1712 1690/1764/1707 f 1690/1764/1707 1695/1769/1712 1696/1770/1713 f 1690/1764/1707 1696/1770/1713 1697/1771/1714 f 1697/1771/1714 1696/1770/1713 1698/1772/1715 f 1697/1771/1714 1052/1095/1069 1699/1773/1716 f 1699/1773/1716 1052/1095/1069 1053/1096/1070 f 1043/1086/1060 1055/1098/1072 1691/1765/1708 f 1691/1765/1708 1055/1098/1072 1700/1774/1717 f 1691/1765/1708 1700/1774/1717 1692/1766/1709 f 1692/1766/1709 1700/1774/1717 1701/1775/1718 f 1058/1101/1075 1060/1103/1077 1702/1776/1719 f 1702/1776/1719 1060/1103/1077 1703/1777/1720 f 1508/1582/1525 1062/1105/1079 842/885/859 f 1510/1584/1527 1062/1105/1079 1508/1582/1525 f 1512/1586/1529 1062/1105/1079 1510/1584/1527 f 1514/1588/1531 1062/1105/1079 1512/1586/1529 f 1516/1590/1533 1062/1105/1079 1514/1588/1531 f 1518/1592/1535 1062/1105/1079 1516/1590/1533 f 1520/1594/1537 1062/1105/1079 1518/1592/1535 f 1522/1596/1539 1062/1105/1079 1520/1594/1537 f 1523/1597/1540 1062/1105/1079 1522/1596/1539 f 1525/1599/1542 1062/1105/1079 1523/1597/1540 f 1528/1602/1545 1062/1105/1079 1525/1599/1542 f 1528/1602/1545 1530/1604/1547 1062/1105/1079 f 1532/1606/1549 1062/1105/1079 1530/1604/1547 f 1704/1778/1721 1705/1779/1722 1706/1780/1723 f 1706/1780/1723 1705/1779/1722 1707/1781/1724 f 1705/1779/1722 1704/1778/1721 1708/1782/1725 f 1708/1782/1725 1704/1778/1721 1709/1783/1726 f 1710/1784/1727 1705/1779/1722 1711/1785/1728 f 1711/1785/1728 1705/1779/1722 1708/1782/1725 f 1705/1779/1722 1710/1784/1727 1707/1781/1724 f 1707/1781/1724 1710/1784/1727 1712/1786/1729 f 1713/1787/1730 1714/1788/1731 1715/1789/1732 f 1715/1789/1732 1714/1788/1731 1716/1790/1733 f 1714/1788/1731 1713/1787/1730 1717/1791/1734 f 1717/1791/1734 1713/1787/1730 1718/1792/1735 f 1704/1778/1721 1714/1788/1731 1709/1783/1726 f 1709/1783/1726 1714/1788/1731 1717/1791/1734 f 1714/1788/1731 1704/1778/1721 1716/1790/1733 f 1716/1790/1733 1704/1778/1721 1706/1780/1723 f 1719/1793/1736 1720/1794/1737 1721/1795/1738 f 1721/1795/1738 1720/1794/1737 1722/1796/1739 f 1720/1794/1737 1719/1793/1736 1723/1797/1740 f 1723/1797/1740 1719/1793/1736 1724/1798/1741 f 1713/1787/1730 1720/1794/1737 1718/1792/1735 f 1718/1792/1735 1720/1794/1737 1723/1797/1740 f 1720/1794/1737 1713/1787/1730 1722/1796/1739 f 1722/1796/1739 1713/1787/1730 1715/1789/1732 f 1725/1799/1742 1726/1800/1743 1727/1801/1744 f 1727/1801/1744 1726/1800/1743 1728/1802/1745 f 1725/1799/1742 1727/1801/1744 1729/1803/1746 f 1729/1803/1746 1727/1801/1744 1730/1804/1747 f 1719/1793/1736 1725/1799/1742 1724/1798/1741 f 1724/1798/1741 1725/1799/1742 1729/1803/1746 f 1725/1799/1742 1719/1793/1736 1726/1800/1743 f 1726/1800/1743 1719/1793/1736 1721/1795/1738 f 1731/1805/1748 1732/1806/1749 1733/1807/1750 f 1733/1807/1750 1732/1806/1749 1734/1808/1751 f 1733/1807/1750 1734/1808/1751 1727/1801/1744 f 1727/1801/1744 1734/1808/1751 1730/1804/1747 f 1733/1807/1750 1735/1809/1752 1731/1805/1748 f 1731/1805/1748 1735/1809/1752 1736/1810/1753 f 1737/1811/1754 1738/1812/1755 1739/1813/1756 f 1739/1813/1756 1738/1812/1755 1740/1814/1757 f 1739/1813/1756 1741/1815/1758 1737/1811/1754 f 1737/1811/1754 1741/1815/1758 1742/1816/1759 f 1737/1811/1754 1742/1816/1759 1731/1805/1748 f 1731/1805/1748 1742/1816/1759 1732/1806/1749 f 1731/1805/1748 1736/1810/1753 1737/1811/1754 f 1737/1811/1754 1736/1810/1753 1738/1812/1755 f 1743/1817/1760 1744/1818/1761 1745/1819/1762 f 1745/1819/1762 1744/1818/1761 1746/1820/1763 f 1745/1819/1762 1747/1821/1764 1743/1817/1760 f 1743/1817/1760 1747/1821/1764 1748/1822/1765 f 1743/1817/1760 1748/1822/1765 1739/1813/1756 f 1739/1813/1756 1748/1822/1765 1741/1815/1758 f 1739/1813/1756 1740/1814/1757 1743/1817/1760 f 1743/1817/1760 1740/1814/1757 1744/1818/1761 f 1749/1823/1766 1750/1824/1767 1751/1825/1768 f 1751/1825/1768 1750/1824/1767 1752/1826/1769 f 1751/1825/1768 1753/1827/1770 1749/1823/1766 f 1749/1823/1766 1753/1827/1770 1754/1828/1771 f 1749/1823/1766 1754/1828/1771 1745/1819/1762 f 1745/1819/1762 1754/1828/1771 1747/1821/1764 f 1745/1819/1762 1746/1820/1763 1749/1823/1766 f 1749/1823/1766 1746/1820/1763 1750/1824/1767 f 1755/1829/1772 1756/1830/1773 1757/1831/1774 f 1757/1831/1774 1756/1830/1773 1758/1832/1775 f 1757/1831/1774 1759/1833/1776 1755/1829/1772 f 1755/1829/1772 1759/1833/1776 1760/1834/1777 f 1755/1829/1772 1760/1834/1777 1751/1825/1768 f 1751/1825/1768 1760/1834/1777 1753/1827/1770 f 1751/1825/1768 1752/1826/1769 1755/1829/1772 f 1755/1829/1772 1752/1826/1769 1756/1830/1773 f 1710/1784/1727 1761/1835/1778 1712/1786/1729 f 1712/1786/1729 1761/1835/1778 1762/1836/1779 f 1761/1835/1778 1710/1784/1727 1763/1837/1780 f 1763/1837/1780 1710/1784/1727 1711/1785/1728 f 1757/1831/1774 1761/1835/1778 1759/1833/1776 f 1759/1833/1776 1761/1835/1778 1763/1837/1780 f 1761/1835/1778 1757/1831/1774 1762/1836/1779 f 1762/1836/1779 1757/1831/1774 1758/1832/1775 f 1712/1786/1729 1752/1826/1769 1707/1781/1724 f 1707/1781/1724 1752/1826/1769 1750/1824/1767 f 1764/1838/1781 1765/1839/1782 1766/1840/1783 f 1766/1840/1783 1765/1839/1782 1767/1841/1784 f 1768/1842/1785 1769/1843/1786 1770/1844/1787 f 1770/1844/1787 1769/1843/1786 1767/1841/1784 f 1771/1845/1788 1772/1846/1789 1773/1847/1790 f 1773/1847/1790 1772/1846/1789 1774/1848/1791 f 1775/1849/1792 1776/1850/1793 1777/1851/1794 f 1777/1851/1794 1776/1850/1793 1774/1848/1791 f 1778/1852/1795 1776/1850/1793 1779/1853/1796 f 1779/1853/1796 1776/1850/1793 1780/1854/1797 f 1781/1855/1798 1765/1839/1782 1782/1856/1799 f 1782/1856/1799 1765/1839/1782 1780/1854/1797 f 1783/1857/1800 1784/1858/1801 1785/1859/1802 f 1785/1859/1802 1784/1858/1801 1769/1843/1786 f 1786/1860/1803 1787/1861/1804 1788/1862/1805 f 1788/1862/1805 1787/1861/1804 1785/1859/1802 f 1789/1863/1806 1787/1861/1804 1790/1864/1807 f 1790/1864/1807 1787/1861/1804 1791/1865/1808 f 1792/1866/1809 1793/1867/1810 1791/1865/1808 f 1791/1865/1808 1793/1867/1810 1794/1868/1811 f 1795/1869/1812 1796/1870/1813 1797/1871/1814 f 1797/1871/1814 1796/1870/1813 1798/1872/1815 f 1799/1873/1816 1800/1874/1817 1797/1871/1814 f 1797/1871/1814 1800/1874/1817 1794/1868/1811 f 1801/1875/1818 1802/1876/1819 1803/1877/1820 f 1803/1877/1820 1802/1876/1819 1798/1872/1815 f 1804/1878/1821 1805/1879/1822 1803/1877/1820 f 1803/1877/1820 1805/1879/1822 1806/1880/1823 f 1807/1881/1824 1808/1882/1825 1809/1883/1826 f 1809/1883/1826 1808/1882/1825 1806/1880/1823 f 1810/1884/1827 1811/1885/1828 1809/1883/1826 f 1809/1883/1826 1811/1885/1828 1812/1886/1829 f 1813/1887/1830 1814/1888/1831 1815/1889/1832 f 1815/1889/1832 1814/1888/1831 1812/1886/1829 f 1816/1890/1833 1817/1891/1834 1815/1889/1832 f 1815/1889/1832 1817/1891/1834 1818/1892/1835 f 1819/1893/1836 1820/1894/1837 1821/1895/1838 f 1821/1895/1838 1820/1894/1837 1818/1892/1835 f 1822/1896/1839 1823/1897/1840 1821/1895/1838 f 1821/1895/1838 1823/1897/1840 1772/1846/1789 f 1824/1898/1841 1825/1899/1842 1724/1798/1741 f 1724/1798/1741 1825/1899/1842 1723/1797/1740 f 1825/1899/1842 1826/1900/1843 1723/1797/1740 f 1723/1797/1740 1826/1900/1843 1718/1792/1735 f 1827/1901/1844 1828/1902/1845 1709/1783/1726 f 1709/1783/1726 1828/1902/1845 1708/1782/1725 f 1828/1902/1845 1829/1903/1846 1708/1782/1725 f 1708/1782/1725 1829/1903/1846 1711/1785/1728 f 1826/1900/1843 1830/1904/1847 1718/1792/1735 f 1718/1792/1735 1830/1904/1847 1717/1791/1734 f 1830/1904/1847 1827/1901/1844 1717/1791/1734 f 1717/1791/1734 1827/1901/1844 1709/1783/1726 f 1831/1905/1848 1832/1906/1849 1730/1804/1747 f 1730/1804/1747 1832/1906/1849 1729/1803/1746 f 1832/1906/1849 1824/1898/1841 1729/1803/1746 f 1729/1803/1746 1824/1898/1841 1724/1798/1741 f 1829/1903/1846 1833/1907/1850 1711/1785/1728 f 1711/1785/1728 1833/1907/1850 1763/1837/1780 f 1833/1907/1850 1834/1908/1851 1763/1837/1780 f 1763/1837/1780 1834/1908/1851 1759/1833/1776 f 1732/1806/1749 1835/1909/1852 1734/1808/1751 f 1734/1808/1751 1835/1909/1852 1836/1910/1853 f 1831/1905/1848 1730/1804/1747 1836/1910/1853 f 1836/1910/1853 1730/1804/1747 1734/1808/1751 f 1759/1833/1776 1834/1908/1851 1760/1834/1777 f 1760/1834/1777 1834/1908/1851 1837/1911/1854 f 1838/1912/1855 1753/1827/1770 1837/1911/1854 f 1837/1911/1854 1753/1827/1770 1760/1834/1777 f 1741/1815/1758 1839/1913/1856 1742/1816/1759 f 1742/1816/1759 1839/1913/1856 1840/1914/1857 f 1835/1909/1852 1732/1806/1749 1840/1914/1857 f 1840/1914/1857 1732/1806/1749 1742/1816/1759 f 1753/1827/1770 1838/1912/1855 1754/1828/1771 f 1754/1828/1771 1838/1912/1855 1841/1915/1858 f 1842/1916/1859 1747/1821/1764 1841/1915/1858 f 1841/1915/1858 1747/1821/1764 1754/1828/1771 f 1747/1821/1764 1842/1916/1859 1748/1822/1765 f 1748/1822/1765 1842/1916/1859 1843/1917/1860 f 1839/1913/1856 1741/1815/1758 1843/1917/1860 f 1843/1917/1860 1741/1815/1758 1748/1822/1765 f 1825/1918/1842 1824/1919/1841 1788/1862/1805 f 1788/1862/1805 1824/1919/1841 1786/1860/1803 f 1788/1862/1805 1768/1842/1785 1825/1918/1842 f 1825/1918/1842 1768/1842/1785 1826/1920/1843 f 1828/1921/1845 1827/1922/1844 1782/1856/1799 f 1782/1856/1799 1827/1922/1844 1781/1855/1798 f 1828/1921/1845 1782/1856/1799 1829/1923/1846 f 1829/1923/1846 1782/1856/1799 1775/1849/1792 f 1830/1924/1847 1826/1920/1843 1770/1844/1787 f 1770/1844/1787 1826/1920/1843 1768/1842/1785 f 1770/1844/1787 1781/1855/1798 1830/1924/1847 f 1830/1924/1847 1781/1855/1798 1827/1922/1844 f 1832/1925/1849 1831/1926/1848 1792/1866/1809 f 1792/1866/1809 1831/1926/1848 1793/1867/1810 f 1792/1866/1809 1786/1860/1803 1832/1925/1849 f 1832/1925/1849 1786/1860/1803 1824/1919/1841 f 1829/1923/1846 1775/1849/1792 1833/1927/1850 f 1833/1927/1850 1775/1849/1792 1777/1851/1794 f 1833/1927/1850 1777/1851/1794 1834/1928/1851 f 1834/1928/1851 1777/1851/1794 1823/1897/1840 f 1836/1929/1853 1835/1930/1852 1795/1869/1812 f 1795/1869/1812 1835/1930/1852 1796/1870/1813 f 1795/1869/1812 1793/1867/1810 1836/1929/1853 f 1836/1929/1853 1793/1867/1810 1831/1926/1848 f 1834/1928/1851 1823/1897/1840 1837/1931/1854 f 1837/1931/1854 1823/1897/1840 1822/1896/1839 f 1837/1931/1854 1822/1896/1839 1838/1932/1855 f 1838/1932/1855 1822/1896/1839 1817/1891/1834 f 1840/1933/1857 1839/1934/1856 1804/1878/1821 f 1804/1878/1821 1839/1934/1856 1805/1879/1822 f 1804/1878/1821 1796/1870/1813 1840/1933/1857 f 1840/1933/1857 1796/1870/1813 1835/1930/1852 f 1838/1932/1855 1817/1891/1834 1841/1935/1858 f 1841/1935/1858 1817/1891/1834 1816/1890/1833 f 1841/1935/1858 1816/1890/1833 1842/1936/1859 f 1842/1936/1859 1816/1890/1833 1811/1885/1828 f 1843/1937/1860 1842/1936/1859 1810/1884/1827 f 1810/1884/1827 1842/1936/1859 1811/1885/1828 f 1810/1884/1827 1805/1879/1822 1843/1937/1860 f 1843/1937/1860 1805/1879/1822 1839/1934/1856 f 1844/1938/1861 1845/1939/1862 1802/1876/1819 f 1802/1876/1819 1845/1939/1862 1799/1873/1816 f 1846/1940/1863 1800/1874/1817 1845/1939/1862 f 1845/1939/1862 1800/1874/1817 1799/1873/1816 f 1847/1941/1864 1848/1942/1865 1808/1882/1825 f 1808/1882/1825 1848/1942/1865 1801/1875/1818 f 1848/1942/1865 1844/1938/1861 1801/1875/1818 f 1801/1875/1818 1844/1938/1861 1802/1876/1819 f 1784/1858/1801 1849/1943/1866 1766/1840/1783 f 1766/1840/1783 1849/1943/1866 1850/1944/1867 f 1851/1945/1868 1764/1838/1781 1850/1944/1867 f 1850/1944/1867 1764/1838/1781 1766/1840/1783 f 1852/1946/1869 1853/1947/1870 1820/1894/1837 f 1820/1894/1837 1853/1947/1870 1813/1887/1830 f 1853/1947/1870 1854/1948/1871 1813/1887/1830 f 1813/1887/1830 1854/1948/1871 1814/1888/1831 f 1778/1852/1795 1855/1949/1872 1773/1847/1790 f 1773/1847/1790 1855/1949/1872 1856/1950/1873 f 1856/1950/1873 1857/1951/1874 1773/1847/1790 f 1773/1847/1790 1857/1951/1874 1771/1845/1788 f 1764/1838/1781 1851/1945/1868 1779/1853/1796 f 1779/1853/1796 1851/1945/1868 1858/1952/1875 f 1855/1949/1872 1778/1852/1795 1858/1952/1875 f 1858/1952/1875 1778/1852/1795 1779/1853/1796 f 1789/1863/1806 1859/1953/1876 1783/1857/1800 f 1783/1857/1800 1859/1953/1876 1860/1954/1877 f 1849/1943/1866 1784/1858/1801 1860/1954/1877 f 1860/1954/1877 1784/1858/1801 1783/1857/1800 f 1800/1874/1817 1846/1940/1863 1790/1864/1807 f 1790/1864/1807 1846/1940/1863 1861/1955/1878 f 1859/1953/1876 1789/1863/1806 1861/1955/1878 f 1861/1955/1878 1789/1863/1806 1790/1864/1807 f 1854/1948/1871 1862/1956/1879 1814/1888/1831 f 1814/1888/1831 1862/1956/1879 1807/1881/1824 f 1862/1956/1879 1847/1941/1864 1807/1881/1824 f 1807/1881/1824 1847/1941/1864 1808/1882/1825 f 1857/1951/1874 1863/1957/1880 1771/1845/1788 f 1771/1845/1788 1863/1957/1880 1819/1893/1836 f 1863/1957/1880 1852/1946/1869 1819/1893/1836 f 1819/1893/1836 1852/1946/1869 1820/1894/1837 f 1844/1938/1861 1864/1958/1881 1845/1939/1862 f 1845/1939/1862 1864/1958/1881 1865/1959/1882 f 1866/1960/1883 1846/1940/1863 1865/1959/1882 f 1865/1959/1882 1846/1940/1863 1845/1939/1862 f 1848/1942/1865 1847/1941/1864 1867/1961/1884 f 1867/1961/1884 1847/1941/1864 1868/1962/1885 f 1844/1938/1861 1848/1942/1865 1864/1958/1881 f 1864/1958/1881 1848/1942/1865 1867/1961/1884 f 1849/1943/1866 1869/1963/1886 1850/1944/1867 f 1850/1944/1867 1869/1963/1886 1870/1964/1887 f 1871/1965/1888 1851/1945/1868 1870/1964/1887 f 1870/1964/1887 1851/1945/1868 1850/1944/1867 f 1853/1947/1870 1852/1946/1869 1872/1966/1889 f 1872/1966/1889 1852/1946/1869 1873/1967/1890 f 1854/1948/1871 1853/1947/1870 1874/1968/1891 f 1874/1968/1891 1853/1947/1870 1872/1966/1889 f 1856/1950/1873 1855/1949/1872 1875/1969/1892 f 1875/1969/1892 1855/1949/1872 1876/1970/1893 f 1857/1951/1874 1856/1950/1873 1877/1971/1894 f 1877/1971/1894 1856/1950/1873 1875/1969/1892 f 1851/1945/1868 1871/1965/1888 1858/1952/1875 f 1858/1952/1875 1871/1965/1888 1878/1972/1895 f 1876/1970/1893 1855/1949/1872 1878/1972/1895 f 1878/1972/1895 1855/1949/1872 1858/1952/1875 f 1859/1953/1876 1879/1973/1896 1860/1954/1877 f 1860/1954/1877 1879/1973/1896 1880/1974/1897 f 1869/1963/1886 1849/1943/1866 1880/1974/1897 f 1880/1974/1897 1849/1943/1866 1860/1954/1877 f 1846/1940/1863 1866/1960/1883 1861/1955/1878 f 1861/1955/1878 1866/1960/1883 1881/1975/1898 f 1879/1973/1896 1859/1953/1876 1881/1975/1898 f 1881/1975/1898 1859/1953/1876 1861/1955/1878 f 1862/1956/1879 1854/1948/1871 1882/1976/1899 f 1882/1976/1899 1854/1948/1871 1874/1968/1891 f 1847/1941/1864 1862/1956/1879 1868/1962/1885 f 1868/1962/1885 1862/1956/1879 1882/1976/1899 f 1863/1957/1880 1857/1951/1874 1883/1977/1900 f 1883/1977/1900 1857/1951/1874 1877/1971/1894 f 1852/1946/1869 1863/1957/1880 1873/1967/1890 f 1873/1967/1890 1863/1957/1880 1883/1977/1900 f 1756/1830/1773 1752/1826/1769 1758/1832/1775 f 1752/1826/1769 1712/1786/1729 1758/1832/1775 f 1712/1786/1729 1762/1836/1779 1758/1832/1775 f 1706/1780/1723 1746/1820/1763 1716/1790/1733 f 1716/1790/1733 1746/1820/1763 1744/1818/1761 f 1707/1781/1724 1750/1824/1767 1706/1780/1723 f 1706/1780/1723 1750/1824/1767 1746/1820/1763 f 1716/1790/1733 1744/1818/1761 1715/1789/1732 f 1715/1789/1732 1744/1818/1761 1740/1814/1757 f 1726/1800/1743 1721/1795/1738 1728/1802/1745 f 1736/1810/1753 1735/1809/1752 1721/1795/1738 f 1721/1795/1738 1735/1809/1752 1728/1802/1745 f 1740/1814/1757 1738/1812/1755 1715/1789/1732 f 1715/1789/1732 1738/1812/1755 1722/1796/1739 f 1727/1801/1744 1728/1802/1745 1733/1807/1750 f 1733/1807/1750 1728/1802/1745 1735/1809/1752 f 1721/1795/1738 1722/1796/1739 1736/1810/1753 f 1736/1810/1753 1722/1796/1739 1738/1812/1755 f 1864/1958/1881 1884/1978/1901 1865/1959/1882 f 1865/1959/1882 1884/1978/1901 1885/1979/1902 f 1865/1959/1882 1885/1979/1902 1866/1960/1883 f 1866/1960/1883 1885/1979/1902 1694/1768/1711 f 1886/1980/1903 1887/1981/1904 1868/1962/1885 f 1868/1962/1885 1887/1981/1904 1867/1961/1884 f 1667/1741/1684 1666/1740/1683 1869/1963/1886 f 1869/1963/1886 1666/1740/1683 1870/1964/1887 f 1870/1964/1887 1666/1740/1683 1871/1965/1888 f 1871/1965/1888 1666/1740/1683 1657/1731/1674 f 1680/1754/1697 1686/1760/1703 1873/1967/1890 f 1873/1967/1890 1686/1760/1703 1872/1966/1889 f 1686/1760/1703 1692/1766/1709 1872/1966/1889 f 1872/1966/1889 1692/1766/1709 1874/1968/1891 f 1876/1970/1893 1655/1729/1672 1875/1969/1892 f 1875/1969/1892 1655/1729/1672 1665/1739/1682 f 1875/1969/1892 1665/1739/1682 1877/1971/1894 f 1877/1971/1894 1665/1739/1682 1672/1746/1689 f 1657/1731/1674 1656/1730/1673 1871/1965/1888 f 1871/1965/1888 1656/1730/1673 1878/1972/1895 f 1878/1972/1895 1656/1730/1673 1876/1970/1893 f 1876/1970/1893 1656/1730/1673 1655/1729/1672 f 1879/1973/1896 1682/1756/1699 1880/1974/1897 f 1880/1974/1897 1682/1756/1699 1676/1750/1693 f 1880/1974/1897 1676/1750/1693 1869/1963/1886 f 1869/1963/1886 1676/1750/1693 1667/1741/1684 f 1881/1975/1898 1688/1762/1705 1879/1973/1896 f 1879/1973/1896 1688/1762/1705 1682/1756/1699 f 1874/1968/1891 1692/1766/1709 1882/1976/1899 f 1882/1976/1899 1692/1766/1709 1701/1775/1718 f 1701/1775/1718 1886/1980/1903 1882/1976/1899 f 1882/1976/1899 1886/1980/1903 1868/1962/1885 f 1877/1971/1894 1672/1746/1689 1883/1977/1900 f 1883/1977/1900 1672/1746/1689 1674/1748/1691 f 1883/1977/1900 1674/1748/1691 1873/1967/1890 f 1873/1967/1890 1674/1748/1691 1680/1754/1697 f 1887/1981/1904 1884/1978/1901 1867/1961/1884 f 1867/1961/1884 1884/1978/1901 1864/1958/1881 f 1672/1746/1689 1665/1739/1682 1664/1738/1681 f 1665/1739/1682 1655/1729/1672 1654/1728/1671 f 1881/1975/1898 1866/1960/1883 1688/1762/1705 f 1688/1762/1705 1866/1960/1883 1694/1768/1711 f 1888/1982/1905 1889/1983/1906 1890/1984/1907 f 1889/1983/1906 1891/1985/1908 1890/1984/1907 f 1890/1984/1907 1891/1985/1908 1888/1982/1905 f 1892/1986/1909 1254/1317/1271 1893/1987/1910 f 1893/1987/1910 1254/1317/1271 1255/1318/1272 f 1894/1988/1911 1892/1986/1909 1895/1989/1912 f 1895/1989/1912 1892/1986/1909 1893/1987/1910 f 1892/1986/1909 1894/1988/1911 1896/1990/1913 f 1896/1990/1913 1894/1988/1911 1897/1991/1914 f 1262/1325/1279 1896/1990/1913 1260/1323/1277 f 1260/1323/1277 1896/1990/1913 1898/1992/1915 f 1254/1317/1271 1892/1986/1909 1262/1325/1279 f 1262/1325/1279 1892/1986/1909 1896/1990/1913 f 1899/1993/1916 1900/1994/1917 1901/1995/1918 f 1901/1995/1918 1900/1994/1917 1902/1996/1919 f 1900/1994/1917 1899/1993/1916 1903/1997/1920 f 1903/1997/1920 1899/1993/1916 1904/1998/1921 f 1905/1999/1922 1906/2000/1923 1907/2001/1924 f 1907/2001/1924 1906/2000/1923 1908/2002/1925 f 1273/1336/1290 1274/1337/1291 1905/1999/1922 f 1905/1999/1922 1274/1337/1291 1906/2000/1923 f 1900/1994/1917 1903/1997/1920 1909/2003/1926 f 1909/2003/1926 1903/1997/1920 1910/2004/1927 f 1911/2005/1928 1912/2006/1929 1910/2004/1927 f 1910/2004/1927 1912/2006/1929 1909/2003/1926 f 1912/2006/1929 1913/2007/1930 1909/2003/1926 f 1909/2003/1926 1913/2007/1930 1914/2008/1931 f 1902/1996/1919 1900/1994/1917 1914/2008/1931 f 1914/2008/1931 1900/1994/1917 1909/2003/1926 f 1915/2009/1932 1916/2010/1933 1917/2011/1934 f 1917/2011/1934 1916/2010/1933 1918/2012/1935 f 1901/1995/1918 1902/1996/1919 1915/2009/1932 f 1915/2009/1932 1902/1996/1919 1916/2010/1933 f 1919/2013/1936 1920/2014/1937 1921/2015/1938 f 1921/2015/1938 1920/2014/1937 1922/2016/1939 f 1923/2017/1940 1924/2018/1941 1922/2016/1939 f 1922/2016/1939 1924/2018/1941 1921/2015/1938 f 1924/2018/1941 1925/2019/1942 1921/2015/1938 f 1925/2019/1942 1926/2020/1943 1921/2015/1938 f 1921/2015/1938 1926/2020/1943 1919/2013/1936 f 1927/2021/1944 1907/2001/1924 1928/2022/1945 f 1928/2022/1945 1907/2001/1924 1929/2023/1946 f 1930/2024/1947 1927/2021/1944 1931/2025/1948 f 1931/2025/1948 1927/2021/1944 1928/2022/1945 f 1927/2021/1944 1930/2024/1947 1932/2026/1949 f 1932/2026/1949 1930/2024/1947 1933/2027/1950 f 1301/1364/1318 1300/1363/1317 1933/2027/1950 f 1933/2027/1950 1300/1363/1317 1932/2026/1949 f 1300/1363/1317 1273/1336/1290 1932/2026/1949 f 1932/2026/1949 1273/1336/1290 1905/1999/1922 f 1907/2001/1924 1927/2021/1944 1905/1999/1922 f 1905/1999/1922 1927/2021/1944 1932/2026/1949 f 1934/2028/1951 1935/2029/1952 1936/2030/1953 f 1936/2030/1953 1935/2029/1952 1937/2031/1954 f 1937/2031/1954 1918/2012/1935 1936/2030/1953 f 1936/2030/1953 1918/2012/1935 1916/2010/1933 f 1902/1996/1919 1914/2008/1931 1916/2010/1933 f 1916/2010/1933 1914/2008/1931 1936/2030/1953 f 1913/2007/1930 1934/2028/1951 1914/2008/1931 f 1914/2008/1931 1934/2028/1951 1936/2030/1953 f 1923/2017/1940 1938/2032/1955 1924/2018/1941 f 1924/2018/1941 1938/2032/1955 1939/2033/1956 f 1939/2033/1956 1940/2034/1957 1924/2018/1941 f 1941/2035/1958 1942/2036/1959 1943/2037/1960 f 1943/2037/1960 1942/2036/1959 1944/2038/1961 f 1942/2036/1959 1945/2039/1962 1944/2038/1961 f 1944/2038/1961 1945/2039/1962 1946/2040/1963 f 1947/2041/1964 1317/1380/1334 1948/2042/1965 f 1948/2042/1965 1317/1380/1334 1318/1381/1335 f 1318/1381/1335 1319/1382/1336 1948/2042/1965 f 1948/2042/1965 1319/1382/1336 1949/2043/1966 f 1945/2039/1962 1942/2036/1959 1949/2043/1966 f 1949/2043/1966 1942/2036/1959 1948/2042/1965 f 1941/2035/1958 1947/2041/1964 1942/2036/1959 f 1942/2036/1959 1947/2041/1964 1948/2042/1965 f 1950/2044/1967 1951/2045/1968 1952/2046/1969 f 1952/2046/1969 1951/2045/1968 1953/2047/1970 f 1954/2048/1971 1955/2049/1972 1953/2047/1970 f 1953/2047/1970 1955/2049/1972 1952/2046/1969 f 1956/2050/1973 1957/2051/1974 1958/2052/1975 f 1958/2052/1975 1957/2051/1974 1959/2053/1976 f 1960/2054/1977 1888/1982/1905 1957/2051/1974 f 1957/2051/1974 1888/1982/1905 1959/2053/1976 f 1959/2053/1976 1961/2055/1978 1958/2052/1975 f 1958/2052/1975 1961/2055/1978 1962/2056/1979 f 1891/1985/1908 1963/2057/1980 1888/1982/1905 f 1888/1982/1905 1963/2057/1980 1959/2053/1976 f 1963/2057/1980 1964/2058/1981 1959/2053/1976 f 1959/2053/1976 1964/2058/1981 1961/2055/1978 f 1965/2059/1982 1966/2060/1983 1967/2061/1984 f 1967/2061/1984 1966/2060/1983 1968/2062/1985 f 1966/2060/1983 1895/1989/1912 1968/2062/1985 f 1968/2062/1985 1895/1989/1912 1893/1987/1910 f 1893/1987/1910 1255/1318/1272 1968/2062/1985 f 1968/2062/1985 1255/1318/1272 1339/1402/1356 f 1340/1403/1357 1967/2061/1984 1339/1402/1356 f 1339/1402/1356 1967/2061/1984 1968/2062/1985 f 1917/2011/1934 1918/2012/1935 1969/2063/1986 f 1969/2063/1986 1918/2012/1935 1970/2064/1987 f 1969/2063/1986 1970/2064/1987 1971/2065/1988 f 1971/2065/1988 1970/2064/1987 1972/2066/1989 f 1973/2067/1990 1974/2068/1991 1975/2069/1992 f 1975/2069/1992 1974/2068/1991 1976/2070/1993 f 1977/2071/1994 1978/2072/1995 1976/2070/1993 f 1976/2070/1993 1978/2072/1995 1975/2069/1992 f 1978/2072/1995 1956/2050/1973 1975/2069/1992 f 1975/2069/1992 1956/2050/1973 1958/2052/1975 f 1974/2068/1991 1973/2067/1990 1979/2073/1996 f 1979/2073/1996 1973/2067/1990 1980/2074/1997 f 1961/2055/1978 1981/2075/1998 1962/2056/1979 f 1982/2076/1999 1962/2056/1979 1981/2075/1998 f 1982/2076/1999 1938/2032/1955 1980/2074/1997 f 1980/2074/1997 1938/2032/1955 1983/2077/2000 f 1984/2078/2001 1979/2073/1996 1983/2077/2000 f 1983/2077/2000 1979/2073/1996 1980/2074/1997 f 1956/2050/1973 1978/2072/1995 1985/2079/2002 f 1985/2079/2002 1978/2072/1995 1986/2080/2003 f 1978/2072/1995 1977/2071/1994 1986/2080/2003 f 1986/2080/2003 1977/2071/1994 1987/2081/2004 f 1988/2082/2005 1989/2083/2006 1987/2081/2004 f 1987/2081/2004 1989/2083/2006 1986/2080/2003 f 1989/2083/2006 1990/2084/2007 1986/2080/2003 f 1986/2080/2003 1990/2084/2007 1985/2079/2002 f 1991/2085/2008 1992/2086/2009 1993/2087/2010 f 1993/2087/2010 1992/2086/2009 1994/2088/2011 f 1992/2086/2009 1991/2085/2008 1995/2089/2012 f 1995/2089/2012 1991/2085/2008 1996/2090/2013 f 1997/2091/2014 1998/2092/2015 1999/2093/2016 f 1999/2093/2016 1998/2092/2015 2000/2094/2017 f 2001/2095/2018 2002/2096/2019 2000/2094/2017 f 2000/2094/2017 2002/2096/2019 1999/2093/2016 f 2002/2096/2019 2003/2097/2020 1999/2093/2016 f 1999/2093/2016 2003/2097/2020 2004/2098/2021 f 2005/2099/2022 1997/2091/2014 2004/2098/2021 f 2004/2098/2021 1997/2091/2014 1999/2093/2016 f 2006/2100/2023 2007/2101/2024 2008/2102/2025 f 2008/2102/2025 2007/2101/2024 2009/2103/2026 f 1954/2048/1971 2010/2104/2027 2009/2103/2026 f 2009/2103/2026 2010/2104/2027 2008/2102/2025 f 1998/2092/2015 1997/2091/2014 2010/2104/2027 f 2010/2104/2027 1997/2091/2014 2008/2102/2025 f 1997/2091/2014 2005/2099/2022 2008/2102/2025 f 2008/2102/2025 2005/2099/2022 2006/2100/2023 f 2011/2105/2028 2012/2106/2029 2013/2107/2030 f 2013/2107/2030 2012/2106/2029 2014/2108/2031 f 2015/2109/2032 2016/2110/2033 2014/2108/2031 f 2014/2108/2031 2016/2110/2033 2013/2107/2030 f 2016/2110/2033 1996/2090/2013 2013/2107/2030 f 2013/2107/2030 1996/2090/2013 1991/2085/2008 f 1993/2087/2010 2011/2105/2028 1991/2085/2008 f 1991/2085/2008 2011/2105/2028 2013/2107/2030 f 2003/2097/2020 2002/2096/2019 2017/2111/2034 f 2017/2111/2034 2002/2096/2019 2018/2112/2035 f 2002/2096/2019 2001/2095/2018 2018/2112/2035 f 2018/2112/2035 2001/2095/2018 2019/2113/2036 f 2020/2114/2037 2021/2115/2038 2019/2113/2036 f 2019/2113/2036 2021/2115/2038 2018/2112/2035 f 2021/2115/2038 2022/2116/2039 2018/2112/2035 f 2018/2112/2035 2022/2116/2039 2017/2111/2034 f 1995/2089/2012 1996/2090/2013 2023/2117/2040 f 2023/2117/2040 1996/2090/2013 2016/2110/2033 f 2024/2118/2041 2023/2117/2040 2015/2109/2032 f 2015/2109/2032 2023/2117/2040 2016/2110/2033 f 2025/2119/2042 2026/2120/2043 2027/2121/2044 f 2027/2121/2044 2026/2120/2043 2028/2122/2045 f 2026/2120/2043 2020/2114/2037 2028/2122/2045 f 2028/2122/2045 2020/2114/2037 2019/2113/2036 f 2019/2113/2036 2001/2095/2018 2028/2122/2045 f 2028/2122/2045 2001/2095/2018 2000/2094/2017 f 1998/2092/2015 2027/2121/2044 2000/2094/2017 f 2000/2094/2017 2027/2121/2044 2028/2122/2045 f 2029/2123/2046 1930/2024/1947 2030/2124/2047 f 2030/2124/2047 1930/2024/1947 1931/2025/1948 f 2031/2125/2048 2029/2123/2046 2032/2126/2049 f 2032/2126/2049 2029/2123/2046 2030/2124/2047 f 2033/2127/2050 2034/2128/2051 2035/2129/2052 f 2035/2129/2052 2034/2128/2051 2036/2130/2053 f 1961/2055/1978 1964/2058/1981 2037/2131/2054 f 2037/2131/2054 1964/2058/1981 2038/2132/2055 f 2038/2132/2055 2039/2133/2056 2040/2134/2057 f 2040/2134/2057 2039/2133/2056 2041/2135/2058 f 2029/2123/2046 2031/2125/2048 2042/2136/2059 f 2042/2136/2059 2031/2125/2048 2043/2137/2060 f 1417/1480/1434 1416/1479/1433 2043/2137/2060 f 2043/2137/2060 1416/1479/1433 2042/2136/2059 f 1416/1479/1433 1301/1364/1318 2042/2136/2059 f 2042/2136/2059 1301/1364/1318 1933/2027/1950 f 1930/2024/1947 2029/2123/2046 1933/2027/1950 f 1933/2027/1950 2029/2123/2046 2042/2136/2059 f 1965/2059/1982 1967/2061/1984 2039/2133/2056 f 2039/2133/2056 1967/2061/1984 2044/2138/2061 f 1967/2061/1984 1340/1403/1357 2044/2138/2061 f 2044/2138/2061 1340/1403/1357 1419/1482/1436 f 1421/1484/1438 2045/2139/2062 1419/1482/1436 f 1419/1482/1436 2045/2139/2062 2044/2138/2061 f 2045/2139/2062 2041/2135/2058 2044/2138/2061 f 2044/2138/2061 2041/2135/2058 2039/2133/2056 f 2046/2140/2063 2031/2125/2048 2047/2141/2064 f 2047/2141/2064 2031/2125/2048 2032/2126/2049 f 2034/2128/2051 2033/2127/2050 2048/2142/2065 f 2048/2142/2065 2033/2127/2050 2049/2143/2066 f 2040/2134/2057 2041/2135/2058 2050/2144/2067 f 2050/2144/2067 2041/2135/2058 2051/2145/2068 f 1428/1491/1445 1417/1480/1434 2052/2146/2069 f 2052/2146/2069 1417/1480/1434 2043/2137/2060 f 2031/2125/2048 2046/2140/2063 2043/2137/2060 f 2043/2137/2060 2046/2140/2063 2052/2146/2069 f 2045/2139/2062 1421/1484/1438 2053/2147/2070 f 2053/2147/2070 1421/1484/1438 1431/1494/1448 f 2041/2135/2058 2045/2139/2062 2051/2145/2068 f 2051/2145/2068 2045/2139/2062 2053/2147/2070 f 2048/2142/2065 2049/2143/2066 1944/2038/1961 f 1944/2038/1961 2049/2143/2066 1943/2037/1960 f 1319/1382/1336 1428/1491/1445 1949/2043/1966 f 1949/2043/1966 1428/1491/1445 2052/2146/2069 f 2046/2140/2063 1945/2039/1962 2052/2146/2069 f 2052/2146/2069 1945/2039/1962 1949/2043/1966 f 2054/2148/2071 2055/2149/2072 2056/2150/2073 f 2056/2150/2073 2055/2149/2072 2057/2151/2074 f 2055/2149/2072 1972/2066/1989 2057/2151/2074 f 2057/2151/2074 1972/2066/1989 1970/2064/1987 f 1918/2012/1935 1937/2031/1954 1970/2064/1987 f 1970/2064/1987 1937/2031/1954 2057/2151/2074 f 1937/2031/1954 1935/2029/1952 2057/2151/2074 f 2057/2151/2074 1935/2029/1952 2056/2150/2073 f 2058/2152/2075 1988/2082/2005 2059/2153/2076 f 2059/2153/2076 1988/2082/2005 1987/2081/2004 f 1977/2071/1994 2060/2154/2077 1987/2081/2004 f 1987/2081/2004 2060/2154/2077 2059/2153/2076 f 2060/2154/2077 2003/2097/2020 2059/2153/2076 f 2059/2153/2076 2003/2097/2020 2017/2111/2034 f 2022/2116/2039 2058/2152/2075 2017/2111/2034 f 2017/2111/2034 2058/2152/2075 2059/2153/2076 f 2061/2155/2078 2007/2101/2024 2062/2156/2079 f 2062/2156/2079 2007/2101/2024 2063/2157/2080 f 1984/2078/2001 2064/2158/2081 2063/2157/2080 f 2063/2157/2080 2064/2158/2081 2062/2156/2079 f 1971/2065/1988 1972/2066/1989 2065/2159/2082 f 2065/2159/2082 1972/2066/1989 2066/2160/2083 f 1993/2087/2010 1994/2088/2011 2066/2160/2083 f 2066/2160/2083 1994/2088/2011 2065/2159/2082 f 2067/2161/2084 2005/2099/2022 2068/2162/2085 f 2068/2162/2085 2005/2099/2022 2004/2098/2021 f 2003/2097/2020 2060/2154/2077 2004/2098/2021 f 2004/2098/2021 2060/2154/2077 2068/2162/2085 f 2060/2154/2077 1977/2071/1994 2068/2162/2085 f 2068/2162/2085 1977/2071/1994 1976/2070/1993 f 1974/2068/1991 2067/2161/2084 1976/2070/1993 f 1976/2070/1993 2067/2161/2084 2068/2162/2085 f 2024/2118/2041 1950/2044/1967 2023/2117/2040 f 2023/2117/2040 1950/2044/1967 1952/2046/1969 f 1955/2049/1972 1995/2089/2012 1952/2046/1969 f 1952/2046/1969 1995/2089/2012 2023/2117/2040 f 1995/2089/2012 1955/2049/1972 1992/2086/2009 f 1992/2086/2009 1955/2049/1972 2069/2163/2086 f 2061/2155/2078 1994/2088/2011 2069/2163/2086 f 2069/2163/2086 1994/2088/2011 1992/2086/2009 f 1971/2065/1988 2064/2158/2081 1969/2063/1986 f 1969/2063/1986 2064/2158/2081 2070/2164/2087 f 1923/2017/1940 1917/2011/1934 2070/2164/2087 f 2070/2164/2087 1917/2011/1934 1969/2063/1986 f 1917/2011/1934 1923/2017/1940 1915/2009/1932 f 1915/2009/1932 1923/2017/1940 1922/2016/1939 f 1920/2014/1937 1901/1995/1918 1922/2016/1939 f 1922/2016/1939 1901/1995/1918 1915/2009/1932 f 1920/2014/1937 1919/2013/1936 1904/1998/1921 f 1904/1998/1921 1919/2013/1936 2071/2165/2088 f 2072/2166/2089 1926/2020/1943 1925/2019/1942 f 2036/2130/2053 2034/2128/2051 2030/2124/2047 f 2030/2124/2047 2034/2128/2051 2032/2126/2049 f 2034/2128/2051 2048/2142/2065 2032/2126/2049 f 2032/2126/2049 2048/2142/2065 2047/2141/2064 f 2048/2142/2065 1944/2038/1961 2047/2141/2064 f 2047/2141/2064 1944/2038/1961 1946/2040/1963 f 1451/1514/1468 1452/1515/1469 1911/2005/1928 f 1911/2005/1928 1452/1515/1469 1912/2006/1929 f 1452/1515/1469 1453/1516/1470 1912/2006/1929 f 1912/2006/1929 1453/1516/1470 1913/2007/1930 f 1453/1516/1470 1454/1517/1471 1913/2007/1930 f 1913/2007/1930 1454/1517/1471 1934/2028/1951 f 1454/1517/1471 1455/1518/1472 1934/2028/1951 f 1934/2028/1951 1455/1518/1472 1935/2029/1952 f 1455/1518/1472 1456/1519/1473 1935/2029/1952 f 1935/2029/1952 1456/1519/1473 2056/2150/2073 f 1457/1520/1474 2054/2148/2071 1456/1519/1473 f 1456/1519/1473 2054/2148/2071 2056/2150/2073 f 2054/2148/2071 1457/1520/1474 2073/2167/2090 f 2073/2167/2090 1457/1520/1474 1459/1522/1476 f 1460/1523/1477 2012/2106/2029 1459/1522/1476 f 1459/1522/1476 2012/2106/2029 2073/2167/2090 f 2012/2106/2029 1460/1523/1477 2014/2108/2031 f 2014/2108/2031 1460/1523/1477 1461/1524/1478 f 1462/1525/1479 2015/2109/2032 1461/1524/1478 f 1461/1524/1478 2015/2109/2032 2014/2108/2031 f 1463/1526/1480 2024/2118/2041 1462/1525/1479 f 1462/1525/1479 2024/2118/2041 2015/2109/2032 f 1464/1527/1481 1950/2044/1967 1463/1526/1480 f 1463/1526/1480 1950/2044/1967 2024/2118/2041 f 1467/1530/1484 1466/1529/1483 1951/2045/1968 f 1951/2045/1968 1466/1529/1483 2074/2168/2091 f 1466/1529/1483 1468/1531/1485 2074/2168/2091 f 2074/2168/2091 1468/1531/1485 2025/2119/2042 f 1469/1532/1486 1470/1533/1487 2026/2120/2043 f 2026/2120/2043 1470/1533/1487 2020/2114/2037 f 1468/1531/1485 1469/1532/1486 2025/2119/2042 f 2025/2119/2042 1469/1532/1486 2026/2120/2043 f 1470/1533/1487 1471/1534/1488 2020/2114/2037 f 2020/2114/2037 1471/1534/1488 2021/2115/2038 f 1471/1534/1488 1472/1535/1489 2021/2115/2038 f 2021/2115/2038 1472/1535/1489 2022/2116/2039 f 1473/1536/1490 1474/1537/1491 2058/2152/2075 f 2058/2152/2075 1474/1537/1491 1988/2082/2005 f 1472/1535/1489 1473/1536/1490 2022/2116/2039 f 2022/2116/2039 1473/1536/1490 2058/2152/2075 f 1474/1537/1491 1475/1538/1492 1988/2082/2005 f 1988/2082/2005 1475/1538/1492 1989/2083/2006 f 1475/1538/1492 1476/1539/1493 1989/2083/2006 f 1989/2083/2006 1476/1539/1493 1990/2084/2007 f 1476/1539/1493 1260/1323/1277 1990/2084/2007 f 1990/2084/2007 1260/1323/1277 1898/1992/1915 f 1947/2041/1964 1941/2035/1958 2075/2169/2092 f 2075/2169/2092 1941/2035/1958 2076/2170/2093 f 2051/2145/2068 2053/2147/2070 2076/2170/2093 f 2076/2170/2093 2053/2147/2070 2075/2169/2092 f 2053/2147/2070 1431/1494/1448 2075/2169/2092 f 2075/2169/2092 1431/1494/1448 1479/1542/1496 f 1317/1380/1334 1947/2041/1964 1479/1542/1496 f 1479/1542/1496 1947/2041/1964 2075/2169/2092 f 1464/1527/1481 1467/1530/1484 1950/2044/1967 f 1950/2044/1967 1467/1530/1484 1951/2045/1968 f 2077/2171/2094 1981/2075/1998 2035/2129/2052 f 2035/2129/2052 1981/2075/1998 2078/2172/2095 f 1981/2075/1998 1961/2055/1978 2078/2172/2095 f 2078/2172/2095 1961/2055/1978 2037/2131/2054 f 2079/2173/2096 2080/2174/2097 2037/2131/2054 f 2037/2131/2054 2080/2174/2097 2078/2172/2095 f 2080/2174/2097 2033/2127/2050 2078/2172/2095 f 2078/2172/2095 2033/2127/2050 2035/2129/2052 f 2064/2158/2081 1984/2078/2001 2070/2164/2087 f 2070/2164/2087 1984/2078/2001 1983/2077/2000 f 1923/2017/1940 2070/2164/2087 1938/2032/1955 f 1938/2032/1955 2070/2164/2087 1983/2077/2000 f 2033/2127/2050 2080/2174/2097 2049/2143/2066 f 2049/2143/2066 2080/2174/2097 2081/2175/2098 f 2080/2174/2097 2079/2173/2096 2081/2175/2098 f 2081/2175/2098 2079/2173/2096 2082/2176/2099 f 2067/2161/2084 1974/2068/1991 2083/2177/2100 f 2083/2177/2100 1974/2068/1991 1979/2073/1996 f 1979/2073/1996 1984/2078/2001 2083/2177/2100 f 2083/2177/2100 1984/2078/2001 2063/2157/2080 f 2007/2101/2024 2006/2100/2023 2063/2157/2080 f 2063/2157/2080 2006/2100/2023 2083/2177/2100 f 2005/2099/2022 2067/2161/2084 2006/2100/2023 f 2006/2100/2023 2067/2161/2084 2083/2177/2100 f 2049/2143/2066 2081/2175/2098 1943/2037/1960 f 1943/2037/1960 2081/2175/2098 2084/2178/2101 f 2082/2176/2099 2050/2144/2067 2081/2175/2098 f 2081/2175/2098 2050/2144/2067 2084/2178/2101 f 1955/2049/1972 1954/2048/1971 2069/2163/2086 f 2069/2163/2086 1954/2048/1971 2009/2103/2026 f 2007/2101/2024 2061/2155/2078 2009/2103/2026 f 2009/2103/2026 2061/2155/2078 2069/2163/2086 f 2076/2170/2093 1941/2035/1958 2084/2178/2101 f 2084/2178/2101 1941/2035/1958 1943/2037/1960 f 2074/2168/2091 2025/2119/2042 2085/2179/2102 f 2085/2179/2102 2025/2119/2042 2027/2121/2044 f 1998/2092/2015 2010/2104/2027 2027/2121/2044 f 2027/2121/2044 2010/2104/2027 2085/2179/2102 f 2010/2104/2027 1954/2048/1971 2085/2179/2102 f 2085/2179/2102 1954/2048/1971 1953/2047/1970 f 1951/2045/1968 2074/2168/2091 1953/2047/1970 f 1953/2047/1970 2074/2168/2091 2085/2179/2102 f 1945/2039/1962 2046/2140/2063 1946/2040/1963 f 1946/2040/1963 2046/2140/2063 2047/2141/2064 f 1901/1995/1918 1920/2014/1937 1899/1993/1916 f 1899/1993/1916 1920/2014/1937 1904/1998/1921 f 1938/2032/1955 1982/2076/1999 1939/2033/1956 f 1939/2033/1956 1982/2076/1999 2077/2171/2094 f 1981/2075/1998 2077/2171/2094 1982/2076/1999 f 1990/2084/2007 1898/1992/1915 1985/2079/2002 f 1985/2079/2002 1898/1992/1915 2086/2180/2103 f 1898/1992/1915 1896/1990/1913 2086/2180/2103 f 1896/1990/1913 1897/1991/1914 2086/2180/2103 f 1957/2051/1974 1956/2050/1973 2086/2180/2103 f 2086/2180/2103 1956/2050/1973 1985/2079/2002 f 2055/2149/2072 2054/2148/2071 2087/2181/2104 f 2087/2181/2104 2054/2148/2071 2073/2167/2090 f 2012/2106/2029 2011/2105/2028 2073/2167/2090 f 2073/2167/2090 2011/2105/2028 2087/2181/2104 f 2011/2105/2028 1993/2087/2010 2087/2181/2104 f 2087/2181/2104 1993/2087/2010 2066/2160/2083 f 1972/2066/1989 2055/2149/2072 2066/2160/2083 f 2066/2160/2083 2055/2149/2072 2087/2181/2104 f 1994/2088/2011 2061/2155/2078 2065/2159/2082 f 2065/2159/2082 2061/2155/2078 2062/2156/2079 f 2064/2158/2081 1971/2065/1988 2062/2156/2079 f 2062/2156/2079 1971/2065/1988 2065/2159/2082 f 1962/2056/1979 1982/2076/1999 1973/2067/1990 f 1973/2067/1990 1982/2076/1999 1980/2074/1997 f 1962/2056/1979 1973/2067/1990 1958/2052/1975 f 1958/2052/1975 1973/2067/1990 1975/2069/1992 f 2079/2173/2096 2040/2134/2057 2082/2176/2099 f 2082/2176/2099 2040/2134/2057 2050/2144/2067 f 2040/2134/2057 2079/2173/2096 2038/2132/2055 f 2038/2132/2055 2079/2173/2096 2037/2131/2054 f 2050/2144/2067 2051/2145/2068 2084/2178/2101 f 2084/2178/2101 2051/2145/2068 2076/2170/2093 f 1964/2058/1981 1965/2059/1982 2038/2132/2055 f 2038/2132/2055 1965/2059/1982 2039/2133/2056 f 1966/2060/1983 1965/2059/1982 1963/2057/1980 f 1963/2057/1980 1965/2059/1982 1964/2058/1981 f 1963/2057/1980 1891/1985/1908 1966/2060/1983 f 1966/2060/1983 1891/1985/1908 1895/1989/1912 f 1889/1983/1906 1894/1988/1911 1891/1985/1908 f 1891/1985/1908 1894/1988/1911 1895/1989/1912 f 1897/1991/1914 1894/1988/1911 1960/2054/1977 f 1960/2054/1977 1894/1988/1911 1889/1983/1906 f 2086/2180/2103 1897/1991/1914 1957/2051/1974 f 1957/2051/1974 1897/1991/1914 1960/2054/1977 f 1911/2182/1928 2088/2183/2105 1451/1556/1468 f 1451/1556/1468 2088/2183/2105 1491/1554/1508 f 1703/1777/1720 2088/2183/2105 1887/1981/1904 f 1887/1981/1904 2088/2183/2105 2089/2184/2106 f 1887/1981/1904 1886/1980/1903 1703/1777/1720 f 1703/1777/1720 1886/1980/1903 1702/1776/1719 f 1886/1980/1903 1701/1775/1718 1702/1776/1719 f 1702/1776/1719 1701/1775/1718 1700/1774/1717 f 1055/1098/1072 1058/1101/1075 1700/1774/1717 f 1700/1774/1717 1058/1101/1075 1702/1776/1719 f 1903/2185/1920 1904/2186/1921 2090/2187/2107 f 2090/2187/2107 1904/2186/1921 2091/2188/2108 f 1694/1768/1711 1885/1979/1902 2092/2189/2109 f 2092/2189/2109 1885/1979/1902 2091/2188/2108 f 2093/2190/2110 2094/2191/2111 2095/2192/2112 f 2095/2192/2112 2094/2191/2111 2096/2193/2113 f 1698/1772/1715 2097/2194/2114 1503/1570/1520 f 1503/1570/1520 2097/2194/2114 1501/1568/1518 f 1887/1981/1904 2089/2184/2106 1884/1978/1901 f 1884/1978/1901 2089/2184/2106 2090/2187/2107 f 1694/1768/1711 2092/2189/2109 1693/1767/1710 f 1693/1767/1710 2092/2189/2109 2096/2193/2113 f 1919/2013/1936 1926/2020/1943 2071/2165/2088 f 2071/2165/2088 1926/2020/1943 2095/2195/2112 f 1907/2001/1924 1908/2002/1925 1929/2023/1946 f 1929/2023/1946 1908/2002/1925 2093/2196/2110 f 1695/1769/1712 2094/2191/2111 1696/1770/1713 f 1696/1770/1713 2094/2191/2111 2098/2197/2115 f 2095/2195/2112 1926/2020/1943 2093/2196/2110 f 2093/2196/2110 1926/2020/1943 1929/2023/1946 f 1060/1103/1077 1491/1554/1508 1703/1777/1720 f 1703/1777/1720 1491/1554/1508 2088/2183/2105 f 1274/1575/1291 1501/1568/1518 1906/2198/1923 f 1906/2198/1923 1501/1568/1518 2097/2194/2114 f 1911/2182/1928 1910/2199/1927 2088/2183/2105 f 2088/2183/2105 1910/2199/1927 2089/2184/2106 f 1910/2199/1927 1903/2185/1920 2089/2184/2106 f 2089/2184/2106 1903/2185/1920 2090/2187/2107 f 1885/1979/1902 1884/1978/1901 2091/2188/2108 f 2091/2188/2108 1884/1978/1901 2090/2187/2107 f 2071/2200/2088 2092/2189/2109 1904/2186/1921 f 1904/2186/1921 2092/2189/2109 2091/2188/2108 f 2095/2192/2112 2096/2193/2113 2071/2200/2088 f 2071/2200/2088 2096/2193/2113 2092/2189/2109 f 1693/1767/1710 2096/2193/2113 1695/1769/1712 f 1695/1769/1712 2096/2193/2113 2094/2191/2111 f 1908/2201/1925 2098/2197/2115 2093/2190/2110 f 2093/2190/2110 2098/2197/2115 2094/2191/2111 f 1906/2198/1923 2097/2194/2114 1908/2201/1925 f 1908/2201/1925 2097/2194/2114 2098/2197/2115 f 2099/2202/2116 2077/2171/2094 2036/2130/2053 f 2036/2130/2053 2077/2171/2094 2035/2129/2052 f 2099/2202/2116 2036/2130/2053 1931/2025/1948 f 1931/2025/1948 2036/2130/2053 2030/2124/2047 f 2099/2202/2116 1931/2025/1948 2100/2203/2117 f 2100/2203/2117 1931/2025/1948 1928/2022/1945 f 1939/2033/1956 2077/2171/2094 1940/2034/1957 f 1940/2034/1957 2077/2171/2094 2099/2202/2116 f 1940/2034/1957 2099/2202/2116 2072/2166/2089 f 2072/2166/2089 2099/2202/2116 2100/2203/2117 f 1926/2020/1943 2072/2166/2089 1929/2023/1946 f 1929/2023/1946 2072/2166/2089 2100/2203/2117 f 1929/2023/1946 2100/2203/2117 1928/2022/1945 f 1924/2018/1941 1940/2034/1957 1925/2019/1942 f 1925/2019/1942 1940/2034/1957 2072/2166/2089 f 1696/1770/1713 2098/2197/2115 1698/1772/1715 f 1698/1772/1715 2098/2197/2115 2097/2194/2114 f 1543/1617/1560 1544/1618/1561 1527/1601/1544 f 1527/1601/1544 1544/1618/1561 1529/1603/1546 f 1556/1630/1573 1557/1631/1574 1543/1617/1560 f 1543/1617/1560 1557/1631/1574 1544/1618/1561 f 1569/1643/1586 1570/1644/1587 1556/1630/1573 f 1556/1630/1573 1570/1644/1587 1557/1631/1574 f 1582/1656/1599 1583/1657/1600 1569/1643/1586 f 1569/1643/1586 1583/1657/1600 1570/1644/1587 f 1582/1656/1599 1595/1669/1612 1583/1657/1600 f 1583/1657/1600 1595/1669/1612 823/866/840 f 1607/1681/1624 826/869/843 1595/1669/1612 f 1595/1669/1612 826/869/843 823/866/840 f 1678/1752/1695 1699/1773/1716 839/882/856 f 1684/1758/1701 1690/1764/1707 1699/1773/1716 f 1699/1773/1716 1690/1764/1707 1697/1771/1714 f 1138/1181/1155 1123/1166/1140 1139/1182/1156 f 1139/1182/1156 1123/1166/1140 1125/1168/1142 f 1124/1167/1141 1144/1187/1161 1126/1169/1143 f 1126/1169/1143 1144/1187/1161 1129/1172/1146 f 1128/1171/1145 1140/1183/1157 1126/1169/1143 f 1126/1169/1143 1140/1183/1157 1125/1168/1142 f 1141/1184/1158 1134/1177/1151 1139/1182/1156 f 1139/1182/1156 1134/1177/1151 1136/1179/1153 f 1146/1189/1163 1127/1170/1144 1143/1186/1160 f 1143/1186/1160 1127/1170/1144 1129/1172/1146 f 1131/1174/1148 1137/1180/1154 1133/1176/1150 f 1133/1176/1150 1137/1180/1154 1136/1179/1153 f 1142/1185/1159 1148/1191/1165 1143/1186/1160 f 1143/1186/1160 1148/1191/1165 1147/1190/1164 f 1135/1178/1152 1182/1225/1199 1133/1176/1150 f 1133/1176/1150 1182/1225/1199 1132/1175/1149 f 1151/1194/1168 1145/1188/1162 1150/1193/1167 f 1150/1193/1167 1145/1188/1162 1147/1190/1164 f 1130/1173/1147 1132/1175/1149 1178/1221/1195 f 1178/1221/1195 1132/1175/1149 1179/1222/1196 f 1159/1202/1176 1153/1196/1170 1149/1192/1166 f 1149/1192/1166 1153/1196/1170 1150/1193/1167 f 1176/1219/1193 1177/1220/1194 1181/1224/1198 f 1181/1224/1198 1177/1220/1194 1179/1222/1196 f 1152/1195/1169 1153/1196/1170 1154/1197/1171 f 1154/1197/1171 1153/1196/1170 1155/1198/1172 f 1180/1223/1197 1177/1220/1194 1172/1215/1189 f 1172/1215/1189 1177/1220/1194 1173/1216/1190 f 1162/1205/1179 1157/1200/1174 1158/1201/1175 f 1158/1201/1175 1157/1200/1174 1155/1198/1172 f 1170/1213/1187 1171/1214/1188 1175/1218/1192 f 1175/1218/1192 1171/1214/1188 1173/1216/1190 f 1156/1199/1173 1157/1200/1174 1163/1206/1180 f 1163/1206/1180 1157/1200/1174 1161/1204/1178 f 1174/1217/1191 1171/1214/1188 1166/1209/1183 f 1166/1209/1183 1171/1214/1188 1167/1210/1184 f 1168/1211/1185 1165/1208/1182 1160/1203/1177 f 1160/1203/1177 1165/1208/1182 1161/1204/1178 f 1164/1207/1181 1165/1208/1182 1169/1212/1186 f 1169/1212/1186 1165/1208/1182 1167/1210/1184 f 1766/1840/1783 1767/1841/1784 1784/1858/1801 f 1784/1858/1801 1767/1841/1784 1769/1843/1786 f 1779/1853/1796 1780/1854/1797 1764/1838/1781 f 1764/1838/1781 1780/1854/1797 1765/1839/1782 f 1770/1844/1787 1767/1841/1784 1781/1855/1798 f 1781/1855/1798 1767/1841/1784 1765/1839/1782 f 1788/1862/1805 1785/1859/1802 1768/1842/1785 f 1768/1842/1785 1785/1859/1802 1769/1843/1786 f 1782/1856/1799 1780/1854/1797 1775/1849/1792 f 1775/1849/1792 1780/1854/1797 1776/1850/1793 f 1783/1857/1800 1785/1859/1802 1789/1863/1806 f 1789/1863/1806 1785/1859/1802 1787/1861/1804 f 1773/1847/1790 1774/1848/1791 1778/1852/1795 f 1778/1852/1795 1774/1848/1791 1776/1850/1793 f 1792/1866/1809 1791/1865/1808 1786/1860/1803 f 1786/1860/1803 1791/1865/1808 1787/1861/1804 f 1777/1851/1794 1774/1848/1791 1823/1897/1840 f 1823/1897/1840 1774/1848/1791 1772/1846/1789 f 1800/1874/1817 1790/1864/1807 1794/1868/1811 f 1794/1868/1811 1790/1864/1807 1791/1865/1808 f 1771/1845/1788 1819/1893/1836 1772/1846/1789 f 1772/1846/1789 1819/1893/1836 1821/1895/1838 f 1793/1867/1810 1795/1869/1812 1794/1868/1811 f 1794/1868/1811 1795/1869/1812 1797/1871/1814 f 1817/1891/1834 1822/1896/1839 1818/1892/1835 f 1818/1892/1835 1822/1896/1839 1821/1895/1838 f 1802/1876/1819 1799/1873/1816 1798/1872/1815 f 1798/1872/1815 1799/1873/1816 1797/1871/1814 f 1820/1894/1837 1813/1887/1830 1818/1892/1835 f 1818/1892/1835 1813/1887/1830 1815/1889/1832 f 1796/1870/1813 1804/1878/1821 1798/1872/1815 f 1798/1872/1815 1804/1878/1821 1803/1877/1820 f 1811/1885/1828 1816/1890/1833 1812/1886/1829 f 1812/1886/1829 1816/1890/1833 1815/1889/1832 f 1808/1882/1825 1801/1875/1818 1806/1880/1823 f 1806/1880/1823 1801/1875/1818 1803/1877/1820 f 1814/1888/1831 1807/1881/1824 1812/1886/1829 f 1812/1886/1829 1807/1881/1824 1809/1883/1826 f 1805/1879/1822 1810/1884/1827 1806/1880/1823 f 1806/1880/1823 1810/1884/1827 1809/1883/1826 f 605/622/622 708/743/725 603/620/620 f 603/620/620 708/743/725 2101/2204/2118 f 545/556/562 686/717/703 539/550/556 f 539/550/556 686/717/703 708/743/725 f 2101/2204/2118 708/743/725 756/795/773 f 756/795/773 708/743/725 686/717/703 f 653/677/670 652/676/669 804/845/821 f 804/845/821 652/676/669 805/846/822 f 663/687/680 803/844/820 656/680/673 f 656/680/673 803/844/820 804/845/821 f 661/685/678 659/683/676 802/843/819 f 802/843/819 659/683/676 803/844/820 f 697/728/714 800/841/817 664/688/681 f 664/688/681 800/841/817 802/843/819 f 697/728/714 733/770/750 800/841/817 f 800/841/817 733/770/750 801/842/818 f 726/761/743 799/840/816 775/814/792 f 775/814/792 799/840/816 797/838/814 f 588/602/605 795/836/812 586/600/603 f 586/600/603 795/836/812 796/837/813 f 668/692/685 795/836/812 669/693/686 f 669/693/686 795/836/812 794/835/811 f 602/619/619 793/834/810 591/605/608 f 591/605/608 793/834/810 794/835/811 f 675/702/692 793/834/810 676/703/693 f 676/703/693 793/834/810 792/833/809 f 600/617/617 705/740/722 792/833/809 f 792/833/809 705/740/722 791/832/808 f 682/711/699 681/710/698 790/831/807 f 790/831/807 681/710/698 791/832/808 f 765/804/782 767/806/784 788/829/805 f 788/829/805 767/806/784 789/830/806 f 1651/1725/1668 1662/1736/1679 833/876/850 f 833/876/850 1662/1736/1679 834/877/851 f 1640/1714/1657 1651/1725/1668 831/874/848 f 831/874/848 1651/1725/1668 833/876/850 f 1629/1703/1646 1640/1714/1657 830/873/847 f 830/873/847 1640/1714/1657 831/874/848 f 1618/1692/1635 1629/1703/1646 827/870/844 f 827/870/844 1629/1703/1646 830/873/847 f 836/879/853 834/877/851 1670/1744/1687 f 1670/1744/1687 834/877/851 1662/1736/1679 f 839/882/856 836/879/853 1678/1752/1695 f 1678/1752/1695 836/879/853 1670/1744/1687 f 839/882/856 1699/1773/1716 470/481/487 f 470/481/487 1699/1773/1716 1053/1096/1070 f 470/481/487 1053/1096/1070 408/416/425 f 408/416/425 1053/1096/1070 1054/1097/1071 f 826/869/843 1607/1681/1624 827/870/844 f 827/870/844 1607/1681/1624 1618/1692/1635 f 2102/2205/2119 1532/1606/1549 2103/2206/2120 f 2103/2206/2120 1532/1606/1549 1531/1605/1548 f 823/866/840 469/480/486 1583/1657/1600 f 1583/1657/1600 469/480/486 1584/1658/1601 f 2104/2207/2121 2103/2206/2120 1545/1619/1562 f 1545/1619/1562 2103/2206/2120 1531/1605/1548 f 2105/2208/2122 2104/2207/2121 1558/1632/1575 f 1558/1632/1575 2104/2207/2121 1545/1619/1562 f 2105/2208/2122 1558/1632/1575 2106/2209/2123 f 2106/2209/2123 1558/1632/1575 1571/1645/1588 f 1596/1670/1613 2106/2209/2123 1584/1658/1601 f 1584/1658/1601 2106/2209/2123 1571/1645/1588 f 445/454/462 446/455/463 814/856/831 f 814/856/831 446/455/463 455/2210/472 f 144/125/161 146/127/163 442/451/459 f 442/451/459 146/127/163 444/453/461 f 1009/1052/1026 403/411/420 1018/1061/1035 f 1018/1061/1035 403/411/420 406/414/423 f 1009/1052/1026 997/1040/1014 403/411/420 f 403/411/420 997/1040/1014 401/409/418 f 865/908/882 864/907/881 869/912/886 f 869/912/886 864/907/881 868/911/885 f 392/400/409 393/401/410 2107/2211/2124 f 2107/2211/2124 393/401/410 925/968/942 f 2108/2212/2125 883/926/900 867/910/884 f 867/910/884 883/926/900 869/912/886 f 2109/2213/2126 897/940/914 2108/2212/2125 f 2108/2212/2125 897/940/914 883/926/900 f 2110/2214/2127 911/954/928 2109/2213/2126 f 2109/2213/2126 911/954/928 897/940/914 f 2107/2211/2124 925/968/942 2110/2214/2127 f 2110/2214/2127 925/968/942 911/954/928 f 961/1004/978 949/992/966 398/406/415 f 398/406/415 949/992/966 397/405/414 f 398/406/415 400/408/417 961/1004/978 f 961/1004/978 400/408/417 973/1016/990 f 973/1016/990 400/408/417 985/1028/1002 f 985/1028/1002 400/408/417 401/409/418 f 997/1040/1014 985/1028/1002 401/409/418 f 408/416/425 1054/1097/1071 2111/2215/2128 f 2111/2215/2128 406/414/423 408/416/425 f 1698/1772/1715 1503/1570/1520 1697/1771/1714 f 1697/1771/1714 1503/1570/1520 1052/1095/1069 f 1050/1093/1067 1052/1095/1069 1051/1094/1068 f 1051/1094/1068 1052/1095/1069 1503/1570/1520 f 34/12/51 23/1/40 36/14/53 f 36/14/53 23/1/40 2112/2216/2129 f 37/15/54 24/2/41 34/12/51 f 34/12/51 24/2/41 23/1/40 f 40/18/57 26/4/43 37/15/54 f 37/15/54 26/4/43 24/2/41 f 41/19/58 27/5/44 40/18/57 f 40/18/57 27/5/44 26/4/43 f 212/203/229 211/202/228 2113/2217/2130 f 2113/2217/2130 211/202/228 2114/2218/2131 f 214/205/231 213/204/230 2115/2219/2132 f 2115/2219/2132 213/204/230 2116/2220/2133 f 211/202/228 214/205/231 2114/2218/2131 f 2114/2218/2131 214/205/231 2115/2219/2132 f 36/14/53 2112/2216/2129 217/208/234 f 217/208/234 2112/2216/2129 2117/2221/2134 f 219/210/236 212/203/229 29/7/46 f 29/7/46 212/203/229 2113/2217/2130 f 217/208/234 2117/2221/2134 366/374/383 f 366/374/383 2117/2221/2134 2118/2222/2135 f 384/392/401 219/210/236 28/6/45 f 28/6/45 219/210/236 29/7/46 f 366/374/383 2118/2222/2135 213/204/230 f 213/204/230 2118/2222/2135 2116/2220/2133 f 27/5/44 41/19/58 30/8/47 f 30/8/47 41/19/58 410/418/427 f 384/392/401 28/6/45 465/476/482 f 465/476/482 28/6/45 2119/2223/2136 f 474/485/491 475/486/492 2120/2224/2137 f 2120/2224/2137 475/486/492 2121/2225/2138 f 477/488/494 474/485/491 2122/2226/2139 f 2122/2226/2139 474/485/491 2120/2224/2137 f 479/490/496 477/488/494 2123/2227/2140 f 2123/2227/2140 477/488/494 2122/2226/2139 f 479/490/496 2123/2227/2140 481/492/498 f 481/492/498 2123/2227/2140 32/10/49 f 650/674/667 2124/2228/2141 651/675/668 f 651/675/668 2124/2228/2141 2125/2229/2142 f 654/678/671 2126/2230/2143 652/676/669 f 652/676/669 2126/2230/2143 2127/2231/2144 f 651/675/668 2125/2229/2142 654/678/671 f 654/678/671 2125/2229/2142 2126/2230/2143 f 475/486/492 657/681/674 2121/2225/2138 f 2121/2225/2138 657/681/674 2128/2232/2145 f 658/682/675 2129/2233/2146 650/674/667 f 650/674/667 2129/2233/2146 2124/2228/2141 f 657/681/674 805/846/822 2128/2232/2145 f 2128/2232/2145 805/846/822 2130/2234/2147 f 481/492/498 32/10/49 807/848/824 f 807/848/824 32/10/49 31/9/48 f 465/476/482 2119/2223/2136 658/682/675 f 658/682/675 2119/2223/2136 2129/2233/2146 f 805/846/822 652/676/669 2130/2234/2147 f 2130/2234/2147 652/676/669 2127/2231/2144 f 2112/2216/2129 23/1/40 25/3/42 f 2113/2217/2130 2114/2218/2131 25/3/42 f 2115/2219/2132 2116/2220/2133 25/3/42 f 2114/2218/2131 2115/2219/2132 25/3/42 f 2117/2221/2134 2112/2216/2129 25/3/42 f 29/7/46 2113/2217/2130 25/3/42 f 2118/2222/2135 2117/2221/2134 25/3/42 f 2116/2220/2133 2118/2222/2135 25/3/42 f 2119/2223/2136 28/6/45 25/3/42 f 2120/2224/2137 2121/2225/2138 25/3/42 f 2122/2226/2139 2120/2224/2137 25/3/42 f 2123/2227/2140 2122/2226/2139 25/3/42 f 32/10/49 2123/2227/2140 25/3/42 f 2125/2229/2142 2124/2228/2141 25/3/42 f 2127/2231/2144 2126/2230/2143 25/3/42 f 2126/2230/2143 2125/2229/2142 25/3/42 f 2121/2225/2138 2128/2232/2145 25/3/42 f 2124/2228/2141 2129/2233/2146 25/3/42 f 2128/2232/2145 2130/2234/2147 25/3/42 f 2129/2233/2146 2119/2223/2136 25/3/42 f 2130/2234/2147 2127/2231/2144 25/3/42 f 1054/1097/1071 1035/1078/1052 2111/2215/2128 f 2111/2215/2128 1035/1078/1052 1028/1071/1045 f 1018/1061/1035 406/414/423 1028/1071/1045 f 1028/1071/1045 406/414/423 2111/2215/2128 f 1678/1752/1695 1684/1758/1701 1699/1773/1716 f 31/9/48 30/8/47 807/848/824 f 807/848/824 30/8/47 410/418/427 f 120/100/137 439/2235/456 119/98/136 f 119/98/136 439/2235/456 436/2236/453 f 808/849/825 806/847/823 412/420/429 f 412/420/429 806/847/823 411/419/428 f 806/847/823 807/848/824 411/419/428 f 411/419/428 807/848/824 410/418/427 f 809/850/826 808/849/825 413/421/430 f 413/421/430 808/849/825 412/420/429 f 809/850/826 413/421/430 813/855/830 f 813/855/830 413/421/430 442/451/459 f 813/855/830 442/451/459 443/452/460 f 443/452/460 442/451/459 444/453/461 f 146/127/163 157/141/174 444/453/461 f 444/453/461 157/141/174 446/455/463 f 157/141/174 159/143/176 446/455/463 f 446/455/463 159/143/176 455/2210/472 f 814/857/831 455/2237/472 815/858/832 f 815/858/832 455/2237/472 456/2238/473 f 815/858/832 456/2238/473 816/859/833 f 816/859/833 456/2238/473 457/2239/474 f 816/859/833 457/2239/474 817/860/834 f 817/860/834 457/2239/474 458/2240/475 f 817/860/834 458/2240/475 811/853/828 f 811/853/828 458/2240/475 440/2241/457 f 812/854/829 811/853/828 441/2242/458 f 441/2242/458 811/853/828 440/2241/457 f 810/851/827 812/854/829 435/2243/452 f 435/2243/452 812/854/829 441/2242/458 f 810/851/827 435/2243/452 437/852/454 f 437/852/454 435/2243/452 436/2244/453 f 439/2235/456 120/100/137 453/2245/470 f 453/2245/470 120/100/137 187/174/204 f 173/160/190 449/2246/466 187/174/204 f 187/174/204 449/2246/466 453/2245/470 f 168/159/185 447/2247/464 173/160/190 f 173/160/190 447/2247/464 449/2246/466 f 168/152/185 110/88/127 447/460/464 f 447/460/464 110/88/127 429/437/446 f 108/86/125 427/435/444 110/88/127 f 110/88/127 427/435/444 429/437/446 f 111/89/128 431/439/448 108/86/125 f 108/86/125 431/439/448 427/435/444 f 111/89/128 114/92/131 431/439/448 f 431/439/448 114/92/131 433/441/450 f 114/92/131 386/394/403 433/441/450 f 433/441/450 386/394/403 461/472/478 f 386/394/403 388/396/405 461/472/478 f 461/472/478 388/396/405 463/474/480 f 391/399/408 466/477/483 388/396/405 f 388/396/405 466/477/483 463/474/480 f 392/400/409 468/479/485 391/399/408 f 391/399/408 468/479/485 466/477/483 f 2107/2211/2124 1596/1670/1613 392/400/409 f 392/400/409 1596/1670/1613 468/479/485 f 2110/2214/2127 2106/2209/2123 2107/2211/2124 f 2107/2211/2124 2106/2209/2123 1596/1670/1613 f 2110/2214/2127 2109/2213/2126 2106/2209/2123 f 2106/2209/2123 2109/2213/2126 2105/2208/2122 f 2109/2213/2126 2108/2212/2125 2105/2208/2122 f 2105/2208/2122 2108/2212/2125 2104/2207/2121 f 2108/2212/2125 867/910/884 2104/2207/2121 f 2104/2207/2121 867/910/884 2103/2206/2120 f 867/910/884 866/909/883 2103/2206/2120 f 2103/2206/2120 866/909/883 2102/2205/2119 f 1062/1105/1079 2102/2205/2119 866/909/883 f 1532/1606/1549 2102/2205/2119 1062/1105/1079 f 1062/1105/1079 868/911/885 864/907/881 f 323/329/340 165/2248/182 325/331/342 f 325/331/342 165/2248/182 321/2249/338 f 329/335/346 409/2250/426 327/333/344 f 327/333/344 409/2250/426 167/2251/184 f 327/333/344 167/2251/184 323/329/340 f 323/329/340 167/2251/184 165/2248/182 f 277/279/294 274/276/291 278/2252/295 f 278/2252/295 274/276/291 270/2253/287 f 275/277/292 283/285/300 271/2254/288 f 271/2254/288 283/285/300 279/2255/296 f 274/276/291 275/277/292 270/2253/287 f 270/2253/287 275/277/292 271/2254/288 f 296/300/313 277/279/294 294/2256/311 f 294/2256/311 277/279/294 278/2252/295 f 325/331/342 321/2249/338 284/286/301 f 284/286/301 321/2249/338 280/2257/297 f 283/285/300 284/286/301 279/2255/296 f 279/2255/296 284/286/301 280/2257/297 f 290/294/307 285/287/302 292/296/309 f 292/296/309 285/287/302 293/297/310 f 360/368/377 361/2258/378 288/290/305 f 288/290/305 361/2258/378 286/2259/303 f 288/290/305 286/2259/303 290/292/307 f 290/292/307 286/2259/303 285/2260/302 f 292/296/309 293/297/310 299/303/316 f 299/303/316 293/297/310 297/301/314 f 360/368/377 296/300/313 361/2258/378 f 361/2258/378 296/300/313 294/2256/311 f 299/303/316 297/301/314 301/305/318 f 301/305/318 297/301/314 302/306/319 f 301/305/318 302/306/319 305/309/322 f 305/309/322 302/306/319 303/307/320 f 307/311/324 305/309/322 308/312/325 f 308/312/325 305/309/322 303/307/320 f 320/326/337 307/311/324 318/324/335 f 318/324/335 307/311/324 308/312/325 f 316/321/333 313/322/330 317/2261/334 f 317/2261/334 313/322/330 309/2262/326 f 314/318/331 320/326/337 310/314/327 f 310/314/327 320/326/337 318/324/335 f 313/317/330 314/318/331 309/313/326 f 309/313/326 314/318/331 310/314/327 f 329/335/346 316/321/333 409/2250/426 f 409/2250/426 316/321/333 317/2261/334 f 768/807/785 766/805/783 2101/2263/2118 f 2101/2263/2118 766/805/783 603/2264/620 f 763/802/780 764/803/781 604/2265/621 f 604/2265/621 764/803/781 760/2266/777 f 766/805/783 763/802/780 603/2264/620 f 603/2264/620 763/802/780 604/2265/621 f 714/749/731 709/2267/726 721/756/738 f 721/756/738 709/2267/726 719/2268/736 f 716/751/733 717/2269/734 712/747/729 f 712/747/729 717/2269/734 710/2270/727 f 712/747/729 710/2270/727 714/749/731 f 714/749/731 710/2270/727 709/2267/726 f 735/772/752 733/2271/750 716/751/733 f 716/751/733 733/2271/750 717/2269/734 f 764/803/781 723/758/740 760/2266/777 f 760/2266/777 723/758/740 718/2272/735 f 721/756/738 719/2268/736 723/758/740 f 723/758/740 719/2268/736 718/2272/735 f 798/839/815 728/763/745 801/2273/818 f 801/2273/818 728/763/745 725/2274/742 f 729/767/746 731/768/748 724/759/741 f 724/759/741 731/768/748 732/769/749 f 728/763/745 729/764/746 725/2274/742 f 725/2274/742 729/764/746 724/2275/741 f 731/768/748 738/775/755 732/769/749 f 732/769/749 738/775/755 736/773/753 f 798/839/815 801/2273/818 735/772/752 f 735/772/752 801/2273/818 733/2271/750 f 738/775/755 740/777/757 736/773/753 f 736/773/753 740/777/757 741/778/758 f 740/777/757 744/781/761 741/778/758 f 741/778/758 744/781/761 742/779/759 f 746/783/763 747/784/764 744/781/761 f 744/781/761 747/784/764 742/779/759 f 759/798/776 757/796/774 746/783/763 f 746/783/763 757/796/774 747/784/764 f 753/790/770 748/785/765 759/798/776 f 759/798/776 748/785/765 757/796/774 f 755/792/772 756/2276/773 751/794/768 f 751/794/768 756/2276/773 749/2277/766 f 751/788/768 749/786/766 753/790/770 f 753/790/770 749/786/766 748/785/765 f 768/807/785 2101/2263/2118 755/792/772 f 755/792/772 2101/2263/2118 756/2276/773 f 1960/2054/1977 1889/1983/1906 1888/1982/1905 f 1249/1312/1266 1248/1311/1265 1251/1314/1268 ================================================ FILE: examples/src/Jeep.obj.txt ================================================ # Blender v2.92.0 OBJ File: 'jeep.blend' # www.blender.org # The Jeep model is courtesy of Kolja Wilcke mtllib jeep.mtl o convex-base_Base v -2.242281 -4.524718 1.272481 v -2.242281 -4.524718 3.488908 v -2.242281 4.246403 1.272481 v -2.242281 3.677963 3.488908 v 2.242281 -4.524718 1.272481 v 2.242281 -4.524718 3.488908 v 2.242281 4.246403 1.272481 v 2.242281 3.677963 3.488908 v -2.242281 4.246403 2.380695 v 2.242281 4.246403 2.380695 vt 0.375000 0.750000 vt 0.625000 0.750000 vt 0.625000 1.000000 vt 0.375000 1.000000 vt 0.500000 0.250000 vt 0.625000 0.250000 vt 0.625000 0.500000 vt 0.500000 0.500000 vt 0.375000 0.000000 vt 0.625000 0.000000 vt 0.375000 0.250000 vt 0.125000 0.500000 vt 0.375000 0.500000 vt 0.125000 0.750000 vt 0.875000 0.500000 vt 0.875000 0.750000 vn 0.0000 -1.0000 0.0000 vn 0.0000 0.8898 0.4564 vn -1.0000 0.0000 0.0000 vn 0.0000 0.0000 -1.0000 vn 0.0000 0.0000 1.0000 vn 0.0000 1.0000 0.0000 vn 1.0000 0.0000 0.0000 usemtl None s off f 5/1/1 6/2/1 2/3/1 1/4/1 f 9/5/2 4/6/2 8/7/2 10/8/2 f 1/9/3 2/10/3 4/6/3 9/5/3 3/11/3 f 3/12/4 7/13/4 5/1/4 1/14/4 f 8/7/5 4/15/5 2/16/5 6/2/5 f 3/11/6 9/5/6 10/8/6 7/13/6 f 7/13/7 10/8/7 8/7/7 6/2/7 5/1/7 o convex-window_Window v -2.242281 0.283938 5.497181 v -2.242281 0.751557 3.488908 v -2.242281 0.635509 5.497181 v -2.242281 1.103128 3.488908 v 2.242281 0.283938 5.497181 v 2.242281 0.751557 3.488908 v 2.242281 0.635509 5.497181 v 2.242281 1.103128 3.488908 vt 0.375000 0.000000 vt 0.375000 0.250000 vt 0.625000 0.250000 vt 0.625000 0.000000 vt 0.375000 0.500000 vt 0.625000 0.500000 vt 0.375000 0.750000 vt 0.625000 0.750000 vt 0.375000 1.000000 vt 0.625000 1.000000 vt 0.125000 0.500000 vt 0.125000 0.750000 vt 0.875000 0.750000 vt 0.875000 0.500000 vn -1.0000 0.0000 0.0000 vn 0.0000 0.9739 0.2268 vn 1.0000 0.0000 0.0000 vn 0.0000 -0.9739 -0.2268 vn 0.0000 0.0000 1.0000 vn 0.0000 0.0000 -1.0000 usemtl None s off f 11/17/8 13/18/8 14/19/8 12/20/8 f 13/18/9 17/21/9 18/22/9 14/19/9 f 17/21/10 15/23/10 16/24/10 18/22/10 f 15/23/11 11/25/11 12/26/11 16/24/11 f 13/27/12 11/28/12 15/23/12 17/21/12 f 18/22/13 16/24/13 12/29/13 14/30/13 o front-right_Axis3d v 1.832790 2.783146 1.139400 v 1.932790 2.783146 1.139400 l 19 20 o rear-right_Axis3d v 1.832790 -2.992498 1.139400 v 1.932790 -2.992498 1.139400 l 21 22 o front-left_Axis3d v -1.810560 2.846034 1.139400 v -1.910560 2.846034 1.139400 l 23 24 o rear-left_Axis3d v -1.810560 -2.858030 1.139400 v -1.910560 -2.858030 1.139400 l 25 26 o chassis_Chassis v 1.277467 3.619387 3.206276 v 0.825481 3.619387 1.542987 v 1.326676 1.110030 1.299445 v 1.478835 1.110030 1.508627 v 1.277467 3.619387 2.455048 v 2.190520 1.110030 1.508627 v 2.190520 1.110030 3.299445 v 1.705413 3.619387 3.206276 v 1.705413 3.619387 2.455048 v 1.478835 -2.012901 2.363739 v 1.478835 -2.012901 3.299445 v 1.326676 -2.012901 1.299445 v 2.190520 -2.012901 2.363739 v 2.190520 -2.012901 3.299445 v 1.478835 -4.041931 1.927432 v 1.326676 -4.041931 1.299445 v 2.190520 -4.041931 1.927432 v 2.190520 -4.041931 3.299445 v 0.655219 3.619387 1.542987 v 1.175348 1.110030 3.299445 v 0.811583 3.619387 3.206276 v 0.730915 3.619387 2.240386 v 0.664338 3.619387 3.501884 v 0.583671 3.502560 2.240386 v 0.523028 3.502560 1.542987 v 0.939721 1.110030 3.595053 v 2.190520 0.775040 1.508627 v 1.478835 0.775040 1.508627 v 1.326676 0.775040 1.299445 v 1.277467 3.619387 3.097432 v 2.190520 0.775040 3.299445 v 1.780108 0.775040 3.299445 v 1.835736 0.632188 5.497213 v 2.093769 0.632188 5.472280 v 2.093769 0.297198 5.472280 v 1.777038 0.297198 5.497213 v 1.478835 0.387261 1.508627 v 1.326676 0.387261 1.299445 v 1.824495 0.387261 2.627326 v 1.326676 -3.388509 1.299445 v 2.190520 0.387261 1.508627 v 2.190520 0.387261 2.627326 v 1.478835 -1.608775 1.508627 v 1.326676 -1.608775 1.299445 v 2.190520 -3.388509 3.299445 v 2.190520 -3.388510 2.363739 v 1.824495 -1.608775 2.627326 v 1.478835 -3.388510 2.363739 v 2.190520 -1.608775 1.508627 v 2.190520 -1.608775 2.627326 v 2.190520 -3.684435 1.927432 v 1.478835 -3.684435 1.927432 v 2.190520 -3.684435 3.299445 v 1.478835 -3.684435 3.299445 v 1.326676 -3.684435 1.299445 v 1.478835 -2.012901 2.618106 v 1.824495 -1.608775 1.835406 v 1.824495 0.775040 1.835406 v 1.824495 0.387261 1.835406 v 1.208939 1.579852 1.299445 v 1.347691 1.579852 2.331105 v 1.996666 1.579852 3.299445 v 1.996666 1.579852 2.331105 v 1.070946 1.579852 3.299445 v 0.856081 1.579852 3.595053 v 2.190520 0.370200 5.170483 v 2.190520 0.705190 5.170483 v 1.780108 0.370200 5.170483 v 1.835736 0.705190 5.170483 v 0.939721 1.030688 3.922539 v 2.190520 1.030688 3.922539 v 1.835736 1.030688 3.922539 v 2.190520 0.695697 3.922539 v 1.780108 0.695697 3.922539 v 2.190520 1.110030 2.871010 v 1.705413 3.619387 3.097432 v 1.478835 -3.388510 2.618106 v 0.730915 3.619387 3.097432 v 0.583671 3.502560 3.322319 v 2.190520 0.775040 2.871010 v 1.996666 1.579852 3.067780 v 1.286705 3.351099 2.438745 v 1.743726 3.351099 3.299445 v 1.743726 3.351099 2.438745 v 0.875922 3.351099 1.299445 v 0.695375 3.351099 1.299445 v 0.856311 3.351099 3.299445 v 0.700172 3.351099 3.595053 v 0.555198 3.351099 1.299445 v 1.743726 3.351099 3.093531 v -1.277467 3.619387 3.206276 v -0.825481 3.619387 1.542987 v -1.326676 1.110030 1.299445 v -1.478835 1.110030 1.508627 v -1.277467 3.619387 2.455048 v -2.190520 1.110030 1.508627 v -2.190520 1.110030 3.299445 v -1.705413 3.619387 3.206276 v -1.705413 3.619387 2.455048 v -1.478835 -2.012901 2.363739 v -1.478835 -2.012901 3.299445 v -1.326676 -2.012901 1.299445 v -2.190520 -2.012901 2.363739 v -2.190520 -2.012901 3.299445 v -1.478835 -4.041931 1.927432 v -1.326676 -4.041931 1.299445 v -2.190520 -4.041931 1.927432 v -2.190520 -4.041931 3.299445 v -0.655219 3.619387 1.542987 v -1.175348 1.110030 3.299445 v -0.811583 3.619387 3.206276 v -0.730915 3.619387 2.240386 v -0.664338 3.619387 3.501884 v -0.583671 3.502560 2.240386 v -0.523028 3.502560 1.542987 v -0.939721 1.110030 3.595053 v -2.190520 0.775040 1.508627 v -1.478835 0.775040 1.508627 v -1.326676 0.775040 1.299445 v -1.277467 3.619387 3.097432 v -2.190520 0.775040 3.299445 v -1.780108 0.775040 3.299445 v -1.835736 0.632188 5.497213 v -2.093769 0.632188 5.472280 v -2.093769 0.297198 5.472280 v -1.777038 0.297198 5.497213 v -1.478835 0.387261 1.508627 v -1.326676 0.387261 1.299445 v -1.824495 0.387261 2.627326 v -1.326676 -3.388509 1.299445 v -2.190520 0.387261 1.508627 v -2.190520 0.387261 2.627326 v -1.478835 -1.608775 1.508627 v -1.326676 -1.608775 1.299445 v -2.190520 -3.388509 3.299445 v -2.190520 -3.388510 2.363739 v -1.824495 -1.608775 2.627326 v -1.478835 -3.388510 2.363739 v -2.190520 -1.608775 1.508627 v -2.190520 -1.608775 2.627326 v -2.190520 -3.684435 1.927432 v -1.478835 -3.684435 1.927432 v -2.190520 -3.684435 3.299445 v -1.478835 -3.684435 3.299445 v -1.326676 -3.684435 1.299445 v -1.478835 -2.012901 2.618106 v -1.824495 -1.608775 1.835406 v -1.824495 0.775040 1.835406 v -1.824495 0.387261 1.835406 v -1.208939 1.579852 1.299445 v -1.347691 1.579852 2.331105 v -1.996666 1.579852 3.299445 v -1.996666 1.579852 2.331105 v -1.070946 1.579852 3.299445 v -0.856081 1.579852 3.595053 v -2.190520 0.370200 5.170483 v -2.190520 0.705190 5.170483 v -1.780108 0.370200 5.170483 v -1.835736 0.705190 5.170483 v -0.939721 1.030688 3.922539 v -2.190520 1.030688 3.922539 v -1.835736 1.030688 3.922539 v -2.190520 0.695697 3.922539 v -1.780108 0.695697 3.922539 v -2.190520 1.110030 2.871010 v -1.705413 3.619387 3.097432 v -1.478835 -3.388510 2.618106 v -0.730915 3.619387 3.097432 v -0.583671 3.502560 3.322319 v -1.996666 1.579852 3.067780 v -1.286705 3.351099 2.438745 v -1.743726 3.351099 3.299445 v -1.743726 3.351099 2.438745 v -0.875922 3.351099 1.299445 v -0.695375 3.351099 1.299445 v -0.856311 3.351099 3.299445 v -0.700172 3.351099 3.595053 v -0.555198 3.351099 1.299445 v -1.743726 3.351099 3.093531 vt 0.706042 0.690913 vt 0.706042 0.702725 vt 0.707871 0.702725 vt 0.707871 0.690913 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.690094 0.653570 vt 0.682588 0.653570 vt 0.682588 0.653570 vt 0.690094 0.653570 vt 0.949194 0.689365 vt 0.972131 0.689365 vt 0.972131 0.689365 vt 0.949194 0.689365 vt 0.657790 0.689124 vt 0.657790 0.689124 vt 0.657790 0.690913 vt 0.657790 0.690913 vt 0.672317 0.669558 vt 0.672317 0.671347 vt 0.682588 0.671347 vt 0.682588 0.669558 vt 0.958083 0.666065 vt 0.958083 0.666065 vt 0.928493 0.666065 vt 0.928493 0.666065 vt 0.688298 0.669558 vt 0.688298 0.671347 vt 0.690094 0.671347 vt 0.690094 0.669558 vt 0.187708 0.674483 vt 0.187708 0.674483 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.688298 0.671347 vt 0.682588 0.671347 vt 0.690094 0.653570 vt 0.682588 0.653570 vt 0.682588 0.653570 vt 0.690094 0.653570 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.704621 0.706901 vt 0.706042 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.704621 0.706901 vt 0.931231 0.723642 vt 0.931231 0.723642 vt 0.931231 0.717873 vt 0.931231 0.688177 vt 0.931231 0.688177 vt 0.931231 0.717873 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.713779 0.700933 vt 0.713779 0.700933 vt 0.711675 0.700933 vt 0.711675 0.700933 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.672317 0.673176 vt 0.682588 0.673176 vt 0.688298 0.673176 vt 0.690094 0.673176 vt 0.690094 0.671347 vt 0.704621 0.702725 vt 0.704621 0.706901 vt 0.706042 0.706901 vt 0.704621 0.690913 vt 0.704621 0.690913 vt 0.704621 0.702725 vt 0.907932 0.683659 vt 0.907932 0.689365 vt 0.949194 0.683659 vt 0.688298 0.674597 vt 0.690094 0.674597 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707652 0.706512 vt 0.707871 0.706901 vt 0.690094 0.653570 vt 0.690094 0.653570 vt 0.690094 0.653570 vt 0.690094 0.653570 vt 0.690094 0.653570 vt 0.682588 0.653570 vt 0.682588 0.653570 vt 0.690094 0.653570 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.958083 0.666065 vt 0.958083 0.666065 vt 0.928493 0.666065 vt 0.928493 0.666065 vt 0.690094 0.653570 vt 0.690094 0.653570 vt 0.698661 0.681849 vt 0.678901 0.681849 vt 0.678901 0.687284 vt 0.698661 0.687284 vt 0.704621 0.706901 vt 0.682588 0.653570 vt 0.682588 0.657746 vt 0.688298 0.657746 vt 0.688298 0.653570 vt 0.673772 0.681848 vt 0.674144 0.685802 vt 0.958083 0.666065 vt 0.928493 0.666065 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.688298 0.653570 vt 0.682588 0.653570 vt 0.690094 0.654414 vt 0.690094 0.653570 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.958083 0.666065 vt 0.928493 0.666065 vt 0.958083 0.666065 vt 0.928493 0.666065 vt 0.337939 0.694193 vt 0.337939 0.694193 vt 0.337939 0.694193 vt 0.337939 0.694193 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.704621 0.689124 vt 0.704621 0.689124 vt 0.187708 0.710090 vt 0.187708 0.710090 vt 0.690094 0.656901 vt 0.928493 0.678094 vt 0.958083 0.678094 vt 0.706042 0.690913 vt 0.707871 0.690913 vt 0.707871 0.702725 vt 0.706042 0.702725 vt 0.682034 0.679580 vt 0.650246 0.679580 vt 0.652330 0.687687 vt 0.684118 0.687687 vt 0.690094 0.653570 vt 0.690094 0.653570 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.972131 0.683659 vt 0.682588 0.669558 vt 0.907932 0.689365 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.321289 vt 0.930620 0.292673 vt 0.935053 0.292673 vt 0.938494 0.292673 vt 0.938494 0.292673 vt 0.935053 0.292673 vt 0.930620 0.292673 vt 0.930620 0.321289 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.930620 0.331406 vt 0.654540 0.689124 vt 0.654540 0.690913 vt 0.656369 0.690913 vt 0.656369 0.689124 vt 0.706042 0.689124 vt 0.688298 0.674597 vt 0.690094 0.674597 vt 0.928493 0.712121 vt 0.958083 0.712121 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.724763 0.687992 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.724763 0.687992 vt 0.000000 0.000000 vt 0.000000 0.000000 vt 0.000000 0.000000 vt 0.000000 0.000000 vt 0.690094 0.724678 vt 0.682588 0.724678 vt 0.682588 0.724678 vt 0.672317 0.724678 vt 0.672317 0.724678 vt 0.682588 0.724678 vt 0.682588 0.724678 vt 0.690094 0.724678 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.707871 0.689124 vt 0.707871 0.689124 vt 0.672317 0.669558 vt 0.682588 0.669558 vt 0.682588 0.671347 vt 0.672317 0.671347 vt 0.958083 0.666065 vt 0.928493 0.666065 vt 0.928493 0.666065 vt 0.958083 0.666065 vt 0.688298 0.669558 vt 0.690094 0.669558 vt 0.690094 0.671347 vt 0.688298 0.671347 vt 0.187708 0.674483 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.187708 0.674483 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.688298 0.671347 vt 0.682588 0.671347 vt 0.690094 0.653570 vt 0.682588 0.653570 vt 0.682588 0.653570 vt 0.690094 0.653570 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.713779 0.700933 vt 0.711675 0.700933 vt 0.711675 0.700933 vt 0.713779 0.700933 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.682588 0.673176 vt 0.672317 0.673176 vt 0.690094 0.671347 vt 0.690094 0.673176 vt 0.688298 0.673176 vt 0.907932 0.683659 vt 0.949194 0.683659 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707652 0.706512 vt 0.707871 0.706901 vt 0.690094 0.653570 vt 0.690094 0.653570 vt 0.690094 0.653570 vt 0.690094 0.653570 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.958083 0.666065 vt 0.928493 0.666065 vt 0.928493 0.666065 vt 0.958083 0.666065 vt 0.690094 0.653570 vt 0.690094 0.653570 vt 0.698661 0.681849 vt 0.698661 0.687284 vt 0.678901 0.687284 vt 0.678901 0.681849 vt 0.682588 0.653570 vt 0.688298 0.653570 vt 0.688298 0.657746 vt 0.682588 0.657746 vt 0.674144 0.685802 vt 0.673772 0.681848 vt 0.698936 0.727990 vt 0.698936 0.727990 vt 0.698936 0.727990 vt 0.698936 0.727990 vt 0.928493 0.666065 vt 0.958083 0.666065 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.707871 0.706901 vt 0.682588 0.653570 vt 0.682588 0.653570 vt 0.690094 0.653570 vt 0.690094 0.653570 vt 0.682588 0.653570 vt 0.690094 0.653570 vt 0.690094 0.654414 vt 0.187708 0.661895 vt 0.187708 0.661895 vt 0.928493 0.666065 vt 0.958083 0.666065 vt 0.928493 0.666065 vt 0.958083 0.666065 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.187708 0.710090 vt 0.187708 0.710090 vt 0.690094 0.656901 vt 0.958083 0.678094 vt 0.928493 0.678094 vt 0.707871 0.689124 vt 0.707871 0.689124 vt 0.706042 0.689124 vt 0.682034 0.679580 vt 0.684118 0.687687 vt 0.652330 0.687687 vt 0.650246 0.679580 vt 0.690094 0.653570 vt 0.690094 0.653570 vt 0.972131 0.683659 vt 0.682588 0.669558 vt 0.654540 0.689124 vt 0.656369 0.689124 vt 0.656369 0.690913 vt 0.654540 0.690913 vt 0.958083 0.712121 vt 0.928493 0.712121 vt 0.311695 0.691093 vt 0.311695 0.691093 vt 0.000000 0.000000 vt 0.000000 0.000000 vt 0.690094 0.653570 vt 0.682588 0.653570 vt 0.682588 0.653570 vt 0.690094 0.653570 vt 0.707871 0.706901 vt 0.936374 0.675071 vt 0.936374 0.675071 vn 0.0000 0.0000 1.0000 vn 0.0000 0.0000 -1.0000 vn 1.0000 0.0000 0.0000 vn 0.0000 1.0000 0.0000 vn 0.0000 0.8492 -0.5281 vn 0.8853 0.2788 -0.3723 vn 0.9445 -0.2388 -0.2257 vn 0.9900 0.1414 0.0000 vn 0.0000 0.8683 -0.4960 vn 0.0000 -0.9041 -0.4273 vn -0.0156 0.9911 0.1321 vn 0.0000 0.8570 0.5153 vn 0.9719 0.0000 -0.2355 vn -0.9990 -0.0401 -0.0197 vn 0.7855 0.1572 0.5985 vn -0.6402 0.7656 0.0626 vn -0.4867 0.8699 -0.0801 vn -0.0078 -0.9738 -0.2274 vn 0.9523 0.0000 0.3053 vn -1.0000 0.0000 0.0000 vn 0.8087 0.0000 -0.5882 vn 0.0864 -0.0076 0.9962 vn 0.0000 0.9676 0.2524 vn 0.0000 0.9719 0.2355 vn 0.9244 0.3814 0.0000 vn 0.0093 0.9737 0.2276 vn 0.9899 0.0000 -0.1415 vn 0.0000 -0.8662 0.4997 vn 0.9759 0.1294 -0.1755 vn 0.0000 0.8276 -0.5613 vn 0.0000 -1.0000 0.0000 vn -0.7599 0.6500 0.0000 vn 0.0000 0.3281 0.9447 vn 0.8437 0.0883 0.5295 vn 0.0000 0.0607 -0.9982 vn 0.8853 0.4162 -0.2076 vn -0.9856 0.1637 0.0427 vn 0.0000 -0.9676 -0.2524 vn 0.0156 0.9911 0.1321 vn -0.6215 0.7834 0.0000 vn -0.0000 0.6721 -0.7404 vn 0.8540 0.2804 0.4382 vn 0.0000 0.8382 -0.5453 vn -0.3003 0.7001 -0.6479 vn 0.9630 0.1145 -0.2438 vn 0.0000 -0.9920 -0.1263 vn 0.0000 0.9172 0.3984 vn -0.8853 0.2788 -0.3723 vn -0.9445 -0.2388 -0.2257 vn -0.9900 0.1414 0.0000 vn -0.9719 0.0000 -0.2355 vn 0.9990 -0.0401 -0.0197 vn -0.7855 0.1572 0.5985 vn 0.6402 0.7656 0.0626 vn 0.4867 0.8699 -0.0801 vn 0.0078 -0.9738 -0.2274 vn -0.9523 0.0000 0.3053 vn 0.0000 -0.9759 -0.2181 vn -0.8087 0.0000 -0.5882 vn -0.0864 -0.0076 0.9962 vn -0.9244 0.3814 0.0000 vn -0.0093 0.9737 0.2276 vn 0.0000 0.9759 0.2181 vn -0.9899 0.0000 -0.1415 vn -0.9759 0.1294 -0.1755 vn 0.7599 0.6500 0.0000 vn -0.8437 0.0883 0.5295 vn -0.8853 0.4162 -0.2076 vn 0.9856 0.1637 0.0427 vn 0.6215 0.7834 0.0000 vn -0.8540 0.2804 0.4382 vn 0.3003 0.7001 -0.6479 vn -0.9630 0.1145 -0.2438 vn 0.0000 0.8886 0.4588 usemtl Material s off f 113/31/14 90/32/14 88/33/14 109/34/14 f 39/35/15 72/36/15 74/37/15 36/38/15 f 76/39/16 75/40/16 67/41/16 68/42/16 f 140/43/17 195/44/17 105/45/17 50/46/17 f 141/47/18 51/48/18 115/49/18 204/50/18 f 111/51/19 28/52/19 31/53/19 108/54/19 f 69/55/20 36/56/20 38/57/20 70/58/20 f 116/59/21 102/60/21 34/61/21 109/62/21 f 87/63/22 89/64/22 32/65/22 30/66/22 f 75/67/23 39/35/23 36/38/23 69/68/23 f 56/69/17 102/60/17 35/70/17 31/53/17 f 71/71/16 72/72/16 39/73/16 40/74/16 f 77/75/15 43/76/15 41/77/15 78/78/15 f 142/79/24 136/80/24 123/81/24 187/82/24 188/83/24 186/84/24 f 84/85/14 174/86/14 175/87/14 173/88/14 83/89/14 85/90/14 f 73/91/25 37/92/25 40/93/25 76/94/25 f 78/95/26 41/96/26 42/97/26 81/98/26 f 58/99/27 84/100/27 85/101/27 65/102/27 f 28/52/17 45/103/17 48/104/17 31/53/17 f 56/69/17 104/105/17 47/106/17 27/107/17 f 91/108/28 52/109/28 46/110/28 90/32/28 f 114/111/14 203/112/14 181/113/14 91/108/14 f 45/114/29 51/115/29 50/46/29 48/116/29 f 104/105/30 105/117/30 49/118/30 47/106/30 f 92/119/31 61/120/31 62/121/31 94/122/31 f 93/123/32 60/124/32 61/125/32 92/126/32 f 158/127/33 157/128/33 165/129/33 166/130/33 f 32/65/15 53/131/15 54/132/15 30/66/15 f 30/133/34 54/134/34 55/135/34 29/136/34 f 59/137/35 62/138/35 61/125/35 60/124/35 f 98/139/36 95/140/36 93/141/36 97/142/36 f 52/109/37 142/79/37 186/84/37 96/143/37 f 32/144/38 89/145/38 107/146/38 101/147/38 f 95/140/39 59/148/39 60/149/39 93/141/39 f 36/56/40 74/150/40 66/151/40 38/57/40 f 58/152/41 65/153/41 68/154/41 57/155/41 f 106/156/16 53/157/16 32/144/16 101/147/16 33/158/16 57/159/16 f 53/131/15 67/160/15 63/161/15 54/132/15 f 54/134/34 63/162/34 64/163/34 55/135/34 f 65/153/14 73/91/14 76/94/14 68/154/14 f 68/42/16 67/41/16 53/157/16 106/156/16 57/159/16 f 67/160/15 75/67/15 69/68/15 63/161/15 f 63/162/34 69/55/34 70/58/34 64/163/34 f 74/150/42 78/164/42 81/165/42 66/151/42 f 72/36/43 77/75/43 78/78/43 74/37/43 f 84/166/44 58/167/44 148/168/44 174/169/44 f 65/102/33 85/101/33 83/170/33 73/171/33 f 73/171/45 83/170/45 82/172/45 37/173/45 f 49/174/46 139/175/46 203/112/46 114/111/46 f 114/111/47 91/108/47 90/32/47 113/31/47 f 108/176/48 110/177/48 89/64/48 87/63/48 f 101/147/38 107/146/38 88/178/38 33/158/38 f 29/136/49 86/179/49 87/180/49 30/133/49 f 202/181/14 198/182/14 178/183/14 180/184/14 f 180/184/14 178/183/14 123/81/14 136/80/14 f 100/185/50 94/186/50 95/187/50 98/188/50 f 97/189/16 93/123/16 92/126/16 99/190/16 f 99/191/51 92/119/51 94/122/51 100/192/51 f 33/158/16 97/189/16 99/190/16 57/159/16 f 52/109/52 96/143/52 98/193/52 97/194/52 33/195/52 46/110/52 f 91/108/14 181/113/14 142/79/14 52/109/14 f 48/116/53 50/46/53 105/45/53 104/196/53 f 31/53/17 48/104/17 104/105/17 56/69/17 f 27/107/17 34/61/17 102/60/17 56/69/17 f 110/197/21 35/70/21 102/60/21 116/59/21 f 141/198/17 140/43/17 50/46/17 51/115/17 f 89/145/21 110/197/21 116/59/21 107/146/21 f 42/199/15 132/200/15 171/201/15 156/202/15 128/203/15 160/204/15 154/205/15 145/206/15 119/207/15 176/208/15 200/209/15 201/210/15 204/211/15 115/212/15 112/213/15 111/214/15 86/215/15 29/216/15 55/217/15 64/218/15 70/219/15 38/220/15 66/221/15 81/222/15 f 31/53/48 35/70/48 110/197/48 108/54/48 f 28/223/54 111/224/54 112/225/54 45/226/54 f 49/174/55 114/111/55 113/31/55 47/227/55 f 195/228/56 139/229/56 49/118/56 105/117/56 f 45/226/57 112/225/57 115/49/57 51/48/57 f 107/146/21 116/59/21 109/62/21 88/178/21 f 86/179/58 111/230/58 108/231/58 87/180/58 f 82/172/33 103/232/33 80/233/33 37/173/33 f 58/152/59 57/155/59 99/191/59 100/234/59 190/235/59 189/236/59 147/237/59 148/238/59 f 170/239/60 80/240/60 103/241/60 193/242/60 f 134/243/44 133/244/44 131/245/44 132/246/44 42/247/44 41/248/44 43/249/44 44/250/44 f 90/32/14 46/110/14 33/195/14 88/33/14 f 129/251/15 126/252/15 164/253/15 162/254/15 f 113/31/46 109/34/46 34/255/46 27/256/46 47/227/46 f 200/257/61 197/258/61 121/259/61 118/260/61 f 159/261/62 160/262/62 128/263/62 126/264/62 f 205/265/63 198/266/63 124/267/63 192/268/63 f 177/269/22 120/270/22 122/271/22 179/272/22 f 165/273/23 159/274/23 126/252/23 129/251/23 f 146/275/17 121/259/17 125/276/17 192/268/17 f 130/277/33 129/278/33 162/279/33 161/280/33 f 167/281/15 168/282/15 131/283/15 133/284/15 f 40/74/16 39/73/16 75/40/16 76/39/16 f 134/285/14 44/286/14 79/287/14 80/288/14 170/289/14 169/290/14 f 163/291/25 166/292/25 130/293/25 127/294/25 f 37/92/14 80/288/14 79/287/14 71/295/14 40/93/14 f 168/296/64 171/297/64 132/298/64 131/299/64 f 148/300/65 155/301/65 175/302/65 174/303/65 f 118/260/17 121/259/17 138/304/17 135/305/17 f 146/275/17 117/306/17 137/307/17 194/308/17 f 181/113/66 180/184/66 136/80/66 142/79/66 f 135/309/67 138/310/67 140/43/67 141/198/67 f 194/308/68 137/307/68 139/229/68 195/228/68 f 182/311/69 184/312/69 152/313/69 151/314/69 f 183/315/70 182/316/70 151/317/70 150/318/70 f 62/121/71 152/313/71 184/312/71 94/122/71 f 122/271/15 120/270/15 144/319/15 143/320/15 f 120/321/72 119/322/72 145/323/72 144/324/72 f 149/325/73 150/318/73 151/317/73 152/326/73 f 188/327/36 187/328/36 183/329/36 185/330/36 f 166/130/33 165/129/33 129/278/33 130/277/33 f 122/331/74 191/332/74 196/333/74 179/334/74 f 185/330/75 183/329/75 150/335/75 149/336/75 f 59/148/76 95/140/76 185/330/76 149/336/76 f 94/337/15 184/338/15 185/339/15 95/340/15 f 126/264/77 128/263/77 156/341/77 164/342/77 f 148/343/41 147/237/41 158/344/41 155/345/41 f 167/346/33 133/347/33 134/348/33 169/349/33 161/280/33 162/279/33 f 191/332/33 122/331/33 143/350/33 147/351/33 123/352/33 f 143/320/15 144/319/15 153/353/15 157/354/15 f 144/324/72 145/323/72 154/355/72 153/356/72 f 155/345/14 158/344/14 166/292/14 163/291/14 f 147/351/33 143/350/33 157/128/33 158/127/33 f 157/354/15 153/353/15 159/274/15 165/273/15 f 153/356/72 154/355/72 160/262/72 159/261/72 f 164/342/78 156/341/78 171/357/78 168/358/78 f 162/254/43 164/253/43 168/282/43 167/281/43 f 155/301/16 163/359/16 173/360/16 175/302/16 f 163/359/79 127/361/79 172/362/79 173/360/79 f 203/112/80 202/181/80 180/184/80 181/113/80 f 197/363/48 177/269/48 179/272/48 199/364/48 f 191/332/74 123/352/74 178/365/74 196/333/74 f 119/322/81 120/321/81 177/366/81 176/367/81 f 117/368/46 124/369/46 198/182/46 202/181/46 137/370/46 f 190/371/82 188/372/82 185/373/82 184/374/82 f 187/375/33 189/376/33 182/316/33 183/315/33 f 189/236/51 190/235/51 184/312/51 182/311/51 f 123/352/33 147/351/33 189/376/33 187/375/33 f 138/310/83 194/377/83 195/44/83 140/43/83 f 121/259/17 146/275/17 194/308/17 138/304/17 f 117/306/17 146/275/17 192/268/17 124/267/17 f 199/378/63 205/265/63 192/268/63 125/276/63 f 179/334/63 196/333/63 205/265/63 199/378/63 f 121/259/48 197/258/48 199/378/48 125/276/48 f 118/379/54 135/380/54 201/381/54 200/382/54 f 139/175/84 137/370/84 202/181/84 203/112/84 f 135/380/85 141/47/85 204/50/85 201/381/85 f 196/333/63 178/365/63 198/266/63 205/265/63 f 176/367/86 177/366/86 197/383/86 200/384/86 f 172/362/16 127/361/16 170/385/16 193/386/16 f 193/242/14 103/241/14 82/387/14 172/388/14 f 44/389/16 43/390/16 77/391/16 72/72/16 71/71/16 79/392/16 f 161/393/14 169/290/14 170/289/14 127/294/14 130/293/14 f 83/89/87 173/88/87 172/394/87 82/395/87 f 96/143/14 186/84/14 188/83/14 190/235/14 100/192/14 98/193/14 f 62/138/14 59/137/14 149/325/14 152/326/14 o chassis-fender-left_fender-left v -2.248215 1.790193 2.516953 v -2.248215 1.760275 2.597573 v -1.964961 3.775234 2.514266 v -1.964961 3.775234 2.600259 v -1.227716 1.790193 2.516953 v -1.227716 1.760275 2.597573 v -1.227716 3.775234 2.514266 v -1.227716 3.775234 2.600259 v -2.248215 1.618749 2.455036 v -2.248215 1.568168 2.524579 v -1.227716 1.568168 2.524579 v -1.227716 1.618749 2.455036 v -2.248215 1.477421 2.359885 v -2.248215 1.398187 2.393303 v -1.227716 1.398187 2.393303 v -1.227716 1.477421 2.359885 v -2.248215 1.178246 1.657272 v -2.248215 1.115317 1.715878 v -1.227716 1.115317 1.715878 v -1.227716 1.178246 1.657272 v -2.248215 1.075977 1.549947 v -2.248215 1.031830 1.623743 v -1.227716 1.031830 1.623743 v -1.227716 1.075977 1.549947 v -2.248215 0.982369 1.499082 v -2.248215 0.975286 1.584783 v -1.227716 0.975286 1.584783 v -1.227716 0.982369 1.499082 v -2.248215 -1.605134 1.542124 v -2.248215 -1.612217 1.627824 v -1.227716 -1.612217 1.627824 v -1.227716 -1.605134 1.542124 v -2.248215 3.470147 2.514679 v -2.248215 3.465549 2.599846 v -1.227716 3.470147 2.514679 v -1.227716 3.465548 2.599846 vt 0.683133 0.664796 vt 0.703679 0.664796 vt 0.703679 0.667954 vt 0.683133 0.667954 vt 0.703679 0.688500 vt 0.683133 0.688500 vt 0.683133 0.691658 vt 0.703679 0.691658 vt 0.703679 0.709046 vt 0.683133 0.709046 vt 0.724225 0.709046 vt 0.724225 0.709046 vt 0.703679 0.709046 vt 0.662587 0.691658 vt 0.662587 0.709046 vt 0.724225 0.691658 vt 0.724225 0.709046 vt 0.703679 0.709046 vt 0.683133 0.709046 vt 0.703679 0.647408 vt 0.683133 0.647408 vt 0.683133 0.647408 vt 0.703679 0.647408 vt 0.662587 0.709046 vt 0.662587 0.709046 vt 0.683133 0.709046 vt 0.683133 0.709046 vt 0.662587 0.709046 vt 0.683133 0.647408 vt 0.703679 0.647408 vt 0.703679 0.709046 vt 0.724225 0.709046 vt 0.724225 0.709046 vt 0.703679 0.709046 vt 0.683133 0.647408 vt 0.703679 0.647408 vt 0.683133 0.709046 vt 0.703679 0.709046 vt 0.683133 0.709046 vt 0.662587 0.709046 vt 0.683133 0.647408 vt 0.703679 0.647408 vt 0.662587 0.709046 vt 0.683133 0.709046 vt 0.662587 0.709046 vt 0.683133 0.647408 vt 0.703679 0.647408 vt 0.724225 0.709046 vt 0.703679 0.709046 vt 0.703679 0.729592 vt 0.683133 0.729592 vt 0.683133 0.647408 vt 0.703679 0.647408 vt 0.724225 0.709046 vt 0.724225 0.688500 vt 0.662587 0.688500 vn -0.7353 0.6775 0.0182 vn 0.0000 1.0000 0.0000 vn 1.0000 0.0000 0.0000 vn 0.0000 -0.3552 0.9348 vn 0.0000 -0.0014 -1.0000 vn 0.0000 -0.0013 1.0000 vn 0.0000 -0.6112 0.7914 vn -1.0000 0.0000 0.0000 vn 0.0000 0.3397 -0.9405 vn 0.0000 0.9201 -0.3918 vn 0.0000 0.5585 -0.8295 vn 0.0000 -0.7410 0.6715 vn 0.0000 -0.9228 0.3853 vn 0.0000 0.7240 -0.6899 vn 0.0000 -0.0166 -0.9999 vn 0.0000 0.4774 -0.8787 vn 0.0000 -0.5674 0.8235 vn 0.0000 -0.9966 -0.0824 vn 0.0000 0.0166 0.9999 usemtl Material s off f 238/396/88 239/397/88 209/398/88 208/399/88 f 208/399/89 209/398/89 213/400/89 212/401/89 f 240/402/90 241/403/90 211/404/90 210/405/90 f 211/404/91 207/406/91 215/407/91 216/408/91 f 238/409/92 240/402/92 210/405/92 206/410/92 f 241/403/93 239/411/93 207/406/93 211/404/93 f 216/408/94 215/407/94 219/412/94 220/413/94 f 210/405/90 211/404/90 216/408/90 217/414/90 f 207/415/95 206/416/95 214/417/95 215/418/95 f 206/410/96 210/405/96 217/414/96 214/419/96 f 218/420/97 221/421/97 225/422/97 222/423/97 f 217/414/90 216/408/90 220/413/90 221/421/90 f 214/419/98 217/414/98 221/421/98 218/420/98 f 215/418/95 214/417/95 218/424/95 219/425/95 f 224/426/99 223/427/99 227/428/99 228/429/99 f 219/425/95 218/424/95 222/430/95 223/431/95 f 220/413/100 219/412/100 223/427/100 224/426/100 f 221/421/90 220/413/90 224/426/90 225/422/90 f 229/432/90 228/429/90 232/433/90 233/434/90 f 225/422/90 224/426/90 228/429/90 229/432/90 f 222/423/101 225/422/101 229/432/101 226/435/101 f 223/431/95 222/430/95 226/436/95 227/437/95 f 230/438/102 233/434/102 237/439/102 234/440/102 f 226/435/103 229/432/103 233/434/103 230/438/103 f 227/437/95 226/436/95 230/441/95 231/442/95 f 228/429/104 227/428/104 231/443/104 232/433/104 f 237/439/105 236/444/105 235/445/105 234/446/105 f 231/442/95 230/441/95 234/447/95 235/448/95 f 232/433/106 231/443/106 235/449/106 236/444/106 f 233/434/90 232/433/90 236/444/90 237/439/90 f 213/400/93 209/450/93 239/411/93 241/403/93 f 208/451/92 212/401/92 240/402/92 238/409/92 f 212/401/90 213/400/90 241/403/90 240/402/90 f 206/416/95 207/415/95 239/397/95 238/396/95 o chassis-fender-right_fender-right v 2.252910 1.790193 2.516953 v 2.252910 1.760275 2.597573 v 1.969656 3.775234 2.514266 v 1.969656 3.775234 2.600259 v 1.232411 1.790193 2.516953 v 1.232411 1.760275 2.597573 v 1.232411 3.775234 2.514266 v 1.232411 3.775234 2.600259 v 2.252910 1.618749 2.455036 v 2.252910 1.568168 2.524579 v 1.232411 1.568168 2.524579 v 1.232411 1.618749 2.455036 v 2.252910 1.477421 2.359885 v 2.252910 1.398187 2.393303 v 1.232411 1.398187 2.393303 v 1.232411 1.477421 2.359885 v 2.252910 1.178246 1.657272 v 2.252910 1.115317 1.715878 v 1.232411 1.115317 1.715878 v 1.232411 1.178246 1.657272 v 2.252910 1.075977 1.549947 v 2.252910 1.031830 1.623743 v 1.232411 1.031830 1.623743 v 1.232411 1.075977 1.549947 v 2.252910 0.982369 1.499082 v 2.252910 0.975286 1.584783 v 1.232411 0.975286 1.584783 v 1.232411 0.982369 1.499082 v 2.252910 -1.605134 1.542124 v 2.252910 -1.612217 1.627824 v 1.232411 -1.612217 1.627824 v 1.232411 -1.605134 1.542124 v 2.252910 3.470147 2.514679 v 2.252910 3.465549 2.599846 v 1.232411 3.470147 2.514679 v 1.232411 3.465548 2.599846 vt 0.673290 0.664796 vt 0.673290 0.667954 vt 0.693836 0.667954 vt 0.693836 0.664796 vt 0.673290 0.688500 vt 0.693836 0.688500 vt 0.673290 0.691658 vt 0.673290 0.709046 vt 0.693836 0.709046 vt 0.693836 0.691658 vt 0.693836 0.709046 vt 0.714382 0.709046 vt 0.714382 0.709046 vt 0.652744 0.691658 vt 0.652744 0.709046 vt 0.714382 0.691658 vt 0.693836 0.709046 vt 0.714382 0.709046 vt 0.673290 0.709046 vt 0.693836 0.647408 vt 0.693836 0.647408 vt 0.673290 0.647408 vt 0.673290 0.647408 vt 0.652744 0.709046 vt 0.652744 0.709046 vt 0.652744 0.709046 vt 0.673290 0.709046 vt 0.673290 0.709046 vt 0.693836 0.647408 vt 0.673290 0.647408 vt 0.693836 0.709046 vt 0.693836 0.709046 vt 0.714382 0.709046 vt 0.714382 0.709046 vt 0.693836 0.647408 vt 0.673290 0.647408 vt 0.673290 0.709046 vt 0.673290 0.709046 vt 0.693836 0.709046 vt 0.652744 0.709046 vt 0.693836 0.647408 vt 0.673290 0.647408 vt 0.652744 0.709046 vt 0.652744 0.709046 vt 0.673290 0.709046 vt 0.693836 0.647408 vt 0.673290 0.647408 vt 0.714382 0.709046 vt 0.673290 0.729592 vt 0.693836 0.729592 vt 0.693836 0.709046 vt 0.693836 0.647408 vt 0.673290 0.647408 vt 0.714382 0.709046 vt 0.714382 0.688500 vt 0.652744 0.688500 vn 0.7353 0.6775 0.0182 vn 0.0000 1.0000 0.0000 vn -1.0000 0.0000 0.0000 vn 0.0000 -0.3552 0.9348 vn 0.0000 -0.0014 -1.0000 vn -0.0000 -0.0013 1.0000 vn 0.0000 -0.6112 0.7914 vn 1.0000 -0.0000 0.0000 vn 0.0000 0.3397 -0.9405 vn 0.0000 0.9201 -0.3918 vn 0.0000 0.5585 -0.8295 vn 0.0000 -0.7410 0.6715 vn 0.0000 -0.9228 0.3853 vn 0.0000 0.7240 -0.6899 vn 0.0000 -0.0166 -0.9999 vn 0.0000 0.4774 -0.8787 vn 0.0000 -0.5674 0.8235 vn 0.0000 -0.9966 -0.0824 vn 0.0000 0.0166 0.9999 usemtl Material.006 s off f 274/452/107 244/453/107 245/454/107 275/455/107 f 244/453/108 248/456/108 249/457/108 245/454/108 f 276/458/109 246/459/109 247/460/109 277/461/109 f 247/460/110 252/462/110 251/463/110 243/464/110 f 274/465/111 242/466/111 246/459/111 276/458/111 f 277/461/112 247/460/112 243/464/112 275/467/112 f 252/462/113 256/468/113 255/469/113 251/463/113 f 246/459/109 253/470/109 252/462/109 247/460/109 f 243/471/114 251/472/114 250/473/114 242/474/114 f 242/466/115 250/475/115 253/470/115 246/459/115 f 254/476/116 258/477/116 261/478/116 257/479/116 f 253/470/109 257/479/109 256/468/109 252/462/109 f 250/475/117 254/476/117 257/479/117 253/470/117 f 251/472/114 255/480/114 254/481/114 250/473/114 f 260/482/118 264/483/118 263/484/118 259/485/118 f 255/480/114 259/486/114 258/487/114 254/481/114 f 256/468/119 260/482/119 259/485/119 255/469/119 f 257/479/109 261/478/109 260/482/109 256/468/109 f 265/488/109 269/489/109 268/490/109 264/483/109 f 261/478/109 265/488/109 264/483/109 260/482/109 f 258/477/120 262/491/120 265/488/120 261/478/120 f 259/486/114 263/492/114 262/493/114 258/487/114 f 266/494/121 270/495/121 273/496/121 269/489/121 f 262/491/122 266/494/122 269/489/122 265/488/122 f 263/492/114 267/497/114 266/498/114 262/493/114 f 264/483/123 268/490/123 267/499/123 263/484/123 f 273/496/124 270/500/124 271/501/124 272/502/124 f 267/497/114 271/503/114 270/504/114 266/498/114 f 268/490/125 272/502/125 271/505/125 267/499/125 f 269/489/109 273/496/109 272/502/109 268/490/109 f 249/457/112 277/461/112 275/467/112 245/506/112 f 244/507/111 274/465/111 276/458/111 248/456/111 f 248/456/109 276/458/109 277/461/109 249/457/109 f 242/474/114 274/452/114 275/455/114 243/471/114 o chassis-bumper_Bumper v -2.054904 4.148100 1.306946 v -2.054904 4.148100 1.733416 v -2.054904 4.295115 1.306946 v -2.054904 4.295115 1.733416 v 2.150561 4.148100 1.306946 v 2.150561 4.148100 1.733416 v 2.150561 4.295115 1.306946 v 2.150561 4.295115 1.733416 v -1.015229 4.295115 1.306946 v -1.015229 4.295115 1.733416 v -1.015229 4.148100 1.306946 v -1.015229 4.148100 1.733416 v -0.846180 4.295115 1.733416 v -0.846180 4.148100 1.306946 v -0.846180 4.295115 1.306946 v -0.846180 4.148100 1.733416 v 0.819656 4.295115 1.733416 v 0.819656 4.148100 1.306946 v 0.819656 4.295115 1.306946 v 0.819656 4.148100 1.733416 v 1.000342 4.295115 1.733416 v 1.000342 4.148100 1.306946 v 1.000342 4.295115 1.306946 v 1.000342 4.148100 1.733416 v -0.846180 3.151901 1.733416 v -1.015229 3.151901 1.733416 v -1.015229 3.151901 1.306946 v -0.846180 3.151901 1.306946 v 1.000342 3.151901 1.733416 v 0.819656 3.151901 1.733416 v 0.819656 3.151901 1.306946 v 1.000342 3.151901 1.306946 vt 0.303628 0.900609 vt 0.322333 0.900609 vt 0.322333 0.919314 vt 0.303628 0.919314 vt 0.303628 0.932903 vt 0.322333 0.932903 vt 0.322333 0.938019 vt 0.303628 0.938019 vt 0.322333 0.956724 vt 0.303628 0.956724 vt 0.303628 0.970805 vt 0.322333 0.970805 vt 0.322333 0.975429 vt 0.303628 0.975429 vt 0.298512 0.938019 vt 0.298512 0.956724 vt 0.336414 0.938019 vt 0.341038 0.938019 vt 0.341038 0.956724 vt 0.336414 0.956724 vt 0.335662 0.938019 vt 0.335662 0.956724 vt 0.284923 0.938019 vt 0.289547 0.938019 vt 0.289547 0.956724 vt 0.284923 0.956724 vt 0.336414 0.956724 vt 0.335662 0.956724 vt 0.322333 0.923939 vt 0.303628 0.923939 vt 0.303628 0.962644 vt 0.322333 0.962644 vt 0.322333 0.970053 vt 0.303628 0.970053 vt 0.328253 0.938019 vt 0.328253 0.956724 vt 0.290299 0.938019 vt 0.290299 0.956724 vt 0.322333 0.924690 vt 0.303628 0.924690 vt 0.327449 0.938019 vt 0.327449 0.956724 vt 0.297708 0.956724 vt 0.298512 0.956724 vt 0.297708 0.956724 vt 0.297708 0.938019 vt 0.322333 0.932100 vt 0.303628 0.932100 vt 0.322333 0.961840 vt 0.303628 0.961840 vt 0.303628 0.970053 vt 0.322333 0.970053 vt 0.322333 0.970805 vt 0.303628 0.970805 vt 0.303628 0.961840 vt 0.322333 0.961840 vt 0.322333 0.962644 vt 0.303628 0.962644 vt 0.328253 0.956724 vt 0.327449 0.956724 vt 0.290299 0.956724 vt 0.289547 0.956724 vn -1.0000 0.0000 0.0000 vn 0.0000 1.0000 0.0000 vn 1.0000 0.0000 0.0000 vn 0.0000 -1.0000 0.0000 vn 0.0000 0.0000 -1.0000 vn 0.0000 0.0000 1.0000 usemtl Material s off f 278/508/126 279/509/126 281/510/126 280/511/126 f 300/512/127 298/513/127 285/514/127 284/515/127 f 284/515/128 285/514/128 283/516/128 282/517/128 f 288/518/129 289/519/129 279/520/129 278/521/129 f 300/522/130 284/515/130 282/517/130 299/523/130 f 287/524/131 281/525/131 279/526/131 289/527/131 f 290/528/131 287/524/131 289/527/131 293/529/131 f 280/530/130 286/531/130 288/532/130 278/533/130 f 293/529/131 289/527/131 303/534/131 302/535/131 f 280/511/127 281/510/127 287/536/127 286/537/127 f 295/538/129 297/539/129 293/540/129 291/541/129 f 294/542/131 290/528/131 293/529/131 297/543/131 f 286/531/130 292/544/130 291/545/130 288/532/130 f 286/537/127 287/536/127 290/546/127 292/547/127 f 298/548/131 294/542/131 297/543/131 301/549/131 f 295/550/130 299/523/130 309/551/130 308/552/130 f 292/544/130 296/553/130 295/550/130 291/545/130 f 292/547/127 290/546/127 294/554/127 296/555/127 f 282/517/129 283/516/129 301/556/129 299/557/129 f 285/514/131 298/548/131 301/549/131 283/516/131 f 296/553/130 300/522/130 299/523/130 295/550/130 f 296/555/127 294/554/127 298/513/127 300/512/127 f 305/558/129 302/559/129 303/560/129 304/561/129 f 309/562/129 306/563/129 307/564/129 308/565/129 f 291/541/128 293/540/128 302/559/128 305/558/128 f 297/539/126 295/538/126 308/565/126 307/564/126 f 301/549/131 297/543/131 307/566/131 306/567/131 f 299/557/128 301/556/128 306/563/128 309/562/128 f 289/519/126 288/518/126 304/561/126 303/560/126 f 288/532/130 291/545/130 305/568/130 304/569/130 o chassis-cannister_Cannister v -1.498120 -4.596298 2.097742 v -1.498120 -4.596298 3.647724 v -1.498120 -4.005651 2.097742 v -1.498120 -4.005651 3.647724 v -0.297338 -4.596298 2.097742 v -0.297338 -4.596298 3.647724 v -0.297338 -4.005651 2.097742 v -0.297338 -4.005651 3.647724 v -0.779052 -4.005651 2.097742 v -1.016407 -4.005651 2.097742 v -1.016407 -4.005651 3.647724 v -0.779052 -4.005651 3.647724 v -1.016407 -4.596298 2.097742 v -0.779052 -4.596298 2.097742 v -0.779052 -4.596298 3.647724 v -1.016407 -4.596298 3.647724 v -1.016407 -4.119907 3.721184 v -0.779052 -4.119907 3.721184 v -1.016407 -4.710554 2.171202 v -0.779052 -4.710554 2.171202 v -0.779052 -4.710554 3.721184 v -1.016407 -4.710554 3.721184 vt 0.306872 0.893900 vt 0.328932 0.893900 vt 0.328932 0.915960 vt 0.306872 0.915960 vt 0.306872 0.930666 vt 0.328932 0.930666 vt 0.328932 0.938019 vt 0.306872 0.938019 vt 0.328932 0.960079 vt 0.306872 0.960079 vt 0.306872 0.974786 vt 0.328932 0.974786 vt 0.328932 0.982139 vt 0.306872 0.982139 vt 0.299519 0.938019 vt 0.299519 0.960079 vt 0.343638 0.938019 vt 0.350992 0.938019 vt 0.350992 0.960079 vt 0.343638 0.960079 vt 0.336285 0.938019 vt 0.336285 0.960079 vt 0.306872 0.967432 vt 0.328932 0.967432 vt 0.328932 0.967432 vt 0.306872 0.967432 vt 0.284812 0.938019 vt 0.292166 0.938019 vt 0.292166 0.960079 vt 0.284812 0.960079 vt 0.328932 0.923313 vt 0.328932 0.923313 vt 0.328932 0.930666 vt 0.306872 0.923313 vt 0.336285 0.938019 vt 0.343638 0.938019 vt 0.343638 0.960079 vt 0.336285 0.960079 vt 0.328932 0.974786 vt 0.306872 0.974786 vt 0.299519 0.960079 vt 0.292166 0.960079 vn -1.0000 0.0000 0.0000 vn 0.0000 1.0000 0.0000 vn 1.0000 0.0000 0.0000 vn 0.0000 -1.0000 0.0000 vn 0.0000 0.0000 -1.0000 vn 0.0000 0.0000 1.0000 vn 0.0000 0.5408 0.8411 vn 0.0000 -0.5408 -0.8411 usemtl Material s off f 310/570/132 311/571/132 313/572/132 312/573/132 f 318/574/133 321/575/133 317/576/133 316/577/133 f 316/577/134 317/576/134 315/578/134 314/579/134 f 322/580/135 325/581/135 311/582/135 310/583/135 f 318/584/136 316/577/136 314/579/136 323/585/136 f 320/586/137 313/587/137 311/588/137 325/589/137 f 317/576/137 321/590/137 324/591/137 315/578/137 f 323/592/134 324/593/134 330/594/134 329/595/134 f 312/596/136 319/597/136 322/598/136 310/599/136 f 319/597/136 318/584/136 323/585/136 322/598/136 f 314/579/135 315/578/135 324/593/135 323/592/135 f 321/575/138 320/600/138 326/601/138 327/602/138 f 312/573/133 313/572/133 320/600/133 319/603/133 f 319/603/133 320/600/133 321/575/133 318/574/133 f 327/604/137 326/605/137 331/606/137 330/607/137 f 329/595/135 330/594/135 331/608/135 328/609/135 f 324/591/134 321/590/134 327/604/134 330/607/134 f 325/581/132 322/580/132 328/609/132 331/608/132 f 320/586/132 325/589/132 331/606/132 326/605/132 f 322/598/139 323/585/139 329/610/139 328/611/139 o chassis-lamp-left_lamp-left v -1.324289 3.825797 3.192716 v -1.324289 3.263589 3.192716 v -1.143598 3.825797 3.126951 v -1.143598 3.263589 3.126950 v -1.047455 3.825797 2.960425 v -1.047455 3.263589 2.960425 v -1.080845 3.825797 2.771060 v -1.080845 3.263589 2.771060 v -1.228145 3.825797 2.647460 v -1.228145 3.263589 2.647460 v -1.420432 3.825797 2.647460 v -1.420432 3.263589 2.647460 v -1.567732 3.825797 2.771060 v -1.567732 3.263589 2.771060 v -1.601122 3.825797 2.960425 v -1.601122 3.263589 2.960425 v -1.504979 3.825797 3.126951 v -1.504979 3.263589 3.126950 vt 0.363359 0.944968 vt 0.363359 0.980203 vt 0.355529 0.980203 vt 0.355529 0.944968 vt 0.347700 0.980203 vt 0.347700 0.944968 vt 0.339870 0.980203 vt 0.339870 0.944968 vt 0.332040 0.980203 vt 0.332040 0.944968 vt 0.324210 0.980203 vt 0.324210 0.944968 vt 0.316380 0.980203 vt 0.316380 0.944968 vt 0.308550 0.980203 vt 0.308550 0.944968 vt 0.321379 0.940307 vt 0.310508 0.944264 vt 0.299637 0.940307 vt 0.293852 0.930288 vt 0.295861 0.918895 vt 0.304723 0.911459 vt 0.316292 0.911459 vt 0.325154 0.918895 vt 0.327163 0.930288 vt 0.300721 0.980203 vt 0.300721 0.944968 vt 0.292891 0.980203 vt 0.292891 0.944968 vt 0.345742 0.944264 vt 0.356613 0.940307 vt 0.362398 0.930288 vt 0.360389 0.918895 vt 0.351527 0.911459 vt 0.339958 0.911459 vt 0.331096 0.918895 vt 0.329087 0.930288 vt 0.334871 0.940307 vn 0.3420 -0.0000 0.9397 vn 0.8660 0.0000 0.5000 vn 0.9848 0.0000 -0.1736 vn 0.6428 0.0000 -0.7660 vn 0.0000 0.0000 -1.0000 vn -0.6428 0.0000 -0.7660 vn -0.9848 0.0000 -0.1736 vn 0.0000 -1.0000 -0.0000 vn -0.8660 0.0000 0.5000 vn -0.3420 -0.0000 0.9397 vn 0.0000 1.0000 -0.0000 usemtl Material s off f 332/612/140 333/613/140 335/614/140 334/615/140 f 334/615/141 335/614/141 337/616/141 336/617/141 f 336/617/142 337/616/142 339/618/142 338/619/142 f 338/619/143 339/618/143 341/620/143 340/621/143 f 340/621/144 341/620/144 343/622/144 342/623/144 f 342/623/145 343/622/145 345/624/145 344/625/145 f 344/625/146 345/624/146 347/626/146 346/627/146 f 335/628/147 333/629/147 349/630/147 347/631/147 345/632/147 343/633/147 341/634/147 339/635/147 337/636/147 f 346/627/148 347/626/148 349/637/148 348/638/148 f 348/638/149 349/637/149 333/639/149 332/640/149 f 332/641/150 334/642/150 336/643/150 338/644/150 340/645/150 342/646/150 344/647/150 346/648/150 348/649/150 o chassis-lamp-right_lamp-right v 1.257088 3.825797 3.192716 v 1.257088 3.263589 3.192716 v 1.437779 3.825797 3.126951 v 1.437779 3.263589 3.126950 v 1.533922 3.825797 2.960425 v 1.533922 3.263589 2.960425 v 1.500532 3.825797 2.771060 v 1.500532 3.263589 2.771060 v 1.353232 3.825797 2.647460 v 1.353232 3.263589 2.647460 v 1.160945 3.825797 2.647460 v 1.160945 3.263589 2.647460 v 1.013645 3.825797 2.771060 v 1.013645 3.263589 2.771060 v 0.980255 3.825797 2.960425 v 0.980255 3.263589 2.960425 v 1.076398 3.825797 3.126951 v 1.076398 3.263589 3.126950 vt 0.363359 0.944968 vt 0.363359 0.980203 vt 0.355529 0.980203 vt 0.355529 0.944968 vt 0.347700 0.980203 vt 0.347700 0.944968 vt 0.339870 0.980203 vt 0.339870 0.944968 vt 0.332040 0.980203 vt 0.332040 0.944968 vt 0.324210 0.980203 vt 0.324210 0.944968 vt 0.316380 0.980203 vt 0.316380 0.944968 vt 0.308550 0.980203 vt 0.308550 0.944968 vt 0.321379 0.940307 vt 0.310508 0.944264 vt 0.299637 0.940307 vt 0.293852 0.930288 vt 0.295861 0.918895 vt 0.304723 0.911459 vt 0.316292 0.911459 vt 0.325154 0.918895 vt 0.327163 0.930288 vt 0.300721 0.980203 vt 0.300721 0.944968 vt 0.292891 0.980203 vt 0.292891 0.944968 vt 0.345742 0.944264 vt 0.356613 0.940307 vt 0.362398 0.930288 vt 0.360389 0.918895 vt 0.351527 0.911459 vt 0.339958 0.911459 vt 0.331096 0.918895 vt 0.329087 0.930288 vt 0.334871 0.940307 vn 0.3420 -0.0000 0.9397 vn 0.8660 -0.0000 0.5000 vn 0.9848 0.0000 -0.1736 vn 0.6428 0.0000 -0.7660 vn 0.0000 0.0000 -1.0000 vn -0.6428 0.0000 -0.7660 vn -0.9848 0.0000 -0.1736 vn 0.0000 -1.0000 0.0000 vn -0.8660 -0.0000 0.5000 vn -0.3420 -0.0000 0.9397 vn 0.0000 1.0000 -0.0000 usemtl Material.005 s off f 350/650/151 351/651/151 353/652/151 352/653/151 f 352/653/152 353/652/152 355/654/152 354/655/152 f 354/655/153 355/654/153 357/656/153 356/657/153 f 356/657/154 357/656/154 359/658/154 358/659/154 f 358/659/155 359/658/155 361/660/155 360/661/155 f 360/661/156 361/660/156 363/662/156 362/663/156 f 362/663/157 363/662/157 365/664/157 364/665/157 f 353/666/158 351/667/158 367/668/158 365/669/158 363/670/158 361/671/158 359/672/158 357/673/158 355/674/158 f 364/665/159 365/664/159 367/675/159 366/676/159 f 366/676/160 367/675/160 351/677/160 350/678/160 f 350/679/161 352/680/161 354/681/161 356/682/161 358/683/161 360/684/161 362/685/161 364/686/161 366/687/161 o chassis-spare_Wheel v 2.023177 -3.732064 2.891503 v 1.218354 -4.292669 2.891503 v 1.892179 -3.732064 2.360024 v 1.179544 -4.292669 2.734044 v 1.529196 -3.732064 1.950301 v 1.072005 -4.292669 2.612658 v 1.017383 -3.732064 1.756196 v 0.920372 -4.292669 2.555151 v 0.473989 -3.732064 1.822176 v 0.759384 -4.292669 2.574699 v 0.023501 -3.732064 2.133126 v 0.625920 -4.292669 2.666822 v -0.230881 -3.732064 2.617811 v 0.550555 -4.292669 2.810417 v -0.230881 -3.732064 3.165195 v 0.550555 -4.292669 2.972588 v 0.023501 -3.732064 3.649880 v 0.625920 -4.292669 3.116184 v 0.473989 -3.732064 3.960829 v 0.759384 -4.292669 3.208307 v 1.017383 -3.732064 4.026810 v 0.920372 -4.292669 3.227855 v 1.529196 -3.732064 3.832704 v 1.072005 -4.292669 3.170348 v 1.892179 -3.732064 3.422981 v 1.179544 -4.292669 3.048961 v 1.892179 -4.521030 2.360024 v 2.023177 -4.521030 2.891503 v 1.529196 -4.521030 1.950301 v 1.017383 -4.521030 1.756196 v 0.473990 -4.521030 1.822176 v 0.023501 -4.521030 2.133126 v -0.230881 -4.521030 2.617811 v -0.230881 -4.521030 3.165195 v 0.023501 -4.521030 3.649880 v 0.473990 -4.521030 3.960829 v 1.017383 -4.521030 4.026810 v 1.529196 -4.521030 3.832704 v 1.892179 -4.521030 3.422981 v 1.552601 -4.521030 2.538249 v 1.639670 -4.521030 2.891503 v 1.311339 -4.521030 2.265921 v 0.971156 -4.521030 2.136907 v 0.609983 -4.521030 2.180762 v 0.310560 -4.521030 2.387438 v 0.141482 -4.521030 2.709590 v 0.141482 -4.521030 3.073416 v 0.310560 -4.521030 3.395568 v 0.609983 -4.521030 3.602244 v 0.971156 -4.521030 3.646099 v 1.311339 -4.521030 3.517084 v 1.552601 -4.521030 3.244757 v 1.347870 -4.292669 2.645700 v 1.408455 -4.292669 2.891503 v 1.179995 -4.292669 2.456207 v 0.943286 -4.292669 2.366436 v 0.691973 -4.292669 2.396951 v 0.483627 -4.292669 2.540762 v 0.365978 -4.292669 2.764923 v 0.365978 -4.292669 3.018083 v 0.483627 -4.292669 3.242244 v 0.691973 -4.292669 3.386055 v 0.943286 -4.292669 3.416570 v 1.179995 -4.292669 3.326798 v 1.347870 -4.292669 3.137306 v 1.179544 -4.420413 2.734044 v 1.218354 -4.420413 2.891503 v 1.072005 -4.420413 2.612658 v 0.920372 -4.420413 2.555151 v 0.759384 -4.420413 2.574699 v 0.625920 -4.420413 2.666822 v 0.550555 -4.420413 2.810417 v 0.550555 -4.420413 2.972588 v 0.625920 -4.420413 3.116184 v 0.759384 -4.420413 3.208307 v 0.920372 -4.420413 3.227855 v 1.072005 -4.420413 3.170348 v 1.179544 -4.420413 3.048961 vt 0.111861 0.941309 vt 0.111861 0.986764 vt 0.104868 0.986764 vt 0.104868 0.941309 vt 0.097875 0.986764 vt 0.097875 0.941309 vt 0.090882 0.986764 vt 0.090882 0.941309 vt 0.083889 0.986764 vt 0.083889 0.941309 vt 0.076896 0.986764 vt 0.076896 0.941309 vt 0.069903 0.986764 vt 0.069903 0.941309 vt 0.062910 0.986764 vt 0.062910 0.941309 vt 0.055917 0.986764 vt 0.055917 0.941309 vt 0.048924 0.986764 vt 0.048924 0.941309 vt 0.041931 0.986764 vt 0.041931 0.941309 vt 0.034938 0.986764 vt 0.034938 0.941309 vt 0.050096 0.919361 vt 0.048999 0.922254 vt 0.048999 0.922254 vt 0.050096 0.919361 vt 0.027945 0.986764 vt 0.027945 0.941309 vt 0.020952 0.986764 vt 0.020952 0.941309 vt 0.089134 0.940400 vt 0.099273 0.937901 vt 0.107090 0.930976 vt 0.110793 0.921212 vt 0.109534 0.910845 vt 0.103602 0.902251 vt 0.094355 0.897398 vt 0.083912 0.897398 vt 0.074665 0.902251 vt 0.068733 0.910845 vt 0.067474 0.921212 vt 0.071178 0.930976 vt 0.078994 0.937901 vt 0.043679 0.933084 vt 0.050418 0.931422 vt 0.053818 0.937901 vt 0.043679 0.940400 vt 0.055614 0.926820 vt 0.061635 0.930976 vt 0.058075 0.920330 vt 0.065338 0.921212 vt 0.057238 0.913440 vt 0.064079 0.910845 vt 0.053295 0.907727 vt 0.058147 0.902251 vt 0.047149 0.904502 vt 0.048900 0.897398 vt 0.040209 0.904502 vt 0.038458 0.897398 vt 0.034063 0.907727 vt 0.029211 0.902251 vt 0.030120 0.913440 vt 0.023279 0.910845 vt 0.029283 0.920330 vt 0.022020 0.921212 vt 0.031744 0.926820 vt 0.025723 0.930976 vt 0.036940 0.931422 vt 0.033540 0.937901 vt 0.063210 0.940391 vt 0.067900 0.939235 vt 0.069950 0.943141 vt 0.063210 0.944802 vt 0.071515 0.936033 vt 0.075145 0.938539 vt 0.073227 0.931517 vt 0.077606 0.932049 vt 0.072645 0.926722 vt 0.076770 0.925158 vt 0.069902 0.922748 vt 0.072827 0.919446 vt 0.065625 0.920503 vt 0.066681 0.916220 vt 0.060795 0.920503 vt 0.059740 0.916220 vt 0.056519 0.922748 vt 0.053594 0.919446 vt 0.053775 0.926722 vt 0.049651 0.925158 vt 0.053193 0.931517 vt 0.048814 0.932049 vt 0.054906 0.936033 vt 0.051276 0.938539 vt 0.058521 0.939235 vt 0.056471 0.943141 vt 0.676491 0.698483 vt 0.679495 0.697743 vt 0.681181 0.700954 vt 0.676491 0.702110 vt 0.681811 0.695691 vt 0.684796 0.697752 vt 0.682908 0.692798 vt 0.686509 0.693236 vt 0.682535 0.689727 vt 0.685926 0.688441 vt 0.680778 0.687181 vt 0.683183 0.684466 vt 0.678038 0.685743 vt 0.678906 0.682222 vt 0.674945 0.685743 vt 0.674077 0.682222 vt 0.672205 0.687181 vt 0.669800 0.684466 vt 0.670448 0.689727 vt 0.667057 0.688441 vt 0.670075 0.692798 vt 0.666474 0.693236 vt 0.671172 0.695691 vt 0.668187 0.697752 vt 0.673487 0.697743 vt 0.671802 0.700954 vt 0.679495 0.697743 vt 0.676491 0.698483 vt 0.673487 0.697743 vt 0.671172 0.695691 vt 0.670075 0.692798 vt 0.670448 0.689727 vt 0.672205 0.687181 vt 0.674945 0.685743 vt 0.678038 0.685743 vt 0.680778 0.687181 vt 0.682535 0.689727 vt 0.682908 0.692798 vt 0.681811 0.695691 vt 0.049723 0.916290 vt 0.049723 0.916290 vt 0.045226 0.912306 vt 0.047965 0.913744 vt 0.047965 0.913744 vt 0.045226 0.912306 vt 0.039393 0.913744 vt 0.042132 0.912306 vt 0.042132 0.912306 vt 0.039393 0.913744 vt 0.046683 0.924305 vt 0.046683 0.924305 vn 0.9709 0.0000 -0.2393 vn 0.7485 0.0000 -0.6631 vn 0.3546 0.0000 -0.9350 vn -0.1205 -0.0000 -0.9927 vn -0.5681 -0.0000 -0.8230 vn -0.8855 0.0000 -0.4647 vn -1.0000 0.0000 0.0000 vn -0.8855 0.0000 0.4647 vn -0.5681 -0.0000 0.8230 vn -0.1205 -0.0000 0.9927 vn 0.3546 0.0000 0.9350 vn 0.7485 0.0000 0.6631 vn 0.9709 0.0000 0.2393 vn -0.0000 1.0000 0.0000 vn 0.0000 -1.0000 -0.0000 vn -0.6924 -0.7010 0.1707 vn -0.5338 -0.7010 0.4729 vn -0.2529 -0.7010 0.6668 vn 0.0860 -0.7010 0.7079 vn 0.4051 -0.7010 0.5869 vn 0.6314 -0.7010 0.3314 vn 0.7131 -0.7010 0.0000 vn 0.6314 -0.7010 -0.3314 vn 0.4051 -0.7010 -0.5869 vn 0.0860 -0.7010 -0.7079 vn -0.2529 -0.7010 -0.6668 vn -0.5338 -0.7010 -0.4729 vn -0.6924 -0.7010 -0.1707 usemtl Material.002 s off f 368/688/162 395/689/162 394/690/162 370/691/162 f 370/691/163 394/690/163 396/692/163 372/693/163 f 372/693/164 396/692/164 397/694/164 374/695/164 f 374/695/165 397/694/165 398/696/165 376/697/165 f 376/697/166 398/696/166 399/698/166 378/699/166 f 378/699/167 399/698/167 400/700/167 380/701/167 f 380/701/168 400/700/168 401/702/168 382/703/168 f 382/703/169 401/702/169 402/704/169 384/705/169 f 384/705/170 402/704/170 403/706/170 386/707/170 f 386/707/171 403/706/171 404/708/171 388/709/171 f 388/709/172 404/708/172 405/710/172 390/711/172 f 375/712/164 373/713/164 435/714/164 436/715/164 f 390/711/173 405/710/173 406/716/173 392/717/173 f 392/717/174 406/716/174 395/718/174 368/719/174 f 368/720/175 370/721/175 372/722/175 374/723/175 376/724/175 378/725/175 380/726/175 382/727/175 384/728/175 386/729/175 388/730/175 390/731/175 392/732/175 f 408/733/176 407/734/176 394/735/176 395/736/176 f 407/734/176 409/737/176 396/738/176 394/735/176 f 409/737/176 410/739/176 397/740/176 396/738/176 f 410/739/176 411/741/176 398/742/176 397/740/176 f 411/741/176 412/743/176 399/744/176 398/742/176 f 412/743/176 413/745/176 400/746/176 399/744/176 f 413/745/176 414/747/176 401/748/176 400/746/176 f 414/747/176 415/749/176 402/750/176 401/748/176 f 415/749/176 416/751/176 403/752/176 402/750/176 f 416/751/176 417/753/176 404/754/176 403/752/176 f 417/753/176 418/755/176 405/756/176 404/754/176 f 418/755/176 419/757/176 406/758/176 405/756/176 f 419/757/176 408/733/176 395/736/176 406/758/176 f 421/759/177 420/760/177 407/761/177 408/762/177 f 420/760/178 422/763/178 409/764/178 407/761/178 f 422/763/179 423/765/179 410/766/179 409/764/179 f 423/765/180 424/767/180 411/768/180 410/766/180 f 424/767/181 425/769/181 412/770/181 411/768/181 f 425/769/182 426/771/182 413/772/182 412/770/182 f 426/771/183 427/773/183 414/774/183 413/772/183 f 427/773/184 428/775/184 415/776/184 414/774/184 f 428/775/185 429/777/185 416/778/185 415/776/185 f 429/777/186 430/779/186 417/780/186 416/778/186 f 430/779/187 431/781/187 418/782/187 417/780/187 f 431/781/188 432/783/188 419/784/188 418/782/188 f 432/783/189 421/759/189 408/762/189 419/784/189 f 369/785/176 371/786/176 420/787/176 421/788/176 f 371/786/176 373/789/176 422/790/176 420/787/176 f 373/789/176 375/791/176 423/792/176 422/790/176 f 375/791/176 377/793/176 424/794/176 423/792/176 f 377/793/176 379/795/176 425/796/176 424/794/176 f 379/795/176 381/797/176 426/798/176 425/796/176 f 381/797/176 383/799/176 427/800/176 426/798/176 f 383/799/176 385/801/176 428/802/176 427/800/176 f 385/801/176 387/803/176 429/804/176 428/802/176 f 387/803/176 389/805/176 430/806/176 429/804/176 f 389/805/176 391/807/176 431/808/176 430/806/176 f 391/807/176 393/809/176 432/810/176 431/808/176 f 393/809/176 369/785/176 421/788/176 432/810/176 f 433/811/176 434/812/176 445/813/176 444/814/176 443/815/176 442/816/176 441/817/176 440/818/176 439/819/176 438/820/176 437/821/176 436/822/176 435/823/176 f 391/807/172 389/805/172 443/815/172 444/814/172 f 377/824/165 375/712/165 436/715/165 437/825/165 f 393/809/173 391/807/173 444/814/173 445/813/173 f 379/795/166 377/793/166 437/821/166 438/820/166 f 369/785/174 393/809/174 445/813/174 434/812/174 f 381/826/167 379/827/167 438/828/167 439/829/167 f 383/799/168 381/797/168 439/819/168 440/818/168 f 385/830/169 383/831/169 440/832/169 441/833/169 f 371/786/162 369/785/162 434/812/162 433/811/162 f 387/803/170 385/801/170 441/817/170 442/816/170 f 373/713/163 371/834/163 433/835/163 435/714/163 f 389/805/171 387/803/171 442/816/171 443/815/171 o Wheel v -0.394486 1.143645 0.000000 v 0.166118 0.338822 0.000000 v -0.394486 1.012648 -0.531478 v 0.166118 0.300012 -0.157459 v -0.394486 0.649665 -0.941202 v 0.166118 0.192473 -0.278845 v -0.394486 0.137851 -1.135307 v 0.166119 0.040841 -0.336352 v -0.394486 -0.405542 -1.069327 v 0.166119 -0.120148 -0.316804 v -0.394486 -0.856031 -0.758377 v 0.166119 -0.253612 -0.224681 v -0.394486 -1.110413 -0.273692 v 0.166119 -0.328977 -0.081085 v -0.394486 -1.110413 0.273692 v 0.166119 -0.328977 0.081085 v -0.394486 -0.856031 0.758377 v 0.166119 -0.253612 0.224681 v -0.394486 -0.405542 1.069327 v 0.166119 -0.120148 0.316804 v -0.394486 0.137851 1.135307 v 0.166119 0.040841 0.336352 v -0.394486 0.649665 0.941201 v 0.166118 0.192473 0.278845 v -0.394486 1.012648 0.531478 v 0.166118 0.300012 0.157459 v 0.394480 1.012648 -0.531478 v 0.394480 1.143645 -0.000000 v 0.394480 0.649665 -0.941202 v 0.394480 0.137851 -1.135307 v 0.394480 -0.405542 -1.069327 v 0.394480 -0.856030 -0.758377 v 0.394480 -1.110413 -0.273692 v 0.394480 -1.110413 0.273692 v 0.394480 -0.856030 0.758377 v 0.394480 -0.405542 1.069327 v 0.394480 0.137851 1.135307 v 0.394480 0.649665 0.941201 v 0.394480 1.012648 0.531478 v 0.394480 0.673069 -0.353254 v 0.394480 0.760138 -0.000000 v 0.394480 0.431808 -0.625581 v 0.394480 0.091625 -0.754596 v 0.394480 -0.269549 -0.710741 v 0.394480 -0.568971 -0.504065 v 0.394480 -0.738050 -0.181913 v 0.394480 -0.738050 0.181913 v 0.394480 -0.568971 0.504065 v 0.394480 -0.269549 0.710741 v 0.394480 0.091625 0.754596 v 0.394480 0.431808 0.625581 v 0.394480 0.673069 0.353254 v 0.166118 0.468339 -0.245803 v 0.166118 0.528924 0.000000 v 0.166118 0.300463 -0.435296 v 0.166119 0.063755 -0.525067 v 0.166119 -0.187559 -0.494552 v 0.166119 -0.395905 -0.350741 v 0.166119 -0.513554 -0.126580 v 0.166119 -0.513554 0.126580 v 0.166119 -0.395905 0.350741 v 0.166119 -0.187559 0.494552 v 0.166119 0.063755 0.525067 v 0.166119 0.300463 0.435296 v 0.166118 0.468339 0.245803 v 0.293863 0.300012 -0.157459 v 0.293863 0.338822 -0.000000 v 0.293863 0.192473 -0.278845 v 0.293863 0.040841 -0.336352 v 0.293863 -0.120148 -0.316804 v 0.293863 -0.253612 -0.224681 v 0.293863 -0.328977 -0.081085 v 0.293863 -0.328977 0.081085 v 0.293863 -0.253612 0.224681 v 0.293863 -0.120148 0.316804 v 0.293863 0.040841 0.336352 v 0.293863 0.192473 0.278845 v 0.293863 0.300012 0.157459 vt 0.111861 0.941309 vt 0.111861 0.986764 vt 0.104868 0.986764 vt 0.104868 0.941309 vt 0.097875 0.986764 vt 0.097875 0.941309 vt 0.090882 0.986764 vt 0.090882 0.941309 vt 0.083889 0.986764 vt 0.083889 0.941309 vt 0.076896 0.986764 vt 0.076896 0.941309 vt 0.069903 0.986764 vt 0.069903 0.941309 vt 0.062910 0.986764 vt 0.062910 0.941309 vt 0.055917 0.986764 vt 0.055917 0.941309 vt 0.048924 0.986764 vt 0.048924 0.941309 vt 0.041931 0.986764 vt 0.041931 0.941309 vt 0.034938 0.986764 vt 0.034938 0.941309 vt 0.050096 0.919361 vt 0.048999 0.922254 vt 0.048999 0.922254 vt 0.050096 0.919361 vt 0.027945 0.986764 vt 0.027945 0.941309 vt 0.020952 0.986764 vt 0.020952 0.941309 vt 0.089134 0.940400 vt 0.099273 0.937901 vt 0.107090 0.930976 vt 0.110793 0.921212 vt 0.109534 0.910845 vt 0.103602 0.902251 vt 0.094355 0.897398 vt 0.083912 0.897398 vt 0.074665 0.902251 vt 0.068733 0.910845 vt 0.067474 0.921212 vt 0.071178 0.930976 vt 0.078994 0.937901 vt 0.043679 0.933084 vt 0.050418 0.931422 vt 0.053818 0.937901 vt 0.043679 0.940400 vt 0.055614 0.926820 vt 0.061635 0.930976 vt 0.058075 0.920330 vt 0.065338 0.921212 vt 0.057238 0.913440 vt 0.064079 0.910845 vt 0.053295 0.907727 vt 0.058147 0.902251 vt 0.047149 0.904502 vt 0.048900 0.897398 vt 0.040209 0.904502 vt 0.038458 0.897398 vt 0.034063 0.907727 vt 0.029211 0.902251 vt 0.030120 0.913440 vt 0.023279 0.910845 vt 0.029283 0.920330 vt 0.022020 0.921212 vt 0.031744 0.926820 vt 0.025723 0.930976 vt 0.036940 0.931422 vt 0.033540 0.937901 vt 0.063210 0.940391 vt 0.067900 0.939235 vt 0.069950 0.943141 vt 0.063210 0.944802 vt 0.071515 0.936033 vt 0.075145 0.938539 vt 0.073227 0.931517 vt 0.077606 0.932049 vt 0.072645 0.926722 vt 0.076770 0.925158 vt 0.069902 0.922748 vt 0.072827 0.919446 vt 0.065625 0.920503 vt 0.066681 0.916220 vt 0.060795 0.920503 vt 0.059740 0.916220 vt 0.056519 0.922748 vt 0.053594 0.919446 vt 0.053775 0.926722 vt 0.049651 0.925158 vt 0.053193 0.931517 vt 0.048814 0.932049 vt 0.054906 0.936033 vt 0.051276 0.938539 vt 0.058521 0.939235 vt 0.056471 0.943141 vt 0.676491 0.698483 vt 0.679495 0.697743 vt 0.681181 0.700954 vt 0.676491 0.702110 vt 0.681811 0.695691 vt 0.684796 0.697752 vt 0.682908 0.692798 vt 0.686509 0.693236 vt 0.682535 0.689727 vt 0.685926 0.688441 vt 0.680778 0.687181 vt 0.683183 0.684466 vt 0.678038 0.685743 vt 0.678906 0.682222 vt 0.674945 0.685743 vt 0.674077 0.682222 vt 0.672205 0.687181 vt 0.669800 0.684466 vt 0.670448 0.689727 vt 0.667057 0.688441 vt 0.670075 0.692798 vt 0.666474 0.693236 vt 0.671172 0.695691 vt 0.668187 0.697752 vt 0.673487 0.697743 vt 0.671802 0.700954 vt 0.679495 0.697743 vt 0.676491 0.698483 vt 0.673487 0.697743 vt 0.671172 0.695691 vt 0.670075 0.692798 vt 0.670448 0.689727 vt 0.672205 0.687181 vt 0.674945 0.685743 vt 0.678038 0.685743 vt 0.680778 0.687181 vt 0.682535 0.689727 vt 0.682908 0.692798 vt 0.681811 0.695691 vt 0.049723 0.916290 vt 0.049723 0.916290 vt 0.045226 0.912306 vt 0.047965 0.913744 vt 0.047965 0.913744 vt 0.045226 0.912306 vt 0.039393 0.913744 vt 0.042132 0.912306 vt 0.042132 0.912306 vt 0.039393 0.913744 vt 0.046683 0.924305 vt 0.046683 0.924305 vn 0.0000 0.9709 -0.2393 vn -0.0000 0.7485 -0.6631 vn -0.0000 0.3546 -0.9350 vn -0.0000 -0.1205 -0.9927 vn 0.0000 -0.5681 -0.8230 vn 0.0000 -0.8855 -0.4647 vn 0.0000 -1.0000 0.0000 vn 0.0000 -0.8855 0.4647 vn 0.0000 -0.5681 0.8230 vn 0.0000 -0.1205 0.9927 vn -0.0000 0.3546 0.9350 vn -0.0000 0.7485 0.6631 vn 0.0000 0.9709 0.2393 vn -1.0000 -0.0000 0.0000 vn 1.0000 0.0000 -0.0000 vn 0.7010 -0.6924 0.1707 vn 0.7010 -0.5338 0.4729 vn 0.7010 -0.2529 0.6668 vn 0.7010 0.0860 0.7079 vn 0.7010 0.4051 0.5869 vn 0.7010 0.6314 0.3314 vn 0.7010 0.7131 0.0000 vn 0.7010 0.6314 -0.3314 vn 0.7010 0.4051 -0.5869 vn 0.7010 0.0860 -0.7079 vn 0.7010 -0.2529 -0.6668 vn 0.7010 -0.5338 -0.4729 vn 0.7010 -0.6924 -0.1707 usemtl Material.002 s off f 446/836/190 473/837/190 472/838/190 448/839/190 f 448/839/191 472/838/191 474/840/191 450/841/191 f 450/841/192 474/840/192 475/842/192 452/843/192 f 452/843/193 475/842/193 476/844/193 454/845/193 f 454/845/194 476/844/194 477/846/194 456/847/194 f 456/847/195 477/846/195 478/848/195 458/849/195 f 458/849/196 478/848/196 479/850/196 460/851/196 f 460/851/197 479/850/197 480/852/197 462/853/197 f 462/853/198 480/852/198 481/854/198 464/855/198 f 464/855/199 481/854/199 482/856/199 466/857/199 f 466/857/200 482/856/200 483/858/200 468/859/200 f 453/860/192 451/861/192 513/862/192 514/863/192 f 468/859/201 483/858/201 484/864/201 470/865/201 f 470/865/202 484/864/202 473/866/202 446/867/202 f 446/868/203 448/869/203 450/870/203 452/871/203 454/872/203 456/873/203 458/874/203 460/875/203 462/876/203 464/877/203 466/878/203 468/879/203 470/880/203 f 486/881/204 485/882/204 472/883/204 473/884/204 f 485/882/204 487/885/204 474/886/204 472/883/204 f 487/885/204 488/887/204 475/888/204 474/886/204 f 488/887/204 489/889/204 476/890/204 475/888/204 f 489/889/204 490/891/204 477/892/204 476/890/204 f 490/891/204 491/893/204 478/894/204 477/892/204 f 491/893/204 492/895/204 479/896/204 478/894/204 f 492/895/204 493/897/204 480/898/204 479/896/204 f 493/897/204 494/899/204 481/900/204 480/898/204 f 494/899/204 495/901/204 482/902/204 481/900/204 f 495/901/204 496/903/204 483/904/204 482/902/204 f 496/903/204 497/905/204 484/906/204 483/904/204 f 497/905/204 486/881/204 473/884/204 484/906/204 f 499/907/205 498/908/205 485/909/205 486/910/205 f 498/908/206 500/911/206 487/912/206 485/909/206 f 500/911/207 501/913/207 488/914/207 487/912/207 f 501/913/208 502/915/208 489/916/208 488/914/208 f 502/915/209 503/917/209 490/918/209 489/916/209 f 503/917/210 504/919/210 491/920/210 490/918/210 f 504/919/211 505/921/211 492/922/211 491/920/211 f 505/921/212 506/923/212 493/924/212 492/922/212 f 506/923/213 507/925/213 494/926/213 493/924/213 f 507/925/214 508/927/214 495/928/214 494/926/214 f 508/927/215 509/929/215 496/930/215 495/928/215 f 509/929/216 510/931/216 497/932/216 496/930/216 f 510/931/217 499/907/217 486/910/217 497/932/217 f 447/933/204 449/934/204 498/935/204 499/936/204 f 449/934/204 451/937/204 500/938/204 498/935/204 f 451/937/204 453/939/204 501/940/204 500/938/204 f 453/939/204 455/941/204 502/942/204 501/940/204 f 455/941/204 457/943/204 503/944/204 502/942/204 f 457/943/204 459/945/204 504/946/204 503/944/204 f 459/945/204 461/947/204 505/948/204 504/946/204 f 461/947/204 463/949/204 506/950/204 505/948/204 f 463/949/204 465/951/204 507/952/204 506/950/204 f 465/951/204 467/953/204 508/954/204 507/952/204 f 467/953/204 469/955/204 509/956/204 508/954/204 f 469/955/204 471/957/204 510/958/204 509/956/204 f 471/957/204 447/933/204 499/936/204 510/958/204 f 511/959/204 512/960/204 523/961/204 522/962/204 521/963/204 520/964/204 519/965/204 518/966/204 517/967/204 516/968/204 515/969/204 514/970/204 513/971/204 f 469/955/200 467/953/200 521/963/200 522/962/200 f 455/972/193 453/860/193 514/863/193 515/973/193 f 471/957/201 469/955/201 522/962/201 523/961/201 f 457/943/194 455/941/194 515/969/194 516/968/194 f 447/933/202 471/957/202 523/961/202 512/960/202 f 459/974/195 457/975/195 516/976/195 517/977/195 f 461/947/196 459/945/196 517/967/196 518/966/196 f 463/978/197 461/979/197 518/980/197 519/981/197 f 449/934/190 447/933/190 512/960/190 511/959/190 f 465/951/198 463/949/198 519/965/198 520/964/198 f 451/861/191 449/982/191 511/983/191 513/862/191 f 467/953/199 465/951/199 520/964/199 521/963/199 o Axis3d v 0.000000 -0.000000 0.000000 v -0.100000 0.000000 -0.000000 l 524 525 ================================================ FILE: examples/src/Lack.elm ================================================ module Lack exposing (main) {-| This demo allows dragging the table with the mouse. 1. Uses `Physics.raycast` on mouse down to pick a body 2. On each tick, creates a temporary mouse body at the drag position 3. Connects the temporary body with the selected body using a point to point constraint 4. Updates the drag position on mouse move 5. Clears the drag target on mouse up Try flipping the table! Or try changing this to be able to move multiple tables. -} import Angle import Axis3d exposing (Axis3d) import Block3d exposing (Block3d) import Browser import Browser.Dom import Browser.Events import Camera3d exposing (Camera3d) import Color import Direction3d import Html exposing (Html) import Html.Attributes import Html.Events import Json.Decode exposing (Decoder) import Length exposing (Meters) import Physics exposing (Body, BodyCoordinates, WorldCoordinates, onEarth) import Physics.Constraint exposing (Constraint) import Physics.Material import Physics.Shape import Pixels exposing (Pixels) import Plane3d import Point2d import Point3d exposing (Point3d) import Quantity exposing (Quantity) import Rectangle2d import Scene3d exposing (Entity) import Scene3d.Material as Material import Sphere3d import Task type Id = Mouse | Floor | Table type alias Model = { bodies : List ( Id, Body ) , contacts : Physics.Contacts Id , dimensions : ( Quantity Int Pixels, Quantity Int Pixels ) , dragTarget : Maybe ( Point3d Meters BodyCoordinates, Point3d Meters WorldCoordinates ) } type Msg = Tick | Resize Int Int | MouseDown (Axis3d Meters WorldCoordinates) | MouseMove (Axis3d Meters WorldCoordinates) | MouseUp main : Program () Model Msg main = Browser.element { init = init , update = \msg model -> ( update msg model, Cmd.none ) , view = view , subscriptions = subscriptions } init : () -> ( Model, Cmd Msg ) init _ = ( { bodies = tableOnFloor , contacts = Physics.emptyContacts , dimensions = ( Pixels.int 0, Pixels.int 0 ) , dragTarget = Nothing } , Task.perform (\{ viewport } -> Resize (round viewport.width) (round viewport.height)) Browser.Dom.getViewport ) tableBlocks : List (Block3d Meters BodyCoordinates) tableBlocks = [ Block3d.from (Point3d.millimeters 222 222 0) (Point3d.millimeters 272 272 400) , Block3d.from (Point3d.millimeters -272 222 0) (Point3d.millimeters -222 272 400) , Block3d.from (Point3d.millimeters -272 -272 0) (Point3d.millimeters -222 -222 400) , Block3d.from (Point3d.millimeters 222 -272 0) (Point3d.millimeters 272 -222 400) , Block3d.from (Point3d.millimeters -275 -275 400) (Point3d.millimeters 275 275 450) ] tableOnFloor : List ( Id, Body ) tableOnFloor = [ ( Table , Physics.dynamic <| List.map (\block -> ( Physics.Shape.block block, Physics.Material.wood )) tableBlocks ) , ( Floor, Physics.plane Plane3d.xy Physics.Material.wood ) ] update : Msg -> Model -> Model update msg model = case msg of Tick -> case model.dragTarget of Just ( pointOnTable, dragPoint ) -> let ( simulated, newContacts ) = Physics.simulate { onEarth | constrain = lockMouseTo pointOnTable, contacts = model.contacts } (( Mouse, Physics.static [] |> Physics.moveTo dragPoint ) :: model.bodies ) in { model | bodies = List.drop 1 simulated, contacts = newContacts } Nothing -> let ( simulated, newContacts ) = Physics.simulate { onEarth | contacts = model.contacts } model.bodies in { model | bodies = simulated, contacts = newContacts } MouseDown mouseRay -> case Physics.raycast mouseRay model.bodies of Just ( Table, body, { point } ) -> let pointOnTable = Point3d.relativeTo (Physics.frame body) point in { model | dragTarget = Just ( pointOnTable, point ) } _ -> model MouseMove mouseRay -> case model.dragTarget of Just ( pointOnTable, dragPoint ) -> let plane = Plane3d.through dragPoint (Camera3d.viewDirection camera) in { model | dragTarget = Just ( pointOnTable , Axis3d.intersectionWithPlane plane mouseRay |> Maybe.withDefault dragPoint ) } Nothing -> model MouseUp -> { model | dragTarget = Nothing } Resize width height -> { model | dimensions = ( Pixels.int width, Pixels.int height ) } camera : Camera3d Meters WorldCoordinates camera = Camera3d.lookAt { eyePoint = Point3d.meters 3 4 2 , focalPoint = Point3d.meters -0.5 -0.5 0 , upDirection = Direction3d.positiveZ , projection = Camera3d.Perspective , fov = Camera3d.angle (Angle.degrees 24) } lockMouseTo : Point3d Meters BodyCoordinates -> Id -> Maybe (Id -> List Constraint) lockMouseTo pointOnTable mouseId = if mouseId == Mouse then Just (\tableId -> if tableId == Table then [ Physics.Constraint.pointToPoint Point3d.origin pointOnTable ] else [] ) else Nothing view : Model -> Html Msg view { bodies, dimensions, dragTarget } = Html.div [ Html.Attributes.style "position" "absolute" , Html.Attributes.style "left" "0" , Html.Attributes.style "top" "0" , Html.Events.on "mousedown" (decodeMouseRay dimensions MouseDown) , Html.Events.on "mousemove" (decodeMouseRay dimensions MouseMove) , Html.Events.onMouseUp MouseUp ] [ Scene3d.sunny { upDirection = Direction3d.positiveZ , sunlightDirection = Direction3d.xyZ (Angle.degrees 135) (Angle.degrees -60) , shadows = True , camera = camera , dimensions = dimensions , background = Scene3d.transparentBackground , clipDepth = Length.meters 0.1 , entities = let mouseEntity = case dragTarget of Just ( _, dragPoint ) -> Scene3d.sphere (Material.matte Color.white) (Sphere3d.atPoint dragPoint (Length.millimeters 20)) Nothing -> Scene3d.nothing in mouseEntity :: List.map bodyEntity bodies } ] bodyEntity : ( Id, Body ) -> Entity WorldCoordinates bodyEntity ( id, body ) = Scene3d.placeIn (Physics.frame body) <| case id of Mouse -> -- Only used in simulation Scene3d.nothing Table -> Scene3d.group <| List.map (Scene3d.blockWithShadow (Material.nonmetal { baseColor = Color.white , roughness = 0.25 } ) ) tableBlocks Floor -> Scene3d.quad (Material.matte Color.darkCharcoal) (Point3d.meters -15 -15 0) (Point3d.meters -15 15 0) (Point3d.meters 15 15 0) (Point3d.meters 15 -15 0) subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Browser.Events.onResize Resize , Browser.Events.onAnimationFrame (\_ -> Tick) ] decodeMouseRay : ( Quantity Int Pixels, Quantity Int Pixels ) -> (Axis3d Meters WorldCoordinates -> msg) -> Decoder msg decodeMouseRay ( width, height ) rayToMsg = Json.Decode.map2 (\x y -> rayToMsg <| Camera3d.ray camera (Rectangle2d.with { x1 = Quantity.zero , y1 = Quantity.toFloatQuantity height , x2 = Quantity.toFloatQuantity width , y2 = Quantity.zero } ) (Point2d.pixels x y) ) (Json.Decode.field "pageX" Json.Decode.float) (Json.Decode.field "pageY" Json.Decode.float) ================================================ FILE: examples/src/Raycast.elm ================================================ module Raycast exposing (main) {-| This demo shows how elm-physics could be used to determine, which object has been clicked, and also to do ray tracing. A mouse ray is cast into the scene, and if it hits an object the reflected ray is computed from the surface normal and cast again. -} import Angle import Axis3d exposing (Axis3d) import Block3d exposing (Block3d) import Browser import Browser.Dom import Browser.Events import Camera3d exposing (Camera3d) import Color import Cylinder3d exposing (Cylinder3d) import Direction3d import Frame3d import Html exposing (Html) import Html.Attributes import Html.Events import Json.Decode exposing (Decoder) import Length exposing (Meters, meters, millimeters) import LineSegment3d exposing (LineSegment3d) import Physics exposing (Body, BodyCoordinates, WorldCoordinates) import Physics.Material import Pixels exposing (Pixels) import Plane3d import Point2d import Point3d import Quantity exposing (Quantity) import Rectangle2d import Scene3d exposing (Entity) import Scene3d.Material as Material import Sphere3d exposing (Sphere3d) import Task type Id = Cylinder | Block | Sphere | Floor type alias Model = { dimensions : ( Quantity Int Pixels, Quantity Int Pixels ) , selection : Maybe Id , rayPath : List (LineSegment3d Meters WorldCoordinates) } type Msg = Resize Int Int | MouseDown (Axis3d Meters WorldCoordinates) main : Program () Model Msg main = Browser.element { init = init , update = \msg model -> ( update msg model, Cmd.none ) , subscriptions = \_ -> Browser.Events.onResize Resize , view = view } init : () -> ( Model, Cmd Msg ) init _ = ( { dimensions = ( Pixels.int 0, Pixels.int 0 ) , selection = Nothing , rayPath = [] } , Task.perform (\{ viewport } -> Resize (round viewport.width) (round viewport.height)) Browser.Dom.getViewport ) bodies : List ( Id, Body ) bodies = [ ( Floor, Physics.block floor Physics.Material.wood |> Physics.moveTo (Point3d.millimeters 0 0 -5) ) , ( Block, Physics.block block Physics.Material.wood |> Physics.moveTo (Point3d.meters -1 -1 0) ) , ( Sphere, Physics.sphere sphere Physics.Material.wood |> Physics.moveTo (Point3d.meters 0 1.5 0) ) , ( Cylinder, Physics.cylinder cylinder Physics.Material.wood |> Physics.moveTo (Point3d.meters 1.5 0 0) ) ] floor : Block3d Meters BodyCoordinates floor = Block3d.centeredOn Frame3d.atOrigin ( meters 5, meters 5, millimeters 10 ) block : Block3d Meters BodyCoordinates block = Block3d.from (Point3d.meters -0.5 -0.5 0) (Point3d.meters 0.5 0.5 1.5) sphere : Sphere3d Meters BodyCoordinates sphere = Sphere3d.atPoint (Point3d.meters 0 0 0.5) (meters 0.5) cylinder : Cylinder3d Meters BodyCoordinates cylinder = Cylinder3d.startingAt Point3d.origin Direction3d.positiveZ { radius = meters 0.5 , length = meters 1.5 } update : Msg -> Model -> Model update msg model = case msg of Resize width height -> { model | dimensions = ( Pixels.int width, Pixels.int height ) } MouseDown mouseRay -> let firstHit = Physics.raycast mouseRay bodies selection = Maybe.map (\( id, _, _ ) -> id) firstHit rayPath = buildPath mouseRay 10 [] in { model | selection = selection, rayPath = rayPath } buildPath : Axis3d Meters WorldCoordinates -> Int -> List (LineSegment3d Meters WorldCoordinates) -> List (LineSegment3d Meters WorldCoordinates) buildPath ray hops segments = case Physics.raycast ray bodies of Nothing -> LineSegment3d.from (Axis3d.originPoint ray) (Point3d.along ray (Length.meters 20)) :: segments Just ( _, _, { point, normal } ) -> let segment = LineSegment3d.from (Axis3d.originPoint ray) point reflectedDir = Direction3d.mirrorAcross (Plane3d.through point normal) (Axis3d.direction ray) reflectedRay = Axis3d.through point reflectedDir in if hops == 0 then segment :: segments else buildPath reflectedRay (hops - 1) (segment :: segments) view : Model -> Html Msg view { selection, dimensions, rayPath } = Html.div [ Html.Attributes.style "position" "absolute" , Html.Attributes.style "left" "0" , Html.Attributes.style "top" "0" , Html.Events.on "mousedown" (decodeMouseRay dimensions MouseDown) ] [ Scene3d.sunny { upDirection = Direction3d.z , sunlightDirection = Direction3d.xyZ (Angle.degrees 135) (Angle.degrees -60) , shadows = True , camera = camera , dimensions = dimensions , background = Scene3d.transparentBackground , clipDepth = Length.meters 0.1 , entities = List.map (bodyToEntity selection) bodies ++ List.map (Scene3d.lineSegment (Material.color Color.green)) rayPath } ] camera : Camera3d Meters WorldCoordinates camera = Camera3d.lookAt { eyePoint = Point3d.meters 5 6 4 , focalPoint = Point3d.meters -0.5 -0.5 0 , upDirection = Direction3d.positiveZ , projection = Camera3d.Perspective , fov = Camera3d.angle (Angle.degrees 24) } bodyToEntity : Maybe Id -> ( Id, Body ) -> Entity WorldCoordinates bodyToEntity selection ( id, body ) = let color defaultColor = if selection == Just id then Color.white else defaultColor in Scene3d.placeIn (Physics.frame body) <| case id of Floor -> Scene3d.block (Material.matte (color Color.darkCharcoal)) floor Block -> Scene3d.blockWithShadow (Material.nonmetal { baseColor = color Color.red , roughness = 0.25 } ) block Sphere -> Scene3d.sphereWithShadow (Material.nonmetal { baseColor = color Color.yellow , roughness = 0.25 } ) sphere Cylinder -> Scene3d.cylinderWithShadow (Material.nonmetal { baseColor = color Color.blue , roughness = 0.25 } ) cylinder decodeMouseRay : ( Quantity Int Pixels, Quantity Int Pixels ) -> (Axis3d Meters WorldCoordinates -> msg) -> Decoder msg decodeMouseRay ( width, height ) rayToMsg = Json.Decode.map2 (\x y -> rayToMsg <| Camera3d.ray camera (Rectangle2d.with { x1 = Quantity.zero , y1 = Quantity.toFloatQuantity height , x2 = Quantity.toFloatQuantity width , y2 = Quantity.zero } ) (Point2d.pixels x y) ) (Json.Decode.field "pageX" Json.Decode.float) (Json.Decode.field "pageY" Json.Decode.float) ================================================ FILE: examples/src/RaycastCar/Car.elm ================================================ module RaycastCar.Car exposing (CarSettings, Wheel, defaultWheel, simulate) {-| This is a complex example implementing raycast vehicle enspired by: - bullet3: - cannon.js: This is a car with fake wheels, that shoots rays and applies impulses to the car body where the rays hit the ground. This allows to simulate suspension and results in smooth behavior. -} import Angle exposing (Angle) import Axis3d exposing (Axis3d) import Direction3d exposing (Direction3d) import Duration exposing (Duration) import Force exposing (Force) import Frame3d exposing (Frame3d) import Length exposing (Length, Meters) import Mass import Physics exposing (Body, BodyCoordinates, WorldCoordinates) import Point3d exposing (Point3d) import Quantity exposing (Quantity(..)) import Vector3d type alias CarSettings = { downDirection : Direction3d BodyCoordinates , rightDirection : Direction3d BodyCoordinates , forwardDirection : Direction3d BodyCoordinates , suspensionRestLength : Length , minSuspensionLength : Length , maxSuspensionLength : Length , radius : Length , suspensionStiffness : Float , dampingCompression : Float , dampingRelaxation : Float , frictionSlip : Float , rollInfluence : Float , maxSuspensionForce : Force , maxEngineForce : Force , maxBrakeForce : Force , maxSteering : Angle } type alias Wheel id = { chassisConnectionPoint : Point3d Meters BodyCoordinates , axis : Axis3d Meters BodyCoordinates , steering : Angle , rotation : Angle , deltaRotation : Angle , suspensionImpulse : Quantity Float (Quantity.Product Force.Newtons Duration.Seconds) , suspensionLength : Length , engineForce : Force , brake : Force , contact : Maybe ( id , Body , { point : Point3d Meters WorldCoordinates , normal : Direction3d WorldCoordinates } ) } simulate : { duration : Duration , bodiesWithoutCar : List ( id, Body ) , speeding : Float , steering : Float , braking : Bool , carSettings : CarSettings } -> List (Wheel id) -> Body -> ( Body, List (Wheel id) ) simulate { duration, bodiesWithoutCar, steering, braking, speeding, carSettings } wheels carBody = case wheels of [ w1, w2, w3, w4 ] -> let engineForce = Quantity.multiplyBy speeding carSettings.maxEngineForce brake = if braking then carSettings.maxBrakeForce else Quantity.zero steeringAngle = Quantity.multiplyBy steering carSettings.maxSteering wheel1 = { w1 | steering = steeringAngle, engineForce = engineForce, brake = brake } wheel2 = { w2 | steering = steeringAngle, engineForce = engineForce, brake = brake } wheel3 = { w3 | engineForce = engineForce, brake = brake } wheel4 = { w4 | engineForce = engineForce, brake = brake } in updateSuspension carSettings duration bodiesWithoutCar (Physics.frame carBody) carBody [ wheel1, wheel2, wheel3, wheel4 ] carBody [] 0 _ -> ( carBody, wheels ) updateSuspension : CarSettings -> Duration -> List ( id, Body ) -> Frame3d Meters WorldCoordinates { defines : BodyCoordinates } -> Body -> List (Wheel id) -> Body -> List (Wheel id) -> Int -> ( Body, List (Wheel id) ) updateSuspension carSettings dt bodies frame originalCar currentWheels updatedCar updatedWheels numWheelsOnGround = case currentWheels of [] -> updateFriction carSettings dt frame updatedCar numWheelsOnGround updatedWheels [] [] False wheel :: remainingWheels -> let ray = Axis3d.through wheel.chassisConnectionPoint carSettings.downDirection |> Axis3d.placeIn frame in case Physics.raycast ray bodies of Just (( _, _, { point, normal } ) as hitResult) -> let distance = Point3d.distanceFrom point (Axis3d.originPoint ray) maxDistance = Quantity.plus carSettings.suspensionRestLength carSettings.radius in if Quantity.lessThan maxDistance distance then let suspensionLength = distance |> Quantity.minus carSettings.radius |> Quantity.clamp carSettings.minSuspensionLength carSettings.maxSuspensionLength difference = carSettings.suspensionRestLength |> Quantity.minus suspensionLength |> Length.inMeters (Quantity projectedVelocity) = Vector3d.dot (Direction3d.toVector normal) (Physics.velocityAt point originalCar) (Quantity denominator) = Vector3d.dot (Direction3d.toVector normal) (Direction3d.toVector (Axis3d.direction ray)) ( suspensionRelativeVelocity, clippedInvContactDotSuspension ) = if denominator >= -0.1 then ( 0, 1 / 0.1 ) else ( -projectedVelocity / denominator, -1 / denominator ) damping = if suspensionRelativeVelocity < 0 then carSettings.dampingCompression else carSettings.dampingRelaxation suspensionImpulse = ((carSettings.suspensionStiffness * difference * clippedInvContactDotSuspension) - (damping * suspensionRelativeVelocity) ) |> (*) (Physics.mass originalCar |> Maybe.map Mass.inKilograms |> Maybe.withDefault 0) |> Force.newtons |> Quantity.clamp Quantity.zero carSettings.maxSuspensionForce |> Quantity.times dt impulse = Vector3d.withLength suspensionImpulse normal in updateSuspension carSettings dt bodies frame originalCar remainingWheels (Physics.applyImpulse impulse point updatedCar) ({ wheel | contact = Just hitResult , suspensionLength = suspensionLength , suspensionImpulse = suspensionImpulse } :: updatedWheels ) (numWheelsOnGround + 1) else updateSuspension carSettings dt bodies frame originalCar remainingWheels updatedCar ({ wheel | contact = Nothing , suspensionLength = carSettings.suspensionRestLength } :: updatedWheels ) numWheelsOnGround Nothing -> updateSuspension carSettings dt bodies frame originalCar remainingWheels updatedCar ({ wheel | contact = Nothing , suspensionLength = carSettings.suspensionRestLength } :: updatedWheels ) numWheelsOnGround type alias WheelFriction = { forward : Direction3d WorldCoordinates , axle : Direction3d WorldCoordinates , sideImpulse : Quantity Float (Quantity.Product Force.Newtons Duration.Seconds) , forwardImpulse : Quantity Float (Quantity.Product Force.Newtons Duration.Seconds) , skidInfo : Float , contactPoint : Point3d Meters WorldCoordinates , contactBody : Body } updateFriction : CarSettings -> Duration -> Frame3d Meters WorldCoordinates { defines : BodyCoordinates } -> Body -> Int -> List (Wheel id) -> List WheelFriction -> List (Wheel id) -> Bool -> ( Body, List (Wheel id) ) updateFriction carSettings dt frame updatedCar numWheelsOnGround currentWheels wheelFrictions updatedWheels sliding = case currentWheels of [] -> applyImpulses carSettings dt frame updatedCar updatedWheels sliding wheelFrictions wheel :: remainingWheels -> case wheel.contact of Just ( _, body, { point, normal } ) -> let worldAxle = carSettings.rightDirection |> Direction3d.rotateAround carSettings.downDirection wheel.steering |> Direction3d.placeIn frame (Quantity proj) = Vector3d.dot (Direction3d.toVector normal) (Direction3d.toVector worldAxle) axle = Direction3d.toVector worldAxle |> Vector3d.minus (Vector3d.scaleBy proj (Direction3d.toVector normal)) |> Vector3d.direction |> Maybe.withDefault normal forward = Vector3d.cross (Direction3d.toVector normal) (Direction3d.toVector axle) |> Vector3d.direction |> Maybe.withDefault normal sideImpulse = resolveSingleBilateral updatedCar body point axle maxImpulse = if wheel.brake == Quantity.zero then -- TODO: think about default rolling friction impulse Quantity.zero else Quantity.times dt wheel.brake forwardImpulse = Quantity.times dt wheel.engineForce |> Quantity.plus (calcRollingFriction updatedCar body point forward maxImpulse numWheelsOnGround) -- Switch between active rolling (throttle), braking and non-active rolling friction (nthrottle/break) maximpSide = Quantity.multiplyBy carSettings.frictionSlip wheel.suspensionImpulse impulseSquared = Quantity.times forwardImpulse forwardImpulse |> Quantity.multiplyBy 0.25 |> Quantity.plus (Quantity.times sideImpulse sideImpulse) isSliding = Quantity.greaterThan (Quantity.times maximpSide maximpSide) impulseSquared skidInfo = if isSliding then Quantity.ratio maximpSide (Quantity.sqrt impulseSquared) else 1 in updateFriction carSettings dt frame updatedCar numWheelsOnGround remainingWheels ({ forward = forward , axle = axle , sideImpulse = sideImpulse , forwardImpulse = forwardImpulse , skidInfo = skidInfo , contactPoint = point , contactBody = body } :: wheelFrictions ) (wheel :: updatedWheels) (sliding || isSliding) Nothing -> updateFriction carSettings dt frame updatedCar numWheelsOnGround remainingWheels wheelFrictions (wheel :: updatedWheels) sliding applyImpulses : CarSettings -> Duration -> Frame3d Meters WorldCoordinates { defines : BodyCoordinates } -> Body -> List (Wheel id) -> Bool -> List WheelFriction -> ( Body, List (Wheel id) ) applyImpulses carSettings dt frame carBody wheels sliding wheelFrictions = case wheelFrictions of [] -> rotateWheels carSettings dt frame carBody wheels [] friction :: remainingFrictions -> let centerOfMass = case Physics.centerOfMass carBody of Just com -> com Nothing -> Frame3d.originPoint frame up = Direction3d.reverse carSettings.downDirection |> Direction3d.placeIn frame verticalDistance = Vector3d.from friction.contactPoint centerOfMass |> Vector3d.componentIn up |> Quantity.multiplyBy (1 - carSettings.rollInfluence) closerToCenterOfMass = Point3d.translateIn up verticalDistance friction.contactPoint forwardImpulse = if sliding then Quantity.multiplyBy friction.skidInfo friction.forwardImpulse else friction.forwardImpulse sideImpulse = if sliding then Quantity.multiplyBy friction.skidInfo friction.sideImpulse else friction.sideImpulse newCar = carBody |> Physics.applyImpulse (Vector3d.withLength forwardImpulse friction.forward) friction.contactPoint |> Physics.applyImpulse (Vector3d.withLength sideImpulse friction.axle) closerToCenterOfMass -- TODO: apply the reverse of the sideImpulse on the ground object too, for now assume it is static in applyImpulses carSettings dt frame newCar wheels sliding remainingFrictions rotateWheels : CarSettings -> Duration -> Frame3d Meters WorldCoordinates { defines : BodyCoordinates } -> Body -> List (Wheel id) -> List (Wheel id) -> ( Body, List (Wheel id) ) rotateWheels carSettings dt frame carBody wheels updatedWheels = case wheels of [] -> ( carBody, List.reverse updatedWheels ) wheel :: remainingWheels -> case wheel.contact of Just ( _, _, { point, normal } ) -> let velocity = Physics.velocityAt point carBody forward = Direction3d.placeIn frame carSettings.forwardDirection proj = Direction3d.componentIn normal forward (Quantity proj2) = forward |> Direction3d.toVector |> Vector3d.minus (Vector3d.withLength (Quantity proj) normal) |> Vector3d.dot velocity deltaRotation = Quantity (proj2 * Duration.inSeconds dt / Length.inMeters carSettings.radius) newWheel = { wheel | deltaRotation = deltaRotation , rotation = Quantity.plus wheel.rotation wheel.deltaRotation } in rotateWheels carSettings dt frame carBody remainingWheels (newWheel :: updatedWheels) Nothing -> let deltaRotation = Quantity.multiplyBy 0.99 wheel.deltaRotation newWheel = { wheel -- damping when not in contact | deltaRotation = Quantity.multiplyBy 0.99 wheel.deltaRotation , rotation = Quantity.plus wheel.rotation deltaRotation } in rotateWheels carSettings dt frame carBody remainingWheels (newWheel :: updatedWheels) resolveSingleBilateral : Body -> Body -> Point3d Meters WorldCoordinates -> Direction3d WorldCoordinates -> Quantity Float (Quantity.Product Force.Newtons Duration.Seconds) resolveSingleBilateral body1 body2 point direction = let velocity1 = Physics.velocityAt point body1 velocity2 = Physics.velocityAt point body2 (Quantity relativeVelocity) = Vector3d.dot (Vector3d.minus velocity2 velocity1) (Direction3d.toVector direction) contactDamping = 0.2 invMass1 = case Physics.mass body1 of Just mass -> 1 / Mass.inKilograms mass Nothing -> 0 invMass2 = case Physics.mass body2 of Just mass -> 1 / Mass.inKilograms mass Nothing -> 0 massTerm = 1 / (invMass1 + invMass2) in Quantity (-contactDamping * relativeVelocity * massTerm) calcRollingFriction : Body -> Body -> Point3d Meters WorldCoordinates -> Direction3d WorldCoordinates -> Quantity Float (Quantity.Product Force.Newtons Duration.Seconds) -> Int -> Quantity Float (Quantity.Product Force.Newtons Duration.Seconds) calcRollingFriction body1 body2 point forward maxImpulse numWheelsOnGround = let velocity1 = Physics.velocityAt point body1 velocity2 = Physics.velocityAt point body2 (Quantity relativeVelocity) = Vector3d.dot (Vector3d.minus velocity2 velocity1) (Direction3d.toVector forward) denom1 = computeImpulseDenominator body1 point forward denom2 = computeImpulseDenominator body2 point forward in Quantity (-relativeVelocity / (denom1 + denom2) / toFloat numWheelsOnGround) |> Quantity.clamp (Quantity.negate maxImpulse) maxImpulse computeImpulseDenominator : Body -> Point3d Meters WorldCoordinates -> Direction3d WorldCoordinates -> Float computeImpulseDenominator body point normal = let bodyFrame = Physics.frame body position = case Physics.centerOfMass body of Just com -> com Nothing -> Frame3d.originPoint bodyFrame r0 = Vector3d.from position point c0 = Vector3d.cross r0 (Direction3d.toVector normal) vec = Vector3d.cross (Physics.applyInverseInertia body c0) r0 (Quantity dot) = Vector3d.dot (Direction3d.toVector normal) vec in case Physics.mass body of Just mass -> 1 / Mass.inKilograms mass + dot Nothing -> dot defaultWheel : Wheel id defaultWheel = { chassisConnectionPoint = Point3d.origin -- set for different wheels , axis = Axis3d.x -- set for different wheels , steering = Quantity.zero , rotation = Quantity.zero , deltaRotation = Quantity.zero , suspensionImpulse = Quantity.zero , suspensionLength = Quantity.zero , engineForce = Quantity.zero , brake = Quantity.zero , contact = Nothing } ================================================ FILE: examples/src/RaycastCar/Jeep.elm ================================================ module RaycastCar.Jeep exposing (Jeep, load, settings, wheels) {-| elm-obj-file is used to decode various objects from the jeep model. Body of the car, used for elm-physics simulation: - `convex-base_Base` - `convex-window_Window` Positions of the wheels: - `front-right_Axis3d` - `rear-right_Axis3d` - `front-left_Axis3d` - `rear-left_Axis3d` Used for rendering: - `chassis_Chassis` - `chassis-fender-left_fender-left` - `chassis-fender-right_fender-right` - `chassis-bumper_Bumper` - `chassis-cannister_Cannister` - `chassis-lamp-left_lamp-left` - `chassis-lamp-right_lamp-right` - `chassis-spare_Wheel` Wheel, positioned at the origin: - `Wheel` The Jeep model is courtesy of Kolja Wilcke -} import Angle import Array import Axis3d exposing (Axis3d) import BoundingBox3d import Direction3d import Force import Frame3d import Http import Length exposing (Length, Meters) import Obj.Decode exposing (Decoder) import Physics exposing (BodyCoordinates) import Physics.Material as Material exposing (Dense, Material) import Physics.Shape as Shape exposing (Shape) import Point3d import Polyline3d import Quantity import RaycastCar.Car exposing (CarSettings, Wheel, defaultWheel) import Scene3d.Material import Scene3d.Mesh exposing (Shadow, Textured) import Task exposing (Task) import TriangularMesh type alias Jeep = { collider : List ( Shape, Material Dense ) , chassis : Textured BodyCoordinates , chassisShadow : Shadow BodyCoordinates , wheel : Textured BodyCoordinates , wheelRadius : Length , wheelWidth : Length , wheelShadow : Shadow BodyCoordinates , wheelAxes : List (Axis3d Meters BodyCoordinates) , material : Scene3d.Material.Textured BodyCoordinates } load : { texture : String, mesh : String } -> Task String Jeep load urls = Http.task { method = "get" , headers = [] , body = Http.emptyBody , url = urls.mesh , resolver = Http.stringResolver (\resp -> case resp of Http.GoodStatus_ _ str -> Obj.Decode.decodeString (\a -> Length.meters (a / 2)) jeepDecoder str _ -> Err "Failed to load mesh" ) , timeout = Nothing } |> Task.andThen (\fn -> Scene3d.Material.load urls.texture |> Task.mapError (\_ -> "Failed to load texture") |> Task.map (\texture -> fn (Scene3d.Material.texturedMatte texture)) ) settings : Jeep -> CarSettings settings jeep = { downDirection = Direction3d.negativeZ , rightDirection = Direction3d.negativeX , forwardDirection = Direction3d.negativeY , suspensionRestLength = Quantity.multiplyBy 0.4 jeep.wheelRadius , minSuspensionLength = Length.meters 0 , maxSuspensionLength = Quantity.multiplyBy 1.2 jeep.wheelRadius , radius = jeep.wheelRadius , suspensionStiffness = 30 , dampingCompression = 4.4 , dampingRelaxation = 2.3 , frictionSlip = 5 , rollInfluence = 0.01 , maxSuspensionForce = Force.newtons 100000 , maxEngineForce = Force.newtons 5000 , maxBrakeForce = Force.newtons 10000 , maxSteering = Angle.degrees 30 } wheels : Jeep -> List (Wheel id) wheels jeep = List.map (\axis -> { defaultWheel | chassisConnectionPoint = Axis3d.originPoint axis |> Point3d.translateIn Direction3d.z (Quantity.multiplyBy 0.2 jeep.wheelRadius) , axis = axis } ) jeep.wheelAxes jeepDecoder : Decoder (Scene3d.Material.Textured BodyCoordinates -> Jeep) jeepDecoder = Obj.Decode.map5 (\convexBase convexWindow chassis wheel wheelAxes -> let wheelBounds = BoundingBox3d.hull Point3d.origin (TriangularMesh.vertices wheel |> Array.toList |> List.map .position) ( wheelWidth, wheelDiameter, _ ) = BoundingBox3d.dimensions wheelBounds wheelMesh = Scene3d.Mesh.texturedFaces wheel chassisMesh = Scene3d.Mesh.texturedFaces chassis in \material -> { collider = [ ( Shape.unsafeConvex convexBase, Material.steel ) , ( Shape.unsafeConvex convexWindow, Material.steel ) ] , chassis = chassisMesh , chassisShadow = Scene3d.Mesh.shadow chassisMesh , wheel = wheelMesh , wheelShadow = Scene3d.Mesh.shadow wheelMesh , wheelAxes = wheelAxes , wheelRadius = Quantity.half wheelDiameter , wheelWidth = wheelWidth , material = material } ) (Obj.Decode.object "convex-base_Base" (Obj.Decode.trianglesIn Frame3d.atOrigin)) (Obj.Decode.object "convex-window_Window" (Obj.Decode.trianglesIn Frame3d.atOrigin)) (startsWith "chassis" (Obj.Decode.texturedFacesIn Frame3d.atOrigin)) (Obj.Decode.object "Wheel" (Obj.Decode.texturedFacesIn Frame3d.atOrigin)) (Obj.Decode.combine [ Obj.Decode.object "front-left_Axis3d" axis3d , Obj.Decode.object "front-right_Axis3d" axis3d , Obj.Decode.object "rear-left_Axis3d" axis3d , Obj.Decode.object "rear-right_Axis3d" axis3d ] ) startsWith : String -> Decoder a -> Decoder a startsWith prefix = Obj.Decode.filter (\properties -> case properties.object of Nothing -> False Just object -> String.startsWith prefix object ) axis3d : Decoder (Axis3d Meters BodyCoordinates) axis3d = Obj.Decode.polylinesIn Frame3d.atOrigin |> Obj.Decode.andThen (\lines -> case lines of line :: _ -> case Polyline3d.vertices line of p1 :: p2 :: _ -> case Axis3d.throughPoints p1 p2 of Just axis -> Obj.Decode.succeed axis Nothing -> Obj.Decode.fail "Failed to constuct axis" _ -> Obj.Decode.fail "Expected at least two points" _ -> Obj.Decode.fail "Expected at least one line" ) ================================================ FILE: examples/src/RaycastCar.elm ================================================ module RaycastCar exposing (main) {-| This demo implements a smooth car simulation. - `RaycastCar.Car` — raycast vehicle simulation algorithm, - `RaycastCar.Jeep` — load the 3d model using elm-obj-file. Press arrow keys to drive, "b" to brake. Try to add more obstacles to the demo by changing the initialBodies, or tweak the settings in the `RaycastCar.Jeep` module! -} import Angle import Axis3d import Block3d exposing (Block3d) import Browser import Browser.Dom import Browser.Events import Camera3d exposing (Camera3d) import Color import Direction3d import Frame3d import Html exposing (Html) import Html.Attributes import Json.Decode import Length exposing (Meters) import Mass import Physics exposing (Body, BodyCoordinates, WorldCoordinates, onEarth) import Physics.Material import Physics.Shape import Pixels exposing (Pixels) import Plane3d import Point3d exposing (Point3d) import Quantity exposing (Quantity) import RaycastCar.Car as Car exposing (Wheel) import RaycastCar.Jeep as Jeep exposing (Jeep) import Scene3d exposing (Entity) import Scene3d.Material import Task import Vector3d type Id = Ramp | Crate | Floor | Car (List (Wheel Id)) type alias Model = { dimensions : ( Quantity Int Pixels, Quantity Int Pixels ) , bodies : List ( Id, Body ) , contacts : Physics.Contacts Id , jeep : Maybe Jeep , speeding : Float , steering : Float , braking : Bool } type Msg = Tick | Resize Int Int | KeyDown Command | KeyUp Command | JeepLoaded (Result String Jeep) type Command = Speed Float | Steer Float | Brake main : Program () Model Msg main = Browser.element { init = init , update = \msg model -> ( update msg model, Cmd.none ) , view = view , subscriptions = subscriptions } init : () -> ( Model, Cmd Msg ) init _ = ( { dimensions = ( Pixels.int 0, Pixels.int 0 ) , bodies = initialBodies , contacts = Physics.emptyContacts , jeep = Nothing , speeding = 0 , steering = 0 , braking = False } , Cmd.batch [ Task.perform (\{ viewport } -> Resize (round viewport.width) (round viewport.height) ) Browser.Dom.getViewport , Jeep.load { texture = "Jeep.png", mesh = "Jeep.obj.txt" } |> Task.attempt JeepLoaded ] ) initialBodies : List ( Id, Body ) initialBodies = ( Floor, Physics.plane Plane3d.xy Physics.Material.wood ) :: ( Ramp , Physics.static [ ( Physics.Shape.block rampBlock, Physics.Material.wood ) ] |> Physics.rotateAround Axis3d.x (Angle.radians (pi / 16)) |> Physics.moveTo (Point3d.meters 0 -2 1.5) ) :: List.map crate [ Point3d.meters 15 -15 0.5 , Point3d.meters 15 -16.5 0.5 , Point3d.meters 15 -18 0.5 , Point3d.meters 15 -16 1.5 , Point3d.meters 15 -17.5 1.5 , Point3d.meters 15 -16.5 2.5 ] rampBlock : Block3d Meters BodyCoordinates rampBlock = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 10 , Length.meters 16 , Length.meters 0.5 ) crateBlock : Block3d Meters BodyCoordinates crateBlock = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 1 , Length.meters 1 , Length.meters 1 ) crate : Point3d Meters WorldCoordinates -> ( Id, Body ) crate position = let innerBlock = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 0.98 , Length.meters 0.98 , Length.meters 0.98 ) in ( Crate , Physics.dynamic [ ( Physics.Shape.block crateBlock |> Physics.Shape.minus (Physics.Shape.block innerBlock) , Physics.Material.wood ) ] |> Physics.moveTo position ) update : Msg -> Model -> Model update msg model = case msg of JeepLoaded result -> case result of Ok jeep -> { model | jeep = Just jeep , bodies = ( Car (Jeep.wheels jeep) , Physics.dynamic jeep.collider |> Physics.scaleMassTo (Mass.kilograms 4000) |> Physics.moveTo (Point3d.meters 0 0 6) ) :: model.bodies } Err _ -> model Tick -> case model.jeep of Just loadedJeep -> let updatedBodies = simulateCar model loadedJeep ( simulated, newContacts ) = Physics.simulate { onEarth | contacts = model.contacts } updatedBodies in { model | bodies = simulated, contacts = newContacts } Nothing -> model Resize width height -> { model | dimensions = ( Pixels.pixels width, Pixels.pixels height ) } KeyDown (Steer k) -> { model | steering = k } KeyDown (Speed k) -> { model | speeding = k } KeyUp (Steer k) -> { model | steering = if k == model.steering then 0 else model.steering } KeyUp (Speed k) -> { model | speeding = if k == model.speeding then 0 else model.speeding } KeyDown Brake -> { model | braking = True } KeyUp Brake -> { model | braking = False } view : Model -> Html Msg view { bodies, jeep, dimensions } = Html.div [ Html.Attributes.style "position" "absolute" , Html.Attributes.style "left" "0" , Html.Attributes.style "top" "0" ] [ Scene3d.sunny { upDirection = Direction3d.positiveZ , sunlightDirection = Direction3d.xyZ (Angle.degrees -15) (Angle.degrees -45) , shadows = True , camera = camera , dimensions = dimensions , background = Scene3d.transparentBackground , clipDepth = Length.meters 0.1 , entities = case jeep of Just loadedJeep -> List.map (bodyToEntity loadedJeep) bodies Nothing -> [] } ] simulateCar : Model -> Jeep -> List ( Id, Body ) simulateCar model jeep = let notACar ( id, _ ) = case id of Car _ -> False _ -> True in List.map (\(( bodyId, body ) as passThrough) -> case bodyId of Car wheels -> let ( newBody, newWheels ) = Car.simulate { duration = onEarth.duration , bodiesWithoutCar = List.filter notACar model.bodies , speeding = model.speeding , steering = model.steering , braking = model.braking , carSettings = Jeep.settings jeep } wheels body in ( Car newWheels, newBody ) _ -> passThrough ) model.bodies camera : Camera3d Meters WorldCoordinates camera = Camera3d.lookAt { eyePoint = Point3d.meters -40 40 30 , focalPoint = Point3d.meters 0 -7 0 , upDirection = Direction3d.positiveZ , projection = Camera3d.Perspective , fov = Camera3d.angle (Angle.degrees 24) } bodyToEntity : Jeep -> ( Id, Body ) -> Entity WorldCoordinates bodyToEntity jeep ( id, body ) = Scene3d.placeIn (Physics.frame body) <| case id of Floor -> Scene3d.quad (Scene3d.Material.matte Color.white) (Point3d.meters -100 -100 0) (Point3d.meters -100 100 0) (Point3d.meters 100 100 0) (Point3d.meters 100 -100 0) Ramp -> Scene3d.blockWithShadow (Scene3d.Material.nonmetal { baseColor = Color.lightGray , roughness = 1 } ) rampBlock Crate -> Scene3d.blockWithShadow (Scene3d.Material.nonmetal { baseColor = Color.orange , roughness = 1 } ) crateBlock Car wheels -> let { downDirection, rightDirection } = Jeep.settings jeep in Scene3d.group (List.foldl (\wheel entities -> let axisDirection = Axis3d.direction wheel.axis applyMirror = if Quantity.greaterThan Quantity.zero (Direction3d.angleFrom rightDirection axisDirection) then identity else Frame3d.mirrorAcross Plane3d.yz wheelPosition = wheel.chassisConnectionPoint |> Point3d.translateBy (Vector3d.withLength wheel.suspensionLength downDirection) wheelFrame = Frame3d.atOrigin |> applyMirror |> Frame3d.rotateAround (Axis3d.through Point3d.origin (Direction3d.reverse rightDirection)) wheel.rotation |> Frame3d.rotateAround (Axis3d.through Point3d.origin downDirection) wheel.steering |> Frame3d.moveTo wheelPosition in (Scene3d.meshWithShadow jeep.material jeep.wheel jeep.wheelShadow |> Scene3d.placeIn wheelFrame ) :: entities ) [ Scene3d.meshWithShadow jeep.material jeep.chassis jeep.chassisShadow ] wheels ) subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Browser.Events.onResize Resize , Browser.Events.onAnimationFrameDelta (\_ -> Tick) , Browser.Events.onKeyDown (keyDecoder KeyDown) , Browser.Events.onKeyUp (keyDecoder KeyUp) ] keyDecoder : (Command -> Msg) -> Json.Decode.Decoder Msg keyDecoder toMsg = Json.Decode.andThen (\string -> case string of "ArrowLeft" -> Json.Decode.succeed (toMsg (Steer -1)) "ArrowRight" -> Json.Decode.succeed (toMsg (Steer 1)) "ArrowUp" -> Json.Decode.succeed (toMsg (Speed 1)) "ArrowDown" -> Json.Decode.succeed (toMsg (Speed -1)) "b" -> Json.Decode.succeed (toMsg Brake) _ -> Json.Decode.fail ("Unrecognized key: " ++ string) ) (Json.Decode.field "key" Json.Decode.string) ================================================ FILE: flake.nix ================================================ { inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; outputs = { nixpkgs, ... }: let systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; forAllSystems = nixpkgs.lib.genAttrs systems; in { devShells = forAllSystems (system: let pkgs = nixpkgs.legacyPackages.${system}; in { default = pkgs.mkShell { buildInputs = with pkgs.elmPackages; [ elm elm-format elm-test elm-review elm-json elm-doc-preview ]; }; }); }; } ================================================ FILE: review/elm.json ================================================ { "type": "application", "source-directories": [ "src" ], "elm-version": "0.19.1", "dependencies": { "direct": { "elm/core": "1.0.5", "elm/json": "1.1.4", "elm/project-metadata-utils": "1.0.2", "jfmengels/elm-review": "2.16.6", "jfmengels/elm-review-performance": "1.0.2", "jfmengels/elm-review-simplify": "2.1.15", "jfmengels/elm-review-unused": "1.2.6", "stil4m/elm-syntax": "7.3.9" }, "indirect": { "elm/bytes": "1.0.8", "elm/html": "1.0.1", "elm/parser": "1.1.0", "elm/random": "1.0.0", "elm/regex": "1.0.0", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.5", "elm-explorations/test": "2.2.1", "pzp1997/assoc-list": "1.0.0", "rtfeldman/elm-hex": "1.0.0", "stil4m/structured-writer": "1.0.3" } }, "test-dependencies": { "direct": { "elm-explorations/test": "2.2.1" }, "indirect": {} } } ================================================ FILE: review/src/ReviewConfig.elm ================================================ module ReviewConfig exposing (config) {-| Do not rename the ReviewConfig module or the config function, because `elm-review` will look for these. To add packages that contain rules, add them to this review project using `elm install author/packagename` when inside the directory containing this file. -} import NoUnoptimizedRecursion import NoUnused.CustomTypeConstructorArgs import NoUnused.CustomTypeConstructors import NoUnused.Dependencies import NoUnused.Exports import NoUnused.Modules import NoUnused.Parameters import NoUnused.Patterns import NoUnused.Variables import Review.Rule exposing (Rule) import Simplify config : List Rule config = [ NoUnused.CustomTypeConstructors.rule [] , NoUnused.CustomTypeConstructorArgs.rule , NoUnused.Dependencies.rule , NoUnused.Exports.rule , NoUnused.Modules.rule , NoUnused.Parameters.rule , NoUnused.Patterns.rule , NoUnused.Variables.rule , Simplify.rule Simplify.defaults , NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optOutWithComment "IGNORE TCO") ] ================================================ FILE: sandbox/elm.json ================================================ { "type": "application", "source-directories": [ "src", "../src" ], "elm-version": "0.19.1", "dependencies": { "direct": { "elm/browser": "1.0.2", "elm/core": "1.0.5", "elm/html": "1.0.1", "elm/json": "1.1.4", "elm/random": "1.0.0", "elm-explorations/linear-algebra": "1.0.3", "elm-explorations/webgl": "1.1.3", "ianmackenzie/elm-geometry": "4.0.0", "ianmackenzie/elm-geometry-linear-algebra-interop": "2.0.3", "ianmackenzie/elm-triangular-mesh": "1.1.0", "ianmackenzie/elm-units": "2.10.0", "w0rm/elm-obj-file": "1.4.0" }, "indirect": { "elm/bytes": "1.0.8", "elm/file": "1.0.5", "elm/http": "2.0.0", "elm/time": "1.0.0", "elm/url": "1.0.0", "elm/virtual-dom": "1.0.5", "ianmackenzie/elm-1d-parameter": "1.0.1", "ianmackenzie/elm-float-extra": "1.1.0", "ianmackenzie/elm-interval": "3.1.0", "ianmackenzie/elm-units-interval": "3.2.0" } }, "test-dependencies": { "direct": { "elm-explorations/test": "2.2.1" }, "indirect": {} } } ================================================ FILE: sandbox/review/elm.json ================================================ { "type": "application", "source-directories": [ "src" ], "elm-version": "0.19.1", "dependencies": { "direct": { "elm/core": "1.0.5", "elm/json": "1.1.3", "elm/project-metadata-utils": "1.0.1", "jfmengels/elm-review": "2.13.1", "jfmengels/elm-review-simplify": "2.1.3", "jfmengels/elm-review-unused": "1.2.0", "jfmengels/elm-review-performance": "1.0.2", "stil4m/elm-syntax": "7.3.2" }, "indirect": { "elm/html": "1.0.0", "elm/parser": "1.1.0", "elm/random": "1.0.0", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.2", "elm-community/list-extra": "8.7.0", "elm-explorations/test": "2.2.0", "rtfeldman/elm-hex": "1.0.0", "stil4m/structured-writer": "1.0.3" } }, "test-dependencies": { "direct": { "elm-explorations/test": "2.2.0" }, "indirect": {} } } ================================================ FILE: sandbox/review/src/ReviewConfig.elm ================================================ module ReviewConfig exposing (config) {-| Do not rename the ReviewConfig module or the config function, because `elm-review` will look for these. To add packages that contain rules, add them to this review project using `elm install author/packagename` when inside the directory containing this file. -} import NoUnoptimizedRecursion import NoUnused.CustomTypeConstructorArgs import NoUnused.CustomTypeConstructors import NoUnused.Dependencies import NoUnused.Exports import NoUnused.Modules import NoUnused.Parameters import NoUnused.Patterns import NoUnused.Variables import Review.Rule exposing (Rule) import Simplify config : List Rule config = [ NoUnused.CustomTypeConstructors.rule [] , NoUnused.CustomTypeConstructorArgs.rule , NoUnused.Dependencies.rule , NoUnused.Exports.rule |> Review.Rule.ignoreErrorsForFiles [ "src/Common/Camera.elm" ] , NoUnused.Modules.rule , NoUnused.Parameters.rule , NoUnused.Patterns.rule , NoUnused.Variables.rule , Simplify.rule Simplify.defaults , NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optOutWithComment "IGNORE TCO") ] ================================================ FILE: sandbox/src/Boxes.elm ================================================ module Boxes exposing (main) {-| This demo is used to test performance. It drops 5×5×5 boxes. Try changing `boxesPerDimension` to drop even more! -} import Array exposing (Array) import Block3d import Browser import Browser.Dom as Dom import Browser.Events as Events import Common.Camera as Camera exposing (Camera) import Common.Fps as Fps import Common.Meshes as Meshes exposing (Attributes) import Common.Scene as Scene import Common.Settings as Settings exposing (Settings, SettingsMsg, settings) import Frame3d import Html exposing (Html) import Html.Events exposing (onClick) import Length exposing (Meters) import Mass import Physics exposing (Body, WorldCoordinates, onEarth) import Physics.Material as Material import Physics.Types exposing (Contacts(..)) import Plane3d import Point3d exposing (Point3d) import Task import WebGL exposing (Mesh) boxesPerDimension : number boxesPerDimension = 5 type alias Model = { bodies : List ( Int, Body ) , meshes : Array (Mesh Attributes) , contacts : Contacts Int , fps : List Float , settings : Settings , camera : Camera } type Msg = ForSettings SettingsMsg | Tick Float | Resize Float Float | Restart main : Program () Model Msg main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view } init : () -> ( Model, Cmd Msg ) init _ = ( { bodies = initialBodies , meshes = initialMeshes , contacts = Physics.emptyContacts , fps = [] , settings = { settings | showFpsMeter = True } , camera = Camera.camera { from = { x = 0, y = 30, z = 20 } , to = { x = 0, y = 0, z = 0 } } } , Task.perform (\{ viewport } -> Resize viewport.width viewport.height) Dom.getViewport ) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ForSettings settingsMsg -> ( { model | settings = Settings.update settingsMsg model.settings } , Cmd.none ) Tick dt -> let ( newBodies, newContacts ) = Physics.simulate { onEarth | contacts = model.contacts } model.bodies in ( { model | fps = Fps.update dt model.fps , bodies = newBodies , contacts = newContacts } , Cmd.none ) Resize width height -> ( { model | camera = Camera.resize width height model.camera } , Cmd.none ) Restart -> ( { model | bodies = initialBodies, meshes = initialMeshes, contacts = Physics.emptyContacts }, Cmd.none ) subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Events.onResize (\w h -> Resize (toFloat w) (toFloat h)) , Events.onAnimationFrameDelta Tick ] view : Model -> Html Msg view { settings, fps, bodies, meshes, contacts, camera } = Html.div [] [ Scene.view { settings = settings , bodies = List.filterMap (\( id, body ) -> Maybe.map (\mesh -> ( mesh, body )) (Array.get id meshes)) bodies , contacts = List.concatMap (\( _, _, c ) -> c) (Physics.contactPoints (\_ _ -> True) contacts) , camera = camera , floorOffset = floorOffset } , Settings.view ForSettings settings [ Html.button [ onClick Restart ] [ Html.text "Restart the demo" ] ] , if settings.showFpsMeter then let (Contacts c) = contacts in Fps.view fps (List.length bodies) c.iterations else Html.text "" ] {-| Shift the floor a little bit down -} floorOffset : { x : Float, y : Float, z : Float } floorOffset = { x = 0, y = 0, z = -1 } initialBodies : List ( Int, Body ) initialBodies = let -- id=0 is the floor floorBody = Physics.plane Plane3d.xy Material.wood |> Physics.moveTo (Point3d.fromMeters floorOffset) dimensions = List.map toFloat (List.range 0 (boxesPerDimension - 1)) distance = 1 block3d = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 1, Length.meters 1, Length.meters 1 ) boxBody = Physics.block block3d Material.wood |> Physics.scaleMassTo (Mass.kilograms 5) boxes = List.indexedMap (\idx ( x, y, z ) -> ( idx + 1 , boxBody |> Physics.moveTo (Point3d.meters ((x - (boxesPerDimension - 1) / 2) * distance) ((y - (boxesPerDimension - 1) / 2) * distance) ((z + (2 * boxesPerDimension + 1) / 2) * distance) ) ) ) (List.concatMap (\x -> List.concatMap (\y -> List.map (\z -> ( x, y, z )) dimensions) dimensions ) dimensions ) in ( 0, floorBody ) :: boxes initialMeshes : Array (Mesh Attributes) initialMeshes = let block3d = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 1, Length.meters 1, Length.meters 1 ) -- id=0 is floor (empty mesh) floorMesh = Meshes.fromTriangles [] boxMesh = Meshes.fromTriangles (Meshes.block block3d) boxCount = boxesPerDimension ^ 3 in Array.fromList (floorMesh :: List.repeat boxCount boxMesh) ================================================ FILE: sandbox/src/Car.elm ================================================ module Car exposing (main) {-| This shows how hinge constrains can be used to assemble a car. Use the arrow keys to steer and speed! -} import Angle import Axis3d import Block3d import Browser import Browser.Dom as Dom import Browser.Events as Events import Common.Camera as Camera exposing (Camera) import Common.Fps as Fps import Common.Meshes as Meshes exposing (Attributes) import Common.Scene as Scene import Common.Settings as Settings exposing (Settings, SettingsMsg, settings) import Dict exposing (Dict) import Direction3d import Force import Frame3d exposing (Frame3d) import Html exposing (Html) import Html.Events exposing (onClick) import Json.Decode import Length exposing (Meters) import Mass import Physics exposing (Body, BodyCoordinates, WorldCoordinates, onEarth) import Physics.Constraint as Constraint exposing (Constraint) import Physics.Material as Material import Physics.Shape as Shape import Physics.Types exposing (Contacts(..)) import Plane3d import Point3d exposing (Point3d) import Sphere3d import Task import Vector3d import WebGL exposing (Mesh) type Command = Speed Float | Steer Float keyDecoder : (Command -> Msg) -> Json.Decode.Decoder Msg keyDecoder toMsg = Json.Decode.field "key" Json.Decode.string |> Json.Decode.andThen (\string -> case string of "ArrowLeft" -> Json.Decode.succeed (toMsg (Steer -1)) "ArrowRight" -> Json.Decode.succeed (toMsg (Steer 1)) "ArrowUp" -> Json.Decode.succeed (toMsg (Speed 1)) "ArrowDown" -> Json.Decode.succeed (toMsg (Speed -1)) _ -> Json.Decode.fail ("Unrecognized key: " ++ string) ) type alias Model = { bodies : List ( String, Body ) , meshes : Dict String (Mesh Attributes) , contacts : Physics.Contacts String , fps : List Float , settings : Settings , camera : Camera , speeding : Float -- -1, 0, 1 , steering : Float -- -1, 0, 1 } type Msg = ForSettings SettingsMsg | Tick Float | Resize Float Float | Restart | KeyDown Command | KeyUp Command main : Program () Model Msg main = Browser.element { init = init , update = \msg model -> ( update msg model, Cmd.none ) , subscriptions = subscriptions , view = view } init : () -> ( Model, Cmd Msg ) init _ = ( { bodies = initialBodies , meshes = initialMeshes , contacts = Physics.emptyContacts , fps = [] , settings = settings , speeding = 0 , steering = 0 , camera = Camera.camera { from = { x = -60, y = 60, z = 40 } , to = { x = 0, y = -7, z = 0 } } } , Task.perform (\{ viewport } -> Resize viewport.width viewport.height) Dom.getViewport ) update : Msg -> Model -> Model update msg model = case msg of ForSettings settingsMsg -> { model | settings = Settings.update settingsMsg model.settings } Tick dt -> let baseFrame = model.bodies |> List.filterMap (\( id, body ) -> if id == "base" then Just (Physics.frame body) else Nothing ) |> List.head |> Maybe.withDefault Frame3d.atOrigin bodiesWithForce = List.map (\( id, body ) -> if model.speeding /= 0 && (id == "wheel1" || id == "wheel2") then ( id, applySpeed model.speeding baseFrame body ) else ( id, body ) ) model.bodies ( newBodies, newContacts ) = Physics.simulate { onEarth | constrain = constrainCar model.steering, contacts = model.contacts } bodiesWithForce in { model | fps = Fps.update dt model.fps , bodies = newBodies , contacts = newContacts } Resize width height -> { model | camera = Camera.resize width height model.camera } Restart -> { model | bodies = initialBodies, contacts = Physics.emptyContacts } KeyDown (Steer k) -> { model | steering = k } KeyDown (Speed k) -> { model | speeding = k } KeyUp (Steer k) -> { model | steering = if k == model.steering then 0 else model.steering } KeyUp (Speed k) -> { model | speeding = if k == model.speeding then 0 else model.speeding } subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Events.onResize (\w h -> Resize (toFloat w) (toFloat h)) , Events.onAnimationFrameDelta Tick , Events.onKeyDown (keyDecoder KeyDown) , Events.onKeyUp (keyDecoder KeyUp) ] view : Model -> Html Msg view { settings, fps, bodies, contacts, meshes, camera } = Html.div [] [ Scene.view { settings = settings , bodies = List.filterMap (\( id, body ) -> Maybe.map (\mesh -> ( mesh, body )) (Dict.get id meshes) ) bodies , contacts = List.concatMap (\( _, _, c ) -> c) (Physics.contactPoints (\_ _ -> True) contacts) , camera = camera , floorOffset = floorOffset } , Settings.view ForSettings settings [ Html.button [ onClick Restart ] [ Html.text "Restart the demo" ] ] , if settings.showFpsMeter then let (Contacts c) = contacts in Fps.view fps (List.length bodies) c.iterations else Html.text "" ] applySpeed : Float -> Frame3d Meters WorldCoordinates { defines : BodyCoordinates } -> Body -> Body applySpeed speed baseFrame body = let forward = Frame3d.yDirection baseFrame up = Frame3d.zDirection baseFrame wheelPoint = Frame3d.originPoint (Physics.frame body) pointOnTheWheel = wheelPoint |> Point3d.translateBy (Vector3d.withLength (Length.meters 1.2) up) pointUnderTheWheel = wheelPoint |> Point3d.translateBy (Vector3d.withLength (Length.meters 1.2) (Direction3d.reverse up)) force = Vector3d.withLength (Force.newtons (speed * 100)) forward in body |> Physics.applyForce force pointUnderTheWheel |> Physics.applyForce (Vector3d.reverse force) pointOnTheWheel constrainCar : Float -> String -> Maybe (String -> List Constraint) constrainCar steering id1 = let steeringAngle = steering * pi / 8 dx = cos steeringAngle dy = sin steeringAngle hinge1 = Constraint.hinge (Axis3d.through (Point3d.meters 3 3 0) (Direction3d.unsafe { x = 1, y = 0, z = 0 }) ) (Axis3d.through (Point3d.meters 0 0 0) (Direction3d.unsafe { x = -1, y = 0, z = 0 }) ) hinge2 = Constraint.hinge (Axis3d.through (Point3d.meters -3 3 0) (Direction3d.unsafe { x = -1, y = 0, z = 0 }) ) (Axis3d.through Point3d.origin (Direction3d.unsafe { x = 1, y = 0, z = 0 }) ) hinge3 = Constraint.hinge (Axis3d.through (Point3d.meters -3 -3 0) (Direction3d.unsafe { x = -dx, y = dy, z = 0 }) ) (Axis3d.through Point3d.origin (Direction3d.unsafe { x = 1, y = 0, z = 0 }) ) hinge4 = Constraint.hinge (Axis3d.through (Point3d.meters 3 -3 0) (Direction3d.unsafe { x = -dx, y = dy, z = 0 }) ) (Axis3d.through Point3d.origin (Direction3d.unsafe { x = -1, y = 0, z = 0 }) ) in if id1 == "base" then Just (\id2 -> case id2 of "wheel1" -> [ hinge1 ] "wheel2" -> [ hinge2 ] "wheel3" -> [ hinge3 ] "wheel4" -> [ hinge4 ] _ -> [] ) else Nothing {-| Shift the floor a little bit down -} floorOffset : { x : Float, y : Float, z : Float } floorOffset = { x = 0, y = 0, z = -1 } initialBodies : List ( String, Body ) initialBodies = let floorBody = Physics.plane Plane3d.xy Material.wood |> Physics.moveTo (Point3d.fromMeters floorOffset) slopeBlock3d = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 10 , Length.meters 16 , Length.meters 0.5 ) slopeBody = Physics.static [ ( Shape.block slopeBlock3d, Material.wood ) ] |> Physics.rotateAround Axis3d.x (Angle.radians (pi / 16)) |> Physics.moveTo (Point3d.meters 0 -2 1) bottom = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 3, Length.meters 6, Length.meters 1 ) top = Block3d.centeredOn (Frame3d.atPoint (Point3d.meters 0 1 1)) ( Length.meters 2, Length.meters 3, Length.meters 1.5 ) baseBody = Physics.dynamic [ ( Shape.sum [ Shape.block top, Shape.block bottom ], Material.wood ) ] |> Physics.scaleMassTo (Mass.kilograms 80) |> Physics.moveTo (Point3d.meters 0 0 5) sphere3d = Sphere3d.atOrigin (Length.meters 1.2) wheelBody = Physics.sphere sphere3d Material.rubber |> Physics.scaleMassTo (Mass.kilograms 2) offset = Point3d.meters 0 0 5 in [ ( "floor", floorBody ) , ( "slope", slopeBody ) , ( "base", baseBody ) , ( "wheel1" , wheelBody |> Physics.moveTo offset |> Physics.translateBy (Vector3d.meters 3 3 0) ) , ( "wheel2" , wheelBody |> Physics.moveTo offset |> Physics.translateBy (Vector3d.meters -3 3 0) ) , ( "wheel3" , wheelBody |> Physics.moveTo offset |> Physics.translateBy (Vector3d.meters -3 -3 0) ) , ( "wheel4" , wheelBody |> Physics.moveTo offset |> Physics.translateBy (Vector3d.meters 3 -3 0) ) ] initialMeshes : Dict String (Mesh Attributes) initialMeshes = let slopeBlock3d = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 10 , Length.meters 16 , Length.meters 0.5 ) bottom = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 3, Length.meters 6, Length.meters 1 ) top = Block3d.centeredOn (Frame3d.atPoint (Point3d.meters 0 1 1)) ( Length.meters 2, Length.meters 3, Length.meters 1.5 ) sphere3d = Sphere3d.atOrigin (Length.meters 1.2) wheelMesh = Meshes.fromTriangles (Meshes.sphere 2 sphere3d) in Dict.fromList [ ( "floor", Meshes.fromTriangles [] ) , ( "slope", Meshes.fromTriangles (Meshes.block slopeBlock3d) ) , ( "base", Meshes.fromTriangles (Meshes.block bottom ++ Meshes.block top) ) , ( "wheel1", wheelMesh ) , ( "wheel2", wheelMesh ) , ( "wheel3", wheelMesh ) , ( "wheel4", wheelMesh ) ] ================================================ FILE: sandbox/src/Character.elm ================================================ module Character exposing (main) {-| Character controller demo, modelled directly on cannon-es's PointerLockControlsCannon: sphere body, mild friction, high linear damping, additive velocity from input. Rotation is locked via [Physics.lock](Physics#lock) so the sphere slides instead of rolling. Arrow keys to move, Space to jump. -} import Block3d exposing (Block3d) import Browser import Browser.Dom as Dom import Browser.Events as Events import Common.Camera as Camera exposing (Camera) import Common.Fps as Fps import Common.Meshes as Meshes exposing (Attributes) import Common.Scene as Scene import Common.Settings as Settings exposing (Settings, SettingsMsg, settings) import Density import Dict exposing (Dict) import Direction3d import Duration import Force import Frame3d exposing (Frame3d) import Html exposing (Html) import Html.Events exposing (onClick) import Json.Decode import Length exposing (Meters) import Mass import Physics exposing (Body, BodyCoordinates, WorldCoordinates, onEarth) import Physics.Lock as Lock import Physics.Material as Material exposing (Material) import Physics.Shape as Shape import Physics.Types exposing (Contacts(..)) import Plane3d import Point3d exposing (Point3d) import Quantity import Sphere3d exposing (Sphere3d) import Task import Vector3d import WebGL exposing (Mesh) {-| Acceleration applied per held input direction, in m/s². With `linearDamping = 0.9` this settles around 3 m/s — a brisk walking pace at this scene scale. -} moveAcceleration : Float moveAcceleration = 35 {-| Initial vertical speed when jumping, in m/s. Under `onEarth` gravity (~9.8 m/s²), this peaks about 0.8 m above takeoff — enough to clear two steps of the staircase. -} jumpSpeed : Float jumpSpeed = 4 {-| Player material with mild friction so the sphere can grip steps and edges instead of sliding back. Combined with the floor's friction via the geometric mean (√(0.3·0.4) ≈ 0.35), this is enough to hold position on stairs but still slippery enough to feel responsive. -} playerMaterial : Material Material.Dense playerMaterial = Material.dense { density = Density.kilogramsPerCubicMeter 700 , friction = 0.2 , bounciness = 0 } {-| Slippery material for the staircase — combined with the player's 0.2 friction via √(0.2·0.02) ≈ 0.063, so the sphere can slide up step edges without getting caught by tangential friction at the corner contact. -} stairMaterial : Material Material.Dense stairMaterial = Material.dense { density = Density.kilogramsPerCubicMeter 700 , friction = 0.02 , bounciness = 0 } {-| Crate material with the same friction as the floor. -} boxMaterial : Material Material.Dense boxMaterial = Material.dense { density = Density.kilogramsPerCubicMeter 700 , friction = 0.4 , bounciness = 0 } type alias Model = { bodies : List ( String, Body ) , meshes : Dict String (Mesh Attributes) , contacts : Physics.Contacts String , fps : List Float , settings : Settings , camera : Camera , forwardInput : Float , rightInput : Float , grounded : Bool } type Msg = ForSettings SettingsMsg | Tick Float | Resize Float Float | Restart | KeyDown Key | KeyUp Key type Key = KeyForward | KeyBack | KeyLeft | KeyRight | KeyJump main : Program () Model Msg main = Browser.element { init = init , update = \msg model -> ( update msg model, Cmd.none ) , subscriptions = subscriptions , view = view } init : () -> ( Model, Cmd Msg ) init _ = ( { bodies = initialBodies , meshes = initialMeshes , contacts = Physics.emptyContacts , fps = [] , settings = { settings | showSettings = True } , camera = Camera.camera { from = { x = 0, y = -12, z = 8 } , to = { x = 0, y = 0, z = 0.5 } } , forwardInput = 0 , rightInput = 0 , grounded = False } , Task.perform (\{ viewport } -> Resize viewport.width viewport.height) Dom.getViewport ) update : Msg -> Model -> Model update msg model = case msg of ForSettings settingsMsg -> { model | settings = Settings.update settingsMsg model.settings } Tick dt -> let bodiesWithInput = if model.grounded then List.map (\( id, body ) -> if id == "player" then ( id, drivePlayer dt model.rightInput model.forwardInput body ) else ( id, body ) ) model.bodies else model.bodies ( newBodies, newContacts ) = Physics.simulate { onEarth | contacts = model.contacts } bodiesWithInput in { model | fps = Fps.update dt model.fps , bodies = newBodies , contacts = newContacts , grounded = playerGrounded newBodies newContacts } Resize width height -> { model | camera = Camera.resize width height model.camera } Restart -> { model | bodies = initialBodies , contacts = Physics.emptyContacts } KeyDown KeyForward -> { model | forwardInput = 1 } KeyDown KeyBack -> { model | forwardInput = -1 } KeyDown KeyLeft -> { model | rightInput = -1 } KeyDown KeyRight -> { model | rightInput = 1 } KeyDown KeyJump -> if model.grounded then { model | bodies = List.map (\( id, body ) -> if id == "player" then ( id, jumpPlayer body ) else ( id, body ) ) model.bodies , grounded = False } else model KeyUp KeyForward -> { model | forwardInput = if model.forwardInput == 1 then 0 else model.forwardInput } KeyUp KeyBack -> { model | forwardInput = if model.forwardInput == -1 then 0 else model.forwardInput } KeyUp KeyLeft -> { model | rightInput = if model.rightInput == -1 then 0 else model.rightInput } KeyUp KeyRight -> { model | rightInput = if model.rightInput == 1 then 0 else model.rightInput } KeyUp KeyJump -> model {-| Add horizontal velocity each frame via an impulse, matching cannon's `velocity.x += inputVelocity.x` pattern. Vertical velocity is left to gravity and contacts so contact rebounds don't get fed back into input. -} drivePlayer : Float -> Float -> Float -> Body -> Body drivePlayer dtMs right forward body = if right == 0 && forward == 0 then body else let duration = Duration.milliseconds dtMs xImpulse = Quantity.times duration (Force.newtons (right * moveAcceleration * playerMass)) yImpulse = Quantity.times duration (Force.newtons (forward * moveAcceleration * playerMass)) impulse = Vector3d.xyz xImpulse yImpulse Quantity.zero in Physics.applyImpulse impulse (Physics.originPoint body) body {-| Apply an upward impulse that produces `jumpSpeed` of vertical velocity on a body at rest (impulse = mass × Δv). -} jumpPlayer : Body -> Body jumpPlayer body = let impulse = Vector3d.xyz Quantity.zero Quantity.zero (Quantity.times (Duration.seconds 1) (Force.newtons (playerMass * jumpSpeed))) in Physics.applyImpulse impulse (Physics.originPoint body) body {-| The player is grounded if any of its contact points sits below its center. Works for flat floor AND step edges (which contact higher up on the sphere than floor contacts). A small 0.1 m offset keeps pure-wall contacts (at the sphere's equator) from falsely grounding the player. -} playerGrounded : List ( String, Body ) -> Physics.Contacts String -> Bool playerGrounded bodies contacts = case List.filter (\( id, _ ) -> id == "player") bodies of [] -> False ( _, player ) :: _ -> let playerZ = Length.inMeters (Point3d.zCoordinate (Physics.originPoint player)) threshold = playerZ - 0.1 in Physics.contactPoints (\a b -> a == "player" || b == "player") contacts |> List.concatMap (\( _, _, pts ) -> pts) |> List.any (\pt -> Length.inMeters (Point3d.zCoordinate pt) < threshold) subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Events.onResize (\w h -> Resize (toFloat w) (toFloat h)) , Events.onAnimationFrameDelta Tick , Events.onKeyDown (keyDecoder KeyDown) , Events.onKeyUp (keyDecoder KeyUp) ] keyDecoder : (Key -> Msg) -> Json.Decode.Decoder Msg keyDecoder toMsg = Json.Decode.field "key" Json.Decode.string |> Json.Decode.andThen (\key -> case String.toLower key of "arrowup" -> Json.Decode.succeed (toMsg KeyForward) "arrowdown" -> Json.Decode.succeed (toMsg KeyBack) "arrowleft" -> Json.Decode.succeed (toMsg KeyLeft) "arrowright" -> Json.Decode.succeed (toMsg KeyRight) " " -> Json.Decode.succeed (toMsg KeyJump) _ -> Json.Decode.fail "ignored key" ) view : Model -> Html Msg view { settings, fps, bodies, meshes, contacts, camera } = Html.div [] [ Scene.view { settings = settings , bodies = List.filterMap (\( id, body ) -> Maybe.map (\mesh -> ( mesh, body )) (Dict.get id meshes) ) bodies , contacts = List.concatMap (\( _, _, c ) -> c) (Physics.contactPoints (\_ _ -> True) contacts) , camera = camera , floorOffset = floorOffset } , Settings.view ForSettings settings [ Html.button [ onClick Restart ] [ Html.text "Restart the demo" ] ] , if settings.showFpsMeter then let (Contacts c) = contacts in Fps.view fps (List.length bodies) c.iterations else Html.text "" ] floorOffset : { x : Float, y : Float, z : Float } floorOffset = { x = 0, y = 0, z = 0 } playerMass : Float playerMass = 5 playerSphere : Sphere3d Meters BodyCoordinates playerSphere = Sphere3d.atOrigin (Length.meters 0.4) boxBlock : Block3d Meters BodyCoordinates boxBlock = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 0.5, Length.meters 0.5, Length.meters 0.8 ) {-| Step blocks for a linear staircase, in body-local coordinates. Each block is a slab that runs from y = level to y = level + 1 and is tall enough to support every step in front of it. Walking forward (+Y) takes you up to the top platform. -} stairBlocks : List (Block3d Meters BodyCoordinates) stairBlocks = let stepHeight = 0.2 stepDepth = 0.5 width = 1.5 topPlatformDepth = 1.2 numSteps = 4 in List.map (\level -> let yMin = toFloat level * stepDepth yMax = yMin + (if level == numSteps then topPlatformDepth else stepDepth ) zMax = toFloat (level + 1) * stepHeight in Block3d.from (Point3d.meters (-width / 2) yMin 0) (Point3d.meters (width / 2) yMax zMax) ) (List.range 0 4) initialBodies : List ( String, Body ) initialBodies = let floorBody = Physics.plane Plane3d.xy Material.wood |> Physics.moveTo (Point3d.fromMeters floorOffset) player = Physics.sphere playerSphere playerMaterial |> Physics.scaleMassTo (Mass.kilograms playerMass) |> Physics.moveTo (Point3d.meters 0 -4 0.4) |> Physics.lock Lock.allRotation boxAt x y = Physics.block boxBlock boxMaterial |> Physics.scaleMassTo (Mass.kilograms 5) |> Physics.moveTo (Point3d.meters x y 0.4) boxes = List.indexedMap (\idx ( x, y ) -> ( "box-" ++ String.fromInt idx, boxAt x y )) [ ( -3, -2 ) , ( -3, 0 ) , ( -3, 2 ) , ( -4, 1 ) , ( 3, -2 ) , ( 3, 0 ) , ( 3, 2 ) , ( 4, 1 ) ] stairsBody = Physics.static (List.map (\b -> ( Shape.block b, stairMaterial )) stairBlocks) |> Physics.moveTo (Point3d.meters 0 1 0) in ( "floor", floorBody ) :: ( "player", player ) :: ( "stairs", stairsBody ) :: boxes initialMeshes : Dict String (Mesh Attributes) initialMeshes = let playerMesh = Meshes.fromTriangles (Meshes.sphere 3 playerSphere) boxMesh = Meshes.fromTriangles (Meshes.block boxBlock) floorMesh = Meshes.fromTriangles [] stairsMesh = Meshes.fromTriangles (List.concatMap Meshes.block stairBlocks) boxKeys = List.range 0 7 |> List.map (\i -> "box-" ++ String.fromInt i) in Dict.fromList (( "floor", floorMesh ) :: ( "player", playerMesh ) :: ( "stairs", stairsMesh ) :: List.map (\k -> ( k, boxMesh )) boxKeys ) ================================================ FILE: sandbox/src/Character2D.elm ================================================ module Character2D exposing (main) {-| 2D character controller demo. Motion is locked to the world XZ plane: Y translation is locked, and all rotation is locked. The camera looks straight down the -Y axis, so the simulation reads as a classic side-scrolling platformer. Left/Right arrows to move, Space to jump. -} import Angle import Axis3d import Block3d exposing (Block3d) import Browser import Browser.Dom as Dom import Browser.Events as Events import Common.Camera as Camera exposing (Camera) import Common.Fps as Fps import Common.Meshes as Meshes exposing (Attributes) import Common.Scene as Scene import Common.Settings as Settings exposing (Settings, SettingsMsg, settings) import Density import Dict exposing (Dict) import Duration import Force import Frame3d import Html exposing (Html) import Html.Events exposing (onClick) import Json.Decode import Length exposing (Meters) import Mass import Physics exposing (Body, BodyCoordinates, onEarth) import Physics.Lock as Lock import Physics.Material as Material exposing (Material) import Physics.Shape as Shape import Physics.Types exposing (Contacts(..)) import Plane3d import Point3d exposing (Point3d) import Quantity import Sphere3d exposing (Sphere3d) import Task import Vector3d import WebGL exposing (Mesh) moveAcceleration : Float moveAcceleration = 35 jumpSpeed : Float jumpSpeed = 4 playerMaterial : Material Material.Dense playerMaterial = Material.dense { density = Density.kilogramsPerCubicMeter 700 , friction = 0.2 , bounciness = 0 } stairMaterial : Material Material.Dense stairMaterial = Material.dense { density = Density.kilogramsPerCubicMeter 700 , friction = 0.02 , bounciness = 0 } boxMaterial : Material Material.Dense boxMaterial = Material.dense { density = Density.kilogramsPerCubicMeter 700 , friction = 0.4 , bounciness = 0 } {-| Lock the sphere to the XZ plane: no Y motion, no rotation. -} planarLock : List Lock.Lock planarLock = Lock.translateY :: Lock.allRotation type alias Model = { bodies : List ( String, Body ) , meshes : Dict String (Mesh Attributes) , contacts : Physics.Contacts String , fps : List Float , settings : Settings , camera : Camera , rightInput : Float , grounded : Bool } type Msg = ForSettings SettingsMsg | Tick Float | Resize Float Float | Restart | KeyDown Key | KeyUp Key type Key = KeyLeft | KeyRight | KeyJump main : Program () Model Msg main = Browser.element { init = init , update = \msg model -> ( update msg model, Cmd.none ) , subscriptions = subscriptions , view = view } init : () -> ( Model, Cmd Msg ) init _ = ( { bodies = initialBodies , meshes = initialMeshes , contacts = Physics.emptyContacts , fps = [] , settings = { settings | showSettings = True } , camera = Camera.camera { from = { x = 0, y = -14, z = 2 } , to = { x = 0, y = 0, z = 1 } } , rightInput = 0 , grounded = False } , Task.perform (\{ viewport } -> Resize viewport.width viewport.height) Dom.getViewport ) update : Msg -> Model -> Model update msg model = case msg of ForSettings settingsMsg -> { model | settings = Settings.update settingsMsg model.settings } Tick dt -> let bodiesWithInput = if model.grounded then List.map (\( id, body ) -> if id == "player" then ( id, drivePlayer dt model.rightInput body ) else ( id, body ) ) model.bodies else model.bodies ( newBodies, newContacts ) = Physics.simulate { onEarth | contacts = model.contacts } bodiesWithInput in { model | fps = Fps.update dt model.fps , bodies = newBodies , contacts = newContacts , grounded = playerGrounded newBodies newContacts , camera = followPlayer newBodies model.camera } Resize width height -> { model | camera = Camera.resize width height model.camera } Restart -> { model | bodies = initialBodies , contacts = Physics.emptyContacts } KeyDown KeyLeft -> { model | rightInput = -1 } KeyDown KeyRight -> { model | rightInput = 1 } KeyDown KeyJump -> if model.grounded then { model | bodies = List.map (\( id, body ) -> if id == "player" then ( id, jumpPlayer body ) else ( id, body ) ) model.bodies , grounded = False } else model KeyUp KeyLeft -> { model | rightInput = if model.rightInput == -1 then 0 else model.rightInput } KeyUp KeyRight -> { model | rightInput = if model.rightInput == 1 then 0 else model.rightInput } KeyUp KeyJump -> model drivePlayer : Float -> Float -> Body -> Body drivePlayer dtMs right body = if right == 0 then body else let duration = Duration.milliseconds dtMs xImpulse = Quantity.times duration (Force.newtons (right * moveAcceleration * playerMass)) impulse = Vector3d.xyz xImpulse Quantity.zero Quantity.zero in Physics.applyImpulse impulse (Physics.originPoint body) body jumpPlayer : Body -> Body jumpPlayer body = let impulse = Vector3d.xyz Quantity.zero Quantity.zero (Quantity.times (Duration.seconds 1) (Force.newtons (playerMass * jumpSpeed))) in Physics.applyImpulse impulse (Physics.originPoint body) body {-| Track the player's X with the camera, keeping the Y offset and Z height that were configured in `init`. -} followPlayer : List ( String, Body ) -> Camera -> Camera followPlayer bodies currentCamera = case List.filter (\( id, _ ) -> id == "player") bodies of [] -> currentCamera ( _, player ) :: _ -> let origin = Physics.originPoint player playerX = Length.inMeters (Point3d.xCoordinate origin) from = currentCamera.from to = currentCamera.to rebuilt = Camera.camera { from = { from | x = playerX } , to = { to | x = playerX } } in Camera.resize currentCamera.width currentCamera.height rebuilt playerGrounded : List ( String, Body ) -> Physics.Contacts String -> Bool playerGrounded bodies contacts = case List.filter (\( id, _ ) -> id == "player") bodies of [] -> False ( _, player ) :: _ -> let playerZ = Length.inMeters (Point3d.zCoordinate (Physics.originPoint player)) threshold = playerZ - 0.1 in Physics.contactPoints (\a b -> a == "player" || b == "player") contacts |> List.concatMap (\( _, _, pts ) -> pts) |> List.any (\pt -> Length.inMeters (Point3d.zCoordinate pt) < threshold) subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Events.onResize (\w h -> Resize (toFloat w) (toFloat h)) , Events.onAnimationFrameDelta Tick , Events.onKeyDown (keyDecoder KeyDown) , Events.onKeyUp (keyDecoder KeyUp) ] keyDecoder : (Key -> Msg) -> Json.Decode.Decoder Msg keyDecoder toMsg = Json.Decode.field "key" Json.Decode.string |> Json.Decode.andThen (\key -> case String.toLower key of "arrowleft" -> Json.Decode.succeed (toMsg KeyLeft) "arrowright" -> Json.Decode.succeed (toMsg KeyRight) " " -> Json.Decode.succeed (toMsg KeyJump) _ -> Json.Decode.fail "ignored key" ) view : Model -> Html Msg view { settings, fps, bodies, meshes, contacts, camera } = Html.div [] [ Scene.view { settings = settings , bodies = List.filterMap (\( id, body ) -> Maybe.map (\mesh -> ( mesh, body )) (Dict.get id meshes) ) bodies , contacts = List.concatMap (\( _, _, c ) -> c) (Physics.contactPoints (\_ _ -> True) contacts) , camera = camera , floorOffset = floorOffset } , Settings.view ForSettings settings [ Html.button [ onClick Restart ] [ Html.text "Restart the demo" ] ] , if settings.showFpsMeter then let (Contacts c) = contacts in Fps.view fps (List.length bodies) c.iterations else Html.text "" ] floorOffset : { x : Float, y : Float, z : Float } floorOffset = { x = 0, y = 0, z = 0 } playerMass : Float playerMass = 5 playerSphere : Sphere3d Meters BodyCoordinates playerSphere = Sphere3d.atOrigin (Length.meters 0.4) boxBlock : Block3d Meters BodyCoordinates boxBlock = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 0.5, Length.meters 0.5, Length.meters 0.3 ) {-| A long, thin board — a wooden plank laid flat along the body's X axis. Rotated around Y in world space to form a ramp. -} plankBlock : Block3d Meters BodyCoordinates plankBlock = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 4, Length.meters 0.6, Length.meters 0.1 ) {-| Tilt angle of the plank ramp. Negative so the left end (where the player approaches) rests near the floor and the right end rises. -} plankTilt : Angle.Angle plankTilt = Angle.degrees -20 {-| Step blocks for a linear staircase, in body-local coordinates. Each block is a slab that runs from x = level to x = level + 1 along the X axis, with Y thickness centered at 0. Walking right (+X) takes you up to the top platform. -} stairBlocks : List (Block3d Meters BodyCoordinates) stairBlocks = let stepHeight = 0.2 stepDepth = 0.5 width = 1.5 topPlatformDepth = 1.2 numSteps = 4 in List.map (\level -> let xMin = toFloat level * stepDepth xMax = xMin + (if level == numSteps then topPlatformDepth else stepDepth ) zMax = toFloat (level + 1) * stepHeight in Block3d.from (Point3d.meters xMin (-width / 2) 0) (Point3d.meters xMax (width / 2) zMax) ) (List.range 0 4) initialBodies : List ( String, Body ) initialBodies = let floorBody = Physics.plane Plane3d.xy Material.wood |> Physics.moveTo (Point3d.fromMeters floorOffset) player = Physics.sphere playerSphere playerMaterial |> Physics.scaleMassTo (Mass.kilograms playerMass) |> Physics.moveTo (Point3d.meters -4 0 0.4) |> Physics.lock planarLock boxAt x z = Physics.block boxBlock boxMaterial |> Physics.scaleMassTo (Mass.kilograms 5) |> Physics.moveTo (Point3d.meters x 0 z) |> Physics.lock planarLock boxes = List.indexedMap (\idx ( x, z ) -> ( "box-" ++ String.fromInt idx, boxAt x z )) [ ( -1.5, 0.15 ) , ( 5, 0.15 ) , ( 5, 0.45 ) , ( 11, 0.15 ) ] stairsBody = Physics.static (List.map (\b -> ( Shape.block b, stairMaterial )) stairBlocks) |> Physics.moveTo (Point3d.meters 0 0 0) plankBody = Physics.static [ ( Shape.block plankBlock, Material.wood ) ] |> Physics.rotateAround Axis3d.y plankTilt |> Physics.moveTo (Point3d.meters 8 0 0.8) in ( "floor", floorBody ) :: ( "player", player ) :: ( "stairs", stairsBody ) :: ( "plank", plankBody ) :: boxes initialMeshes : Dict String (Mesh Attributes) initialMeshes = let playerMesh = Meshes.fromTriangles (Meshes.sphere 3 playerSphere) boxMesh = Meshes.fromTriangles (Meshes.block boxBlock) floorMesh = Meshes.fromTriangles [] stairsMesh = Meshes.fromTriangles (List.concatMap Meshes.block stairBlocks) plankMesh = Meshes.fromTriangles (Meshes.block plankBlock) boxKeys = List.range 0 3 |> List.map (\i -> "box-" ++ String.fromInt i) in Dict.fromList (( "floor", floorMesh ) :: ( "player", playerMesh ) :: ( "stairs", stairsMesh ) :: ( "plank", plankMesh ) :: List.map (\k -> ( k, boxMesh )) boxKeys ) ================================================ FILE: sandbox/src/Cloth.elm ================================================ module Cloth exposing (main) {-| Cloth simulation built using many particle bodies, connected with distance constraints. -} import Array exposing (Array) import Browser import Browser.Dom as Dom import Browser.Events as Events import Common.Camera as Camera exposing (Camera) import Common.Fps as Fps import Common.Meshes as Meshes exposing (Attributes) import Common.Scene as Scene import Common.Settings as Settings exposing (Settings, SettingsMsg, settings) import Html exposing (Html) import Html.Events exposing (onClick) import Length exposing (Meters) import Mass import Physics exposing (Body, WorldCoordinates, onEarth) import Physics.Constraint as Constraint exposing (Constraint) import Physics.Material as Material import Physics.Types exposing (Contacts(..)) import Plane3d import Point3d exposing (Point3d) import Sphere3d import Task import WebGL exposing (Mesh) particlesPerDimension : Int particlesPerDimension = 10 distanceBetweenParticles : Float distanceBetweenParticles = 0.5 {-| IDs: - 0 = floor - 1 = sphere - 2 + x \* particlesPerDimension + y = particle at (x, y) -} particleId : Int -> Int -> Int particleId x y = 2 + x * particlesPerDimension + y type alias Model = { bodies : List ( Int, Body ) , meshes : Array (Mesh Attributes) , contacts : Physics.Contacts Int , fps : List Float , settings : Settings , camera : Camera } type Msg = ForSettings SettingsMsg | Tick Float | Resize Float Float | Restart main : Program () Model Msg main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view } init : () -> ( Model, Cmd Msg ) init _ = ( { bodies = initialBodies , meshes = initialMeshes , contacts = Physics.emptyContacts , fps = [] , settings = { settings | showFpsMeter = True } , camera = Camera.camera { from = { x = 0, y = 30, z = 20 } , to = { x = 0, y = 0, z = 0 } } } , Task.perform (\{ viewport } -> Resize viewport.width viewport.height) Dom.getViewport ) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ForSettings settingsMsg -> ( { model | settings = Settings.update settingsMsg model.settings } , Cmd.none ) Tick dt -> let ( newBodies, newContacts ) = Physics.simulate { onEarth | constrain = constrainCloth, contacts = model.contacts } model.bodies in ( { model | fps = Fps.update dt model.fps , bodies = newBodies , contacts = newContacts } , Cmd.none ) Resize width height -> ( { model | camera = Camera.resize width height model.camera } , Cmd.none ) Restart -> ( { model | bodies = initialBodies, contacts = Physics.emptyContacts }, Cmd.none ) subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Events.onResize (\w h -> Resize (toFloat w) (toFloat h)) , Events.onAnimationFrameDelta Tick ] view : Model -> Html Msg view { settings, fps, bodies, contacts, meshes, camera } = Html.div [] [ Scene.view { settings = settings , bodies = List.filterMap (\( id, body ) -> Maybe.map (\mesh -> ( mesh, body )) (Array.get id meshes)) bodies , contacts = List.concatMap (\( _, _, c ) -> c) (Physics.contactPoints (\_ _ -> True) contacts) , camera = camera , floorOffset = floorOffset } , Settings.view ForSettings settings [ Html.button [ onClick Restart ] [ Html.text "Restart the demo" ] ] , if settings.showFpsMeter then let (Contacts c) = contacts in Fps.view fps (List.length bodies) c.iterations else Html.text "" ] {-| Set up constraints between adjacent particles using id arithmetic. Particles have IDs: 2 + x \* n + y where n = particlesPerDimension. Two particles are horizontally adjacent if they share the same x and differ by 1 in y. Two particles are vertically adjacent if they share the same y and differ by n in id. -} constrainCloth : Int -> Maybe (Int -> List Constraint) constrainCloth id1 = let n = particlesPerDimension firstParticleId = 2 lastParticleId = 2 + n * n - 1 in if id1 < firstParticleId || id1 > lastParticleId then Nothing else Just (\id2 -> if id2 < firstParticleId || id2 > lastParticleId then [] else let -- Convert IDs back to (x, y) coordinates idx1 = id1 - firstParticleId idx2 = id2 - firstParticleId x1 = idx1 // n y1 = modBy n idx1 x2 = idx2 // n y2 = modBy n idx2 in if x1 == x2 && y2 - y1 == 1 || y1 == y2 && x2 - x1 == 1 then [ Constraint.distance (Length.meters distanceBetweenParticles) ] else [] ) {-| Shift the floor a little bit down -} floorOffset : { x : Float, y : Float, z : Float } floorOffset = { x = 0, y = 0, z = -1 } initialBodies : List ( Int, Body ) initialBodies = let floorBody = Physics.plane Plane3d.xy Material.wood |> Physics.moveTo (Point3d.fromMeters floorOffset) sphere3d = Sphere3d.atOrigin (Length.meters 2) sphereBody = Physics.sphere sphere3d Material.wood |> Physics.scaleMassTo (Mass.kilograms 5) |> Physics.moveTo (Point3d.meters 0 0 1) dimensions = List.range 0 (particlesPerDimension - 1) particleMaterial = Material.surface { friction = 0.3, bounciness = 0 } particles = List.concatMap (\x -> List.map (\y -> ( particleId x y , Physics.pointMass (Point3d.meters ((toFloat x - (toFloat particlesPerDimension - 1) / 2) * distanceBetweenParticles) ((toFloat y - (toFloat particlesPerDimension - 1) / 2) * distanceBetweenParticles) 8 ) (Mass.kilograms 5) particleMaterial ) ) dimensions ) dimensions in ( 0, floorBody ) :: ( 1, sphereBody ) :: particles initialMeshes : Array (Mesh Attributes) initialMeshes = let sphere3d = Sphere3d.atOrigin (Length.meters 2) particleSphere3d = Sphere3d.atOrigin (Length.meters 0.1) -- Total size: 2 (floor + sphere) + n*n particles particleCount = particlesPerDimension * particlesPerDimension particleMesh = Meshes.fromTriangles (Meshes.sphere 1 particleSphere3d) in Array.fromList (Meshes.fromTriangles [] :: Meshes.fromTriangles (Meshes.sphere 3 sphere3d) :: List.repeat particleCount particleMesh ) ================================================ FILE: sandbox/src/Common/Camera.elm ================================================ module Common.Camera exposing ( Camera , camera , mouseDirection , resize ) import Math.Matrix4 as Mat4 exposing (Mat4) import Math.Vector3 as Vec3 import Math.Vector4 as Vec4 exposing (Vec4) type alias Camera = { from : { x : Float , y : Float , z : Float } , to : { x : Float , y : Float , z : Float } , width : Float , height : Float , cameraTransform : Mat4 , perspectiveTransform : Mat4 } camera : { from : { x : Float, y : Float, z : Float } , to : { x : Float, y : Float, z : Float } } -> Camera camera { from, to } = { from = from , to = to , width = 1 , height = 1 , cameraTransform = Mat4.makeLookAt (Vec3.fromRecord from) (Vec3.fromRecord to) Vec3.k , perspectiveTransform = Mat4.identity } resize : Float -> Float -> Camera -> Camera resize width height camera_ = { camera_ | width = width , height = height , perspectiveTransform = Mat4.makePerspective 24 (width / height) 5 2000 } {-| Converts mouse coordinates into direction within within the world coordinate system. -} mouseDirection : Camera -> Float -> Float -> { x : Float, y : Float, z : Float } mouseDirection { cameraTransform, perspectiveTransform, width, height } x y = let homogeneousClipCoordinates = Vec4.vec4 (x * 2 / width - 1) (1 - y * 2 / height) -1 1 invertedProjectionMatrix = Maybe.withDefault Mat4.identity (Mat4.inverse perspectiveTransform) vec4CameraCoordinates = transform4 invertedProjectionMatrix homogeneousClipCoordinates direction = Vec4.vec4 (Vec4.getX vec4CameraCoordinates) (Vec4.getY vec4CameraCoordinates) -1 0 vec4WorldCoordinates = transform4 (Mat4.inverseOrthonormal cameraTransform) direction vec3WorldCoordinates = Vec3.vec3 (Vec4.getX vec4WorldCoordinates) (Vec4.getY vec4WorldCoordinates) (Vec4.getZ vec4WorldCoordinates) in vec3WorldCoordinates |> Vec3.normalize |> Vec3.toRecord transform4 : Mat4 -> Vec4 -> Vec4 transform4 mat v = let r = Mat4.toRecord mat in Vec4.vec4 (Vec4.dot (Vec4.vec4 r.m11 r.m12 r.m13 r.m14) v) (Vec4.dot (Vec4.vec4 r.m21 r.m22 r.m23 r.m24) v) (Vec4.dot (Vec4.vec4 r.m31 r.m32 r.m33 r.m34) v) (Vec4.dot (Vec4.vec4 r.m41 r.m42 r.m43 r.m44) v) ================================================ FILE: sandbox/src/Common/Fps.elm ================================================ module Common.Fps exposing ( update , view ) {-| This module is used to show the FPS meter. We keep the last 50 time deltas and show the weighted average. -} import Html exposing (Html) import Html.Attributes exposing (style) update : Float -> List Float -> List Float update dt fps = List.take 50 (dt :: fps) view : List Float -> Int -> Int -> Html a view fps numBodies iterations = let average currentWeight sumOfWeights weightedSum list = case list of [] -> weightedSum / sumOfWeights el :: rest -> average (currentWeight * 0.9) (currentWeight + sumOfWeights) (el * currentWeight + weightedSum) rest in Html.div [ style "position" "fixed" , style "font-family" "monospaced" , style "right" "250px" , style "top" "0" , style "color" "white" ] [ Html.span [ style "font" "50px sans-serif" ] [ Html.text (String.fromInt (round (1000 / average 1 0 0 fps))) ] , Html.text " fps" , Html.span [ style "font" "50px sans-serif" ] [ Html.text (" " ++ String.fromInt numBodies) ] , Html.text " bodies" , Html.span [ style "font" "50px sans-serif" ] [ Html.text (" " ++ String.fromInt iterations) ] , Html.text " iterations" ] ================================================ FILE: sandbox/src/Common/Math.elm ================================================ module Common.Math exposing ( makeRotateKTo , makeShadow ) {-| Some useful Math utilities. -} import Math.Matrix4 as Mat4 exposing (Mat4) import Math.Vector3 as Vec3 exposing (Vec3, vec3) {-| A "squash" matrix that smashes things to the ground plane, defined by position, normal, parallel to a given light vector -} makeShadow : Vec3 -> Vec3 -> Vec3 -> Mat4 makeShadow position normal light = let n = Vec3.toRecord normal nw = -(Vec3.dot position normal) l = Vec3.toRecord light d = Vec3.dot normal light in Mat4.fromRecord { m11 = l.x * n.x - d , m21 = l.y * n.x , m31 = l.z * n.x , m41 = 0 , m12 = l.x * n.y , m22 = l.y * n.y - d , m32 = l.z * n.y , m42 = 0 , m13 = l.x * n.z , m23 = l.y * n.z , m33 = l.z * n.z - d , m43 = 0 , m14 = l.x * nw , m24 = l.y * nw , m34 = l.z * nw , m44 = -d } {-| A 3D utility function that provides a transform for a rotation that reorients the z axis to the given direction (unit vector), choosing any convenient rotation for the xy plane. -} makeRotateKTo : Vec3 -> Mat4 makeRotateKTo direction = let distance = Vec3.distance Vec3.k direction in -- Specially handle the boundary cases that may throw off the general -- case trig formulas used below. -- These occur when the direction is (almost) vertical -- i.e. ~= Vec3.k or ~= (Vec3.negate Vec3.k) -- giving extreme distance values of ~0.0 or ~2.0. if distance <= precision then Mat4.identity else if abs (distance - 2.0) <= precision then -- A U-turn around the x=y line in the z=0 plane -- negates all x y and z values Mat4.makeRotate pi (vec3 1.0 1.0 0.0) else Mat4.makeRotate (2.0 * asin (distance / 2.0)) (Vec3.cross Vec3.k direction) precision : Float precision = 1.0e-6 ================================================ FILE: sandbox/src/Common/Meshes.elm ================================================ module Common.Meshes exposing ( Attributes , block , contact , cylinder , fromTriangles , normal , sphere , triangularMesh ) import Block3d exposing (Block3d) import Cylinder3d exposing (Cylinder3d) import Direction3d import Frame3d import Length exposing (Meters, inMeters) import Math.Vector3 as Vec3 exposing (Vec3, vec3) import Physics exposing (BodyCoordinates) import Point3d exposing (Point3d) import Sphere3d exposing (Sphere3d) import TriangularMesh exposing (TriangularMesh) import WebGL exposing (Mesh) type alias Attributes = { position : Vec3 , barycentric : Vec3 } -- Meshes normal : Mesh Attributes normal = fromTriangles (pyramid 0.05 0.05) contact : Mesh Attributes contact = fromTriangles (sphere 2 (Sphere3d.atOrigin (Length.meters 0.07))) fromTriangles : List ( Attributes, Attributes, Attributes ) -> Mesh Attributes fromTriangles = WebGL.triangles block : Block3d Meters BodyCoordinates -> List ( Attributes, Attributes, Attributes ) block block3d = let ( sizeX, sizeY, sizeZ ) = Block3d.dimensions block3d blockFrame3d = Block3d.axes block3d x = inMeters sizeX * 0.5 y = inMeters sizeY * 0.5 z = inMeters sizeZ * 0.5 transform px py pz = Point3d.placeIn blockFrame3d (Point3d.meters px py pz) |> Point3d.toMeters |> Vec3.fromRecord v0 = transform -x -y -z v1 = transform x -y -z v2 = transform x y -z v3 = transform -x y -z v4 = transform -x -y z v5 = transform x -y z v6 = transform x y z v7 = transform -x y z in [ facet v2 v1 v3 1 , facet v0 v3 v1 1 , facet v5 v6 v4 1 , facet v7 v4 v6 1 , facet v4 v0 v5 1 , facet v1 v5 v0 1 , facet v3 v7 v2 1 , facet v6 v2 v7 1 , facet v4 v7 v0 1 , facet v3 v0 v7 1 , facet v2 v6 v1 1 , facet v5 v1 v6 1 ] triangularMesh : TriangularMesh (Point3d Meters BodyCoordinates) -> List ( Attributes, Attributes, Attributes ) triangularMesh mesh = mesh |> TriangularMesh.mapVertices Point3d.toMeters |> TriangularMesh.faceVertices |> List.map (\( v1, v2, v3 ) -> facet (Vec3.fromRecord v1) (Vec3.fromRecord v2) (Vec3.fromRecord v3) 0 ) pyramid : Float -> Float -> List ( Attributes, Attributes, Attributes ) pyramid halfbase baserise = let top = vec3 0 0 1 rbb = vec3 halfbase -halfbase baserise rfb = vec3 halfbase halfbase baserise lfb = vec3 -halfbase halfbase baserise lbb = vec3 -halfbase -halfbase baserise in [ facet rfb lfb lbb 0 , facet lbb rbb rfb 0 , facet top rfb rbb 0 , facet top lfb rfb 0 , facet top lbb lfb 0 , facet top rbb lbb 0 ] {-| Code taken from elm-3d-scene's Primitives module. -} cylinder : Int -> Cylinder3d Meters BodyCoordinates -> List ( Attributes, Attributes, Attributes ) cylinder subdivisions cylinder3d = let wedgeAngle = 2 * pi / toFloat subdivisions length = Length.inMeters (Cylinder3d.length cylinder3d) radius = Length.inMeters (Cylinder3d.radius cylinder3d) bottomZ = -0.5 * length topZ = 0.5 * length ( a, b ) = Cylinder3d.axialDirection cylinder3d |> Direction3d.perpendicularBasis cylinderFrame3d = Frame3d.unsafe { originPoint = Cylinder3d.centerPoint cylinder3d , xDirection = a , yDirection = b , zDirection = Cylinder3d.axialDirection cylinder3d } transform px py pz = Point3d.placeIn cylinderFrame3d (Point3d.meters px py pz) |> Point3d.toMeters |> Vec3.fromRecord bottomCenter = transform 0 0 bottomZ topCenter = transform 0 0 topZ wedge startIndex = let startAngle = wedgeAngle * toFloat startIndex endIndex = startIndex + 1 |> modBy subdivisions endAngle = wedgeAngle * toFloat endIndex startX = radius * cos startAngle endX = radius * cos endAngle startY = radius * sin startAngle endY = radius * sin endAngle p0 = transform startX startY bottomZ p1 = transform endX endY bottomZ p2 = transform startX startY topZ p3 = transform endX endY topZ in [ facet topCenter p2 p3 2 , facet p1 p3 p0 1 , facet p2 p0 p3 1 , facet bottomCenter p1 p0 2 ] in List.range 0 (subdivisions - 1) |> List.concatMap wedge sphere : Int -> Sphere3d Meters BodyCoordinates -> List ( Attributes, Attributes, Attributes ) sphere iterations sphere3d = let position p = Point3d.toMeters (Sphere3d.centerPoint sphere3d) |> Vec3.fromRecord |> Vec3.add p radius = Length.inMeters (Sphere3d.radius sphere3d) in divideSphere iterations radius (octahedron radius) |> List.map (\( p1, p2, p3 ) -> facet (position p1) (position p2) (position p3) 0 ) {-| Recursively divide an octahedron to turn it into a sphere -} divideSphere : Int -> Float -> List ( Vec3, Vec3, Vec3 ) -> List ( Vec3, Vec3, Vec3 ) divideSphere step radius triangles = if step == 0 then triangles else divideSphere (step - 1) radius (List.foldl (divide radius) [] triangles) {-| 1 / \ b /___\ c /\ /\ /__\ /__\ 0 a 2 -} divide : Float -> ( Vec3, Vec3, Vec3 ) -> List ( Vec3, Vec3, Vec3 ) -> List ( Vec3, Vec3, Vec3 ) divide radius ( v0, v1, v2 ) result = let a = Vec3.add v0 v2 |> Vec3.normalize |> Vec3.scale radius b = Vec3.add v0 v1 |> Vec3.normalize |> Vec3.scale radius c = Vec3.add v1 v2 |> Vec3.normalize |> Vec3.scale radius in ( v0, b, a ) :: ( b, v1, c ) :: ( a, b, c ) :: ( a, c, v2 ) :: result {-| Octahedron -} octahedron : Float -> List ( Vec3, Vec3, Vec3 ) octahedron radius = [ ( vec3 radius 0 0, vec3 0 radius 0, vec3 0 0 radius ) , ( vec3 0 radius 0, vec3 -radius 0 0, vec3 0 0 radius ) , ( vec3 -radius 0 0, vec3 0 -radius 0, vec3 0 0 radius ) , ( vec3 0 -radius 0, vec3 radius 0 0, vec3 0 0 radius ) , ( vec3 radius 0 0, vec3 0 0 -radius, vec3 0 radius 0 ) , ( vec3 0 radius 0, vec3 0 0 -radius, vec3 -radius 0 0 ) , ( vec3 -radius 0 0, vec3 0 0 -radius, vec3 0 -radius 0 ) , ( vec3 0 -radius 0, vec3 0 0 -radius, vec3 radius 0 0 ) ] facet : Vec3 -> Vec3 -> Vec3 -> Int -> ( Attributes, Attributes, Attributes ) facet a b c remove = case remove of 1 -> -- b c is removed ( Attributes a (vec3 0 0 1) , Attributes b (vec3 0 1 0) , Attributes c (vec3 1 0 1) ) 2 -> -- only b c is kept ( Attributes a (vec3 1 1 1) , Attributes b (vec3 1 0 0) , Attributes c (vec3 1 0 0) ) _ -> -- all edges are kept ( Attributes a (vec3 0 0 1) , Attributes b (vec3 0 1 0) , Attributes c (vec3 1 0 0) ) ================================================ FILE: sandbox/src/Common/Scene.elm ================================================ module Common.Scene exposing (view) import Common.Camera exposing (Camera) import Common.Math as Math import Common.Meshes as Meshes exposing (Attributes) import Common.Settings exposing (Settings) import Common.Shaders as Shaders import Direction3d exposing (Direction3d) import Frame3d import Geometry.Interop.LinearAlgebra.Direction3d as Direction3d import Geometry.Interop.LinearAlgebra.Frame3d as Frame3d import Geometry.Interop.LinearAlgebra.Point3d as Point3d import Html exposing (Html) import Html.Attributes as Attributes import Internal.Body as InternalBody import Internal.Shape exposing (CenterOfMassCoordinates) import Internal.Transform3d as Transform3d import Length exposing (Meters) import Math.Matrix4 as Mat4 exposing (Mat4) import Math.Vector3 as Vec3 exposing (Vec3, vec3) import Physics exposing (Body, BodyCoordinates, WorldCoordinates) import Physics.Types import Point3d exposing (Point3d) import WebGL exposing (Entity, Mesh) import WebGL.Settings exposing (Setting) import WebGL.Settings.Blend import WebGL.Settings.DepthTest type alias Params = { settings : Settings , bodies : List ( Mesh Attributes, Body ) , contacts : List (Point3d Meters WorldCoordinates) , camera : Camera , floorOffset : { x : Float , y : Float , z : Float } } view : Params -> Html msg view { settings, bodies, contacts, floorOffset, camera } = let lightDirection = Vec3.normalize (Vec3.vec3 -1 -1 -1) sceneParams = { lightDirection = lightDirection , camera = camera , debugWireframes = settings.debugWireframes , debugCenterOfMass = settings.debugCenterOfMass , shadow = Math.makeShadow (Vec3.fromRecord floorOffset) Vec3.k lightDirection } in WebGL.toHtmlWith [ WebGL.depth 1 , WebGL.alpha True , WebGL.antialias , WebGL.clearColor 0.3 0.3 0.3 1 ] [ Attributes.width (round camera.width) , Attributes.height (round camera.height) , Attributes.style "position" "absolute" , Attributes.style "top" "0" , Attributes.style "left" "0" ] ([ ( True , \entities -> List.foldl (addBodyEntities sceneParams) entities bodies ) , ( settings.debugContacts , \entities -> List.foldl (addContactIndicator sceneParams) entities contacts ) ] |> List.filter Tuple.first |> List.map Tuple.second |> List.foldl (<|) [] ) type alias SceneParams = { lightDirection : Vec3 , camera : Camera , debugWireframes : Bool , debugCenterOfMass : Bool , shadow : Mat4 } addBodyEntities : SceneParams -> ( Mesh Attributes, Body ) -> List Entity -> List Entity addBodyEntities ({ lightDirection, shadow, camera, debugWireframes, debugCenterOfMass } as sceneParams) ( mesh, body ) entities = let frame = Physics.frame body transform = Frame3d.toMat4 frame color = Vec3.vec3 0.9 0.9 0.9 addCenterOfMass acc = if debugCenterOfMass then case Physics.centerOfMass body of Just com -> acc |> addContactIndicator sceneParams com |> addEigenvectorAxes sceneParams body Nothing -> acc else acc in entities |> addCenterOfMass |> (if debugWireframes then (::) (WebGL.entityWith defaultSettings Shaders.wireframeVertex Shaders.wireframeFragment mesh { camera = camera.cameraTransform , perspective = camera.perspectiveTransform , color = color , lightDirection = lightDirection , transform = transform } ) else (::) (WebGL.entityWith defaultSettings Shaders.vertex Shaders.fragment mesh { camera = camera.cameraTransform , perspective = camera.perspectiveTransform , color = color , lightDirection = lightDirection , transform = transform } ) ) |> (if debugWireframes then identity else (::) (WebGL.entityWith defaultSettings Shaders.vertex Shaders.shadowFragment mesh { camera = camera.cameraTransform , perspective = camera.perspectiveTransform , color = Vec3.vec3 0.25 0.25 0.25 , lightDirection = lightDirection , transform = Mat4.mul shadow transform } ) ) {-| Render a collision point for the purpose of debugging -} addContactIndicator : SceneParams -> Point3d Meters WorldCoordinates -> List Entity -> List Entity addContactIndicator { lightDirection, camera } point tail = WebGL.entityWith defaultSettings Shaders.vertex Shaders.fragment Meshes.contact { camera = camera.cameraTransform , perspective = camera.perspectiveTransform , color = Vec3.vec3 1 0 0 , lightDirection = lightDirection , transform = Frame3d.toMat4 (Frame3d.atPoint point) } :: tail {-| Render the principal-axes (eigenvector) frame of a body as three colored line segments and the Poinsot inertia ellipsoid as three cross-section loops in the principal planes. Each axis is scaled by 1/sqrt(eigenvalue) (the Poinsot ellipsoid radius along that axis), normalized so the longest principal axis equals the body's bounding sphere radius. -} addEigenvectorAxes : SceneParams -> Body -> List Entity -> List Entity addEigenvectorAxes sceneParams (Physics.Types.Body internalBody) tail = if internalBody.mass > 0 then let invI = internalBody.invInertia maxInvI = max invI.x (max invI.y invI.z) scaleFor v = internalBody.boundingSphereRadius * sqrt (v / maxInvI) transform = Mat4.scale3 (scaleFor invI.x) (scaleFor invI.y) (scaleFor invI.z) (centerOfMassMat4 internalBody) ellipseColor = vec3 1 1 0 in axisEntity sceneParams transform xAxisLine (vec3 1 0 0) :: axisEntity sceneParams transform yAxisLine (vec3 0 1 0) :: axisEntity sceneParams transform zAxisLine (vec3 0 0 1) :: axisEntity sceneParams transform circleXY ellipseColor :: axisEntity sceneParams transform circleXZ ellipseColor :: axisEntity sceneParams transform circleYZ ellipseColor :: tail else tail axisEntity : SceneParams -> Mat4 -> Mesh Attributes -> Vec3 -> Entity axisEntity { lightDirection, camera } transform mesh color = WebGL.entityWith defaultSettings Shaders.vertex Shaders.colorFragment mesh { camera = camera.cameraTransform , perspective = camera.perspectiveTransform , color = color , lightDirection = lightDirection , transform = transform } centerOfMassMat4 : InternalBody.Body -> Mat4 centerOfMassMat4 body = let { m11, m21, m31, m12, m22, m32, m13, m23, m33 } = Transform3d.orientation body.transform3d comFrame3d : Frame3d.Frame3d Meters WorldCoordinates { defines : CenterOfMassCoordinates } comFrame3d = Frame3d.unsafe { originPoint = Point3d.fromMeters (Transform3d.originPoint body.transform3d) , xDirection = Direction3d.unsafe { x = m11, y = m21, z = m31 } , yDirection = Direction3d.unsafe { x = m12, y = m22, z = m32 } , zDirection = Direction3d.unsafe { x = m13, y = m23, z = m33 } } in Frame3d.toMat4 comFrame3d xAxisLine : Mesh Attributes xAxisLine = axisLine 1 0 0 yAxisLine : Mesh Attributes yAxisLine = axisLine 0 1 0 zAxisLine : Mesh Attributes zAxisLine = axisLine 0 0 1 axisLine : Float -> Float -> Float -> Mesh Attributes axisLine x y z = WebGL.lines [ ( { position = vec3 0 0 0, barycentric = vec3 0 0 0 } , { position = vec3 x y z, barycentric = vec3 0 0 0 } ) ] circleXY : Mesh Attributes circleXY = circleLoop (\c s -> vec3 c s 0) circleXZ : Mesh Attributes circleXZ = circleLoop (\c s -> vec3 c 0 s) circleYZ : Mesh Attributes circleYZ = circleLoop (\c s -> vec3 0 c s) circleLoop : (Float -> Float -> Vec3) -> Mesh Attributes circleLoop toPos = let segments = 48 in List.range 0 (segments - 1) |> List.map (\i -> let t = 2 * pi * toFloat i / toFloat segments in { position = toPos (cos t) (sin t), barycentric = vec3 0 0 0 } ) |> WebGL.lineLoop defaultSettings : List Setting defaultSettings = [ WebGL.Settings.Blend.add WebGL.Settings.Blend.one WebGL.Settings.Blend.oneMinusSrcAlpha , WebGL.Settings.DepthTest.default ] ================================================ FILE: sandbox/src/Common/Settings.elm ================================================ module Common.Settings exposing ( Settings , SettingsMsg , settings , update , view ) {-| This module is used to render the settings panel. More controls can be injected with view’s extraContent. -} import Html exposing (Html) import Html.Attributes exposing (checked, style, type_) import Html.Events exposing (onCheck, onClick) type alias Settings = { debugContacts : Bool -- Set to True to see collision points , debugWireframes : Bool -- Set to True to see wireframes , debugCenterOfMass : Bool -- Set to True to see center of mass , showFpsMeter : Bool , showSettings : Bool } type SettingsMsg = ToggleContacts Bool | ToggleWireframes Bool | ToggleFpsMeter Bool | ToggleCenterOfMass Bool | ToggleSettings settings : Settings settings = { debugContacts = False , debugWireframes = False , showSettings = False , showFpsMeter = False , debugCenterOfMass = False } update : SettingsMsg -> Settings -> Settings update msg model = case msg of ToggleSettings -> { model | showSettings = not model.showSettings } ToggleContacts debugContacts -> { model | debugContacts = debugContacts } ToggleWireframes debugWireframes -> { model | debugWireframes = debugWireframes } ToggleFpsMeter showFpsMeter -> { model | showFpsMeter = showFpsMeter } ToggleCenterOfMass debugCenterOfMass -> { model | debugCenterOfMass = debugCenterOfMass } view : (SettingsMsg -> msg) -> Settings -> List (Html msg) -> Html msg view msg { showSettings, debugContacts, debugWireframes, debugCenterOfMass, showFpsMeter } extraContent = Html.div [ style "position" "fixed" , style "right" "6px" , style "top" "0" , style "font-family" "monospace" , style "color" "white" ] (if showSettings then [ button (msg ToggleSettings) "Hide Settings" , Html.div [ style "padding" "6px" , style "min-width" "24ch" , style "background" "rgb(50, 50, 50)" , style "border-radius" "0 0 4px 4px" ] ([ checkbox (ToggleContacts >> msg) debugContacts "collision points" , checkbox (ToggleCenterOfMass >> msg) debugCenterOfMass "center of mass" , checkbox (ToggleWireframes >> msg) debugWireframes "wireframes" , checkbox (ToggleFpsMeter >> msg) showFpsMeter "fps meter" ] ++ List.map wrapWithMargin extraContent ) ] else [ button (msg ToggleSettings) "Show Settings" ] ) wrapWithMargin : Html msg -> Html msg wrapWithMargin el = Html.div [ style "margin" "10px 0 5px" ] [ el ] button : msg -> String -> Html msg button msg text = Html.button [ style "padding" "6px" , style "box-sizing" "content-box" , style "min-width" "24ch" , style "color" "inherit" , style "border" "none" , style "font" "inherit" , style "text-align" "center" , style "margin" "0" , style "display" "block" , style "background" "rgb(61, 61, 61)" , onClick msg ] [ Html.text text ] checkbox : (Bool -> msg) -> Bool -> String -> Html msg checkbox msg value label = Html.label [ style "display" "block", style "padding" "5px 0" ] [ Html.input [ onCheck msg , checked value , type_ "checkbox" , style "margin-right" "10px" ] [] , Html.text label ] ================================================ FILE: sandbox/src/Common/Shaders.elm ================================================ module Common.Shaders exposing ( Uniforms , colorFragment , fragment , shadowFragment , vertex , wireframeFragment , wireframeVertex ) {-| This file contains shaders that are used in examples. Shaders support simple flat lighting. -} import Common.Meshes exposing (Attributes) import Math.Matrix4 exposing (Mat4) import Math.Vector3 exposing (Vec3) import WebGL exposing (Shader) type alias Uniforms = { camera : Mat4 , perspective : Mat4 , transform : Mat4 , color : Vec3 , lightDirection : Vec3 } vertex : Shader Attributes Uniforms { vposition : Vec3 } vertex = [glsl| attribute vec3 position; attribute vec3 barycentric; uniform mat4 camera; uniform mat4 perspective; uniform mat4 transform; varying vec3 vposition; void main () { vec4 worldPosition = transform * vec4(position, 1.0); vposition = worldPosition.xyz; gl_Position = perspective * camera * worldPosition; } |] fragment : Shader {} Uniforms { vposition : Vec3 } fragment = [glsl| precision mediump float; uniform vec3 color; uniform vec3 lightDirection; varying vec3 vposition; void main () { float ambientLight = 0.4; float directionalLight = 0.6; vec3 normal = -normalize(cross(dFdx(vposition), dFdy(vposition))); float directional = max(dot(normal, lightDirection), 0.0); float vlighting = ambientLight + directional * directionalLight; gl_FragColor = vec4(vlighting * color, 1.0); } |] wireframeVertex : Shader Attributes Uniforms { vbarycentric : Vec3 } wireframeVertex = [glsl| attribute vec3 position; attribute vec3 barycentric; uniform mat4 camera; uniform mat4 perspective; uniform mat4 transform; varying vec3 vbarycentric; void main () { vec4 worldPosition = transform * vec4(position, 1.0); vbarycentric = barycentric; gl_Position = perspective * camera * worldPosition; } |] wireframeFragment : Shader {} Uniforms { vbarycentric : Vec3 } wireframeFragment = [glsl| precision mediump float; uniform vec3 color; varying vec3 vbarycentric; void main () { float width = 0.5; vec3 d = fwidth(vbarycentric); vec3 step = smoothstep(d * (width - 0.5), d * (width + 0.5), vbarycentric); float alpha = 1.0 - min(min(step.x, step.y), step.z); if (alpha < 0.01) { discard; } gl_FragColor = vec4(color * alpha, alpha); } |] shadowFragment : Shader {} Uniforms { vposition : Vec3 } shadowFragment = [glsl| precision mediump float; uniform vec3 color; varying vec3 vposition; void main () { gl_FragColor = vec4(color, 1); } |] colorFragment : Shader {} Uniforms { vposition : Vec3 } colorFragment = [glsl| precision mediump float; uniform vec3 color; varying vec3 vposition; void main () { gl_FragColor = vec4(color, 1.0); } |] ================================================ FILE: sandbox/src/CompoundVsLock.elm ================================================ module CompoundVsLock exposing (main) {-| This demo shows two possible ways to create complex objects. One way is through a compound body out of multiple shapes. The second way is by using the lock constraint. -} import Block3d exposing (Block3d) import Browser import Browser.Dom as Dom import Browser.Events as Events import Common.Camera as Camera exposing (Camera) import Common.Fps as Fps import Common.Meshes as Meshes exposing (Attributes) import Common.Scene as Scene import Common.Settings as Settings exposing (Settings, SettingsMsg, settings) import Dict exposing (Dict) import Frame3d import Html exposing (Html) import Html.Events exposing (onClick) import Length exposing (Meters) import Mass import Physics exposing (Body, BodyCoordinates, WorldCoordinates, onEarth) import Physics.Constraint as Constraint exposing (Constraint) import Physics.Material as Material import Physics.Shape as Shape import Physics.Types exposing (Contacts(..)) import Plane3d import Point3d exposing (Point3d) import Task import WebGL exposing (Mesh) type alias Model = { bodies : List ( String, Body ) , meshes : Dict String (Mesh Attributes) , contacts : Physics.Contacts String , fps : List Float , settings : Settings , camera : Camera } type Msg = ForSettings SettingsMsg | Tick Float | Resize Float Float | Restart main : Program () Model Msg main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view } init : () -> ( Model, Cmd Msg ) init _ = ( { bodies = initialBodies , meshes = initialMeshes , contacts = Physics.emptyContacts , fps = [] , settings = { settings | showSettings = True } , camera = Camera.camera { from = { x = 0, y = 30, z = 20 } , to = { x = 0, y = 0, z = 0 } } } , Task.perform (\{ viewport } -> Resize viewport.width viewport.height) Dom.getViewport ) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ForSettings settingsMsg -> ( { model | settings = Settings.update settingsMsg model.settings } , Cmd.none ) Tick dt -> let bodyDict = Dict.fromList model.bodies constrain id1 = case id1 of "first" -> Just (\id2 -> case id2 of "second" -> case ( Dict.get "first" bodyDict, Dict.get "second" bodyDict ) of ( Just b1, Just b2 ) -> [ lockTwoBodies b1 b2 ] _ -> [] _ -> [] ) "second" -> Just (\id2 -> case id2 of "third" -> case ( Dict.get "second" bodyDict, Dict.get "third" bodyDict ) of ( Just b1, Just b2 ) -> [ lockTwoBodies b1 b2 ] _ -> [] _ -> [] ) _ -> Nothing ( newBodies, newContacts ) = Physics.simulate { onEarth | constrain = constrain , collide = \one two -> not (List.member one [ "first", "second", "third" ] && List.member two [ "first", "second", "third" ] ) , contacts = model.contacts } model.bodies in ( { model | fps = Fps.update dt model.fps , bodies = newBodies , contacts = newContacts } , Cmd.none ) Resize width height -> ( { model | camera = Camera.resize width height model.camera } , Cmd.none ) Restart -> ( { model | bodies = initialBodies, contacts = Physics.emptyContacts }, Cmd.none ) subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Events.onResize (\w h -> Resize (toFloat w) (toFloat h)) , Events.onAnimationFrameDelta Tick ] view : Model -> Html Msg view { settings, fps, bodies, contacts, meshes, camera } = Html.div [] [ Scene.view { settings = settings , bodies = List.filterMap (\( id, body ) -> Maybe.map (\mesh -> ( mesh, body )) (Dict.get id meshes) ) bodies , contacts = List.concatMap (\( _, _, c ) -> c) (Physics.contactPoints (\_ _ -> True) contacts) , camera = camera , floorOffset = floorOffset } , Settings.view ForSettings settings [ Html.button [ onClick Restart ] [ Html.text "Restart the demo" ] ] , if settings.showFpsMeter then let (Contacts c) = contacts in Fps.view fps (List.length bodies) c.iterations else Html.text "" ] lockTwoBodies : Body -> Body -> Constraint lockTwoBodies b1 b2 = let center1 = Physics.originPoint b1 center2 = Physics.originPoint b2 middle = Point3d.midpoint center1 center2 frame1 = Frame3d.atPoint (Point3d.relativeTo (Physics.frame b1) middle) frame2 = Frame3d.atPoint (Point3d.relativeTo (Physics.frame b2) middle) in Constraint.lock frame1 frame2 {-| Shift the floor a little bit down -} floorOffset : { x : Float, y : Float, z : Float } floorOffset = { x = 0, y = 0, z = -1 } block3d : Block3d Meters BodyCoordinates block3d = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 1 , Length.meters 1 , Length.meters 1 ) pos1 : Point3d Meters BodyCoordinates pos1 = Point3d.meters -0.5 0 -0.5 pos2 : Point3d Meters BodyCoordinates pos2 = Point3d.meters -0.5 0 0.5 pos3 : Point3d Meters BodyCoordinates pos3 = Point3d.meters 0.5 0 0.5 initialBodies : List ( String, Body ) initialBodies = let lockedPosition = Frame3d.atPoint (Point3d.meters -2 0 5) compoundPosition = Point3d.meters 2 0 5 floorBody = Physics.plane Plane3d.xy Material.wood |> Physics.moveTo (Point3d.fromMeters floorOffset) blocks = List.map (\center -> Block3d.placeIn (Frame3d.atPoint center) block3d ) [ pos1, pos2, pos3 ] compoundBody = Physics.dynamic (List.map (\b -> ( Shape.block b, Material.wood )) blocks) |> Physics.scaleMassTo (Mass.kilograms 5) |> Physics.moveTo compoundPosition boxBody = Physics.block block3d Material.wood |> Physics.scaleMassTo (Mass.kilograms 5) in [ ( "floor", floorBody ) , ( "compound", compoundBody ) , ( "first", boxBody |> Physics.moveTo (Point3d.placeIn lockedPosition pos1) ) , ( "second", boxBody |> Physics.moveTo (Point3d.placeIn lockedPosition pos2) ) , ( "third", boxBody |> Physics.moveTo (Point3d.placeIn lockedPosition pos3) ) ] initialMeshes : Dict String (Mesh Attributes) initialMeshes = let blocks = List.map (\center -> Block3d.placeIn (Frame3d.atPoint center) block3d ) [ pos1, pos2, pos3 ] boxMesh = Meshes.fromTriangles (Meshes.block block3d) in Dict.fromList [ ( "floor", Meshes.fromTriangles [] ) , ( "compound", Meshes.fromTriangles (List.concatMap Meshes.block blocks) ) , ( "first", boxMesh ) , ( "second", boxMesh ) , ( "third", boxMesh ) ] ================================================ FILE: sandbox/src/Dominoes.elm ================================================ module Dominoes exposing (main) {-| This demo is used to show custom materials. Without the slippy material, dominoes would not slide along each other. Try to make the floor slippy too! -} import Angle import Array exposing (Array) import Axis3d exposing (Axis3d) import Block3d import Browser import Browser.Dom as Dom import Browser.Events as Events import Common.Camera as Camera exposing (Camera) import Common.Fps as Fps import Common.Meshes as Meshes exposing (Attributes) import Common.Scene as Scene import Common.Settings as Settings exposing (Settings, SettingsMsg, settings) import Density import Frame3d import Html exposing (Html) import Html.Events exposing (onClick) import Length exposing (Meters) import Mass import Physics exposing (Body, BodyCoordinates, WorldCoordinates, onEarth) import Physics.Material as Material exposing (Material) import Physics.Types exposing (Contacts(..)) import Plane3d import Point3d exposing (Point3d) import Task import WebGL exposing (Mesh) type alias Model = { bodies : List ( Int, Body ) , meshes : Array (Mesh Attributes) , contacts : Physics.Contacts Int , fps : List Float , settings : Settings , camera : Camera } type Msg = ForSettings SettingsMsg | Tick Float | Resize Float Float | Restart main : Program () Model Msg main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view } init : () -> ( Model, Cmd Msg ) init _ = ( { bodies = initialBodies , meshes = initialMeshes , contacts = Physics.emptyContacts , fps = [] , settings = settings , camera = Camera.camera { from = { x = 0, y = 30, z = 20 } , to = { x = 0, y = 0, z = 0 } } } , Task.perform (\{ viewport } -> Resize viewport.width viewport.height) Dom.getViewport ) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ForSettings settingsMsg -> ( { model | settings = Settings.update settingsMsg model.settings } , Cmd.none ) Tick dt -> let ( newBodies, newContacts ) = Physics.simulate { onEarth | contacts = model.contacts } model.bodies in ( { model | fps = Fps.update dt model.fps , bodies = newBodies , contacts = newContacts } , Cmd.none ) Resize width height -> ( { model | camera = Camera.resize width height model.camera } , Cmd.none ) Restart -> ( { model | bodies = initialBodies, meshes = initialMeshes, contacts = Physics.emptyContacts }, Cmd.none ) subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Events.onResize (\w h -> Resize (toFloat w) (toFloat h)) , Events.onAnimationFrameDelta Tick ] view : Model -> Html Msg view { settings, fps, bodies, contacts, meshes, camera } = Html.div [] [ Scene.view { settings = settings , bodies = List.filterMap (\( id, body ) -> Maybe.map (\mesh -> ( mesh, body )) (Array.get id meshes)) bodies , contacts = List.concatMap (\( _, _, c ) -> c) (Physics.contactPoints (\_ _ -> True) contacts) , camera = camera , floorOffset = floorOffset } , Settings.view ForSettings settings [ Html.button [ onClick Restart ] [ Html.text "Restart the demo" ] ] , if settings.showFpsMeter then let (Contacts c) = contacts in Fps.view fps (List.length bodies) c.iterations else Html.text "" ] {-| Shift the floor a little bit down -} floorOffset : { x : Float, y : Float, z : Float } floorOffset = { x = 0, y = 0, z = -1 } slippy = Material.dense { density = Density.kilogramsPerCubicMeter 600, bounciness = 0, friction = 0.001 } dominoBlock3d : Block3d.Block3d Length.Meters BodyCoordinates dominoBlock3d = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 0.1 , Length.meters 1 , Length.meters 2 ) initialBodies : List ( Int, Body ) initialBodies = let floorBody = Physics.plane Plane3d.xy (Material.surface { friction = 0.3, bounciness = 0 }) |> Physics.moveTo (Point3d.fromMeters floorOffset) dominoBody = Physics.block dominoBlock3d slippy |> Physics.scaleMassTo (Mass.kilograms 5) -- id=0 floor, id=1 tilted first domino, ids 2..12 regular dominos tiltedDomino = dominoBody |> Physics.rotateAround Axis3d.y (Angle.radians (pi / 8)) |> Physics.rotateAround Axis3d.z (Angle.radians (pi / 4)) |> Physics.moveTo (Point3d.meters -5.5 -5.5 0) regularDominos = List.indexedMap (\idx i -> ( idx + 2 , dominoBody |> Physics.rotateAround Axis3d.z (Angle.radians (pi / 4)) |> Physics.moveTo (Point3d.meters (toFloat (5 - i)) (toFloat (5 - i)) 0) ) ) (List.range 0 10) in ( 0, floorBody ) :: ( 1, tiltedDomino ) :: regularDominos initialMeshes : Array (Mesh Attributes) initialMeshes = let floorMesh = Meshes.fromTriangles [] dominoMesh = Meshes.fromTriangles (Meshes.block dominoBlock3d) -- 13 bodies total: floor + 1 tilted + 11 regular dominoCount = 12 in Array.fromList (floorMesh :: List.repeat dominoCount dominoMesh) ================================================ FILE: sandbox/src/Kinematic.elm ================================================ module Kinematic exposing (main) {-| This demo shows a kinematic platform sliding back and forth between two endpoints. A stack of two boxes sits on top and rides along thanks to friction with the moving surface. The platform is kinematic: it has infinite mass like a static body, but the engine integrates its position from the velocity the user sets each frame. Here the velocity follows a cosine wave so the platform decelerates smoothly toward each endpoint and accelerates back — a constant velocity flipped instantaneously at the endpoints would jolt the boxes. -} import Array exposing (Array) import Block3d exposing (Block3d) import Browser import Browser.Dom as Dom import Browser.Events as Events import Common.Camera as Camera exposing (Camera) import Common.Fps as Fps import Common.Meshes as Meshes exposing (Attributes) import Common.Scene as Scene import Common.Settings as Settings exposing (Settings, SettingsMsg, settings) import Frame3d import Html exposing (Html) import Html.Events exposing (onClick) import Length exposing (Meters) import Physics exposing (Body, BodyCoordinates, onEarth) import Physics.Material as Material import Physics.Shape as Shape import Physics.Types exposing (Contacts(..)) import Plane3d import Point3d import Task import Vector3d import WebGL exposing (Mesh) platformId : Int platformId = 1 platformAmplitude : Float platformAmplitude = 3 platformOmega : Float platformOmega = 0.6 peakSpeed : Float peakSpeed = platformAmplitude * platformOmega simDt : Float simDt = 1 / 60 type alias Model = { bodies : List ( Int, Body ) , meshes : Array (Mesh Attributes) , contacts : Physics.Contacts Int , fps : List Float , settings : Settings , camera : Camera , phase : Float } type Msg = ForSettings SettingsMsg | Tick Float | Resize Float Float | Restart main : Program () Model Msg main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view } init : () -> ( Model, Cmd Msg ) init _ = ( { bodies = initialBodies , meshes = initialMeshes , contacts = Physics.emptyContacts , fps = [] , settings = { settings | showFpsMeter = True } , camera = Camera.camera { from = { x = 0, y = 18, z = 10 } , to = { x = 0, y = 0, z = 1 } } , phase = -pi / 2 } , Task.perform (\{ viewport } -> Resize viewport.width viewport.height) Dom.getViewport ) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ForSettings settingsMsg -> ( { model | settings = Settings.update settingsMsg model.settings } , Cmd.none ) Tick dt -> let newPhase = model.phase + simDt * platformOmega platformVx = peakSpeed * cos newPhase bodiesPreSim = model.bodies |> List.map (\( id, body ) -> if id == platformId then ( id , body |> Physics.setVelocityTo (Vector3d.metersPerSecond platformVx 0 0) ) else ( id, body ) ) ( simulated, newContacts ) = Physics.simulate { onEarth | contacts = model.contacts } bodiesPreSim in ( { model | fps = Fps.update dt model.fps , bodies = simulated , contacts = newContacts , phase = newPhase } , Cmd.none ) Resize width height -> ( { model | camera = Camera.resize width height model.camera } , Cmd.none ) Restart -> ( { model | bodies = initialBodies , contacts = Physics.emptyContacts , phase = -pi / 2 } , Cmd.none ) subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Events.onResize (\w h -> Resize (toFloat w) (toFloat h)) , Events.onAnimationFrameDelta Tick ] view : Model -> Html Msg view { settings, fps, bodies, meshes, contacts, camera } = Html.div [] [ Scene.view { settings = settings , bodies = List.filterMap (\( id, body ) -> Maybe.map (\mesh -> ( mesh, body )) (Array.get id meshes)) bodies , contacts = List.concatMap (\( _, _, c ) -> c) (Physics.contactPoints (\_ _ -> True) contacts) , camera = camera , floorOffset = floorOffset } , Settings.view ForSettings settings [ Html.button [ onClick Restart ] [ Html.text "Restart the demo" ] ] , if settings.showFpsMeter then let (Contacts c) = contacts in Fps.view fps (List.length bodies) c.iterations else Html.text "" ] floorOffset : { x : Float, y : Float, z : Float } floorOffset = { x = 0, y = 0, z = -1 } platformBlock : Block3d Meters BodyCoordinates platformBlock = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 4, Length.meters 2, Length.meters 0.4 ) riderBlock : Block3d Meters BodyCoordinates riderBlock = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 0.8, Length.meters 0.8, Length.meters 0.8 ) initialBodies : List ( Int, Body ) initialBodies = let floorBody = Physics.plane Plane3d.xy Material.wood |> Physics.moveTo (Point3d.fromMeters floorOffset) platform = Physics.kinematic [ ( Shape.block platformBlock, Material.steel ) ] |> Physics.moveTo (Point3d.meters -platformAmplitude 0 0.2) rider = Physics.block riderBlock Material.wood bottomBox = rider |> Physics.moveTo (Point3d.meters -platformAmplitude 0 0.8) topBox = rider |> Physics.moveTo (Point3d.meters -platformAmplitude 0 1.6) in [ ( 0, floorBody ) , ( platformId, platform ) , ( 2, bottomBox ) , ( 3, topBox ) ] initialMeshes : Array (Mesh Attributes) initialMeshes = let emptyMesh = Meshes.fromTriangles [] platformMesh = Meshes.fromTriangles (Meshes.block platformBlock) riderMesh = Meshes.fromTriangles (Meshes.block riderBlock) in Array.fromList [ emptyMesh -- 0: floor , platformMesh -- 1: platform , riderMesh -- 2: bottom box , riderMesh -- 3: top box ] ================================================ FILE: sandbox/src/Randomize.elm ================================================ module Randomize exposing (main) {-| This demo drops random bodies. It also shows how to make a compound body out of multiple shapes. -} import Angle import Array exposing (Array) import Axis3d import Block3d import Browser import Browser.Dom as Dom import Browser.Events as Events import Common.Camera as Camera exposing (Camera) import Common.Fps as Fps import Common.Meshes as Meshes exposing (Attributes) import Common.Scene as Scene import Common.Settings as Settings exposing (Settings, SettingsMsg, settings) import Cylinder3d import Direction3d import Frame3d import Html exposing (Html) import Html.Events exposing (onClick) import Length exposing (Meters) import Mass import Physics exposing (Body, BodyCoordinates, WorldCoordinates, onEarth) import Physics.Material as Material import Physics.Shape as Shape import Physics.Types exposing (Contacts(..)) import Plane3d import Point3d exposing (Point3d) import Random import Sphere3d import Task import Vector3d import WebGL exposing (Mesh) type BodyShape = BoxShape | SphereShape | CylinderShape | CompoundShape type alias Model = { bodies : List ( Int, Body ) , meshes : Array (Mesh Attributes) , contacts : Physics.Contacts Int , nextId : Int , fps : List Float , settings : Settings , camera : Camera } type Msg = ForSettings SettingsMsg | Tick Float | Resize Float Float | Restart | Random | AddRandom ( Body, Mesh Attributes ) main : Program () Model Msg main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view } init : () -> ( Model, Cmd Msg ) init _ = let ( bodies, meshes, nextId ) = initialBodiesAndMeshes in ( { bodies = bodies , contacts = Physics.emptyContacts , meshes = meshes , nextId = nextId , fps = [] , settings = { settings | showSettings = True } , camera = Camera.camera { from = { x = 0, y = 30, z = 20 } , to = { x = 0, y = 0, z = 0 } } } , Task.perform (\{ viewport } -> Resize viewport.width viewport.height) Dom.getViewport ) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ForSettings settingsMsg -> ( { model | settings = Settings.update settingsMsg model.settings } , Cmd.none ) Tick dt -> let ( newBodies, newContacts ) = Physics.simulate { onEarth | contacts = model.contacts } model.bodies in ( { model | fps = Fps.update dt model.fps , bodies = newBodies , contacts = newContacts } , Cmd.none ) Resize width height -> ( { model | camera = Camera.resize width height model.camera } , Cmd.none ) Restart -> let ( bodies, meshes, nextId ) = initialBodiesAndMeshes in ( { model | bodies = bodies, meshes = meshes, nextId = nextId, contacts = Physics.emptyContacts }, Cmd.none ) Random -> ( model, Random.generate AddRandom randomBody ) AddRandom ( body, mesh ) -> ( { model | bodies = ( model.nextId, body ) :: model.bodies , meshes = Array.push mesh model.meshes , nextId = model.nextId + 1 } , Cmd.none ) subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Events.onResize (\w h -> Resize (toFloat w) (toFloat h)) , Events.onAnimationFrameDelta Tick ] view : Model -> Html Msg view { settings, fps, bodies, contacts, meshes, camera } = Html.div [] [ Scene.view { settings = settings , bodies = List.filterMap (\( id, body ) -> Maybe.map (\mesh -> ( mesh, body )) (Array.get id meshes)) bodies , contacts = List.concatMap (\( _, _, c ) -> c) (Physics.contactPoints (\_ _ -> True) contacts) , camera = camera , floorOffset = floorOffset } , Settings.view ForSettings settings [ Html.button [ onClick Random ] [ Html.text "Drop a random body" ] , Html.button [ onClick Restart ] [ Html.text "Restart the demo" ] ] , if settings.showFpsMeter then let (Contacts c) = contacts in Fps.view fps (List.length bodies) c.iterations else Html.text "" ] {-| Shift the floor a little bit down -} floorOffset : { x : Float, y : Float, z : Float } floorOffset = { x = 0, y = 0, z = -1 } boxBlock3d : Block3d.Block3d Length.Meters BodyCoordinates boxBlock3d = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 2 , Length.meters 2 , Length.meters 2 ) sphere3d : Sphere3d.Sphere3d Length.Meters BodyCoordinates sphere3d = Sphere3d.atOrigin (Length.meters 1.2) cylinder3d : Cylinder3d.Cylinder3d Length.Meters BodyCoordinates cylinder3d = Cylinder3d.centeredOn Point3d.origin Direction3d.x { radius = Length.meters 0.5, length = Length.meters 2 } compoundBlocks : List (Block3d.Block3d Length.Meters BodyCoordinates) compoundBlocks = List.map (\center -> Block3d.centeredOn (Frame3d.atPoint center) ( Length.meters 1 , Length.meters 1 , Length.meters 1 ) ) [ Point3d.meters -0.5 0 -0.5 , Point3d.meters -0.5 0 0.5 , Point3d.meters 0.5 0 0.5 ] makeBox : Body makeBox = Physics.block boxBlock3d Material.wood |> Physics.scaleMassTo (Mass.kilograms 5) makeSphere : Body makeSphere = Physics.sphere sphere3d Material.wood |> Physics.scaleMassTo (Mass.kilograms 5) makeCylinder : Body makeCylinder = Physics.cylinder cylinder3d Material.wood |> Physics.scaleMassTo (Mass.kilograms 5) makeCompound : Body makeCompound = Physics.dynamic (List.map (\b -> ( Shape.block b, Material.wood )) compoundBlocks) |> Physics.scaleMassTo (Mass.kilograms 5) initialBodiesAndMeshes : ( List ( Int, Body ), Array (Mesh Attributes), Int ) initialBodiesAndMeshes = let -- id=0 floor, 1 box, 2 sphere, 3 cylinder, 4 compound floorBody = Physics.plane Plane3d.xy Material.wood |> Physics.moveTo (Point3d.fromMeters floorOffset) boxBody = makeBox |> Physics.rotateAround Axis3d.y (Angle.radians (-pi / 5)) |> Physics.moveTo (Point3d.meters 0 0 2) sphereBody = makeSphere |> Physics.moveTo (Point3d.meters 0.5 0 8) cylinderBody = makeCylinder |> Physics.rotateAround (Axis3d.through Point3d.origin (Direction3d.unsafe { x = 0.7071, y = 0.7071, z = 0 })) (Angle.radians (pi / 2)) |> Physics.moveTo (Point3d.meters 0.5 0 11) compoundBody = makeCompound |> Physics.rotateAround (Axis3d.through Point3d.origin (Direction3d.unsafe { x = 0.7071, y = 0.7071, z = 0 })) (Angle.radians (pi / 5)) |> Physics.moveTo (Point3d.meters -1.2 0 5) bodies = [ ( 0, floorBody ) , ( 1, boxBody ) , ( 2, sphereBody ) , ( 3, cylinderBody ) , ( 4, compoundBody ) ] meshes = Array.fromList [ Meshes.fromTriangles [] , Meshes.fromTriangles (Meshes.block boxBlock3d) , Meshes.fromTriangles (Meshes.sphere 2 sphere3d) , Meshes.fromTriangles (Meshes.cylinder 12 cylinder3d) , Meshes.fromTriangles (List.concatMap Meshes.block compoundBlocks) ] in ( bodies, meshes, 5 ) {-| A random body raised above the plane, shifted or rotated to a random 3d angle -} randomBody : Random.Generator ( Body, Mesh Attributes ) randomBody = Random.map5 (\angle x y z bodyKind -> let ( body, mesh ) = case bodyKind of 0 -> ( makeBox, Meshes.fromTriangles (Meshes.block boxBlock3d) ) 1 -> ( makeSphere, Meshes.fromTriangles (Meshes.sphere 2 sphere3d) ) 2 -> ( makeCylinder, Meshes.fromTriangles (Meshes.cylinder 12 cylinder3d) ) _ -> ( makeCompound, Meshes.fromTriangles (List.concatMap Meshes.block compoundBlocks) ) in ( body |> Physics.rotateAround (Axis3d.through Point3d.origin (Maybe.withDefault Direction3d.x (Vector3d.direction (Vector3d.from Point3d.origin (Point3d.meters x y z))))) (Angle.radians angle) |> Physics.moveTo (Point3d.meters 0 0 10) , mesh ) ) (Random.float (-pi / 2) (pi / 2)) (Random.float -1 1) (Random.float -1 1) (Random.float -1 1) (Random.int 0 3) ================================================ FILE: sandbox/src/Stability/Metrics.elm ================================================ module Stability.Metrics exposing (Metrics, compute) {-| Stability metrics computed from a list of bodies after N simulation frames. Only dynamic bodies (those with non-zero mass) are included. Static bodies like ground planes are excluded. -} import Physics import Speed import Vector3d type alias Metrics = { maxSpeed : Float , avgSpeed : Float , dynamicBodyCount : Int } {-| Compute stability metrics from the current body state. -} compute : List ( id, Physics.Body ) -> Metrics compute bodies = let dynamicBodies = List.filterMap (\( _, body ) -> case Physics.mass body of Nothing -> Nothing Just _ -> Just body ) bodies speeds = List.map (\body -> Speed.inMetersPerSecond (Vector3d.length (Physics.velocity body)) ) dynamicBodies count = List.length dynamicBodies in { maxSpeed = Maybe.withDefault 0 (List.maximum speeds) , avgSpeed = if count == 0 then 0 else List.sum speeds / toFloat count , dynamicBodyCount = count } ================================================ FILE: sandbox/src/Stability/Scenarios.elm ================================================ module Stability.Scenarios exposing ( Scenario , stackOf5 , unitBlock ) {-| Repeatable, deterministic test scenarios for stability benchmarking. Each scenario is a named initial body configuration. Body IDs follow the sandbox convention: 0 = ground/floor, 1..n = dynamic bodies (array index into meshes). The exposed shape constant (`unitBlock`) lets browser scenes build a corresponding mesh. Ground plane: z = 0, normal pointing +z. Boxes: 1 m × 1 m × 1 m wood, centered at origin in body coordinates. -} import Block3d exposing (Block3d) import Frame3d import Length exposing (Meters) import Physics exposing (BodyCoordinates) import Physics.Material as Material import Plane3d import Point3d type alias Scenario = { name : String , bodies : List ( Int, Physics.Body ) } unitBlock : Block3d Meters BodyCoordinates unitBlock = Block3d.centeredOn Frame3d.atOrigin ( Length.meters 1, Length.meters 1, Length.meters 1 ) ground : ( Int, Physics.Body ) ground = ( 0, Physics.plane Plane3d.xy Material.wood ) {-| Five boxes placed at their exact resting positions, already touching, no drop. Use with `consecutiveStableFrames` — the score starts near zero and rises as solver drift accumulates, so the frame count until maxSpeed ≥ 0.05 m/s is a clean single-number stability metric. -} stackOf5 : Scenario stackOf5 = { name = "stack of 5 boxes" , bodies = let n = 5 in List.indexedMap (\i _ -> ( n - i , Physics.block unitBlock Material.wood |> Physics.moveTo (Point3d.meters 0 0 (toFloat (n - i) - 0.5)) ) ) (List.repeat n ()) ++ [ ground ] } ================================================ FILE: sandbox/src/StabilityScenes.elm ================================================ module StabilityScenes exposing (main) {-| Interactive browser view for the stack-of-5 stability benchmark. The current maxSpeed is shown in the top-left corner — watch it stay near zero until the stack collapses around frame 443. -} import Array exposing (Array) import Browser import Browser.Dom as Dom import Browser.Events as Events import Common.Camera as Camera exposing (Camera) import Common.Fps as Fps import Common.Meshes as Meshes exposing (Attributes) import Common.Scene as Scene import Common.Settings as Settings exposing (Settings, SettingsMsg, settings) import Html exposing (Html) import Html.Attributes exposing (style) import Length exposing (Meters) import Physics exposing (Body, WorldCoordinates, onEarth) import Physics.Types exposing (Contacts(..)) import Point3d exposing (Point3d) import Stability.Metrics as Metrics import Stability.Scenarios as Scenarios import Task import WebGL exposing (Mesh) type alias Model = { bodies : List ( Int, Body ) , meshes : Array (Mesh Attributes) , contacts : Physics.Contacts Int , fps : List Float , settings : Settings , camera : Camera , frame : Int , score : Float } type Msg = ForSettings SettingsMsg | Tick Float | Resize Float Float main : Program () Model Msg main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view } init : () -> ( Model, Cmd Msg ) init _ = ( initialModel , Task.perform (\{ viewport } -> Resize viewport.width viewport.height) Dom.getViewport ) initialModel : Model initialModel = { bodies = Scenarios.stackOf5.bodies , meshes = meshes , contacts = Physics.emptyContacts , fps = [] , settings = settings , camera = Camera.camera { from = { x = 0, y = 30, z = 20 } , to = { x = 0, y = 0, z = 5 } } , frame = 0 , score = 0 } meshes : Array (Mesh Attributes) meshes = let empty = Meshes.fromTriangles [] blockMesh = Meshes.fromTriangles (Meshes.block Scenarios.unitBlock) in Array.fromList (empty :: List.repeat 10 blockMesh) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ForSettings settingsMsg -> ( { model | settings = Settings.update settingsMsg model.settings } , Cmd.none ) Tick dt -> let ( newBodies, newContacts ) = Physics.simulate { onEarth | contacts = model.contacts } model.bodies newScore = (Metrics.compute newBodies).maxSpeed in ( { model | fps = Fps.update dt model.fps , bodies = newBodies , contacts = newContacts , frame = model.frame + 1 , score = newScore } , Cmd.none ) Resize width height -> ( { model | camera = Camera.resize width height model.camera } , Cmd.none ) subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Events.onResize (\w h -> Resize (toFloat w) (toFloat h)) , Events.onAnimationFrameDelta Tick ] view : Model -> Html Msg view { settings, fps, bodies, contacts, camera, score, frame } = Html.div [] [ Scene.view { settings = settings , bodies = List.filterMap (\( id, body ) -> Maybe.map (\mesh -> ( mesh, body )) (Array.get id meshes)) bodies , contacts = List.concatMap (\( _, _, c ) -> c) (Physics.contactPoints (\_ _ -> True) contacts) , camera = camera , floorOffset = { x = 0, y = 0, z = 0 } } , scoreOverlay score frame , Settings.view ForSettings settings [] , if settings.showFpsMeter then let (Contacts c) = contacts in Fps.view fps (List.length bodies) c.iterations else Html.text "" ] scoreOverlay : Float -> Int -> Html msg scoreOverlay s frameNum = Html.div [ style "position" "fixed" , style "left" "6px" , style "top" "0" , style "font-family" "monospace" , style "color" "white" , style "padding" "6px" , style "background" "rgba(0,0,0,0.4)" , style "border-radius" "0 0 4px 0" , style "pointer-events" "none" ] [ Html.div [] [ Html.text ("maxSpeed " ++ formatFloat s ++ " m/s") ] , Html.div [] [ Html.text ("frame " ++ String.fromInt frameNum) ] ] formatFloat : Float -> String formatFloat f = let s = String.fromFloat (toFloat (round (f * 1000)) / 1000) in if String.contains "." s then s else s ++ ".0" ================================================ FILE: sandbox/src/UnsafeConvex.elm ================================================ module UnsafeConvex exposing (main) {-| This is used to demonstrate loading `unsafeConvex` shape from the OBJ file! -} import Array exposing (Array) import Browser import Browser.Dom as Dom import Browser.Events as Events import Common.Camera as Camera exposing (Camera) import Common.Fps as Fps import Common.Meshes as Meshes exposing (Attributes) import Common.Scene as Scene import Common.Settings as Settings exposing (Settings, SettingsMsg, settings) import Frame3d import Html exposing (Html) import Html.Events exposing (onClick) import Length exposing (Meters) import Mass import Obj.Decode import Physics exposing (Body, WorldCoordinates, onEarth) import Physics.Material as Material import Physics.Shape as Shape import Physics.Types exposing (Contacts(..)) import Plane3d import Point3d exposing (Point3d) import Task import WebGL exposing (Mesh) type alias Model = { bodies : List ( Int, Body ) , contacts : Physics.Contacts Int , meshes : Array (Mesh Attributes) , fps : List Float , settings : Settings , camera : Camera } type Msg = ForSettings SettingsMsg | Tick Float | Resize Float Float | Restart main : Program () Model Msg main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view } init : () -> ( Model, Cmd Msg ) init _ = ( { bodies = initialBodies , contacts = Physics.emptyContacts , meshes = initialMeshes , fps = [] , settings = { settings | showFpsMeter = True } , camera = Camera.camera { from = { x = 0, y = 30, z = 20 } , to = { x = 0, y = 0, z = 0 } } } , Task.perform (\{ viewport } -> Resize viewport.width viewport.height) Dom.getViewport ) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ForSettings settingsMsg -> ( { model | settings = Settings.update settingsMsg model.settings } , Cmd.none ) Tick dt -> let ( newBodies, newContacts ) = Physics.simulate { onEarth | contacts = model.contacts } model.bodies in ( { model | fps = Fps.update dt model.fps , bodies = newBodies , contacts = newContacts } , Cmd.none ) Resize width height -> ( { model | camera = Camera.resize width height model.camera } , Cmd.none ) Restart -> ( { model | bodies = initialBodies, contacts = Physics.emptyContacts }, Cmd.none ) subscriptions : Model -> Sub Msg subscriptions _ = Sub.batch [ Events.onResize (\w h -> Resize (toFloat w) (toFloat h)) , Events.onAnimationFrameDelta Tick ] view : Model -> Html Msg view { settings, fps, bodies, contacts, meshes, camera } = Html.div [] [ Scene.view { settings = settings , bodies = List.filterMap (\( id, body ) -> Maybe.map (\mesh -> ( mesh, body )) (Array.get id meshes)) bodies , contacts = List.concatMap (\( _, _, c ) -> c) (Physics.contactPoints (\_ _ -> True) contacts) , camera = camera , floorOffset = floorOffset } , Settings.view ForSettings settings [ Html.button [ onClick Restart ] [ Html.text "Restart the demo" ] ] , if settings.showFpsMeter then let (Contacts c) = contacts in Fps.view fps (List.length bodies) c.iterations else Html.text "" ] {-| Shift the floor a little bit down -} floorOffset : { x : Float, y : Float, z : Float } floorOffset = { x = 0, y = 0, z = -1 } icoSphereBody : Body icoSphereBody = case Obj.Decode.decodeString Length.meters (Obj.Decode.trianglesIn Frame3d.atOrigin) icoSphereObj of Ok triangularMesh -> Physics.dynamic [ ( Shape.unsafeConvex triangularMesh, Material.wood ) ] |> Physics.scaleMassTo (Mass.kilograms 5) Err _ -> Physics.dynamic [] cubeBody : Body cubeBody = case Obj.Decode.decodeString Length.meters (Obj.Decode.trianglesIn Frame3d.atOrigin) cubeObj of Ok triangularMesh -> Physics.dynamic [ ( Shape.unsafeConvex triangularMesh, Material.wood ) ] |> Physics.scaleMassTo (Mass.kilograms 5) Err _ -> Physics.dynamic [] wedgeBody : Body wedgeBody = case Obj.Decode.decodeString Length.meters (Obj.Decode.trianglesIn Frame3d.atOrigin) wedgeObj of Ok triangularMesh -> Physics.dynamic [ ( Shape.unsafeConvex triangularMesh, Material.wood ) ] |> Physics.scaleMassTo (Mass.kilograms 5) Err _ -> Physics.dynamic [] tetraBody : Body tetraBody = case Obj.Decode.decodeString Length.meters (Obj.Decode.trianglesIn Frame3d.atOrigin) tetraObj of Ok triangularMesh -> Physics.dynamic [ ( Shape.unsafeConvex triangularMesh, Material.wood ) ] |> Physics.scaleMassTo (Mass.kilograms 5) Err _ -> Physics.dynamic [] parallelepipedBody : Body parallelepipedBody = case Obj.Decode.decodeString Length.meters (Obj.Decode.trianglesIn Frame3d.atOrigin) parallelepipedObj of Ok triangularMesh -> Physics.dynamic [ ( Shape.unsafeConvex triangularMesh, Material.wood ) ] |> Physics.scaleMassTo (Mass.kilograms 5) Err _ -> Physics.dynamic [] icoSphereMesh : Mesh Attributes icoSphereMesh = case Obj.Decode.decodeString Length.meters (Obj.Decode.trianglesIn Frame3d.atOrigin) icoSphereObj of Ok triangularMesh -> Meshes.fromTriangles (Meshes.triangularMesh triangularMesh) Err _ -> Meshes.fromTriangles [] cubeMesh : Mesh Attributes cubeMesh = case Obj.Decode.decodeString Length.meters (Obj.Decode.trianglesIn Frame3d.atOrigin) cubeObj of Ok triangularMesh -> Meshes.fromTriangles (Meshes.triangularMesh triangularMesh) Err _ -> Meshes.fromTriangles [] wedgeMesh : Mesh Attributes wedgeMesh = case Obj.Decode.decodeString Length.meters (Obj.Decode.trianglesIn Frame3d.atOrigin) wedgeObj of Ok triangularMesh -> Meshes.fromTriangles (Meshes.triangularMesh triangularMesh) Err _ -> Meshes.fromTriangles [] tetraMesh : Mesh Attributes tetraMesh = case Obj.Decode.decodeString Length.meters (Obj.Decode.trianglesIn Frame3d.atOrigin) tetraObj of Ok triangularMesh -> Meshes.fromTriangles (Meshes.triangularMesh triangularMesh) Err _ -> Meshes.fromTriangles [] parallelepipedMesh : Mesh Attributes parallelepipedMesh = case Obj.Decode.decodeString Length.meters (Obj.Decode.trianglesIn Frame3d.atOrigin) parallelepipedObj of Ok triangularMesh -> Meshes.fromTriangles (Meshes.triangularMesh triangularMesh) Err _ -> Meshes.fromTriangles [] initialBodies : List ( Int, Body ) initialBodies = [ ( 0, Physics.plane Plane3d.xy Material.wood |> Physics.moveTo (Point3d.fromMeters floorOffset) ) , ( 1, cubeBody |> Physics.moveTo (Point3d.meters 0 0 8) ) , ( 2, icoSphereBody |> Physics.moveTo (Point3d.meters 0.3 0 5) ) , ( 3, wedgeBody |> Physics.moveTo (Point3d.meters 0 -3 12) ) , ( 4, tetraBody |> Physics.moveTo (Point3d.meters -3 3 15) ) , ( 5, parallelepipedBody |> Physics.moveTo (Point3d.meters 4 0 18) ) ] initialMeshes : Array (Mesh Attributes) initialMeshes = Array.fromList [ Meshes.fromTriangles [] , cubeMesh , icoSphereMesh , wedgeMesh , tetraMesh , parallelepipedMesh ] cubeObj : String cubeObj = """v 1.000000 1.000000 -1.000000 v 1.000000 -1.000000 -1.000000 v 1.000000 1.000000 1.000000 v 1.000000 -1.000000 1.000000 v -1.000000 1.000000 -1.000000 v -1.000000 -1.000000 -1.000000 v -1.000000 1.000000 1.000000 v -1.000000 -1.000000 1.000000 f 1 5 7 3 f 4 3 7 8 f 8 7 5 6 f 6 2 4 8 f 2 1 3 4 f 6 5 1 2 """ {-| Right triangular prism (doorstop wedge): 3 long in x, 2 in y, 1.5 tall in z. The asymmetric triangular cross-section in the xz plane gives non-axis-aligned principal axes — two eigenvectors tilt within xz, one points along y. -} wedgeObj : String wedgeObj = """v 0 -1 0 v 3 -1 0 v 3 -1 1.5 v 0 1 0 v 3 1 0 v 3 1 1.5 f 1 2 3 f 4 6 5 f 1 4 5 2 f 2 5 6 3 f 1 3 6 4 """ {-| Irregular tetrahedron with no symmetry plane — all three eigenvectors end up tilted relative to body xyz. -} tetraObj : String tetraObj = """v -1 -1 -1 v 3 -1 -1 v -1 2 -1 v -1 -1 2.5 f 1 3 2 f 1 2 4 f 1 4 3 f 2 3 4 """ {-| Sheared parallelepiped — a box whose top is shifted +2 in x relative to its bottom. Symmetric in y, so one eigenvector stays along body-y; the other two tilt within the xz plane along the parallelepiped's diagonal directions. -} parallelepipedObj : String parallelepipedObj = """v -1 -1 -1 v 1 -1 -1 v 1 1 -1 v -1 1 -1 v 1 -1 1 v 3 -1 1 v 3 1 1 v 1 1 1 f 1 4 3 2 f 5 6 7 8 f 1 2 6 5 f 4 8 7 3 f 1 5 8 4 f 2 3 7 6 """ icoSphereObj : String icoSphereObj = """v 0.000000 0.000000 -1.000000 v 0.723607 -0.525725 -0.447220 v -0.276388 -0.850649 -0.447220 v -0.894426 0.000000 -0.447216 v -0.276388 0.850649 -0.447220 v 0.723607 0.525725 -0.447220 v 0.276388 -0.850649 0.447220 v -0.723607 -0.525725 0.447220 v -0.723607 0.525725 0.447220 v 0.276388 0.850649 0.447220 v 0.894426 0.000000 0.447216 v 0.000000 0.000000 1.000000 v -0.162456 -0.499995 -0.850654 v 0.425323 -0.309011 -0.850654 v 0.262869 -0.809012 -0.525738 v 0.850648 0.000000 -0.525736 v 0.425323 0.309011 -0.850654 v -0.525730 0.000000 -0.850652 v -0.688189 -0.499997 -0.525736 v -0.162456 0.499995 -0.850654 v -0.688189 0.499997 -0.525736 v 0.262869 0.809012 -0.525738 v 0.951058 -0.309013 0.000000 v 0.951058 0.309013 0.000000 v 0.000000 -1.000000 0.000000 v 0.587786 -0.809017 0.000000 v -0.951058 -0.309013 0.000000 v -0.587786 -0.809017 0.000000 v -0.587786 0.809017 0.000000 v -0.951058 0.309013 0.000000 v 0.587786 0.809017 0.000000 v 0.000000 1.000000 0.000000 v 0.688189 -0.499997 0.525736 v -0.262869 -0.809012 0.525738 v -0.850648 0.000000 0.525736 v -0.262869 0.809012 0.525738 v 0.688189 0.499997 0.525736 v 0.162456 -0.499995 0.850654 v 0.525730 0.000000 0.850652 v -0.425323 -0.309011 0.850654 v -0.425323 0.309011 0.850654 v 0.162456 0.499995 0.850654 s off f 1 14 13 f 2 14 16 f 1 13 18 f 1 18 20 f 1 20 17 f 2 16 23 f 3 15 25 f 4 19 27 f 5 21 29 f 6 22 31 f 2 23 26 f 3 25 28 f 4 27 30 f 5 29 32 f 6 31 24 f 7 33 38 f 8 34 40 f 9 35 41 f 10 36 42 f 11 37 39 f 39 42 12 f 39 37 42 f 37 10 42 f 42 41 12 f 42 36 41 f 36 9 41 f 41 40 12 f 41 35 40 f 35 8 40 f 40 38 12 f 40 34 38 f 34 7 38 f 38 39 12 f 38 33 39 f 33 11 39 f 24 37 11 f 24 31 37 f 31 10 37 f 32 36 10 f 32 29 36 f 29 9 36 f 30 35 9 f 30 27 35 f 27 8 35 f 28 34 8 f 28 25 34 f 25 7 34 f 26 33 7 f 26 23 33 f 23 11 33 f 31 32 10 f 31 22 32 f 22 5 32 f 29 30 9 f 29 21 30 f 21 4 30 f 27 28 8 f 27 19 28 f 19 3 28 f 25 26 7 f 25 15 26 f 15 2 26 f 23 24 11 f 23 16 24 f 16 6 24 f 17 22 6 f 17 20 22 f 20 5 22 f 20 21 5 f 20 18 21 f 18 4 21 f 18 19 4 f 18 13 19 f 13 3 19 f 16 17 6 f 16 14 17 f 14 1 17 f 13 15 3 f 13 14 15 f 14 2 15 """ ================================================ FILE: sandbox/tests/StabilityTest.elm ================================================ module StabilityTest exposing (stability) {-| Regression test for physics solver stability. stackOf5: 5 boxes at exact resting positions, no initial velocity. Simulation must stay stable (maxSpeed < 0.05) for 100000 frames. -} import Expect import Physics exposing (onEarth) import Stability.Metrics as Metrics import Stability.Scenarios as Scenarios import Test exposing (Test, describe, test) stableFrames : Float -> Int -> Physics.Config id -> List ( id, Physics.Body ) -> Int stableFrames threshold remaining config bodies = stableFramesHelp threshold remaining config bodies 0 stableFramesHelp : Float -> Int -> Physics.Config id -> List ( id, Physics.Body ) -> Int -> Int stableFramesHelp threshold remaining config bodies count = if remaining <= 0 then count else let ( next, newContacts ) = Physics.simulate config bodies in if (Metrics.compute next).maxSpeed >= threshold then count else stableFramesHelp threshold (remaining - 1) { config | contacts = newContacts } next (count + 1) coldStableFrames : Float -> Int -> Physics.Config id -> List ( id, Physics.Body ) -> Int coldStableFrames threshold remaining config bodies = coldStableFramesHelp threshold remaining config bodies 0 coldStableFramesHelp : Float -> Int -> Physics.Config id -> List ( id, Physics.Body ) -> Int -> Int coldStableFramesHelp threshold remaining config bodies count = if remaining <= 0 then count else let ( next, _ ) = Physics.simulate config bodies in if (Metrics.compute next).maxSpeed >= threshold then count else coldStableFramesHelp threshold (remaining - 1) config next (count + 1) stability : Test stability = describe "Stability benchmarks" [ test "stack of 5 boxes with contacts at 13 iterations: 100000 frames" <| \_ -> let config = { onEarth | solverIterations = 13 } ( bodies, newContacts ) = Physics.simulate config Scenarios.stackOf5.bodies in stableFrames 0.05 100000 { config | contacts = newContacts } bodies |> Expect.equal 100000 , test "stack of 5 boxes without contacts at 25 iterations: 100000 frames" <| \_ -> let config = { onEarth | solverIterations = 25 } ( bodies, _ ) = Physics.simulate config Scenarios.stackOf5.bodies in coldStableFrames 0.05 100000 config bodies |> Expect.equal 100000 ] ================================================ FILE: scripts/elm-publish.sh ================================================ #!/bin/bash set -euxo pipefail version=${1:-} if [ -z "$version" ]; then echo "Please set the desired version" exit 1 fi if [[ -n $(git status --porcelain) ]]; then echo "Please commit all your changes" exit 1 fi echo "Y" | elm bump elm_version=$(grep -m1 version elm.json | awk -F: '{ print $2 }' | sed 's/[", ]//g') if [ $version != $elm_version ]; then echo "Versions $elm_version and $version do not match!" exit 1 fi git add elm.json git commit -m "Bump to $version" git push last_commit=$(git rev-parse HEAD) git rm -rf --ignore-unmatch .github benchmarks examples scripts tests elm-physics.gif flake.nix flake.lock docs.json sed -i.bak "s+https://unsoundscapes.com/elm-physics/+https://unsoundscapes.com/elm-physics/$version/+g" README.md sed -i.bak "s+https://github.com/w0rm/elm-physics/tree/main/+https://github.com/w0rm/elm-physics/tree/$last_commit/+g" README.md rm README.md.bak git add README.md git commit -m "Release $version" git tag -a $version -m "Release $version" git push origin $version elm publish # restore the main branch git checkout $last_commit ================================================ FILE: scripts/gh-pages.sh ================================================ #!/bin/bash set -euxo pipefail version=${1:-} if [ -z "$version" ]; then version_path="" else version_path="/$version" fi cd gh-pages if [ "$(git rev-parse --abbrev-ref HEAD)" != "gh-pages" ]; then echo "Please checkout gh-pages branch" exit 1; fi if [[ -n $(git status --porcelain) ]]; then echo "Please commit all your changes" exit 1 fi cd ../examples/src for example in *.elm; do # rename CamelCase to snake-case lower=$( echo "${example%.*}" \ | sed 's/\(.\)\([A-Z]\)/\1-\2/g' \ | tr '[:upper:]' '[:lower:]' \ ) mkdir -p ../../gh-pages$version_path/examples/$lower elm make $example --optimize --output ../../gh-pages$version_path/examples/$lower/index.html done cp Duckling.obj.txt Duckling.png ../../gh-pages$version_path/examples/duckling cp Jeep.obj.txt Jeep.png ../../gh-pages$version_path/examples/raycast-car cp ../../elm-physics.gif ../../gh-pages$version_path/examples cd ../../gh-pages git add . git diff --cached --quiet && echo "No changes to deploy" && exit 0 git commit -m "Deploying $version_path to GH Pages" git push ================================================ FILE: src/Collision/ConvexConvex.elm ================================================ module Collision.ConvexConvex exposing ( addContacts , findSeparatingAxis , project , testSeparatingAxis ) import Internal.Const as Const import Internal.Contact exposing (Contact) import Internal.Vector3 as Vec3 exposing (Vec3) import Shapes.Convex as Convex exposing (Convex, Face) addContacts : String -> Convex -> Convex -> List Contact -> List Contact addContacts idPrefix convex1 convex2 contacts = case findSeparatingAxis convex1 convex2 of Just separatingAxis -> let reversedSeparatingAxis = Vec3.negate separatingAxis in case bestFace convex1.faces separatingAxis of Just ( id1, face1 ) -> case bestFace convex2.faces reversedSeparatingAxis of Just ( id2, face2 ) -> clipTwoFaces (idPrefix ++ "-" ++ String.fromInt id1 ++ "-" ++ String.fromInt id2) face1 face2 reversedSeparatingAxis contacts Nothing -> contacts Nothing -> contacts Nothing -> contacts clipTwoFaces : String -> Face -> Face -> Vec3 -> List Contact -> List Contact clipTwoFaces idPrefix face { vertices } separatingAxis contacts = let point = case face.vertices of first :: _ -> first [] -> Vec3.zero facePlaneConstant = -(Vec3.dot face.normal point) in clipTwoFacesHelp idPrefix separatingAxis face facePlaneConstant 0 (clipAgainstAdjacentFaces face vertices) contacts clipTwoFacesHelp : String -> Vec3 -> Face -> Float -> Int -> List Vec3 -> List Contact -> List Contact clipTwoFacesHelp idPrefix separatingAxis face facePlaneConstant n vertices result = case vertices of vertex :: remainingVertices -> let -- used to be (max minDist depth), where minDist = -100 depth = Vec3.dot face.normal vertex + facePlaneConstant in if depth <= 0 then clipTwoFacesHelp idPrefix separatingAxis face facePlaneConstant (n + 1) remainingVertices ({ id = idPrefix ++ String.fromInt (n + 1) , ni = separatingAxis , pi = { x = vertex.x - depth * face.normal.x , y = vertex.y - depth * face.normal.y , z = vertex.z - depth * face.normal.z } , pj = vertex } :: result ) else clipTwoFacesHelp idPrefix separatingAxis face facePlaneConstant (n + 1) remainingVertices result [] -> result bestFace : List Face -> Vec3 -> Maybe ( Int, Face ) bestFace faces separatingAxis = case faces of face :: restFaces -> Just (bestFaceHelp separatingAxis 1 restFaces 1 face (Vec3.dot face.normal separatingAxis) ) [] -> Nothing bestFaceHelp : Vec3 -> Int -> List Face -> Int -> Face -> Float -> ( Int, Face ) bestFaceHelp separatingAxis faceId faces currentBestFaceId currentBestFace currentBestDistance = case faces of face :: remainingFaces -> let faceDistance = Vec3.dot face.normal separatingAxis in if currentBestDistance - faceDistance > 0 then bestFaceHelp separatingAxis (faceId + 1) remainingFaces faceId face faceDistance else bestFaceHelp separatingAxis (faceId + 1) remainingFaces currentBestFaceId currentBestFace currentBestDistance [] -> ( currentBestFaceId, currentBestFace ) clipAgainstAdjacentFaces : Face -> List Vec3 -> List Vec3 clipAgainstAdjacentFaces { vertices, normal } faceVertices = Convex.foldFaceEdges (\v1 v2 -> let edge = Vec3.normalize (Vec3.sub v1 v2) planeNormal = Vec3.cross normal edge planeConstant = -(Vec3.dot v1 planeNormal) in Convex.foldFaceEdges (clipFaceAgainstPlaneAdd planeNormal planeConstant) [] ) faceVertices vertices clipFaceAgainstPlaneAdd : Vec3 -> Float -> Vec3 -> Vec3 -> List Vec3 -> List Vec3 clipFaceAgainstPlaneAdd planeNormal planeConstant prev next result = let nDotPrev = Vec3.dot planeNormal prev + planeConstant nDotNext = Vec3.dot planeNormal next + planeConstant in if nDotPrev < 0 then if nDotNext < 0 then next :: result else Vec3.lerp (nDotPrev / (nDotPrev - nDotNext)) prev next :: result else if nDotNext < 0 then next :: Vec3.lerp (nDotPrev / (nDotPrev - nDotNext)) prev next :: result else result findSeparatingAxis : Convex -> Convex -> Maybe Vec3 findSeparatingAxis convex1 convex2 = testUniqueNormals convex1 convex2 (convex1.uniqueNormals ++ convex2.uniqueNormals) Vec3.zero Const.maxNumber testUniqueNormals : Convex -> Convex -> List Vec3 -> Vec3 -> Float -> Maybe Vec3 testUniqueNormals convex1 convex2 normals target dmin = case normals of [] -> testUniqueEdges convex1 convex2 convex2.uniqueEdges convex1.uniqueEdges convex2.uniqueEdges target dmin normal :: restNormals -> case testSeparatingAxis convex1 convex2 normal of Nothing -> Nothing Just dist -> if dist - dmin < 0 then testUniqueNormals convex1 convex2 restNormals normal dist else testUniqueNormals convex1 convex2 restNormals target dmin testUniqueEdges : Convex -> Convex -> List Vec3 -> List Vec3 -> List Vec3 -> Vec3 -> Float -> Maybe Vec3 testUniqueEdges convex1 convex2 initEdges2 edges1 edges2 target dmin = case edges1 of [] -> if Vec3.dot (Vec3.sub convex2.position convex1.position) target > 0 then Just (Vec3.negate target) else Just target edge1 :: remainingEdges1 -> case edges2 of [] -> -- requeue edges2 testUniqueEdges convex1 convex2 initEdges2 remainingEdges1 initEdges2 target dmin edge2 :: remainingEdges2 -> let cross = Vec3.cross edge1 edge2 in if Vec3.almostZero cross then -- continue because edges are parallel testUniqueEdges convex1 convex2 initEdges2 edges1 remainingEdges2 target dmin else let normalizedCross = Vec3.normalize cross in case testSeparatingAxis convex1 convex2 normalizedCross of Nothing -> -- exit because hulls don't collide Nothing Just dist -> if dist - dmin < 0 then -- update target and dmin testUniqueEdges convex1 convex2 initEdges2 edges1 remainingEdges2 normalizedCross dist else -- continue testUniqueEdges convex1 convex2 initEdges2 edges1 remainingEdges2 target dmin {-| If projections of two convexes don’t overlap, then they don’t collide. -} testSeparatingAxis : Convex -> Convex -> Vec3 -> Maybe Float testSeparatingAxis convex1 convex2 separatingAxis = let p1 = project separatingAxis Const.maxNumber -Const.maxNumber convex1.vertices p2 = project separatingAxis Const.maxNumber -Const.maxNumber convex2.vertices d1 = p1.max - p2.min d2 = p2.max - p1.min in if d1 < 0 || d2 < 0 then Nothing else if d1 - d2 > 0 then Just d2 else Just d1 {-| Get max and min dot product of a convex hull at ShapeWorldTransform3d projected onto an axis. -} project : Vec3 -> Float -> Float -> List Vec3 -> { min : Float, max : Float } project localAxis minVal maxVal currentVertices = case currentVertices of [] -> { min = minVal, max = maxVal } vec :: remainingVertices -> let val = vec.x * localAxis.x + vec.y * localAxis.y + vec.z * localAxis.z in project localAxis (if minVal - val > 0 then val else minVal ) (if maxVal - val > 0 then maxVal else val ) remainingVertices ================================================ FILE: src/Collision/ParticleConvex.elm ================================================ module Collision.ParticleConvex exposing (addContacts) import Internal.Const as Const import Internal.Contact exposing (Contact) import Internal.Vector3 as Vec3 exposing (Vec3) import Shapes.Convex exposing (Convex, Face) addContacts : String -> (Contact -> Contact) -> Vec3 -> Convex -> List Contact -> List Contact addContacts idPrefix orderContact particlePosition { faces } contacts = case convexContact idPrefix particlePosition faces Const.maxNumber Nothing of Just contact -> orderContact contact :: contacts Nothing -> contacts convexContact : String -> Vec3 -> List Face -> Float -> Maybe Contact -> Maybe Contact convexContact idPrefix particlePosition faces bestDepth bestContact = case faces of [] -> bestContact { vertices, normal } :: remainingFaces -> let point = case vertices of first :: _ -> first [] -> Vec3.zero dot = Vec3.dot normal (Vec3.sub point particlePosition) in if dot >= 0 then if dot - bestDepth < 0 then convexContact idPrefix particlePosition remainingFaces dot (Just { id = idPrefix , ni = Vec3.negate normal , pi = particlePosition , pj = Vec3.add particlePosition (Vec3.scale dot normal) } ) else convexContact idPrefix particlePosition remainingFaces bestDepth bestContact else Nothing ================================================ FILE: src/Collision/PlaneConvex.elm ================================================ module Collision.PlaneConvex exposing (addContacts) import Internal.Contact exposing (Contact) import Internal.Vector3 exposing (Vec3) import Shapes.Convex exposing (Convex) import Shapes.Plane exposing (Plane) addContacts : String -> (Contact -> Contact) -> Plane -> Convex -> List Contact -> List Contact addContacts idPrefix orderContact plane { vertices } contacts = addContactsHelp idPrefix orderContact plane.position plane.normal 0 vertices contacts addContactsHelp : String -> (Contact -> Contact) -> Vec3 -> Vec3 -> Int -> List Vec3 -> List Contact -> List Contact addContactsHelp idPrefix orderContact planePosition planeNormal vertexId vertices contacts = case vertices of vertex :: remainingVertices -> let dot = ((vertex.x - planePosition.x) * planeNormal.x) + ((vertex.y - planePosition.y) * planeNormal.y) + ((vertex.z - planePosition.z) * planeNormal.z) in if dot <= 0 then addContactsHelp idPrefix orderContact planePosition planeNormal (vertexId + 1) remainingVertices (orderContact { id = idPrefix ++ "-" ++ String.fromInt (vertexId + 1) , ni = planeNormal , pi = { x = vertex.x - dot * planeNormal.x , y = vertex.y - dot * planeNormal.y , z = vertex.z - dot * planeNormal.z } , pj = vertex } :: contacts ) else addContactsHelp idPrefix orderContact planePosition planeNormal (vertexId + 1) remainingVertices contacts [] -> contacts ================================================ FILE: src/Collision/PlaneParticle.elm ================================================ module Collision.PlaneParticle exposing (addContacts) import Internal.Contact exposing (Contact) import Internal.Vector3 exposing (Vec3) import Shapes.Plane exposing (Plane) addContacts : String -> (Contact -> Contact) -> Plane -> Vec3 -> List Contact -> List Contact addContacts idPrefix orderContact { position, normal } particlePosition contacts = let dot = ((particlePosition.x - position.x) * normal.x) + ((particlePosition.y - position.y) * normal.y) + ((particlePosition.z - position.z) * normal.z) in if dot <= 0 then orderContact { id = idPrefix , ni = normal , pi = { x = particlePosition.x - dot * normal.x , y = particlePosition.y - dot * normal.y , z = particlePosition.z - dot * normal.z } , pj = particlePosition } :: contacts else contacts ================================================ FILE: src/Collision/PlaneSphere.elm ================================================ module Collision.PlaneSphere exposing (addContacts) import Internal.Contact exposing (Contact) import Shapes.Plane exposing (Plane) import Shapes.Sphere exposing (Sphere) addContacts : String -> (Contact -> Contact) -> Plane -> Sphere -> List Contact -> List Contact addContacts idPrefix orderContact { normal, position } sphere contacts = let { x, y, z } = sphere.position vertex = { x = x - sphere.radius * normal.x , y = y - sphere.radius * normal.y , z = z - sphere.radius * normal.z } dot = ((vertex.x - position.x) * normal.x) + ((vertex.y - position.y) * normal.y) + ((vertex.z - position.z) * normal.z) in if dot <= 0 then orderContact { id = idPrefix , ni = normal , pi = { x = vertex.x - dot * normal.x , y = vertex.y - dot * normal.y , z = vertex.z - dot * normal.z } , pj = vertex } :: contacts else contacts ================================================ FILE: src/Collision/SphereConvex.elm ================================================ module Collision.SphereConvex exposing (addContacts) import Internal.Contact exposing (Contact) import Internal.Vector3 as Vec3 exposing (Vec3) import Shapes.Convex as Convex exposing (Convex) import Shapes.Sphere exposing (Sphere) addContacts : String -> (Contact -> Contact) -> Sphere -> Convex -> List Contact -> List Contact addContacts idPrefix orderContact { radius, position } hull2 contacts = let ( maybeContact, penetration ) = sphereContact position radius hull2 in case maybeContact of Just contact2 -> let normal = Vec3.direction contact2 position in orderContact { id = idPrefix , ni = normal , pi = { x = contact2.x + penetration * normal.x , y = contact2.y + penetration * normal.y , z = contact2.z + penetration * normal.z } , pj = contact2 } :: contacts Nothing -> contacts {-| Encapsulated result of sphereTestFace -} type TestFaceResult = QualifiedEdges (List (List ( Vec3, Vec3 ))) | FaceContact Vec3 Float isAFaceContact : TestFaceResult -> Bool isAFaceContact testFaceResult = case testFaceResult of FaceContact _ _ -> True _ -> False type TestBoundaryResult = PossibleVertexContact ( Maybe Vec3, Float ) | EdgeContact ( Vec3, Float ) isAnEdgeContact : TestBoundaryResult -> Bool isAnEdgeContact testEdgeResult = case testEdgeResult of EdgeContact _ -> True _ -> False {-| The contact point, if any, of a Convex with a sphere, and the sphere's penetration into the Convex beyond that contact. -} sphereContact : Vec3 -> Float -> Convex -> ( Maybe Vec3, Float ) sphereContact center radius { faces } = let sphereFaceContact : Vec3 -> Float -> ( Maybe Vec3, Float ) sphereFaceContact normal distance = -- The contact is located distance away from -- the sphere center in the OPPOSITE direction of -- the normal. ( Just (Vec3.sub center (Vec3.scale distance normal)) , radius - distance ) sphereBoundaryContact : Vec3 -> Float -> ( Maybe Vec3, Float ) sphereBoundaryContact localContact distanceSq = ( Just (Vec3.add localContact center) , radius - sqrt distanceSq ) spherePossibleBoundaryContact : List (List ( Vec3, Vec3 )) -> ( Maybe Vec3, Float ) spherePossibleBoundaryContact faceEdgeList = case sphereTestBoundaries radius faceEdgeList of PossibleVertexContact ( Just localContact, distanceSq ) -> sphereBoundaryContact localContact distanceSq PossibleVertexContact noContact -> noContact EdgeContact ( localContact, distanceSq ) -> sphereBoundaryContact localContact distanceSq reframedVertices faceVertices = List.foldl (\vertex acc -> Vec3.sub vertex center :: acc ) [] faceVertices -- Find the details of the closest faces. testFaceResult = listRecurseUntil isAFaceContact (\face statusQuo -> case statusQuo of QualifiedEdges acc -> sphereTestFace radius face.normal (reframedVertices face.vertices) acc FaceContact _ _ -> -- Since a FaceContact short circuits the -- recursion, this case is not expected. statusQuo ) (QualifiedEdges []) faces in case testFaceResult of QualifiedEdges faceEdgeList -> -- Check the candidate faces' edges and vertices. spherePossibleBoundaryContact faceEdgeList FaceContact faceNormal faceDistance -> sphereFaceContact faceNormal faceDistance {-| The contact point and distance, if any, of a Convex's face with a sphere, or otherwise a list of the face's edges that may contain an edge or vertex contact. -} sphereTestFace : Float -> Vec3 -> List Vec3 -> List (List ( Vec3, Vec3 )) -> TestFaceResult sphereTestFace radius normal vertices acc = let -- Use an arbitrary vertex from the face to measure the distance to -- the origin (sphere center) along the face normal. faceDistance = case vertices of point :: _ -> -(Vec3.dot normal point) [] -> -- a negative value prevents a face or edge contact match -1 in if faceDistance - radius < 0 && faceDistance > 0.0 then -- Sphere intersects the face plane. -- Assume 3 or more valid vertices to proceed -- Check if the sphere center projects onto the face plane INSIDE the face polygon. case originProjection vertices normal of [] -> -- The projection falls within all the face's edges. FaceContact normal faceDistance separatingEdges -> -- These origin-excluding edges are candidates for -- having an edge or vertex contact. QualifiedEdges (separatingEdges :: acc) else QualifiedEdges acc {-| The edge or vertex contact point and its distance (squared), if any, of a Convex's edges with a sphere, limited to a pre-qualified list of edges per face. -} sphereTestBoundaries : Float -> List (List ( Vec3, Vec3 )) -> TestBoundaryResult sphereTestBoundaries radius faceEdgeList = List.foldl sphereTestBoundary (PossibleVertexContact ( Nothing, radius * radius )) faceEdgeList {-| The edge or possible vertex contact point and its distance (squared), if any, of a Convex face's pre-qualified edges with a sphere. -} sphereTestBoundary : List ( Vec3, Vec3 ) -> TestBoundaryResult -> TestBoundaryResult sphereTestBoundary faceEdges statusQuo = listRecurseUntil isAnEdgeContact (\( prevVertex, vertex ) statusQuo1 -> case statusQuo1 of PossibleVertexContact soFar -> sphereTestEdge prevVertex vertex soFar EdgeContact _ -> -- Since an EdgeContact stops the recursion, -- this case is not expected. statusQuo1 ) statusQuo faceEdges {-| The edge or possible vertex contact point and its distance (squared), if any, of a Convex face's pre-qualified edge with a sphere. -} sphereTestEdge : Vec3 -> Vec3 -> ( Maybe Vec3, Float ) -> TestBoundaryResult sphereTestEdge prevVertex vertex (( _, minDistanceSq ) as statusQuo) = let betterVertexContact : Vec3 -> ( Maybe Vec3, Float ) betterVertexContact candidate = let -- Note: the vector length of a sphere-framed vertex -- is its distance from the sphere center vertexLengthSq = Vec3.lengthSquared candidate in if vertexLengthSq - minDistanceSq < 0 then ( Just candidate, vertexLengthSq ) else statusQuo edge = Vec3.sub vertex prevVertex edgeUnit = Vec3.normalize edge -- The potential contact is where the sphere center -- projects onto the edge. -- offset is the directed distance between the edge's -- starting vertex and that projection. If it is not -- between 0 and the edge's length, there is no edge contact. -- Yet there may be a contact with whichever vertex is closest -- to the projection. offset = -(Vec3.dot prevVertex edgeUnit) in if offset < 0 then -- prevVertex is closest in this edge, -- but there may be a closer edge or -- no contact. PossibleVertexContact (betterVertexContact prevVertex) else if offset * offset - Vec3.lengthSquared edge > 0 then -- vertex is closest in this edge, -- but there may be a closer edge or -- no contact. PossibleVertexContact (betterVertexContact vertex) else let edgeContact = Vec3.add prevVertex (Vec3.scale offset edgeUnit) edgeDistanceSq = Vec3.lengthSquared edgeContact in if edgeDistanceSq - minDistanceSq < 0 then EdgeContact ( edgeContact, edgeDistanceSq ) else PossibleVertexContact statusQuo {-| A 2D point-in-polygon check for the projection of the origin (e.g. the center of a sphere within its own frame of reference) within a polygon (e.g. a Convex face). To simplify post-processing, return a relatively short but complete list of qualified edges (adjacent vertex pairs) whose lines separate the projection from the polygon. If the list is empty, the projection is within the polygon. -} originProjection : List Vec3 -> Vec3 -> List ( Vec3, Vec3 ) originProjection vertices normal = Convex.foldFaceEdges (\prevVertex vertex acc -> let edge_x_normal = Vec3.cross normal (Vec3.sub vertex prevVertex) in -- The sign of this dot product determines on which -- side of the directed edge the projected point lies, -- left or right, within the face plane. -- For the projection to be within the face, the sign -- must always be non-negative when circling from vertex -- to vertex in the listed (counter-clockwise) direction. -- Retain any edge that tests negative as a candidate -- for an edge or vertex contact. if Vec3.dot edge_x_normal prevVertex < 0 then ( prevVertex, vertex ) :: acc else acc ) [] vertices {-| Recursively "foldl" the function over the elements of the list, until the result passes a test. Using recursion in the place of a true fold allows a short-circuit return as soon as the test passes. Note: If the short-circuit condition is unlikely, especially towards the beginning of the list, it MAY be more efficient to use foldl, integrating the short-circuit test into the folding function as an up-front pass-through condition. -} listRecurseUntil : (b -> Bool) -> (a -> b -> b) -> b -> List a -> b listRecurseUntil test fn resultSoFar list = if test resultSoFar then resultSoFar else case list of head :: tail -> let acc = fn head resultSoFar in listRecurseUntil test fn acc tail _ -> resultSoFar ================================================ FILE: src/Collision/SphereParticle.elm ================================================ module Collision.SphereParticle exposing (addContacts) import Internal.Contact exposing (Contact) import Internal.Vector3 as Vec3 exposing (Vec3) import Shapes.Sphere exposing (Sphere) addContacts : String -> (Contact -> Contact) -> Sphere -> Vec3 -> List Contact -> List Contact addContacts idPrefix orderContact { radius, position } particlePosition contacts = let distance = Vec3.distance particlePosition position - radius normal = Vec3.direction particlePosition position in if distance > 0 then contacts else orderContact { id = idPrefix , ni = normal , pi = Vec3.add position (Vec3.scale (radius - distance) normal) , pj = particlePosition } :: contacts ================================================ FILE: src/Collision/SphereSphere.elm ================================================ module Collision.SphereSphere exposing (addContacts) import Internal.Contact exposing (Contact) import Internal.Vector3 as Vec3 import Shapes.Sphere exposing (Sphere) addContacts : String -> Sphere -> Sphere -> List Contact -> List Contact addContacts idPrefix sphere1 sphere2 contacts = let radius1 = sphere1.radius radius2 = sphere2.radius center1 = sphere1.position center2 = sphere2.position distance = Vec3.distance center2 center1 - radius1 - radius2 normal = Vec3.direction center2 center1 in if distance > 0 then contacts else { id = idPrefix , ni = normal , pi = Vec3.add center1 (Vec3.scale (radius1 - distance) normal) , pj = Vec3.add center2 (Vec3.scale -radius2 normal) } :: contacts ================================================ FILE: src/Internal/AssignIds.elm ================================================ module Internal.AssignIds exposing (assignIds) import Internal.Body as InternalBody import Physics.Types as Types assignIds : List ( id, Types.Body ) -> ( List ( id, InternalBody.Body ), Int ) assignIds bodiesWithIds = let ( existingIds, newCount, mx ) = collect bodiesWithIds [] 0 -1 sorted = List.sort existingIds ( dupIds, dupCount ) = findDups sorted -2 [] 0 freeIds = findFree 0 sorted (newCount + dupCount) [] in case freeIds of [] -> stable bodiesWithIds mx [] _ -> assign bodiesWithIds freeIds dupIds -1 mx [] collect : List ( id, Types.Body ) -> List Int -> Int -> Int -> ( List Int, Int, Int ) collect bodies existingIds newCount mx = case bodies of [] -> ( existingIds, newCount, mx ) ( _, Types.Body body ) :: rest -> if body.id == -1 then collect rest existingIds (newCount + 1) mx else collect rest (body.id :: existingIds) newCount (max mx body.id) findDups : List Int -> Int -> List Int -> Int -> ( List Int, Int ) findDups sorted prev acc count = case sorted of [] -> ( acc, count ) x :: rest -> if x - prev == 0 then findDups rest x (x :: acc) (count + 1) else findDups rest x acc count findFree : Int -> List Int -> Int -> List Int -> List Int findFree n sorted needed revAcc = if needed == 0 then List.reverse revAcc else case sorted of [] -> fillFrom n needed revAcc x :: rest -> if x > n then findFree (n + 1) sorted (needed - 1) (n :: revAcc) else if x == n then findFree (n + 1) rest needed revAcc else findFree n rest needed revAcc fillFrom : Int -> Int -> List Int -> List Int fillFrom n needed revAcc = if needed == 0 then List.reverse revAcc else fillFrom (n + 1) (needed - 1) (n :: revAcc) assign : List ( id, Types.Body ) -> List Int -> List Int -> Int -> Int -> List ( id, InternalBody.Body ) -> ( List ( id, InternalBody.Body ), Int ) assign bodies freeIds dupIds dir mx acc = case freeIds of [] -> stable bodies mx acc freshId :: remainingFree -> case bodies of [] -> ( acc, mx ) ( extId, Types.Body body ) :: rest -> if body.id == -1 then assign rest remainingFree dupIds dir (max mx freshId) (( extId, withId freshId body ) :: acc) else if memberSorted dir body.id dupIds then case removeFirstReversing body.id [] dupIds of [] -> assign rest remainingFree [] dir (max mx freshId) (( extId, withId freshId body ) :: acc) newDupIds -> assign rest remainingFree newDupIds (negate dir) (max mx freshId) (( extId, withId freshId body ) :: acc) else assign rest freeIds dupIds dir mx (( extId, body ) :: acc) stable : List ( id, Types.Body ) -> Int -> List ( id, InternalBody.Body ) -> ( List ( id, InternalBody.Body ), Int ) stable bodies mx acc = case bodies of [] -> ( List.reverse acc, mx ) ( extId, Types.Body body ) :: rest -> stable rest mx (( extId, body ) :: acc) withId : Int -> InternalBody.Body -> InternalBody.Body withId freshId body = { id = freshId , kind = body.kind , transform3d = body.transform3d , centerOfMassTransform3d = body.centerOfMassTransform3d , velocity = body.velocity , angularVelocity = body.angularVelocity , mass = body.mass , volume = body.volume , shapesWithMaterials = body.shapesWithMaterials , worldShapesWithMaterials = body.worldShapesWithMaterials , force = body.force , torque = body.torque , boundingSphereRadius = body.boundingSphereRadius , linearDamping = body.linearDamping , angularDamping = body.angularDamping , invMass = body.invMass , invInertia = body.invInertia , invInertiaWorld = body.invInertiaWorld , linearLock = body.linearLock , angularLock = body.angularLock } memberSorted : Int -> Int -> List Int -> Bool memberSorted dir x list = case list of [] -> False y :: rest -> if y - x == 0 then True else if dir * (y - x) > 0 then False else memberSorted dir x rest removeFirstReversing : Int -> List Int -> List Int -> List Int removeFirstReversing x acc remaining = case remaining of [] -> acc y :: rest -> if y == x then accumulateReversed acc rest else removeFirstReversing x (y :: acc) rest accumulateReversed : List Int -> List Int -> List Int accumulateReversed acc remaining = case remaining of [] -> acc y :: rest -> accumulateReversed (y :: acc) rest ================================================ FILE: src/Internal/Body.elm ================================================ module Internal.Body exposing ( Body , Kind(..) , applyAngularImpulse , applyForce , applyImpulse , applyTorque , compound , lock , pointMass , raycast ) import Internal.Coordinates exposing (BodyCoordinates, WorldCoordinates) import Internal.Lock as Lock exposing (Lock) import Internal.Material exposing (Material) import Internal.Matrix3 as Mat3 exposing (Mat3) import Internal.Shape as Shape exposing (CenterOfMassCoordinates, Shape(..)) import Internal.Transform3d as Transform3d exposing (Transform3d) import Internal.Vector3 as Vec3 exposing (Vec3) {-| Static: not moved by the engine, not moved by the user (an immobile wall). Dynamic: moved by the engine in response to forces, gravity, and contacts. Kinematic: moved by the engine according to the user-set velocity, but ignores forces, gravity, and contacts. Other dynamic bodies see the kinematic's velocity and respond with friction and contact forces accordingly. -} type Kind = Static | Dynamic | Kinematic type alias Body = { id : Int -- ephemeral index assigned during simulation, -1 when not in a simulation , kind : Kind , transform3d : Transform3d WorldCoordinates { defines : CenterOfMassCoordinates } , centerOfMassTransform3d : Transform3d BodyCoordinates { defines : CenterOfMassCoordinates } , velocity : Vec3 , angularVelocity : Vec3 , mass : Float , volume : Float -- net volume: solid shapes minus void shapes (m³) , shapesWithMaterials : List ( Shape CenterOfMassCoordinates, Material ) , worldShapesWithMaterials : List ( Shape WorldCoordinates, Material ) , force : Vec3 , torque : Vec3 , boundingSphereRadius : Float -- damping , linearDamping : Float , angularDamping : Float -- mass props , invMass : Float , invInertia : Vec3 , invInertiaWorld : Mat3 -- world-axis DOF masks: 0 = locked, 1 = free , linearLock : Vec3 , angularLock : Vec3 } {-| Accumulate total mass, net volume, and unscaled center-of-mass sum in one pass. Divides the CoM sum by totalMass at the end; returns zero if totalMass is zero. -} accumulateMassProps : List ( Shape BodyCoordinates, Material, Float ) -> Float -> Float -> Float -> Float -> Float -> { totalMass : Float, totalVolume : Float, centerOfMassPoint : Vec3 } accumulateMassProps shapes totalMass totalVolume comX comY comZ = case shapes of [] -> { totalMass = totalMass , totalVolume = totalVolume , centerOfMassPoint = if totalMass > 0 then { x = comX / totalMass , y = comY / totalMass , z = comZ / totalMass } else Vec3.zero } ( shape, { density }, sign ) :: rest -> let signedVolume = sign * Shape.volume shape signedMass = signedVolume * density { x, y, z } = Shape.centerOfMass shape in accumulateMassProps rest (totalMass + signedMass) (totalVolume + signedVolume) (comX + signedMass * x) (comY + signedMass * y) (comZ + signedMass * z) {-| Move shapes to center-of-mass coordinates, accumulate inertia, net volume, bounding sphere radius, and collect solid shapes. -} placeShapes : Transform3d CenterOfMassCoordinates { defines : BodyCoordinates } -> Transform3d BodyCoordinates { defines : CenterOfMassCoordinates } -> List ( Shape BodyCoordinates, Material, Float ) -> Mat3 -> List ( Shape CenterOfMassCoordinates, Material ) -> Float -> { inertia : Mat3 , solidShapes : List ( Shape CenterOfMassCoordinates, Material ) , boundingSphereRadius : Float } placeShapes inverseCenterOfMassTransform3d centerOfMassTransform3d shapes inertia solidShapes boundingSphereRadius = case shapes of [] -> { inertia = inertia , solidShapes = solidShapes , boundingSphereRadius = boundingSphereRadius } ( shape, mat, sign ) :: rest -> let movedShape = Shape.placeIn inverseCenterOfMassTransform3d shape volume = Shape.volume movedShape resultInertia = Transform3d.inertiaPlaceIn centerOfMassTransform3d (Transform3d.pointPlaceIn centerOfMassTransform3d (Shape.centerOfMass movedShape)) volume (Shape.inertia movedShape) in placeShapes inverseCenterOfMassTransform3d centerOfMassTransform3d rest (Mat3.add inertia (Mat3.scale (sign * mat.density) resultInertia)) (if sign > 0 then ( movedShape, mat ) :: solidShapes else solidShapes ) (if sign > 0 then Shape.expandBoundingSphereRadius movedShape boundingSphereRadius else boundingSphereRadius ) compound : Kind -> List ( Shape BodyCoordinates, Material, Float ) -> Body compound kind rawShapesWithMaterials = let -- Static and kinematic bodies have infinite mass — strip user densities -- so totalMass and inertia come out zero regardless of the materials passed. shapesWithMaterials = case kind of Dynamic -> rawShapesWithMaterials _ -> List.map (\( shape, mat, sign ) -> ( shape, { mat | density = 0 }, sign )) rawShapesWithMaterials { totalMass, totalVolume, centerOfMassPoint } = accumulateMassProps shapesWithMaterials 0 0 0 0 0 initialCenterOfMassTransform3d = Transform3d.atPoint centerOfMassPoint initialInverseCenterOfMassTransform3d = Transform3d.inverse initialCenterOfMassTransform3d initialPlaced = placeShapes initialInverseCenterOfMassTransform3d initialCenterOfMassTransform3d shapesWithMaterials Mat3.zero [] 0 { eigenvalues, v1, v2, v3 } = Mat3.eigenDecomposition initialPlaced.inertia eigenRotation = Transform3d.fromOriginAndBasis Vec3.zero v1 v2 v3 centerOfMassTransform3d = Transform3d.placeIn initialCenterOfMassTransform3d eigenRotation inverseCenterOfMassTransform3d = Transform3d.inverse centerOfMassTransform3d placed = placeShapes inverseCenterOfMassTransform3d centerOfMassTransform3d shapesWithMaterials Mat3.zero [] 0 transform3d = Transform3d.placeIn Transform3d.atOrigin centerOfMassTransform3d invInertia = { x = if eigenvalues.x == 0 then 0 else 1 / eigenvalues.x , y = if eigenvalues.y == 0 then 0 else 1 / eigenvalues.y , z = if eigenvalues.z == 0 then 0 else 1 / eigenvalues.z } in { id = -1 , kind = kind , velocity = Vec3.zero , angularVelocity = Vec3.zero , transform3d = transform3d , centerOfMassTransform3d = centerOfMassTransform3d , mass = totalMass , volume = totalVolume , shapesWithMaterials = placed.solidShapes , worldShapesWithMaterials = List.map (\( s, m ) -> ( Shape.placeIn transform3d s, m )) placed.solidShapes , boundingSphereRadius = placed.boundingSphereRadius , linearDamping = 0.01 , angularDamping = 0.01 , invMass = if totalMass == 0 then 0 else 1 / totalMass , invInertia = invInertia , invInertiaWorld = Transform3d.invertedInertiaRotateIn transform3d invInertia , force = Vec3.zero , torque = Vec3.zero , linearLock = { x = 1, y = 1, z = 1 } , angularLock = { x = 1, y = 1, z = 1 } } pointMass : Vec3 -> Float -> Material -> Body pointMass position mass { friction, bounciness } = let contactMaterial = { friction = friction , bounciness = bounciness , density = 0 } in { id = -1 , kind = Dynamic , velocity = Vec3.zero , angularVelocity = Vec3.zero , transform3d = Transform3d.atPoint position , centerOfMassTransform3d = Transform3d.atOrigin , mass = mass , volume = 0 , shapesWithMaterials = [ ( Particle Vec3.zero, contactMaterial ) ] , worldShapesWithMaterials = [ ( Particle position, contactMaterial ) ] , boundingSphereRadius = 0 , linearDamping = 0.01 , angularDamping = 0.01 , invMass = 1 / mass , invInertia = Vec3.zero , invInertiaWorld = Mat3.zero , force = Vec3.zero , torque = Vec3.zero , linearLock = Vec3.one , angularLock = Vec3.one } applyImpulse : Vec3 -> Vec3 -> Body -> Body applyImpulse impulse point body = let relativePoint = Vec3.sub point (Transform3d.originPoint body.transform3d) { x, y, z } = Vec3.cross relativePoint impulse { angularVelocity, invInertiaWorld, velocity, invMass } = body in { body | velocity = { x = velocity.x + invMass * impulse.x , y = velocity.y + invMass * impulse.y , z = velocity.z + invMass * impulse.z } , angularVelocity = { x = angularVelocity.x + invInertiaWorld.m11 * x + invInertiaWorld.m12 * y + invInertiaWorld.m13 * z , y = angularVelocity.y + invInertiaWorld.m21 * x + invInertiaWorld.m22 * y + invInertiaWorld.m23 * z , z = angularVelocity.z + invInertiaWorld.m31 * x + invInertiaWorld.m32 * y + invInertiaWorld.m33 * z } } applyForce : Vec3 -> Vec3 -> Body -> Body applyForce force point body = let relativePoint = Vec3.sub point (Transform3d.originPoint body.transform3d) torque = Vec3.cross relativePoint force in { body | force = Vec3.add body.force force , torque = Vec3.add body.torque torque } applyTorque : Vec3 -> Body -> Body applyTorque torque body = { body | torque = Vec3.add body.torque torque } applyAngularImpulse : Vec3 -> Body -> Body applyAngularImpulse angularImpulse body = let { x, y, z } = angularImpulse { angularVelocity, invInertiaWorld } = body in { body | angularVelocity = { x = angularVelocity.x + invInertiaWorld.m11 * x + invInertiaWorld.m12 * y + invInertiaWorld.m13 * z , y = angularVelocity.y + invInertiaWorld.m21 * x + invInertiaWorld.m22 * y + invInertiaWorld.m23 * z , z = angularVelocity.z + invInertiaWorld.m31 * x + invInertiaWorld.m32 * y + invInertiaWorld.m33 * z } } {-| Replace the body’s locked degrees of freedom. The list fully describes the lock state — calling `lock` again replaces the previous locks. An empty list clears all locks. The body’s current velocities are left untouched; the mask is enforced at the next simulation step. -} lock : List Lock -> Body -> Body lock locks body = let ( linearLock, angularLock ) = Lock.masks locks in { body | linearLock = linearLock , angularLock = angularLock } raycast : { from : Vec3, direction : Vec3 } -> Body -> Maybe { distance : Float, point : Vec3, normal : Vec3 } raycast ray body = List.foldl (\( shape, _ ) maybeClosestRaycastResult -> case Shape.raycast ray shape of (Just raycastResult) as passThrough -> case maybeClosestRaycastResult of Just closestRaycastResult -> if raycastResult.distance - closestRaycastResult.distance < 0 then passThrough else maybeClosestRaycastResult Nothing -> passThrough Nothing -> maybeClosestRaycastResult ) Nothing body.worldShapesWithMaterials ================================================ FILE: src/Internal/BroadPhase.elm ================================================ module Internal.BroadPhase exposing (getContacts) {-| This is very naive implementation of BroadPhase, that checks if the bounding spheres of each two bodies overlap -} import Internal.Body as Body exposing (Body) import Internal.Contact exposing (ContactGroup) import Internal.NarrowPhase as NarrowPhase import Internal.Transform3d as Transform3d getContacts : (id -> id -> Bool) -> List ( id, Body ) -> List ContactGroup getContacts collide bodies = case bodies of ( id1, body1 ) :: restBodies -> getContactsHelp collide id1 body1 restBodies restBodies [] [] -> [] {-| This will generate all pairs for body1, then all pairs for body2, etc. We rely on this order in the Solver.elm -} getContactsHelp : (id -> id -> Bool) -> id -> Body -> List ( id, Body ) -> List ( id, Body ) -> List ContactGroup -> List ContactGroup getContactsHelp collide id1 body1 currentBodies restBodies result = case restBodies of ( id2, body2 ) :: newRestBodies -> getContactsHelp collide id1 body1 currentBodies newRestBodies (if bodiesMayContact collide id1 body1 id2 body2 then case NarrowPhase.getContacts (String.fromInt body1.id ++ "-" ++ String.fromInt body2.id) body1.worldShapesWithMaterials body2.worldShapesWithMaterials of [] -> result contacts -> { body1 = body1 , body2 = body2 , contacts = contacts } :: result else result ) [] -> case currentBodies of ( newId1, newBody1 ) :: newRestBodies -> getContactsHelp collide newId1 newBody1 newRestBodies newRestBodies result [] -> result bodiesMayContact : (id -> id -> Bool) -> id -> Body -> id -> Body -> Bool bodiesMayContact collide id1 body1 id2 body2 = let boundingRadiuses = body1.boundingSphereRadius + body2.boundingSphereRadius p1 = Transform3d.originPoint body1.transform3d p2 = Transform3d.originPoint body2.transform3d dx = p2.x - p1.x dy = p2.y - p1.y dz = p2.z - p1.z distanceSquared = dx * dx + dy * dy + dz * dz in (boundingRadiuses * boundingRadiuses - distanceSquared > 0) && (body1.kind == Body.Dynamic || body2.kind == Body.Dynamic) && collide id1 id2 ================================================ FILE: src/Internal/Const.elm ================================================ module Internal.Const exposing (maxNumber, precision) maxNumber : Float maxNumber = 3.40282347e38 precision : Float precision = 1.0e-6 ================================================ FILE: src/Internal/Constraint.elm ================================================ module Internal.Constraint exposing (Constraint(..), ConstraintGroup, getConstraints) import Internal.Body as Body import Internal.Coordinates exposing (BodyCoordinates) import Internal.Shape exposing (CenterOfMassCoordinates) import Internal.Transform3d as Transform3d exposing (Transform3d) import Internal.Vector3 exposing (Vec3) type Constraint coordinates = PointToPoint Vec3 Vec3 | Hinge Vec3 Vec3 Vec3 Vec3 | Lock Vec3 Vec3 Vec3 Vec3 Vec3 Vec3 Vec3 Vec3 | Distance Float type alias ConstraintGroup = { bodyId1 : Int , bodyId2 : Int , constraints : List (Constraint CenterOfMassCoordinates) } relativeToCenterOfMass : Transform3d BodyCoordinates { defines : CenterOfMassCoordinates } -> Transform3d BodyCoordinates { defines : CenterOfMassCoordinates } -> Constraint BodyCoordinates -> Constraint CenterOfMassCoordinates relativeToCenterOfMass centerOfMassFrame3d1 centerOfMassFrame3d2 constraint = case constraint of PointToPoint pivot1 pivot2 -> PointToPoint (Transform3d.pointRelativeTo centerOfMassFrame3d1 pivot1) (Transform3d.pointRelativeTo centerOfMassFrame3d2 pivot2) Hinge pivot1 axis1 pivot2 axis2 -> Hinge (Transform3d.pointRelativeTo centerOfMassFrame3d1 pivot1) (Transform3d.directionRelativeTo centerOfMassFrame3d1 axis1) (Transform3d.pointRelativeTo centerOfMassFrame3d2 pivot2) (Transform3d.directionRelativeTo centerOfMassFrame3d2 axis2) Lock pivot1 x1 y1 z1 pivot2 x2 y2 z2 -> Lock (Transform3d.pointRelativeTo centerOfMassFrame3d1 pivot1) (Transform3d.directionRelativeTo centerOfMassFrame3d1 x1) (Transform3d.directionRelativeTo centerOfMassFrame3d1 y1) (Transform3d.directionRelativeTo centerOfMassFrame3d1 z1) (Transform3d.pointRelativeTo centerOfMassFrame3d2 pivot2) (Transform3d.directionRelativeTo centerOfMassFrame3d2 x2) (Transform3d.directionRelativeTo centerOfMassFrame3d2 y2) (Transform3d.directionRelativeTo centerOfMassFrame3d2 z2) Distance length -> Distance length getConstraints : (id -> Maybe (id -> List (Constraint BodyCoordinates))) -> List ( id, Body.Body ) -> List ConstraintGroup getConstraints constrain bodiesWithIds = buildConstraintsOuter constrain bodiesWithIds bodiesWithIds [] buildConstraintsOuter : (id -> Maybe (id -> List (Constraint BodyCoordinates))) -> List ( id, Body.Body ) -> List ( id, Body.Body ) -> List ConstraintGroup -> List ConstraintGroup buildConstraintsOuter constrain bodies allBodies acc = case bodies of [] -> acc ( id1, body1 ) :: rest -> case constrain id1 of Nothing -> buildConstraintsOuter constrain rest allBodies acc Just fn -> buildConstraintsOuter constrain rest allBodies (buildConstraintsInner fn body1 allBodies acc) buildConstraintsInner : (id -> List (Constraint BodyCoordinates)) -> Body.Body -> List ( id, Body.Body ) -> List ConstraintGroup -> List ConstraintGroup buildConstraintsInner fn body1 bodies acc = case bodies of [] -> acc ( id2, body2 ) :: rest -> if body1.id - body2.id == 0 || (body1.kind /= Body.Dynamic && body2.kind /= Body.Dynamic) then buildConstraintsInner fn body1 rest acc else case fn id2 of [] -> buildConstraintsInner fn body1 rest acc cs -> buildConstraintsInner fn body1 rest ({ bodyId1 = body1.id , bodyId2 = body2.id , constraints = List.map (relativeToCenterOfMass body1.centerOfMassTransform3d body2.centerOfMassTransform3d ) cs } :: acc ) ================================================ FILE: src/Internal/Contact.elm ================================================ module Internal.Contact exposing (Contact, ContactGroup, SolverContact, flip) import Internal.Body exposing (Body) import Internal.Vector3 as Vec3 exposing (Vec3) type alias ContactGroup = { body1 : Body , body2 : Body , contacts : List SolverContact } type alias SolverContact = { friction : Float , bounciness : Float , contact : Contact } type alias Contact = { id : String , ni : Vec3 -- contact normal, pointing out of body1 , pi : Vec3 -- contact point on body1 , pj : Vec3 -- contact point on body2 } {-| Flips the order of two bodies in the contact, this is useful to e.g. use the same collision function for adding sphere-convex and convex-sphere contacts into the same contact group -} flip : Contact -> Contact flip contact = { id = contact.id , ni = Vec3.negate contact.ni , pi = contact.pj , pj = contact.pi } ================================================ FILE: src/Internal/Coordinates.elm ================================================ module Internal.Coordinates exposing (BodyCoordinates, WorldCoordinates) type WorldCoordinates = WorldCoordinates Never type BodyCoordinates = BodyCoordinates Never ================================================ FILE: src/Internal/Equation.elm ================================================ module Internal.Equation exposing ( Equation , EquationsGroup , SolverEquation , constraintEquationsGroup , contactEquationsGroup ) import Dict exposing (Dict) import Internal.Body as Body exposing (Body) import Internal.Constraint exposing (Constraint(..)) import Internal.Contact exposing (Contact, ContactGroup, SolverContact) import Internal.Shape exposing (CenterOfMassCoordinates) import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 exposing (Vec3) type alias Equation = { id : String , minForce : Float , maxForce : Float , solverB : Float , solverInvC : Float , spookA : Float , spookB : Float , spookEps : Float , wA : Vec3 , vB : Vec3 -- vA = Vec3.negate vB , wB : Vec3 } type alias Ctx = { dt : Float , gravity : Vec3 , gravityLength : Float , lambdas : Dict String Float } type alias EquationsGroup = { bodyId1 : Int , bodyId2 : Int , equations : List SolverEquation } constraintEquationsGroup : Ctx -> Body -> Body -> List (Constraint CenterOfMassCoordinates) -> EquationsGroup constraintEquationsGroup ctx body1 body2 constraints = { bodyId1 = body1.id , bodyId2 = body2.id , equations = List.foldl (addConstraintEquations ctx body1 body2) [] constraints } contactEquationsGroup : Ctx -> ContactGroup -> EquationsGroup contactEquationsGroup ctx { body1, body2, contacts } = { bodyId1 = body1.id , bodyId2 = body2.id , equations = List.foldl (addContactEquations ctx body1 body2) [] contacts } addConstraintEquations : Ctx -> Body -> Body -> Constraint CenterOfMassCoordinates -> List SolverEquation -> List SolverEquation addConstraintEquations ctx body1 body2 constraint = case constraint of PointToPoint pivot1 pivot2 -> addPointToPointConstraintEquations ctx body1 body2 pivot1 pivot2 Hinge pivot1 axis1 pivot2 axis2 -> addPointToPointConstraintEquations ctx body1 body2 pivot1 pivot2 >> addHingeRotationalConstraintEquations ctx body1 body2 axis1 axis2 Lock pivot1 x1 y1 z1 pivot2 x2 y2 z2 -> addPointToPointConstraintEquations ctx body1 body2 pivot1 pivot2 >> addLockRotationalConstraintEquations ctx body1 body2 x1 x2 y1 y2 z1 z2 Distance distance -> addDistanceConstraintEquations ctx body1 body2 distance addDistanceConstraintEquations : Ctx -> Body -> Body -> Float -> List SolverEquation -> List SolverEquation addDistanceConstraintEquations ctx body1 body2 distance = let halfDistance = distance / 2 ni = Vec3.direction (Transform3d.originPoint body2.transform3d) (Transform3d.originPoint body1.transform3d) ri = Vec3.scale halfDistance ni rj = Vec3.scale -halfDistance ni spookA = 4.0 / (ctx.dt * (1 + 4 * defaultRelaxation)) spookB = (4.0 * defaultRelaxation) / (1 + 4 * defaultRelaxation) spookEps = 4.0 / (ctx.dt * ctx.dt * defaultStiffness * (1 + 4 * defaultRelaxation)) in (::) (initSolverParams (computeContactB 0 { id = "" , pi = Vec3.add ri (Transform3d.originPoint body1.transform3d) , pj = Vec3.add rj (Transform3d.originPoint body2.transform3d) , ni = ni } ) ctx body1 body2 { id = "" , minForce = -1000000 , maxForce = 1000000 , solverB = 0 , solverInvC = 0 , spookA = spookA , spookB = spookB , spookEps = spookEps , wA = Vec3.cross ni ri , vB = ni , wB = Vec3.cross rj ni } ) addHingeRotationalConstraintEquations : Ctx -> Body -> Body -> Vec3 -> Vec3 -> List SolverEquation -> List SolverEquation addHingeRotationalConstraintEquations ctx body1 body2 axis1 axis2 equations = let spookA = 4.0 / (ctx.dt * (1 + 4 * defaultRelaxation)) spookB = (4.0 * defaultRelaxation) / (1 + 4 * defaultRelaxation) spookEps = 4.0 / (ctx.dt * ctx.dt * defaultStiffness * (1 + 4 * defaultRelaxation)) worldAxis1 = Transform3d.directionPlaceIn body1.transform3d axis1 worldAxis2 = Transform3d.directionPlaceIn body2.transform3d axis2 ( ni1, ni2 ) = Vec3.tangents worldAxis1 nj1 = worldAxis2 nj2 = worldAxis2 in initSolverParams (computeRotationalB { ni = ni1 , nj = nj1 , maxAngleCos = 0 -- cos (pi / 2) } ) ctx body1 body2 { id = "" , minForce = -1000000 , maxForce = 1000000 , solverB = 0 , solverInvC = 0 , spookA = spookA , spookB = spookB , spookEps = spookEps , wA = Vec3.cross nj1 ni1 , vB = Vec3.zero , wB = Vec3.cross ni1 nj1 } :: initSolverParams (computeRotationalB { ni = ni2 , nj = nj2 , maxAngleCos = 0 -- cos (pi / 2) } ) ctx body1 body2 { id = "" , minForce = -1000000 , maxForce = 1000000 , solverB = 0 , solverInvC = 0 , spookA = spookA , spookB = spookB , spookEps = spookEps , wA = Vec3.cross nj2 ni2 , vB = Vec3.zero , wB = Vec3.cross ni2 nj2 } :: equations addLockRotationalConstraintEquations : Ctx -> Body -> Body -> Vec3 -> Vec3 -> Vec3 -> Vec3 -> Vec3 -> Vec3 -> List SolverEquation -> List SolverEquation addLockRotationalConstraintEquations ctx body1 body2 x1 x2 y1 y2 z1 z2 equations = let spookA = 4.0 / (ctx.dt * (1 + 4 * defaultRelaxation)) spookB = (4.0 * defaultRelaxation) / (1 + 4 * defaultRelaxation) spookEps = 4.0 / (ctx.dt * ctx.dt * defaultStiffness * (1 + 4 * defaultRelaxation)) ni1 = Transform3d.directionPlaceIn body1.transform3d x1 nj1 = Transform3d.directionPlaceIn body2.transform3d y2 ni2 = Transform3d.directionPlaceIn body1.transform3d y1 nj2 = Transform3d.directionPlaceIn body2.transform3d z2 ni3 = Transform3d.directionPlaceIn body1.transform3d z1 nj3 = Transform3d.directionPlaceIn body2.transform3d x2 in initSolverParams (computeRotationalB { ni = ni1 , nj = nj1 , maxAngleCos = 0 -- cos (pi / 2) } ) ctx body1 body2 { id = "" , minForce = -1000000 , maxForce = 1000000 , solverB = 0 , solverInvC = 0 , spookA = spookA , spookB = spookB , spookEps = spookEps , wA = Vec3.cross nj1 ni1 , vB = Vec3.zero , wB = Vec3.cross ni1 nj1 } :: initSolverParams (computeRotationalB { ni = ni2 , nj = nj2 , maxAngleCos = 0 -- cos (pi / 2) } ) ctx body1 body2 { id = "" , minForce = -1000000 , maxForce = 1000000 , solverB = 0 , solverInvC = 0 , spookA = spookA , spookB = spookB , spookEps = spookEps , wA = Vec3.cross nj2 ni2 , vB = Vec3.zero , wB = Vec3.cross ni2 nj2 } :: initSolverParams (computeRotationalB { ni = ni3 , nj = nj3 , maxAngleCos = 0 -- cos (pi / 2) } ) ctx body1 body2 { id = "" , minForce = -1000000 , maxForce = 1000000 , solverB = 0 , solverInvC = 0 , spookA = spookA , spookB = spookB , spookEps = spookEps , wA = Vec3.cross nj3 ni3 , vB = Vec3.zero , wB = Vec3.cross ni3 nj3 } :: equations addPointToPointConstraintEquations : Ctx -> Body -> Body -> Vec3 -> Vec3 -> List SolverEquation -> List SolverEquation addPointToPointConstraintEquations ctx body1 body2 pivot1 pivot2 equations = let ri = Transform3d.directionPlaceIn body1.transform3d pivot1 rj = Transform3d.directionPlaceIn body2.transform3d pivot2 spookA = 4.0 / (ctx.dt * (1 + 4 * defaultRelaxation)) spookB = (4.0 * defaultRelaxation) / (1 + 4 * defaultRelaxation) spookEps = 4.0 / (ctx.dt * ctx.dt * defaultStiffness * (1 + 4 * defaultRelaxation)) in List.foldl (\ni -> (::) (initSolverParams (computeContactB 0 { id = "" , pi = Vec3.add (Transform3d.originPoint body1.transform3d) ri , pj = Vec3.add (Transform3d.originPoint body2.transform3d) rj , ni = ni } ) ctx body1 body2 { id = "" , minForce = -1000000 , maxForce = 1000000 , solverB = 0 , solverInvC = 0 , spookA = spookA , spookB = spookB , spookEps = spookEps , wA = Vec3.cross ni ri , vB = ni , wB = Vec3.cross rj ni } ) ) equations Vec3.basis addContactEquations : Ctx -> Body -> Body -> SolverContact -> List SolverEquation -> List SolverEquation addContactEquations ctx body1 body2 { friction, bounciness, contact } equations = let maxFrictionForce = if body1.invMass + body2.invMass > 0 then friction * ctx.gravityLength / (body1.invMass + body2.invMass) else 0 ri = Vec3.sub contact.pi (Transform3d.originPoint body1.transform3d) rj = Vec3.sub contact.pj (Transform3d.originPoint body2.transform3d) ( t1, t2 ) = Vec3.tangents contact.ni spookA = 4.0 / (ctx.dt * (1 + 4 * defaultRelaxation)) spookB = (4.0 * defaultRelaxation) / (1 + 4 * defaultRelaxation) spookEps = 4.0 / (ctx.dt * ctx.dt * defaultStiffness * (1 + 4 * defaultRelaxation)) in initSolverParams (computeContactB bounciness contact) ctx body1 body2 { id = contact.id , minForce = 0 , maxForce = 1000000 , solverB = 0 , solverInvC = 0 , spookA = spookA , spookB = spookB , spookEps = spookEps , wA = Vec3.cross contact.ni ri , vB = contact.ni , wB = Vec3.cross rj contact.ni } :: initSolverParams computeFrictionB ctx body1 body2 { id = "" , minForce = -maxFrictionForce , maxForce = maxFrictionForce , solverB = 0 , solverInvC = 0 , spookA = spookA , spookB = spookB , spookEps = spookEps , wA = Vec3.cross t1 ri , vB = t1 , wB = Vec3.cross rj t1 } :: initSolverParams computeFrictionB ctx body1 body2 { id = "" , minForce = -maxFrictionForce , maxForce = maxFrictionForce , solverB = 0 , solverInvC = 0 , spookA = spookA , spookB = spookB , spookEps = spookEps , wA = Vec3.cross t2 ri , vB = t2 , wB = Vec3.cross rj t2 } :: equations defaultRelaxation : Float defaultRelaxation = 3 defaultStiffness : Float defaultStiffness = 10000000 type alias SolverEquation = { equation : Equation , solverLambda : Float } initSolverParams : ComputeB -> Ctx -> Body -> Body -> Equation -> SolverEquation initSolverParams computeB ctx bi bj solverEquation = { solverLambda = if solverEquation.id == "" then 0 else case Dict.get solverEquation.id ctx.lambdas of Just lambda -> lambda Nothing -> 0 , equation = { id = solverEquation.id , minForce = solverEquation.minForce , maxForce = solverEquation.maxForce , solverB = -- the RHS of the SPOOK equation computeB bi bj solverEquation - (ctx.dt * computeGiMf ctx.gravity bi bj solverEquation) , solverInvC = 1 / (computeGimgt bi bj solverEquation + solverEquation.spookEps) , spookA = solverEquation.spookA , spookB = solverEquation.spookB , spookEps = solverEquation.spookEps , wA = solverEquation.wA , vB = solverEquation.vB , wB = solverEquation.wB } } type alias ComputeB = Body -> Body -> Equation -> Float computeContactB : Float -> Contact -> ComputeB computeContactB bounciness { pi, pj, ni } bi bj { spookA, spookB, wA, wB } = let g = ((pj.x - pi.x) * ni.x) + ((pj.y - pi.y) * ni.y) + ((pj.z - pi.z) * ni.z) gW = (bounciness + 1) * (Vec3.dot bj.velocity ni - Vec3.dot bi.velocity ni) + Vec3.dot bj.angularVelocity wB + Vec3.dot bi.angularVelocity wA in -g * spookA - gW * spookB type alias RotationalEquation = { ni : Vec3 , nj : Vec3 , maxAngleCos : Float } computeRotationalB : RotationalEquation -> ComputeB computeRotationalB { ni, nj, maxAngleCos } bi bj ({ spookA, spookB } as solverEquation) = let g = maxAngleCos - Vec3.dot ni nj gW = computeGW bi bj solverEquation in -g * spookA - gW * spookB computeFrictionB : ComputeB computeFrictionB bi bj ({ spookB } as solverEquation) = let gW = computeGW bi bj solverEquation in -gW * spookB {-| Computes G x inv(M) x f, where - M is the mass matrix with diagonal blocks for each body - f are the forces on the bodies -} computeGiMf : Vec3 -> Body -> Body -> Equation -> Float computeGiMf gravity bi bj { wA, vB, wB } = let gravityi = if bi.kind == Body.Dynamic then gravity else Vec3.zero gravityj = if bj.kind == Body.Dynamic then gravity else Vec3.zero in -(vB.x * (bi.invMass * bi.force.x + gravityi.x) + vB.y * (bi.invMass * bi.force.y + gravityi.y) + vB.z * (bi.invMass * bi.force.z + gravityi.z)) + (vB.x * (bj.invMass * bj.force.x + gravityj.x) + vB.y * (bj.invMass * bj.force.y + gravityj.y) + vB.z * (bj.invMass * bj.force.z + gravityj.z)) + (wA.x * (bi.invInertiaWorld.m11 * bi.torque.x + bi.invInertiaWorld.m12 * bi.torque.y + bi.invInertiaWorld.m13 * bi.torque.z)) + (wA.y * (bi.invInertiaWorld.m21 * bi.torque.x + bi.invInertiaWorld.m22 * bi.torque.y + bi.invInertiaWorld.m23 * bi.torque.z)) + (wA.z * (bi.invInertiaWorld.m31 * bi.torque.x + bi.invInertiaWorld.m32 * bi.torque.y + bi.invInertiaWorld.m33 * bi.torque.z)) + (wB.x * (bj.invInertiaWorld.m11 * bj.torque.x + bj.invInertiaWorld.m12 * bj.torque.y + bj.invInertiaWorld.m13 * bj.torque.z)) + (wB.y * (bj.invInertiaWorld.m21 * bj.torque.x + bj.invInertiaWorld.m22 * bj.torque.y + bj.invInertiaWorld.m23 * bj.torque.z)) + (wB.z * (bj.invInertiaWorld.m31 * bj.torque.x + bj.invInertiaWorld.m32 * bj.torque.y + bj.invInertiaWorld.m33 * bj.torque.z)) {-| Compute G x inv(M) x G', the effective inverse mass for this constraint. -} computeGimgt : Body -> Body -> Equation -> Float computeGimgt bi bj { wA, wB } = bi.invMass + bj.invMass + (wA.x * (bi.invInertiaWorld.m11 * wA.x + bi.invInertiaWorld.m12 * wA.y + bi.invInertiaWorld.m13 * wA.z)) + (wA.y * (bi.invInertiaWorld.m21 * wA.x + bi.invInertiaWorld.m22 * wA.y + bi.invInertiaWorld.m23 * wA.z)) + (wA.z * (bi.invInertiaWorld.m31 * wA.x + bi.invInertiaWorld.m32 * wA.y + bi.invInertiaWorld.m33 * wA.z)) + (wB.x * (bj.invInertiaWorld.m11 * wB.x + bj.invInertiaWorld.m12 * wB.y + bj.invInertiaWorld.m13 * wB.z)) + (wB.y * (bj.invInertiaWorld.m21 * wB.x + bj.invInertiaWorld.m22 * wB.y + bj.invInertiaWorld.m23 * wB.z)) + (wB.z * (bj.invInertiaWorld.m31 * wB.x + bj.invInertiaWorld.m32 * wB.y + bj.invInertiaWorld.m33 * wB.z)) {-| Computes G x W, where W are the body velocities -} computeGW : Body -> Body -> Equation -> Float computeGW bi bj { wA, vB, wB } = -(vB.x * bi.velocity.x + vB.y * bi.velocity.y + vB.z * bi.velocity.z) + (wA.x * bi.angularVelocity.x + wA.y * bi.angularVelocity.y + wA.z * bi.angularVelocity.z) + (vB.x * bj.velocity.x + vB.y * bj.velocity.y + vB.z * bj.velocity.z) + (wB.x * bj.angularVelocity.x + wB.y * bj.angularVelocity.y + wB.z * bj.angularVelocity.z) ================================================ FILE: src/Internal/Lock.elm ================================================ module Internal.Lock exposing (Lock(..), masks) import Internal.Vector3 as Vec3 exposing (Vec3) type Lock = TranslateX | TranslateY | TranslateZ | RotateX | RotateY | RotateZ {-| Reduce a list of locks to a pair of (1/0) component-wise masks. The first Vec3 masks linear velocity; the second masks angular velocity. A component is 0 when locked, 1 when free. An empty list yields fully free masks. -} masks : List Lock -> ( Vec3, Vec3 ) masks locks = foldMasks locks Vec3.one Vec3.one foldMasks : List Lock -> Vec3 -> Vec3 -> ( Vec3, Vec3 ) foldMasks locks linear angular = case locks of [] -> ( linear, angular ) TranslateX :: rest -> foldMasks rest { linear | x = 0 } angular TranslateY :: rest -> foldMasks rest { linear | y = 0 } angular TranslateZ :: rest -> foldMasks rest { linear | z = 0 } angular RotateX :: rest -> foldMasks rest linear { angular | x = 0 } RotateY :: rest -> foldMasks rest linear { angular | y = 0 } RotateZ :: rest -> foldMasks rest linear { angular | z = 0 } ================================================ FILE: src/Internal/Material.elm ================================================ module Internal.Material exposing ( Material , combineBounciness , combineFriction , ice , plastic , rubber , steel , wood ) type alias Material = { bounciness : Float , friction : Float , density : Float } {-| Geometric mean of two frictions: sqrt(f1 \* f2). If one surface is slippery, the result stays low. -} combineFriction : Float -> Float -> Float combineFriction f1 f2 = sqrt (f1 * f2) {-| Branchless max of two bounciness values. The bouncier surface wins. -} combineBounciness : Float -> Float -> Float combineBounciness b1 b2 = (b1 + b2 + abs (b1 - b2)) * 0.5 wood : Material wood = { friction = 0.4, bounciness = 0.3, density = 700 } rubber : Material rubber = { friction = 0.8, bounciness = 0.7, density = 1100 } steel : Material steel = { friction = 0.3, bounciness = 0.2, density = 7800 } ice : Material ice = { friction = 0.03, bounciness = 0.1, density = 900 } plastic : Material plastic = { friction = 0.35, bounciness = 0.45, density = 1050 } ================================================ FILE: src/Internal/Matrix3.elm ================================================ module Internal.Matrix3 exposing ( Mat3 , add , cylinderInertia , eigenDecomposition , inverse , mul , pointInertia , scale , sphereInertia , sub , tetrahedronInertia , transpose , zero ) import Internal.Vector3 exposing (Vec3) {-| 3x3 matrix type -} type alias Mat3 = { m11 : Float , m21 : Float , m31 : Float , m12 : Float , m22 : Float , m32 : Float , m13 : Float , m23 : Float , m33 : Float } zero : Mat3 zero = { m11 = 0 , m21 = 0 , m31 = 0 , m12 = 0 , m22 = 0 , m32 = 0 , m13 = 0 , m23 = 0 , m33 = 0 } inverse : Mat3 -> Mat3 inverse { m11, m21, m31, m12, m22, m32, m13, m23, m33 } = let det = (m11 * (m22 * m33 - m32 * m23)) - (m12 * (m21 * m33 - m23 * m31)) + (m13 * (m21 * m32 - m22 * m31)) invdet = 1 / det in if det == 0 then zero else { m11 = (m22 * m33 - m32 * m23) * invdet , m12 = (m13 * m32 - m12 * m33) * invdet , m13 = (m12 * m23 - m13 * m22) * invdet , m21 = (m23 * m31 - m21 * m33) * invdet , m22 = (m11 * m33 - m13 * m31) * invdet , m23 = (m21 * m13 - m11 * m23) * invdet , m31 = (m21 * m32 - m31 * m22) * invdet , m32 = (m31 * m12 - m11 * m32) * invdet , m33 = (m11 * m22 - m21 * m12) * invdet } {-| Matrix multiplcation: a \* b -} mul : Mat3 -> Mat3 -> Mat3 mul a b = { m11 = a.m11 * b.m11 + a.m12 * b.m21 + a.m13 * b.m31 , m21 = a.m21 * b.m11 + a.m22 * b.m21 + a.m23 * b.m31 , m31 = a.m31 * b.m11 + a.m32 * b.m21 + a.m33 * b.m31 , m12 = a.m11 * b.m12 + a.m12 * b.m22 + a.m13 * b.m32 , m22 = a.m21 * b.m12 + a.m22 * b.m22 + a.m23 * b.m32 , m32 = a.m31 * b.m12 + a.m32 * b.m22 + a.m33 * b.m32 , m13 = a.m11 * b.m13 + a.m12 * b.m23 + a.m13 * b.m33 , m23 = a.m21 * b.m13 + a.m22 * b.m23 + a.m23 * b.m33 , m33 = a.m31 * b.m13 + a.m32 * b.m23 + a.m33 * b.m33 } scale : Float -> Mat3 -> Mat3 scale k m = { m11 = k * m.m11 , m21 = k * m.m21 , m31 = k * m.m31 , m12 = k * m.m12 , m22 = k * m.m22 , m32 = k * m.m32 , m13 = k * m.m13 , m23 = k * m.m23 , m33 = k * m.m33 } add : Mat3 -> Mat3 -> Mat3 add a b = { m11 = a.m11 + b.m11 , m21 = a.m21 + b.m21 , m31 = a.m31 + b.m31 , m12 = a.m12 + b.m12 , m22 = a.m22 + b.m22 , m32 = a.m32 + b.m32 , m13 = a.m13 + b.m13 , m23 = a.m23 + b.m23 , m33 = a.m33 + b.m33 } sub : Mat3 -> Mat3 -> Mat3 sub a b = { m11 = a.m11 - b.m11 , m21 = a.m21 - b.m21 , m31 = a.m31 - b.m31 , m12 = a.m12 - b.m12 , m22 = a.m22 - b.m22 , m32 = a.m32 - b.m32 , m13 = a.m13 - b.m13 , m23 = a.m23 - b.m23 , m33 = a.m33 - b.m33 } {-| Flip the matrix across the diagonal by swapping row index and column index. -} transpose : Mat3 -> Mat3 transpose m = { m11 = m.m11 , m21 = m.m12 , m31 = m.m13 , m12 = m.m21 , m22 = m.m22 , m32 = m.m23 , m13 = m.m31 , m23 = m.m32 , m33 = m.m33 } {-| Calculates the moment of inertia of a point mass at a certain position -} pointInertia : Float -> Float -> Float -> Float -> Mat3 pointInertia m x y z = let m21 = -m * x * y m31 = -m * x * z m32 = -m * y * z in { m11 = m * (y * y + z * z) , m21 = m21 , m31 = m31 , m12 = m21 , m22 = m * (z * z + x * x) , m32 = m32 , m13 = m31 , m23 = m32 , m33 = m * (x * x + y * y) } sphereInertia : Float -> Float -> Mat3 sphereInertia m radius = let i = m * 2 / 5 * radius * radius in { m11 = i , m21 = 0 , m31 = 0 , m12 = 0 , m22 = i , m32 = 0 , m13 = 0 , m23 = 0 , m33 = i } cylinderInertia : Float -> Float -> Float -> Mat3 cylinderInertia m radius height = let a = (m * (3 * radius ^ 2 + height ^ 2)) / 12 in { m11 = a , m21 = 0 , m31 = 0 , m12 = 0 , m22 = a , m32 = 0 , m13 = 0 , m23 = 0 , m33 = (m * radius ^ 2) / 2 } tetrahedronInertia : Float -> Vec3 -> Vec3 -> Vec3 -> Vec3 -> Mat3 tetrahedronInertia m p0 p1 p2 p3 = let x1 = p1.x - p0.x x2 = p2.x - p0.x x3 = p3.x - p0.x y1 = p1.y - p0.y y2 = p2.y - p0.y y3 = p3.y - p0.y z1 = p1.z - p0.z z2 = p2.z - p0.z z3 = p3.z - p0.z ix = m / 10 * (x1 * x1 + x2 * x2 + x3 * x3 + x1 * x2 + x1 * x3 + x2 * x3) iy = m / 10 * (y1 * y1 + y2 * y2 + y3 * y3 + y1 * y2 + y1 * y3 + y2 * y3) iz = m / 10 * (z1 * z1 + z2 * z2 + z3 * z3 + z1 * z2 + z1 * z3 + z2 * z3) ixx = iy + iz iyy = ix + iz izz = ix + iy ixy = m / 20 * (2 * (x1 * y1 + x2 * y2 + x3 * y3) + x1 * y2 + x2 * y1 + x1 * y3 + x3 * y1 + x2 * y3 + x3 * y2) iyz = m / 20 * (2 * (z1 * y1 + z2 * y2 + z3 * y3) + z1 * y2 + z2 * y1 + z1 * y3 + z3 * y1 + z2 * y3 + z3 * y2) izx = m / 20 * (2 * (x1 * z1 + x2 * z2 + x3 * z3) + x1 * z2 + x2 * z1 + x1 * z3 + x3 * z1 + x2 * z3 + x3 * z2) in { m11 = ixx , m12 = -ixy , m13 = -izx , m21 = -ixy , m22 = iyy , m23 = -iyz , m31 = -izx , m32 = -iyz , m33 = izz } {-| Eigendecomposition of a symmetric 3x3 matrix using Jacobi iteration. -} eigenDecomposition : Mat3 -> { eigenvalues : Vec3, v1 : Vec3, v2 : Vec3, v3 : Vec3 } eigenDecomposition { m11, m22, m33, m12, m13, m23 } = let result = jacobiIterate { steps = 30 , d11 = m11 , d22 = m22 , d33 = m33 , a12 = m12 , a13 = m13 , a23 = m23 , r11 = 1 , r21 = 0 , r31 = 0 , r12 = 0 , r22 = 1 , r32 = 0 , r13 = 0 , r23 = 0 , r33 = 1 } in { eigenvalues = { x = result.d11, y = result.d22, z = result.d33 } , v1 = { x = result.r11, y = result.r21, z = result.r31 } , v2 = { x = result.r12, y = result.r22, z = result.r32 } , v3 = { x = result.r13, y = result.r23, z = result.r33 } } jacobiIterate : { steps : Int, d11 : Float, d22 : Float, d33 : Float, a12 : Float, a13 : Float, a23 : Float, r11 : Float, r21 : Float, r31 : Float, r12 : Float, r22 : Float, r32 : Float, r13 : Float, r23 : Float, r33 : Float } -> { steps : Int, d11 : Float, d22 : Float, d33 : Float, a12 : Float, a13 : Float, a23 : Float, r11 : Float, r21 : Float, r31 : Float, r12 : Float, r22 : Float, r32 : Float, r13 : Float, r23 : Float, r33 : Float } jacobiIterate s = let abs12 = abs s.a12 abs13 = abs s.a13 abs23 = abs s.a23 in if s.steps <= 0 || (abs12 < 1.0e-12 && abs13 < 1.0e-12 && abs23 < 1.0e-12) then s else if abs12 - abs13 >= 0 && abs12 - abs23 >= 0 then let theta = (s.d22 - s.d11) / (2 * s.a12) t = jacobiT theta c = 1 / sqrt (1 + t * t) k = t * c in jacobiIterate { steps = s.steps - 1 , d11 = s.d11 - t * s.a12 , d22 = s.d22 + t * s.a12 , d33 = s.d33 , a12 = 0 , a13 = c * s.a13 - k * s.a23 , a23 = k * s.a13 + c * s.a23 , r11 = c * s.r11 - k * s.r12 , r21 = c * s.r21 - k * s.r22 , r31 = c * s.r31 - k * s.r32 , r12 = k * s.r11 + c * s.r12 , r22 = k * s.r21 + c * s.r22 , r32 = k * s.r31 + c * s.r32 , r13 = s.r13 , r23 = s.r23 , r33 = s.r33 } else if abs13 - abs23 >= 0 then let theta = (s.d33 - s.d11) / (2 * s.a13) t = jacobiT theta c = 1 / sqrt (1 + t * t) k = t * c in jacobiIterate { steps = s.steps - 1 , d11 = s.d11 - t * s.a13 , d22 = s.d22 , d33 = s.d33 + t * s.a13 , a12 = c * s.a12 - k * s.a23 , a13 = 0 , a23 = k * s.a12 + c * s.a23 , r11 = c * s.r11 - k * s.r13 , r21 = c * s.r21 - k * s.r23 , r31 = c * s.r31 - k * s.r33 , r12 = s.r12 , r22 = s.r22 , r32 = s.r32 , r13 = k * s.r11 + c * s.r13 , r23 = k * s.r21 + c * s.r23 , r33 = k * s.r31 + c * s.r33 } else let theta = (s.d33 - s.d22) / (2 * s.a23) t = jacobiT theta c = 1 / sqrt (1 + t * t) k = t * c in jacobiIterate { steps = s.steps - 1 , d11 = s.d11 , d22 = s.d22 - t * s.a23 , d33 = s.d33 + t * s.a23 , a12 = c * s.a12 - k * s.a13 , a13 = k * s.a12 + c * s.a13 , a23 = 0 , r11 = s.r11 , r21 = s.r21 , r31 = s.r31 , r12 = c * s.r12 - k * s.r13 , r22 = c * s.r22 - k * s.r23 , r32 = c * s.r32 - k * s.r33 , r13 = k * s.r12 + c * s.r13 , r23 = k * s.r22 + c * s.r23 , r33 = k * s.r32 + c * s.r33 } jacobiT : Float -> Float jacobiT theta = if theta >= 0 then 1 / (theta + sqrt (1 + theta * theta)) else 1 / (theta - sqrt (1 + theta * theta)) ================================================ FILE: src/Internal/NarrowPhase.elm ================================================ module Internal.NarrowPhase exposing (getContacts) import Collision.ConvexConvex import Collision.ParticleConvex import Collision.PlaneConvex import Collision.PlaneParticle import Collision.PlaneSphere import Collision.SphereConvex import Collision.SphereParticle import Collision.SphereSphere import Internal.Contact as Contact exposing (Contact, SolverContact) import Internal.Coordinates exposing (WorldCoordinates) import Internal.Material as Material exposing (Material) import Internal.Shape exposing (Shape(..)) getContacts : String -> List ( Shape WorldCoordinates, Material ) -> List ( Shape WorldCoordinates, Material ) -> List SolverContact getContacts idPrefix pairs1 pairs2 = case pairs1 of pair1 :: remainingPairs1 -> getContactsHelp idPrefix 1 pair1 remainingPairs1 1 pairs2 pairs2 [] [] -> [] getContactsHelp : String -> Int -> ( Shape WorldCoordinates, Material ) -> List ( Shape WorldCoordinates, Material ) -> Int -> List ( Shape WorldCoordinates, Material ) -> List ( Shape WorldCoordinates, Material ) -> List SolverContact -> List SolverContact getContactsHelp idPrefix shapeId1 pair1 currentPairs1 shapeId2 currentPairs2 pairs2 result = case currentPairs2 of pair2 :: remainingPairs2 -> getContactsHelp idPrefix shapeId1 pair1 currentPairs1 (shapeId2 + 1) remainingPairs2 pairs2 (addShapeContacts (idPrefix ++ "-" ++ String.fromInt shapeId1 ++ "-" ++ String.fromInt shapeId2) pair1 pair2 result) [] -> case currentPairs1 of newPair1 :: remainingPairs1 -> getContactsHelp idPrefix (shapeId1 + 1) newPair1 remainingPairs1 1 pairs2 pairs2 result [] -> result addShapeContacts : String -> ( Shape WorldCoordinates, Material ) -> ( Shape WorldCoordinates, Material ) -> List SolverContact -> List SolverContact addShapeContacts idPrefix ( shape1, mat1 ) ( shape2, mat2 ) contacts = let bounciness = Material.combineBounciness mat1.bounciness mat2.bounciness friction = Material.combineFriction mat1.friction mat2.friction rawContacts = addRawShapeContacts idPrefix shape1 shape2 [] in List.foldl (\contact acc -> { bounciness = bounciness , friction = friction , contact = contact } :: acc ) contacts rawContacts addRawShapeContacts : String -> Shape WorldCoordinates -> Shape WorldCoordinates -> List Contact -> List Contact addRawShapeContacts idPrefix shape1 shape2 contacts = case shape1 of Convex convex1 -> case shape2 of Convex convex2 -> Collision.ConvexConvex.addContacts idPrefix convex1 convex2 contacts Plane plane2 -> Collision.PlaneConvex.addContacts idPrefix Contact.flip plane2 convex1 contacts Sphere sphere2 -> Collision.SphereConvex.addContacts idPrefix Contact.flip sphere2 convex1 contacts Particle particle2 -> Collision.ParticleConvex.addContacts idPrefix Contact.flip particle2 convex1 contacts Plane plane1 -> case shape2 of Plane _ -> -- don't collide two planes contacts Convex convex2 -> Collision.PlaneConvex.addContacts idPrefix identity plane1 convex2 contacts Sphere sphere2 -> Collision.PlaneSphere.addContacts idPrefix identity plane1 sphere2 contacts Particle particle2 -> Collision.PlaneParticle.addContacts idPrefix identity plane1 particle2 contacts Sphere sphere1 -> case shape2 of Plane plane2 -> Collision.PlaneSphere.addContacts idPrefix Contact.flip plane2 sphere1 contacts Convex convex2 -> Collision.SphereConvex.addContacts idPrefix identity sphere1 convex2 contacts Sphere sphere2 -> Collision.SphereSphere.addContacts idPrefix sphere1 sphere2 contacts Particle particle2 -> Collision.SphereParticle.addContacts idPrefix identity sphere1 particle2 contacts Particle particle1 -> case shape2 of Plane plane2 -> Collision.PlaneParticle.addContacts idPrefix Contact.flip plane2 particle1 contacts Convex convex2 -> Collision.ParticleConvex.addContacts idPrefix identity particle1 convex2 contacts Sphere sphere2 -> Collision.SphereParticle.addContacts idPrefix Contact.flip sphere2 particle1 contacts Particle _ -> -- don't collide two particles contacts ================================================ FILE: src/Internal/Shape.elm ================================================ module Internal.Shape exposing ( CenterOfMassCoordinates , Shape(..) , centerOfMass , expandBoundingSphereRadius , inertia , placeIn , raycast , volume ) import Internal.Const as Const import Internal.Coordinates exposing (WorldCoordinates) import Internal.Matrix3 as Mat3 exposing (Mat3) import Internal.Transform3d as Transform3d exposing (Transform3d) import Internal.Vector3 as Vec3 exposing (Vec3) import Shapes.Convex as Convex exposing (Convex) import Shapes.Plane as Plane exposing (Plane) import Shapes.Sphere as Sphere exposing (Sphere) type CenterOfMassCoordinates = CenterOfMassCoordinates type Shape coordinates = Convex Convex | Plane Plane | Sphere Sphere | Particle Vec3 volume : Shape coordinates -> Float volume shape = case shape of Sphere sphere -> sphere.volume Convex convex -> convex.volume Plane _ -> 0 Particle _ -> 0 inertia : Shape coordinates -> Mat3 inertia shape = case shape of Sphere sphere -> sphere.inertia Convex convex -> convex.inertia Plane _ -> Mat3.zero Particle _ -> Mat3.zero centerOfMass : Shape coordinates -> Vec3 centerOfMass shape = case shape of Sphere sphere -> sphere.position Convex convex -> convex.position Plane _ -> Vec3.zero Particle _ -> Vec3.zero {-| Transforms shapes, reverses the original order -} placeIn : Transform3d coordinates { defines : originalCoords } -> Shape originalCoords -> Shape coordinates placeIn transform3d shape = case shape of Convex convex -> Convex (Convex.placeIn transform3d convex) Plane plane -> Plane (Plane.placeIn transform3d plane) Sphere sphere -> Sphere (Sphere.placeIn transform3d sphere) Particle position -> Particle (Transform3d.pointPlaceIn transform3d position) expandBoundingSphereRadius : Shape CenterOfMassCoordinates -> Float -> Float expandBoundingSphereRadius shape boundingSphereRadius = case shape of Convex convex -> Convex.expandBoundingSphereRadius convex boundingSphereRadius Sphere sphere -> Sphere.expandBoundingSphereRadius sphere boundingSphereRadius Plane _ -> Const.maxNumber Particle position -> max boundingSphereRadius (Vec3.length position) raycast : { from : Vec3, direction : Vec3 } -> Shape WorldCoordinates -> Maybe { distance : Float, point : Vec3, normal : Vec3 } raycast ray shape = case shape of Plane plane -> Plane.raycast ray plane Sphere sphere -> Sphere.raycast ray sphere Convex convex -> Convex.raycast ray convex Particle _ -> Nothing ================================================ FILE: src/Internal/Solver.elm ================================================ module Internal.Solver exposing (solve) import Array exposing (Array) import Dict exposing (Dict) import Internal.Body as Body exposing (Body) import Internal.Const as Const import Internal.Constraint exposing (ConstraintGroup) import Internal.Contact exposing (ContactGroup) import Internal.Equation as Equation exposing (EquationsGroup, SolverEquation) import Internal.Matrix3 as Mat3 import Internal.SolverBody as SolverBody exposing (SolverBody) import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 exposing (Vec3) {-| Fills unused slots in the solver body array. id = -1 is impossible for real bodies, so any array lookup that returns this sentinel can be ignored. -} sentinel : id -> SolverBody id sentinel extId = { body = { id = -1 , kind = Body.Static , transform3d = Transform3d.atOrigin , centerOfMassTransform3d = Transform3d.atOrigin , velocity = Vec3.zero , angularVelocity = Vec3.zero , mass = 0 , volume = 0 , shapesWithMaterials = [] , worldShapesWithMaterials = [] , force = Vec3.zero , torque = Vec3.zero , boundingSphereRadius = 0 , linearDamping = 0 , angularDamping = 0 , invMass = 0 , invInertia = Vec3.zero , invInertiaWorld = Mat3.zero , linearLock = Vec3.one , angularLock = Vec3.one } , extId = extId , vX = 0 , vY = 0 , vZ = 0 , wX = 0 , wY = 0 , wZ = 0 } {-| Build a sparse array indexed by body.id. Bodies may have non-consecutive IDs (e.g. 0, 2, 5) when some were added mid-simulation. Unused slots are filled with the sentinel. -} makeSolverBodies : Int -> List ( id, Body ) -> Array (SolverBody id) makeSolverBodies maxId bodiesWithIds = case bodiesWithIds of [] -> Array.empty ( firstExtId, _ ) :: _ -> List.foldl (\( extId, body ) arr -> Array.set body.id (SolverBody.fromBody extId body) arr) (Array.repeat (maxId + 1) (sentinel firstExtId)) bodiesWithIds {-| Apply the impulse corresponding to a seeded lambda to both solver bodies. This pre-loads the body delta-v so the solver starts from a warm state. -} applyGroupWarmStart : SolverBody id -> SolverBody id -> List SolverEquation -> ( SolverBody id, SolverBody id ) applyGroupWarmStart body1 body2 equations = case equations of [] -> ( body1, body2 ) { solverLambda, equation } :: rest -> if solverLambda == 0 then applyGroupWarmStart body1 body2 rest else let { vB, wA, wB } = equation newBody1 = case body1.body.kind of Body.Dynamic -> let invI1 = body1.body.invInertiaWorld k1 = solverLambda * body1.body.invMass in { body = body1.body , extId = body1.extId , vX = body1.vX - k1 * vB.x , vY = body1.vY - k1 * vB.y , vZ = body1.vZ - k1 * vB.z , wX = body1.wX + (invI1.m11 * wA.x + invI1.m12 * wA.y + invI1.m13 * wA.z) * solverLambda , wY = body1.wY + (invI1.m21 * wA.x + invI1.m22 * wA.y + invI1.m23 * wA.z) * solverLambda , wZ = body1.wZ + (invI1.m31 * wA.x + invI1.m32 * wA.y + invI1.m33 * wA.z) * solverLambda } _ -> body1 newBody2 = case body2.body.kind of Body.Dynamic -> let invI2 = body2.body.invInertiaWorld k2 = solverLambda * body2.body.invMass in { body = body2.body , extId = body2.extId , vX = body2.vX + k2 * vB.x , vY = body2.vY + k2 * vB.y , vZ = body2.vZ + k2 * vB.z , wX = body2.wX + (invI2.m11 * wB.x + invI2.m12 * wB.y + invI2.m13 * wB.z) * solverLambda , wY = body2.wY + (invI2.m21 * wB.x + invI2.m22 * wB.y + invI2.m23 * wB.z) * solverLambda , wZ = body2.wZ + (invI2.m31 * wB.x + invI2.m32 * wB.y + invI2.m33 * wB.z) * solverLambda } _ -> body2 in applyGroupWarmStart newBody1 newBody2 rest applyWarmStart : SolverBody id -> Array (SolverBody id) -> List EquationsGroup -> Array (SolverBody id) applyWarmStart prevBody1 solverBodies equationsGroups = case equationsGroups of [] -> Array.set prevBody1.body.id prevBody1 solverBodies { bodyId1, bodyId2, equations } :: rest -> let body1 = if prevBody1.body.id - bodyId1 == 0 then prevBody1 else case Array.get bodyId1 solverBodies of Just b -> b Nothing -> prevBody1 newSolverBodies = if prevBody1.body.id - bodyId1 == 0 || prevBody1.body.kind /= Body.Dynamic then solverBodies else Array.set prevBody1.body.id prevBody1 solverBodies body2 = case Array.get bodyId2 newSolverBodies of Just b -> b Nothing -> prevBody1 ( newBody1, newBody2 ) = applyGroupWarmStart body1 body2 equations in applyWarmStart newBody1 (if newBody2.body.kind == Body.Dynamic then Array.set bodyId2 newBody2 newSolverBodies else newSolverBodies ) rest solve : Float -> Vec3 -> Int -> List ConstraintGroup -> List ContactGroup -> Int -> List ( id, Body ) -> Dict String Float -> ( Array (SolverBody id), Dict String Float, Int ) solve dt gravity iterations constraintGroups contactGroups maxId bodiesWithIds lambdas = case bodiesWithIds of [] -> ( Array.empty, Dict.empty, 0 ) ( firstExtId, _ ) :: _ -> let ctx = { dt = dt , gravity = gravity , gravityLength = Vec3.length gravity , lambdas = lambdas } fillingBody = sentinel firstExtId solverBodies = makeSolverBodies maxId bodiesWithIds -- make equations from contacts contactEquationsGroups = List.foldl (\contactGroup groups -> Equation.contactEquationsGroup ctx contactGroup :: groups ) [] contactGroups -- add equations from constraints equationsGroups = List.foldl (\{ bodyId1, bodyId2, constraints } groups -> case Array.get bodyId1 solverBodies of Nothing -> groups Just body1 -> case Array.get bodyId2 solverBodies of Nothing -> groups Just body2 -> Equation.constraintEquationsGroup ctx body1.body body2.body constraints :: groups ) contactEquationsGroups constraintGroups -- warm start: apply cached lambdas as impulses to body delta-v warmStartedBodies = case equationsGroups of [] -> solverBodies { bodyId1 } :: _ -> case Array.get bodyId1 solverBodies of Just firstBody -> applyWarmStart firstBody solverBodies equationsGroups Nothing -> solverBodies ( finalSolverBodies, finalEquationsGroups, remainingIterations ) = step iterations 0 [] equationsGroups fillingBody warmStartedBodies iterationsUsed = max 1 (iterations - remainingIterations) finalLambdas = List.foldl (\{ equations } acc -> List.foldl (\{ equation, solverLambda } lambdaAcc -> if equation.id == "" then lambdaAcc else Dict.insert equation.id solverLambda lambdaAcc ) acc equations ) Dict.empty finalEquationsGroups in ( finalSolverBodies, finalLambdas, iterationsUsed ) step : Int -> Float -> List EquationsGroup -> List EquationsGroup -> SolverBody id -> Array (SolverBody id) -> ( Array (SolverBody id), List EquationsGroup, Int ) step remainingIterations deltalambdaTot equationsGroups currentEquationsGroups prevBody1 solverBodies = case currentEquationsGroups of [] -> if remainingIterations == 0 then -- the max number of iterations elapsed ( Array.set prevBody1.body.id prevBody1 solverBodies, equationsGroups, 0 ) else if deltalambdaTot - Const.precision < 0 then -- tolerance reached ( Array.set prevBody1.body.id prevBody1 solverBodies, equationsGroups, remainingIterations - 1 ) else -- requeue equationsGroups for the next step step (remainingIterations - 1) 0 [] (List.reverse equationsGroups) prevBody1 solverBodies { bodyId1, bodyId2, equations } :: remainingEquationsGroups -> let body1 = if prevBody1.body.id - bodyId1 == 0 then -- if the next equations group has the same body -- then no need to get it from the array prevBody1 else case Array.get bodyId1 solverBodies of Just nextBody -> nextBody Nothing -> -- this shouldn’t happen, we just -- don’t want to allocate a Just prevBody1 newSolverBodies = if prevBody1.body.id - bodyId1 == 0 || prevBody1.body.kind /= Body.Dynamic then -- if the next equations group has the same body, -- then no need to set it to the array -- also no need to update non-dynamic bodies (static, kinematic) solverBodies else Array.set prevBody1.body.id prevBody1 solverBodies body2 = case Array.get bodyId2 newSolverBodies of Just nextBody -> nextBody Nothing -> -- this shouldn’t happen, we just -- don’t want to allocate a Just prevBody1 groupContext = solveEquationsGroup body1 body2 [] deltalambdaTot equations in step remainingIterations groupContext.deltalambdaTot ({ bodyId1 = bodyId1 , bodyId2 = bodyId2 , equations = groupContext.equations } :: equationsGroups ) remainingEquationsGroups -- we don’t put body1 in the array, because we might need it -- in the next iteration, because this is the order in -- which we generated the contact equation groups (b1, b2), (b1, b3) -- this lets us reduce Array.set operations groupContext.body1 (if groupContext.body2.body.kind == Body.Dynamic then Array.set bodyId2 groupContext.body2 newSolverBodies else -- static and kinematic bodies don’t change newSolverBodies ) type alias GroupSolveResult id = { body1 : SolverBody id , body2 : SolverBody id , equations : List SolverEquation , deltalambdaTot : Float } solveEquationsGroup : SolverBody id -> SolverBody id -> List SolverEquation -> Float -> List SolverEquation -> GroupSolveResult id solveEquationsGroup body1 body2 equations deltalambdaTot currentEquations = case currentEquations of [] -> { body1 = body1 , body2 = body2 , equations = List.reverse equations , deltalambdaTot = deltalambdaTot } { solverLambda, equation } :: remainingEquations -> let { wA, vB, wB, minForce, maxForce, solverB, spookEps, solverInvC } = equation -- G x Wlambda, where W are the body velocities gWlambda = -(vB.x * body1.vX + vB.y * body1.vY + vB.z * body1.vZ) + (wA.x * body1.wX + wA.y * body1.wY + wA.z * body1.wZ) + (vB.x * body2.vX + vB.y * body2.vY + vB.z * body2.vZ) + (wB.x * body2.wX + wB.y * body2.wY + wB.z * body2.wZ) deltalambdaPrev = solverInvC * (solverB - gWlambda - spookEps * solverLambda) deltalambda = if solverLambda + deltalambdaPrev - minForce < 0 then minForce - solverLambda else if solverLambda + deltalambdaPrev - maxForce > 0 then maxForce - solverLambda else deltalambdaPrev newBody1 = case body1.body.kind of Body.Dynamic -> let invI1 = body1.body.invInertiaWorld k1 = deltalambda * body1.body.invMass in { body = body1.body , extId = body1.extId , vX = body1.vX - k1 * vB.x , vY = body1.vY - k1 * vB.y , vZ = body1.vZ - k1 * vB.z , wX = body1.wX + (invI1.m11 * wA.x + invI1.m12 * wA.y + invI1.m13 * wA.z) * deltalambda , wY = body1.wY + (invI1.m21 * wA.x + invI1.m22 * wA.y + invI1.m23 * wA.z) * deltalambda , wZ = body1.wZ + (invI1.m31 * wA.x + invI1.m32 * wA.y + invI1.m33 * wA.z) * deltalambda } _ -> -- static and kinematic bodies don’t respond to impulses body1 newBody2 = case body2.body.kind of Body.Dynamic -> let invI2 = body2.body.invInertiaWorld k2 = deltalambda * body2.body.invMass in { body = body2.body , extId = body2.extId , vX = body2.vX + k2 * vB.x , vY = body2.vY + k2 * vB.y , vZ = body2.vZ + k2 * vB.z , wX = body2.wX + (invI2.m11 * wB.x + invI2.m12 * wB.y + invI2.m13 * wB.z) * deltalambda , wY = body2.wY + (invI2.m21 * wB.x + invI2.m22 * wB.y + invI2.m23 * wB.z) * deltalambda , wZ = body2.wZ + (invI2.m31 * wB.x + invI2.m32 * wB.y + invI2.m33 * wB.z) * deltalambda } _ -> -- static and kinematic bodies don’t respond to impulses body2 in solveEquationsGroup newBody1 newBody2 ({ solverLambda = solverLambda + deltalambda , equation = equation } :: equations ) (deltalambdaTot + abs deltalambda) remainingEquations ================================================ FILE: src/Internal/SolverBody.elm ================================================ module Internal.SolverBody exposing ( SolverBody , fromBody , toBody ) import Internal.Body as Body exposing (Body) import Internal.Shape as Shape import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 exposing (Vec3) type alias SolverBody id = { body : Body , extId : id , vX : Float , vY : Float , vZ : Float , wX : Float , wY : Float , wZ : Float } fromBody : id -> Body -> SolverBody id fromBody extId body = { body = body , extId = extId , vX = 0 , vY = 0 , vZ = 0 , wX = 0 , wY = 0 , wZ = 0 } toBody : Float -> Vec3 -> SolverBody id -> Body toBody dt gravity { body, vX, vY, vZ, wX, wY, wZ } = case body.kind of Body.Static -> body Body.Kinematic -> let v = body.velocity w = body.angularVelocity newTransform3d = Transform3d.normalize (Transform3d.translateBy { x = v.x * dt, y = v.y * dt, z = v.z * dt } (Transform3d.rotateBy { x = w.x * dt, y = w.y * dt, z = w.z * dt } body.transform3d ) ) in { id = body.id , kind = body.kind , velocity = body.velocity , angularVelocity = body.angularVelocity , transform3d = newTransform3d , centerOfMassTransform3d = body.centerOfMassTransform3d , mass = body.mass , volume = body.volume , shapesWithMaterials = body.shapesWithMaterials , worldShapesWithMaterials = List.map (\( s, m ) -> ( Shape.placeIn newTransform3d s, m )) body.shapesWithMaterials , boundingSphereRadius = body.boundingSphereRadius , linearDamping = body.linearDamping , angularDamping = body.angularDamping , invMass = body.invMass , invInertia = body.invInertia , invInertiaWorld = body.invInertiaWorld , linearLock = body.linearLock , angularLock = body.angularLock -- clear forces , force = Vec3.zero , torque = Vec3.zero } Body.Dynamic -> let -- Apply damping https://code.google.com/archive/p/bullet/issues/74 ld = (1.0 - body.linearDamping) ^ dt ad = (1.0 - body.angularDamping) ^ dt newVelocity = { x = ((gravity.x + body.force.x * body.invMass) * dt + body.velocity.x * ld + vX) * body.linearLock.x , y = ((gravity.y + body.force.y * body.invMass) * dt + body.velocity.y * ld + vY) * body.linearLock.y , z = ((gravity.z + body.force.z * body.invMass) * dt + body.velocity.z * ld + vZ) * body.linearLock.z } velocityLength = Vec3.length newVelocity -- This hack is needed to minimize tunnelling -- we don't let the body to move more -- than half of its bounding radius in a frame cappedVelocity = if (velocityLength == 0) || (body.boundingSphereRadius == 0) || (velocityLength * dt - body.boundingSphereRadius < 0) then newVelocity else Vec3.scale (body.boundingSphereRadius / (velocityLength * dt)) newVelocity newAngularVelocity = { x = ((body.invInertiaWorld.m11 * body.torque.x + body.invInertiaWorld.m12 * body.torque.y + body.invInertiaWorld.m13 * body.torque.z) * dt + body.angularVelocity.x * ad + wX) * body.angularLock.x , y = ((body.invInertiaWorld.m21 * body.torque.x + body.invInertiaWorld.m22 * body.torque.y + body.invInertiaWorld.m23 * body.torque.z) * dt + body.angularVelocity.y * ad + wY) * body.angularLock.y , z = ((body.invInertiaWorld.m31 * body.torque.x + body.invInertiaWorld.m32 * body.torque.y + body.invInertiaWorld.m33 * body.torque.z) * dt + body.angularVelocity.z * ad + wZ) * body.angularLock.z } newTransform3d = Transform3d.normalize (Transform3d.translateBy { x = cappedVelocity.x * dt, y = cappedVelocity.y * dt, z = cappedVelocity.z * dt } (Transform3d.rotateBy { x = newAngularVelocity.x * dt, y = newAngularVelocity.y * dt, z = newAngularVelocity.z * dt } body.transform3d ) ) in { id = body.id , kind = body.kind , velocity = newVelocity , angularVelocity = newAngularVelocity , transform3d = newTransform3d , centerOfMassTransform3d = body.centerOfMassTransform3d , mass = body.mass , volume = body.volume , shapesWithMaterials = body.shapesWithMaterials , worldShapesWithMaterials = List.map (\( s, m ) -> ( Shape.placeIn newTransform3d s, m )) body.shapesWithMaterials , boundingSphereRadius = body.boundingSphereRadius , linearDamping = body.linearDamping , angularDamping = body.angularDamping , invMass = body.invMass , invInertia = body.invInertia , invInertiaWorld = Transform3d.invertedInertiaRotateIn newTransform3d body.invInertia , linearLock = body.linearLock , angularLock = body.angularLock -- clear forces , force = Vec3.zero , torque = Vec3.zero } ================================================ FILE: src/Internal/Transform3d.elm ================================================ module Internal.Transform3d exposing ( Transform3d , atOrigin , atPoint , directionPlaceIn , directionRelativeTo , directionsPlaceIn , fromOriginAndBasis , inertiaPlaceIn , inertiaRotateIn , inverse , invertedInertiaRotateIn , moveTo , normalize , orientation , originPoint , placeIn , pointPlaceIn , pointRelativeTo , pointsPlaceIn , relativeTo , rotateAroundOwn , rotateBy , translateBy ) import Internal.Matrix3 as Mat3 exposing (Mat3) import Internal.Vector3 as Vec3 exposing (Vec3) type Transform3d coordinates defines = Transform3d Vec3 Orientation3d fromOriginAndBasis : Vec3 -> Vec3 -> Vec3 -> Vec3 -> Transform3d coordinates defines fromOriginAndBasis origin x y z = let m00 = x.x m10 = x.y m20 = x.z m01 = y.x m11 = y.y m21 = y.z m02 = z.x m12 = z.y m22 = z.z tr = m00 + m11 + m22 in if tr > 0 then let s = -- s=4*qw sqrt (tr + 1.0) * 2 in Transform3d origin (Orientation3d ((m21 - m12) / s) ((m02 - m20) / s) ((m10 - m01) / s) (0.25 * s) ) else if (m00 - m11 > 0) && (m00 - m22 > 0) then let s = -- s=4*qx sqrt (1.0 + m00 - m11 - m22) * 2 in Transform3d origin (Orientation3d (0.25 * s) ((m01 + m10) / s) ((m02 + m20) / s) ((m21 - m12) / s) ) else if m11 - m22 > 0 then let s = -- s=4*qy sqrt (1.0 + m11 - m00 - m22) * 2 in Transform3d origin (Orientation3d ((m01 + m10) / s) (0.25 * s) ((m12 + m21) / s) ((m02 - m20) / s) ) else let s = -- s=4*qz sqrt (1.0 + m22 - m00 - m11) * 2 in Transform3d origin (Orientation3d ((m02 + m20) / s) ((m12 + m21) / s) (0.25 * s) ((m10 - m01) / s) ) atOrigin : Transform3d coordinates defines atOrigin = Transform3d Vec3.zero identity atPoint : Vec3 -> Transform3d coordinates defines atPoint point = Transform3d point identity {-| Transforms list of points, reverses the order -} pointsPlaceIn : Transform3d coordinates defines -> List Vec3 -> List Vec3 pointsPlaceIn transform points = pointsPlaceInHelp transform points [] pointsPlaceInHelp : Transform3d coordinates defines -> List Vec3 -> List Vec3 -> List Vec3 pointsPlaceInHelp transform points result = case points of point :: remainingPoints -> pointsPlaceInHelp transform remainingPoints (pointPlaceIn transform point :: result) [] -> result pointPlaceIn : Transform3d coordinates defines -> Vec3 -> Vec3 pointPlaceIn (Transform3d globalOrigin (Orientation3d qx qy qz qw)) { x, y, z } = let ix = qw * x + qy * z - qz * y iy = qw * y + qz * x - qx * z iz = qw * z + qx * y - qy * x iw = -qx * x - qy * y - qz * z in -- Vec3.add globalOrigin (rotate globalOrientation localPoint) { x = ix * qw + iw * -qx + iy * -qz - iz * -qy + globalOrigin.x , y = iy * qw + iw * -qy + iz * -qx - ix * -qz + globalOrigin.y , z = iz * qw + iw * -qz + ix * -qy - iy * -qx + globalOrigin.z } inertiaPlaceIn : Transform3d coordinates defines -> Vec3 -> Float -> Mat3 -> Mat3 inertiaPlaceIn ((Transform3d { x, y, z } _) as transform3d) centerOfMass mass inertia = let -- rotate inertia into the frame rotatedInertia = inertiaRotateIn transform3d inertia -- calculate the translation of inertia inertiaOffset = Mat3.pointInertia mass (x - centerOfMass.x) (y - centerOfMass.y) (z - centerOfMass.z) in Mat3.add rotatedInertia inertiaOffset inertiaRotateIn : Transform3d coordinates defines -> Mat3 -> Mat3 inertiaRotateIn transform3d inertia = let rotation = orientation transform3d in Mat3.mul rotation (Mat3.mul inertia (Mat3.transpose rotation)) invertedInertiaRotateIn : Transform3d coordinates defines -> Vec3 -> Mat3 invertedInertiaRotateIn transform3d invInertia = let { m11, m21, m31, m12, m22, m32, m13, m23, m33 } = orientation transform3d a = invInertia.x b = invInertia.y c = invInertia.z in { m11 = m11 * m11 * a + m12 * m12 * b + m13 * m13 * c , m21 = m21 * m11 * a + m22 * m12 * b + m23 * m13 * c , m31 = m31 * m11 * a + m32 * m12 * b + m33 * m13 * c , m12 = m11 * m21 * a + m12 * m22 * b + m13 * m23 * c , m22 = m21 * m21 * a + m22 * m22 * b + m23 * m23 * c , m32 = m31 * m21 * a + m32 * m22 * b + m33 * m23 * c , m13 = m11 * m31 * a + m12 * m32 * b + m13 * m33 * c , m23 = m21 * m31 * a + m22 * m32 * b + m23 * m33 * c , m33 = m31 * m31 * a + m32 * m32 * b + m33 * m33 * c } pointRelativeTo : Transform3d coordinates defines -> Vec3 -> Vec3 pointRelativeTo (Transform3d localOrigin localOrientation) worldPoint = derotate localOrientation (Vec3.sub worldPoint localOrigin) directionRelativeTo : Transform3d coordinates defines -> Vec3 -> Vec3 directionRelativeTo (Transform3d _ localOrientation) worldVector = derotate localOrientation worldVector directionPlaceIn : Transform3d coordinates defines -> Vec3 -> Vec3 directionPlaceIn (Transform3d _ globalOrientation) worldVector = rotate globalOrientation worldVector {-| Transforms list of points, reverses the order -} directionsPlaceIn : Transform3d coordinates defines -> List Vec3 -> List Vec3 directionsPlaceIn transform directions = directionsPlaceInHelp transform directions [] directionsPlaceInHelp : Transform3d coordinates defines -> List Vec3 -> List Vec3 -> List Vec3 directionsPlaceInHelp transform directions result = case directions of point :: remainingdirections -> directionsPlaceInHelp transform remainingdirections (directionPlaceIn transform point :: result) [] -> result placeIn : Transform3d globalCoordinates { defines : localCoordinates } -> Transform3d localCoordinates defines -> Transform3d globalCoordinates defines placeIn (Transform3d globalPosition globalOrientation) (Transform3d localPosition localOrientation) = Transform3d (Vec3.add globalPosition (rotate globalOrientation localPosition)) (mul globalOrientation localOrientation) relativeTo : Transform3d globalCoordinates { defines : localCoordinates } -> Transform3d globalCoordinates defines -> Transform3d localCoordinates defines relativeTo ((Transform3d _ (Orientation3d x y z w)) as t1) (Transform3d p2 o2) = Transform3d (pointRelativeTo t1 p2) -- Assuming normalized quaternion, we can conjugate it to inverse (mul (Orientation3d -x -y -z w) o2) {-| The same as `Transform.relativeTo Transform.atOrigin` -} inverse : Transform3d globalCoordinates { defines : localCoordinates } -> Transform3d localCoordinates { defines : globalCoordinates } inverse ((Transform3d _ (Orientation3d x y z w)) as transform3d) = Transform3d (pointRelativeTo transform3d Vec3.zero) -- Assuming normalized quaternion, we can conjugate it to inverse (Orientation3d -x -y -z w) normalize : Transform3d coordinates defines -> Transform3d coordinates defines normalize (Transform3d localOrigin (Orientation3d x y z w)) = let len = sqrt (x * x + y * y + z * z + w * w) in Transform3d localOrigin (Orientation3d (x / len) (y / len) (z / len) (w / len)) originPoint : Transform3d coordinates defines -> Vec3 originPoint (Transform3d localOrigin _) = localOrigin orientation : Transform3d coordinates defines -> Mat3 orientation (Transform3d _ (Orientation3d x y z w)) = { m11 = 1 - 2 * y * y - 2 * z * z , m12 = 2 * x * y - 2 * w * z , m13 = 2 * x * z + 2 * w * y , m21 = 2 * x * y + 2 * w * z , m22 = 1 - 2 * x * x - 2 * z * z , m23 = 2 * y * z - 2 * w * x , m31 = 2 * x * z - 2 * w * y , m32 = 2 * y * z + 2 * w * x , m33 = 1 - 2 * x * x - 2 * y * y } moveTo : Vec3 -> Transform3d coordinates defines1 -> Transform3d coordinates defines2 moveTo newOrigin (Transform3d _ localOrientation) = Transform3d newOrigin localOrientation translateBy : Vec3 -> Transform3d coordinates defines1 -> Transform3d coordinates defines2 translateBy vector (Transform3d localOrigin localOrientation) = Transform3d (Vec3.add vector localOrigin) localOrientation rotateAroundOwn : Vec3 -> Float -> Transform3d coordinates defines1 -> Transform3d coordinates defines2 rotateAroundOwn axis angle (Transform3d localOrigin localOrientation) = Transform3d localOrigin (mul (fromAngleAxis angle axis) localOrientation) {-| angularDistance = angularVelocity x dt -} rotateBy : Vec3 -> Transform3d coordinates defines1 -> Transform3d coordinates defines2 rotateBy { x, y, z } (Transform3d localOrigin (Orientation3d qx qy qz qw)) = Transform3d localOrigin (Orientation3d (qx + (x * qw + y * qz - z * qy) * 0.5) (qy + (y * qw + z * qx - x * qz) * 0.5) (qz + (z * qw + x * qy - y * qx) * 0.5) (qw + (-x * qx - y * qy - z * qz) * 0.5) ) type Orientation3d = Orientation3d Float Float Float Float identity : Orientation3d identity = Orientation3d 0 0 0 1 fromAngleAxis : Float -> Vec3 -> Orientation3d fromAngleAxis angle axis = let { x, y, z } = Vec3.normalize axis theta = angle * 0.5 c = cos theta s = sin theta in Orientation3d (x * s) (y * s) (z * s) c mul : Orientation3d -> Orientation3d -> Orientation3d mul (Orientation3d q1x q1y q1z q1w) (Orientation3d q2x q2y q2z q2w) = Orientation3d (q1x * q2w + q1y * q2z - q1z * q2y + q1w * q2x) (-q1x * q2z + q1y * q2w + q1z * q2x + q1w * q2y) (q1x * q2y - q1y * q2x + q1z * q2w + q1w * q2z) (-q1x * q2x - q1y * q2y - q1z * q2z + q1w * q2w) rotate : Orientation3d -> Vec3 -> Vec3 rotate (Orientation3d qx qy qz qw) { x, y, z } = -- faster quaternion rotation -- https://www.johndcook.com/blog/2021/06/16/faster-quaternion-rotations/ let ix = 2 * (qy * z - qz * y) iy = 2 * (qz * x - qx * z) iz = 2 * (qx * y - qy * x) in { x = x + qw * ix + qy * iz - qz * iy , y = y + qw * iy + qz * ix - qx * iz , z = z + qw * iz + qx * iy - qy * ix } derotate : Orientation3d -> Vec3 -> Vec3 derotate (Orientation3d qx qy qz qw) { x, y, z } = let ix = -qw * x + qy * z - qz * y iy = -qw * y + qz * x - qx * z iz = -qw * z + qx * y - qy * x iw = -qx * x - qy * y - qz * z in { x = ix * -qw + iw * -qx + iy * -qz - iz * -qy , y = iy * -qw + iw * -qy + iz * -qx - ix * -qz , z = iz * -qw + iw * -qz + ix * -qy - iy * -qx } ================================================ FILE: src/Internal/Vector3.elm ================================================ module Internal.Vector3 exposing ( Vec3 , add , almostZero , basis , cross , direction , distance , dot , length , lengthSquared , lerp , negate , normalize , one , scale , sub , tangents , xAxis , xNegative , yAxis , yNegative , zAxis , zNegative , zero ) import Internal.Const as Const almostZero : Vec3 -> Bool almostZero { x, y, z } = (abs x - Const.precision <= 0) && (abs y - Const.precision <= 0) && (abs z - Const.precision <= 0) {-| Three dimensional vector type -} type alias Vec3 = { x : Float , y : Float , z : Float } {-| x, y and z directions -} basis : List Vec3 basis = [ xAxis, yAxis, zAxis ] {-| The zero vector -} zero : Vec3 zero = { x = 0, y = 0, z = 0 } {-| The unit vector which points in the x direction: `vec3 1 0 0` -} xAxis : Vec3 xAxis = { x = 1, y = 0, z = 0 } {-| The unit vector which points in the y direction: `vec3 0 1 0` -} yAxis : Vec3 yAxis = { x = 0, y = 1, z = 0 } {-| The unit vector which points in the z direction: `vec3 0 0 1` -} zAxis : Vec3 zAxis = { x = 0, y = 0, z = 1 } {-| The unit vector which points in the direction opposite to x: `vec3 -1 0 0` -} xNegative : Vec3 xNegative = { x = -1, y = 0, z = 0 } {-| The unit vector which points in the direction opposite to y: `vec3 0 -1 0` -} yNegative : Vec3 yNegative = { x = 0, y = -1, z = 0 } {-| The unit vector which points in the direction opposite to z: `vec3 0 0 -1` -} zNegative : Vec3 zNegative = { x = 0, y = 0, z = -1 } one : Vec3 one = { x = 1, y = 1, z = 1 } {-| Vector addition: a + b -} add : Vec3 -> Vec3 -> Vec3 add a b = { x = a.x + b.x, y = a.y + b.y, z = a.z + b.z } {-| Vector subtraction: a - b -} sub : Vec3 -> Vec3 -> Vec3 sub a b = { x = a.x - b.x, y = a.y - b.y, z = a.z - b.z } {-| Vector negation: -a -} negate : Vec3 -> Vec3 negate v3 = { x = -v3.x, y = -v3.y, z = -v3.z } {-| The normalized direction from b to a: (a - b) / |a - b| -} direction : Vec3 -> Vec3 -> Vec3 direction a b = let c = sub a b len = length c in { x = c.x / len, y = c.y / len, z = c.z / len } {-| The length of the given vector: |a| -} length : Vec3 -> Float length { x, y, z } = sqrt (x * x + y * y + z * z) {-| The square of the length of the given vector: |a| \* |a| -} lengthSquared : Vec3 -> Float lengthSquared { x, y, z } = x * x + y * y + z * z {-| The distance between two vectors. -} distance : Vec3 -> Vec3 -> Float distance a b = let x = b.x - a.x y = b.y - a.y z = b.z - a.z in sqrt (x * x + y * y + z * z) {-| A unit vector with the same direction as the given vector: a / |a| -} normalize : Vec3 -> Vec3 normalize v3 = let len = length v3 in { x = v3.x / len, y = v3.y / len, z = v3.z / len } {-| Multiply the vector by a scalar: s \* v -} scale : Float -> Vec3 -> Vec3 scale s v3 = { x = s * v3.x, y = s * v3.y, z = s * v3.z } {-| The dot product of a and b -} dot : Vec3 -> Vec3 -> Float dot a b = a.x * b.x + a.y * b.y + a.z * b.z {-| The cross product of a and b -} cross : Vec3 -> Vec3 -> Vec3 cross a b = { x = a.y * b.z - a.z * b.y , y = a.z * b.x - a.x * b.z , z = a.x * b.y - a.y * b.x } {-| Get normalized tangents -} tangents : Vec3 -> ( Vec3, Vec3 ) tangents vec = if lengthSquared vec > 0 then let normalized = normalize vec v = normalize (if abs normalized.x < 0.9 then cross normalized xAxis else cross normalized yAxis ) in ( v, cross normalized v ) else ( xAxis, yAxis ) lerp : Float -> Vec3 -> Vec3 -> Vec3 lerp t v1 v2 = { x = v1.x + t * (v2.x - v1.x) , y = v1.y + t * (v2.y - v1.y) , z = v1.z + t * (v2.z - v1.z) } ================================================ FILE: src/Physics/Constraint.elm ================================================ module Physics.Constraint exposing ( Constraint , pointToPoint, hinge, distance, lock ) {-| @docs Constraint @docs pointToPoint, hinge, distance, lock -} import Axis3d exposing (Axis3d) import Direction3d import Frame3d exposing (Frame3d) import Internal.Constraint as Internal import Internal.Coordinates exposing (BodyCoordinates) import Length exposing (Length, Meters) import Physics.Types as Types import Point3d exposing (Point3d) {-| Limit the freedom of movement of two bodies relative to each other. Pass a `constrain` function in the [simulation config](Physics#Config). For example, to drag a body with the mouse, connect a mouse to a point on the box with [pointToPoint](#pointToPoint): constrain id = if id == "mouse" then Just (\otherId -> if otherId == "box" then [ pointToPoint Point3d.origin pointOnBox ] else [] ) else Nothing Constraints are skipped unless at least one body is [dynamic](Physics-Body#dynamic). -} type alias Constraint = Types.Constraint {-| Connect a point on the first body with a point on the second body. This doesn’t limit the freedom of rotation of two bodies. -} pointToPoint : Point3d Meters BodyCoordinates -> Point3d Meters BodyCoordinates -> Constraint pointToPoint pivot1 pivot2 = Internal.PointToPoint (Point3d.toMeters pivot1) (Point3d.toMeters pivot2) {-| Keep two bodies connected with each other and limit the freedom of rotation. Useful for e.g. connecting a window to a window frame, or to connect a wheel to a car. -} hinge : Axis3d Meters BodyCoordinates -> Axis3d Meters BodyCoordinates -> Constraint hinge axis3d1 axis3d2 = let pivot1 = Point3d.toMeters (Axis3d.originPoint axis3d1) axis1 = Direction3d.unwrap (Axis3d.direction axis3d1) pivot2 = Point3d.toMeters (Axis3d.originPoint axis3d2) axis2 = Direction3d.unwrap (Axis3d.direction axis3d2) in Internal.Hinge pivot1 axis1 pivot2 axis2 {-| Keep two bodies connected with each other and remove all degrees of freedom between bodies. -} lock : Frame3d Meters BodyCoordinates {} -> Frame3d Meters BodyCoordinates {} -> Constraint lock frame3d1 frame3d2 = let pivot1 = Point3d.toMeters (Frame3d.originPoint frame3d1) x1 = Direction3d.unwrap (Frame3d.xDirection frame3d1) y1 = Direction3d.unwrap (Frame3d.yDirection frame3d1) z1 = if Frame3d.isRightHanded frame3d1 then Direction3d.unwrap (Frame3d.zDirection frame3d1) else Direction3d.unwrap (Direction3d.reverse (Frame3d.zDirection frame3d1)) pivot2 = Point3d.toMeters (Frame3d.originPoint frame3d2) x2 = Direction3d.unwrap (Frame3d.xDirection frame3d2) y2 = Direction3d.unwrap (Frame3d.yDirection frame3d2) z2 = if Frame3d.isRightHanded frame3d2 then Direction3d.unwrap (Frame3d.zDirection frame3d2) else Direction3d.unwrap (Direction3d.reverse (Frame3d.zDirection frame3d2)) in Internal.Lock pivot1 x1 y1 z1 pivot2 x2 y2 z2 {-| Keep the centers of mass of two bodies at the constant distance from each other. -} distance : Length -> Constraint distance length = Internal.Distance (Length.inMeters length) ================================================ FILE: src/Physics/Lock.elm ================================================ module Physics.Lock exposing ( Lock , translateX, translateY, translateZ , rotateX, rotateY, rotateZ , allTranslation, allRotation ) {-| Restrict a body’s degrees of freedom along world axes. Pass a list of `Lock` tokens to [Physics.lock](Physics#lock). Each entry removes one degree of freedom from the body. The list fully describes the body’s lock state — calling `lock` again with a different list replaces the previous one. An empty list clears all locks. @docs Lock # Translation @docs translateX, translateY, translateZ # Rotation @docs rotateX, rotateY, rotateZ # Presets @docs allTranslation, allRotation -} import Internal.Lock as Internal import Physics.Types as Types {-| A single degree of freedom to lock. -} type alias Lock = Types.Lock {-| Lock translation along the world X axis. -} translateX : Lock translateX = Internal.TranslateX {-| Lock translation along the world Y axis. -} translateY : Lock translateY = Internal.TranslateY {-| Lock translation along the world Z axis. -} translateZ : Lock translateZ = Internal.TranslateZ {-| Lock rotation about the world X axis. -} rotateX : Lock rotateX = Internal.RotateX {-| Lock rotation about the world Y axis. -} rotateY : Lock rotateY = Internal.RotateY {-| Lock rotation about the world Z axis. -} rotateZ : Lock rotateZ = Internal.RotateZ {-| All three translation axes locked. The body cannot move. -} allTranslation : List Lock allTranslation = [ translateX, translateY, translateZ ] {-| All three rotation axes locked. The body cannot tip or spin. Useful for character capsules whose orientation is driven outside of physics. -} allRotation : List Lock allRotation = [ rotateX, rotateY, rotateZ ] ================================================ FILE: src/Physics/Material.elm ================================================ module Physics.Material exposing ( Material , wood, rubber, steel, ice, plastic , Dense, dense, Surface, surface ) {-| @docs Material @docs wood, rubber, steel, ice, plastic # Custom materials @docs Dense, dense, Surface, surface -} import Density exposing (Density) import Internal.Material as Internal import Physics.Types as Types {-| Material encodes friction, bounciness, and optionally density. The type parameter tracks capabilities: - `Material Dense` — carries density (required for volumetric bodies) - `Material Surface` — surface properties only (for static bodies and point masses) **Friction** controls how much a body resists sliding against another. 0 means frictionless (like ice), 1 means maximum grip (like rubber). **Bounciness** (coefficient of restitution) controls how much kinetic energy is preserved after a collision. 0 means no bounce (the body absorbs the impact), 1 means a perfectly elastic bounce. When two shapes collide, their friction values are combined using the geometric mean √(f1 · f2), so a slippery surface dominates. Bounciness uses the maximum of the two values, so the bouncier surface wins. -} type alias Material kind = Types.Material kind {-| Density 700 kg/m³, friction 0.4, bounciness 0.3. -} wood : Material any wood = Types.Material Internal.wood {-| Density 1100 kg/m³, friction 0.8, bounciness 0.7. -} rubber : Material any rubber = Types.Material Internal.rubber {-| Density 7800 kg/m³, friction 0.3, bounciness 0.2. -} steel : Material any steel = Types.Material Internal.steel {-| Density 900 kg/m³, friction 0.03, bounciness 0.1. -} ice : Material any ice = Types.Material Internal.ice {-| Density 1050 kg/m³, friction 0.35, bounciness 0.45. -} plastic : Material any plastic = Types.Material Internal.plastic {-| Material with density, required for dynamic bodies where mass is computed from geometry. -} type Dense = Dense Never {-| Create a dense material. Density is clamped to at least 1 kg/m³. Friction and bounciness are clamped to [0, 1]. -} dense : { density : Density, friction : Float, bounciness : Float } -> Material Dense dense cfg = Types.Material { density = max 1 (Density.inKilogramsPerCubicMeter cfg.density) , friction = clamp 0 1 cfg.friction , bounciness = clamp 0 1 cfg.bounciness } {-| Material with surface properties only — friction and bounciness, no density. Used for static bodies and point masses. -} type Surface = Surface Never {-| Create a surface material. Friction and bounciness are clamped to [0, 1]. -} surface : { friction : Float, bounciness : Float } -> Material Surface surface cfg = Types.Material { density = 0 , friction = clamp 0 1 cfg.friction , bounciness = clamp 0 1 cfg.bounciness } ================================================ FILE: src/Physics/Shape.elm ================================================ module Physics.Shape exposing ( Shape, block, sphere, cylinder , minus, plus, sum, unsafeConvex ) {-| @docs Shape, block, sphere, cylinder # Complex shapes @docs minus, plus, sum, unsafeConvex -} import Block3d exposing (Block3d) import Cylinder3d exposing (Cylinder3d) import Direction3d import Frame3d import Internal.Coordinates exposing (BodyCoordinates) import Internal.Shape as Internal import Internal.Transform3d as Transform3d import Length exposing (Meters) import Physics.Types as Types import Point3d exposing (Point3d) import Shapes.Convex as Convex import Shapes.Sphere as Sphere import Sphere3d exposing (Sphere3d) import TriangularMesh exposing (TriangularMesh) {-| Shapes are needed for creating compound [dynamic](Physics#dynamic) and [static](Physics#static) bodies. The supported primitive shapes are [block](#block), [sphere](#sphere), and [cylinder](#cylinder). For complex geometry use [unsafeConvex](#unsafeConvex). Shapes within a body **should not overlap** — composing shapes only affects physical properties like mass, inertia, and center of mass. Use [plus](#plus) and [minus](#minus) to combine shapes, for example to create hollow bodies. -} type alias Shape = Types.Shape {-| -} block : Block3d Meters BodyCoordinates -> Shape block block3d = let ( sizeX, sizeY, sizeZ ) = Block3d.dimensions block3d frame3d = Block3d.axes block3d rightHandedFrame3d = if Frame3d.isRightHanded frame3d then frame3d else Frame3d.reverseZ frame3d origin = Point3d.unwrap (Frame3d.originPoint rightHandedFrame3d) x = Direction3d.unwrap (Frame3d.xDirection rightHandedFrame3d) y = Direction3d.unwrap (Frame3d.yDirection rightHandedFrame3d) z = Direction3d.unwrap (Frame3d.zDirection rightHandedFrame3d) tranform3d = Transform3d.fromOriginAndBasis origin x y z in Types.Shape [ ( Internal.Convex (Convex.fromBlock (Length.inMeters sizeX) (Length.inMeters sizeY) (Length.inMeters sizeZ) |> Convex.placeIn tranform3d ) , 1 ) ] {-| -} sphere : Sphere3d Meters BodyCoordinates -> Shape sphere sphere3d = let radius = Length.inMeters (Sphere3d.radius sphere3d) origin = Point3d.toMeters (Sphere3d.centerPoint sphere3d) in Types.Shape [ ( Internal.Sphere (Sphere.atOrigin radius |> Sphere.placeIn (Transform3d.atPoint origin) ) , 1 ) ] {-| Create a cylinder shape with the given number of side faces, clamped to at least 3. Even numbers are more efficient, because collision performance depends on the number of unique non-parallel faces and edges. -} cylinder : Int -> Cylinder3d Meters BodyCoordinates -> Shape cylinder subdivisions cylinder3d = let ( a, b ) = Cylinder3d.axialDirection cylinder3d |> Direction3d.perpendicularBasis transform3d = Transform3d.fromOriginAndBasis (Point3d.toMeters (Cylinder3d.centerPoint cylinder3d)) (Direction3d.unwrap a) (Direction3d.unwrap b) (Direction3d.unwrap (Cylinder3d.axialDirection cylinder3d)) in Types.Shape [ ( Convex.fromCylinder (max 3 subdivisions) (Length.inMeters (Cylinder3d.radius cylinder3d)) (Length.inMeters (Cylinder3d.length cylinder3d)) |> Convex.placeIn transform3d |> Internal.Convex , 1 ) ] {-| Create a shape from a triangular mesh. This is useful if you want to import from Blender using [elm-obj-file](https://package.elm-lang.org/packages/w0rm/elm-obj-file/latest). **Note:** this may cause unexpected behavior, unless you make sure that: - the mesh is a [convex polyhedron](https://en.wikipedia.org/wiki/Convex_polytope); - the mesh is watertight, consisting of one closed surface; - all faces have counterclockwise [winding order](https://cmichel.io/understanding-front-faces-winding-order-and-normals). -} unsafeConvex : TriangularMesh (Point3d Meters BodyCoordinates) -> Shape unsafeConvex triangularMesh = let faceIndices = TriangularMesh.faceIndices triangularMesh vertices = triangularMesh |> TriangularMesh.mapVertices Point3d.toMeters |> TriangularMesh.vertices in Types.Shape [ ( Internal.Convex (Convex.fromTriangularMesh faceIndices vertices) , 1 ) ] {-| Add a shape to another. snowman = Shape.sphere bottom |> Shape.plus (Shape.sphere top) -} plus : Shape -> Shape -> Shape plus (Types.Shape toAdd) (Types.Shape base) = Types.Shape (base ++ toAdd) {-| Subtract the first shape from the second. The subtracted shape must be fully contained within the other. It reduces volume, mass, and inertia, and is excluded from collision detection. Useful for hollow bodies. crate = Shape.block outer |> Shape.minus (Shape.block inner) -} minus : Shape -> Shape -> Shape minus (Types.Shape toSubtract) (Types.Shape base) = Types.Shape (base ++ List.map (Tuple.mapSecond negate) toSubtract) {-| Combine a list of shapes. dumbbell = Shape.sum [ Shape.cylinder 12 leftWeight , Shape.cylinder 12 bar , Shape.cylinder 12 rightWeight ] -} sum : List Shape -> Shape sum = List.foldl plus (Types.Shape []) ================================================ FILE: src/Physics/Types.elm ================================================ module Physics.Types exposing (Body(..), Constraint, Contacts(..), Lock, Material(..), Shape(..)) import Array exposing (Array) import Dict exposing (Dict) import Internal.Body as InternalBody import Internal.Constraint as InternalConstraint import Internal.Contact as InternalContact import Internal.Coordinates exposing (BodyCoordinates) import Internal.Lock as InternalLock import Internal.Material as InternalMaterial import Internal.Shape as InternalShape import Internal.SolverBody as SolverBody import Internal.Vector3 exposing (Vec3) type Body = Body InternalBody.Body type Contacts id = Contacts { lambdas : Dict String Float , iterations : Int , dt : Float , gravity : Vec3 , contactGroups : List InternalContact.ContactGroup , solverBodies : Array (SolverBody.SolverBody id) } type alias Constraint = InternalConstraint.Constraint BodyCoordinates type alias Lock = InternalLock.Lock type Material kind = Material InternalMaterial.Material type Shape = Shape (List ( InternalShape.Shape BodyCoordinates, Float )) ================================================ FILE: src/Physics.elm ================================================ module Physics exposing ( Body, BodyCoordinates, WorldCoordinates , block, plane, sphere, cylinder, pointMass , moveTo, translateBy, rotateAround, place , simulate, onEarth, Config , Contacts, emptyContacts, contactPoints , frame, originPoint, velocity, angularVelocity, velocityAt , centerOfMass, mass , raycast, applyForce, applyImpulse, applyTorque, applyAngularImpulse , dynamic, static, kinematic , setVelocityTo, setAngularVelocityTo, scaleMassTo , damp, lock, applyInverseInertia, angularAccelerationFromTorque, angularVelocityDeltaFromAngularImpulse ) {-| # Bodies @docs Body, BodyCoordinates, WorldCoordinates @docs block, plane, sphere, cylinder, pointMass # Positioning @docs moveTo, translateBy, rotateAround, place # Simulation @docs simulate, onEarth, Config @docs Contacts, emptyContacts, contactPoints # Properties @docs frame, originPoint, velocity, angularVelocity, velocityAt @docs centerOfMass, mass # Interaction @docs raycast, applyForce, applyImpulse, applyTorque, applyAngularImpulse # Composite bodies @docs dynamic, static, kinematic # Overrides Change body state directly, bypassing the simulation. @docs setVelocityTo, setAngularVelocityTo, scaleMassTo # Advanced @docs damp, lock, applyInverseInertia, angularAccelerationFromTorque, angularVelocityDeltaFromAngularImpulse -} import Acceleration import Angle exposing (Angle) import AngularAcceleration exposing (RadiansPerSecondSquared) import AngularSpeed exposing (RadiansPerSecond) import Area exposing (SquareMeters) import Array exposing (Array) import Axis3d exposing (Axis3d) import Block3d exposing (Block3d) import Cylinder3d exposing (Cylinder3d) import Dict import Direction3d exposing (Direction3d) import Duration exposing (Duration, Seconds) import Force exposing (Newtons) import Frame3d exposing (Frame3d) import Internal.AssignIds import Internal.Body as InternalBody import Internal.BroadPhase as BroadPhase import Internal.Const as Const import Internal.Constraint as InternalConstraint import Internal.Contact as InternalContact import Internal.Coordinates import Internal.Shape as InternalShape import Internal.Solver as Solver import Internal.SolverBody as SolverBody import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 import Length exposing (Meters) import Mass exposing (Kilograms, Mass) import Physics.Constraint exposing (Constraint) import Physics.Lock exposing (Lock) import Physics.Material exposing (Dense, Material) import Physics.Shape as Shape exposing (Shape) import Physics.Types as Types import Plane3d exposing (Plane3d) import Point3d exposing (Point3d) import Quantity exposing (Product, Rate) import Speed exposing (MetersPerSecond) import Sphere3d exposing (Sphere3d) import Torque exposing (NewtonMeters) import Vector3d exposing (Vector3d) {-| -} type alias WorldCoordinates = Internal.Coordinates.WorldCoordinates {-| -} type alias BodyCoordinates = Internal.Coordinates.BodyCoordinates {-| A body is anything the simulation moves or collides — a ball, a box, a wall, a moving platform. It is defined in [BodyCoordinates](#BodyCoordinates) and positioned in [WorldCoordinates](#WorldCoordinates). Bodies start out centered on the origin; use [moveTo](#moveTo) to set the position. There are three kinds of bodies: - **dynamic** — moved by the engine in response to forces, gravity, and contacts. The default for [block](#block), [sphere](#sphere), [cylinder](#cylinder), and [pointMass](#pointMass); use [dynamic](#dynamic) to combine several [shapes](Physics-Shape#Shape) into one body. - **static** — never moves. Used for floors, walls, and other immovable scenery. The default for [plane](#plane); use [static](#static) to combine several [shapes](Physics-Shape#Shape) into one body. - **kinematic** — moves at the velocity you set via [setVelocityTo](#setVelocityTo) and [setAngularVelocityTo](#setAngularVelocityTo); ignores forces, gravity, and contacts, but other dynamic bodies feel its motion through friction. Used for moving platforms, elevators, and turntables. Construct with [kinematic](#kinematic). -} type alias Body = Types.Body {-| -} block : Block3d Meters BodyCoordinates -> Material Dense -> Body block block3d mat = dynamic [ ( Shape.block block3d, mat ) ] {-| Create a static plane, collidable only from the direction of the normal, e.g. for +Z: floor = Physics.plane Plane3d.xy Material.wood -} plane : Plane3d Meters BodyCoordinates -> Material any -> Body plane plane3d (Types.Material internalMat) = let position = Point3d.toMeters (Plane3d.originPoint plane3d) normal = Direction3d.unwrap (Plane3d.normalDirection plane3d) in Types.Body (InternalBody.compound InternalBody.Static [ ( InternalShape.Plane { position = position, normal = normal } , internalMat , 1 ) ] ) {-| -} sphere : Sphere3d Meters BodyCoordinates -> Material Dense -> Body sphere sphere3d mat = dynamic [ ( Shape.sphere sphere3d, mat ) ] {-| Create a cylinder, approximated with 12 side faces. For more subdivisions, use [dynamic](#dynamic) with [Shape.cylinder](Physics-Shape#cylinder). -} cylinder : Cylinder3d Meters BodyCoordinates -> Material Dense -> Body cylinder cylinder3d mat = dynamic [ ( Shape.cylinder 12 cylinder3d, mat ) ] {-| Create a point mass — a body with mass but no extent. Two point masses pass through each other; with all other bodies they collide normally. -} pointMass : Point3d Meters WorldCoordinates -> Mass -> Material any -> Body pointMass position massVal (Types.Material internalMat) = Types.Body (InternalBody.pointMass (Point3d.toMeters position) (max Const.precision (Mass.inKilograms massVal)) internalMat ) {-| Create a dynamic body from shapes and materials. Mass and center of mass are derived from geometry and density. -} dynamic : List ( Shape, Material Dense ) -> Body dynamic shapesWithMaterials = Types.Body (InternalBody.compound InternalBody.Dynamic (List.concatMap (\( Types.Shape entries, Types.Material internalMat ) -> List.map (\( shape, sign ) -> ( shape, internalMat, sign )) entries ) shapesWithMaterials ) ) {-| Create a static body from shapes and materials. Static bodies only collide with dynamic bodies, not other static bodies. -} static : List ( Shape, Material any ) -> Body static shapesWithMaterials = Types.Body (InternalBody.compound InternalBody.Static (List.concatMap (\( Types.Shape entries, Types.Material internalMat ) -> List.map (\( shape, sign ) -> ( shape, internalMat, sign )) entries ) shapesWithMaterials ) ) {-| Create a kinematic body from shapes and materials. Kinematic bodies have infinite mass like static bodies — forces, gravity, and contacts don’t push them — but they’re moved by the engine according to their velocity, set via [setVelocityTo](#setVelocityTo) and [setAngularVelocityTo](#setAngularVelocityTo). Useful for moving platforms, elevators, conveyor belts, and turntables. Dynamic bodies see the kinematic’s velocity at the contact, so a box rests on a moving elevator without sliding off, and a conveyor carries crates along its surface. elevator = Physics.kinematic [ ( Shape.block platform, Material.steel ) ] |> Physics.setVelocityTo (Vector3d.metersPerSecond 0 0 0.5) turntable = Physics.kinematic [ ( Shape.cylinder 16 disc, Material.plastic ) ] |> Physics.setAngularVelocityTo (Vector3d.withLength (AngularSpeed.radiansPerSecond 1) Direction3d.positiveZ ) -} kinematic : List ( Shape, Material any ) -> Body kinematic shapesWithMaterials = Types.Body (InternalBody.compound InternalBody.Kinematic (List.concatMap (\( Types.Shape entries, Types.Material internalMat ) -> List.map (\( shape, sign ) -> ( shape, internalMat, sign )) entries ) shapesWithMaterials ) ) {-| Set the position of the body, e.g. to raise a body 5 meters above the origin: movedBody = body |> moveTo (Point3d.meters 0 0 5) -} moveTo : Point3d Meters WorldCoordinates -> Body -> Body moveTo point3d (Types.Body body) = let bodyCoordinatesTransform3d = Transform3d.placeIn body.transform3d (Transform3d.inverse body.centerOfMassTransform3d) newTransform3d = Transform3d.placeIn (Transform3d.moveTo (Point3d.toMeters point3d) bodyCoordinatesTransform3d) body.centerOfMassTransform3d in Types.Body { body | transform3d = newTransform3d , worldShapesWithMaterials = List.map (\( s, m ) -> ( InternalShape.placeIn newTransform3d s, m )) body.shapesWithMaterials } {-| Move the body relative to its current position, e.g. to translate a body down by 5 meters: translatedBody = body |> translateBy (Vector3d.meters 0 0 -5) -} translateBy : Vector3d Meters WorldCoordinates -> Body -> Body translateBy vector3d (Types.Body body) = let bodyCoordinatesTransform3d = Transform3d.placeIn body.transform3d (Transform3d.inverse body.centerOfMassTransform3d) newTransform3d = Transform3d.placeIn (Transform3d.translateBy (Vector3d.toMeters vector3d) bodyCoordinatesTransform3d ) body.centerOfMassTransform3d in Types.Body { body | transform3d = newTransform3d , worldShapesWithMaterials = List.map (\( s, m ) -> ( InternalShape.placeIn newTransform3d s, m )) body.shapesWithMaterials } {-| Rotate the body around an axis in the world, e.g. to rotate a body 45 degrees around the Z axis: rotatedBody = body |> rotateAround Axis3d.z (Angle.degrees 45) -} rotateAround : Axis3d Meters WorldCoordinates -> Angle -> Body -> Body rotateAround axis angle (Types.Body body) = let bodyCoordinatesTransform3d = Transform3d.placeIn body.transform3d (Transform3d.inverse body.centerOfMassTransform3d) rotatedOrigin = Point3d.rotateAround axis angle (Point3d.fromMeters (Transform3d.originPoint bodyCoordinatesTransform3d) ) newBodyCoordinatesTransform3d = bodyCoordinatesTransform3d |> Transform3d.moveTo (Point3d.toMeters rotatedOrigin) |> Transform3d.rotateAroundOwn (Direction3d.unwrap (Axis3d.direction axis)) (Angle.inRadians angle) newTransform3d = Transform3d.placeIn newBodyCoordinatesTransform3d body.centerOfMassTransform3d in Types.Body { body | transform3d = newTransform3d , worldShapesWithMaterials = List.map (\( s, m ) -> ( InternalShape.placeIn newTransform3d s, m )) body.shapesWithMaterials , invInertiaWorld = if body.kind == InternalBody.Dynamic then Transform3d.invertedInertiaRotateIn newTransform3d body.invInertia else body.invInertiaWorld } {-| Set the position and orientation of a body. Like [moveTo](#moveTo) but also sets the orientation. placedBody = body |> place (Frame3d.atPoint (Point3d.meters 2 0 1) |> Frame3d.rotateAround Axis3d.z (Angle.degrees 45) ) Left-handed frames are not supported — Z is recomputed from X and Y. -} place : Frame3d Meters WorldCoordinates { defines : BodyCoordinates } -> Body -> Body place frame3d (Types.Body body) = let origin = Point3d.toMeters (Frame3d.originPoint frame3d) x = Direction3d.unwrap (Frame3d.xDirection frame3d) y = Direction3d.unwrap (Frame3d.yDirection frame3d) z = Vec3.cross x y newBodyCoordinatesTransform3d = Transform3d.fromOriginAndBasis origin x y z newTransform3d = Transform3d.placeIn newBodyCoordinatesTransform3d body.centerOfMassTransform3d in Types.Body { body | transform3d = newTransform3d , worldShapesWithMaterials = List.map (\( s, m ) -> ( InternalShape.placeIn newTransform3d s, m )) body.shapesWithMaterials , invInertiaWorld = if body.kind == InternalBody.Dynamic then Transform3d.invertedInertiaRotateIn newTransform3d body.invInertia else body.invInertiaWorld } {-| Simulates one frame. Returns updated bodies and contacts. Call this on a message from the `onAnimationFrame` subscription with [onEarth](#onEarth) to simulate 1/60th of a second in Earth gravity: ( simulated, contacts ) = simulate onEarth model.bodies To improve solver stability for stacked objects, pass contacts from the previous frame back via the config’s `contacts` field: ( simulated, contacts ) = simulate { onEarth | contacts = model.contacts } model.bodies -} simulate : Config id -> List ( id, Body ) -> ( List ( id, Body ), Contacts id ) simulate config bodiesWithIds = let (Types.Contacts cache) = config.contacts ( internalBodiesWithIds, maxId ) = Internal.AssignIds.assignIds bodiesWithIds gravityVec = Vector3d.unwrap config.gravity dt = Duration.inSeconds config.duration constraints = InternalConstraint.getConstraints config.constrain internalBodiesWithIds -- Sort bodies by projection onto gravity axis so BroadPhase -- discovers ground contacts first, giving bottom-up solver order -- regardless of user body list order. sortedBodies = let projection ( _, body ) = let p = Transform3d.originPoint body.transform3d in -(p.x * gravityVec.x + p.y * gravityVec.y + p.z * gravityVec.z) in List.sortBy projection internalBodiesWithIds contactGroups = BroadPhase.getContacts config.collide sortedBodies ( solverBodies, newLambdas, iters ) = Solver.solve dt gravityVec config.solverIterations constraints contactGroups maxId internalBodiesWithIds cache.lambdas in ( List.reverse (outputBodiesHelp dt gravityVec solverBodies internalBodiesWithIds []) , Types.Contacts { lambdas = newLambdas , iterations = iters , dt = dt , gravity = gravityVec , contactGroups = contactGroups , solverBodies = solverBodies } ) {-| A ready-to-use simulation config with Earth gravity pointing down (-Z) at 60 fps. Customize it by updating individual fields, see [Config](#Config) for available options. -} onEarth : Config id onEarth = { gravity = Vector3d.gees 0 0 -1 , duration = Duration.seconds (1 / 60) , solverIterations = 20 , contacts = emptyContacts , constrain = \_ -> Nothing , collide = \_ _ -> True } {-| Configures a simulation. onEarth = { gravity = Vector3d.gees 0 0 -1 , duration = Duration.seconds (1 / 60) , solverIterations = 20 , contacts = emptyContacts , constrain = \_ -> Nothing , collide = \_ _ -> True } - `gravity` — set the gravity vector, or `Vector3d.zero` for no gravity - `duration` — set to `Duration.seconds (1 / 60)` for 60 fps - `solverIterations` — balance between precision and performance, 20 is a sweet spot - `contacts` — pass [Contacts](#Contacts) from the previous frame for warm starting, or leave as default for cold start - `constrain` — limit body movement relative to each other, see [Constraint](Physics-Constraint#Constraint) - `collide` — decide which bodies can collide with each other -} type alias Config id = { gravity : Vector3d Acceleration.MetersPerSecondSquared WorldCoordinates , duration : Duration , solverIterations : Int , contacts : Contacts id , constrain : id -> Maybe (id -> List Constraint) , collide : id -> id -> Bool } {-| Contacts from the most recent simulation frame. Contains contact points and solver state for warm starting. -} type alias Contacts id = Types.Contacts id {-| Get contact points from the most recent simulation frame, filtered by a predicate. Each entry is a pair of body ids and a list of contact points between them. crash = Physics.contactPoints (\a b -> a == "jeep" && b == "wall") contacts -} contactPoints : (id -> id -> Bool) -> Contacts id -> List ( id, id, List (Point3d Meters WorldCoordinates) ) contactPoints predicate (Types.Contacts c) = contactPointsHelp predicate c.dt c.gravity c.contactGroups c.solverBodies [] {-| Empty contacts for the first simulation frame (no warm starting). -} emptyContacts : Contacts id emptyContacts = Types.Contacts { lambdas = Dict.empty , iterations = 0 , dt = 0 , gravity = Vec3.zero , contactGroups = [] , solverBodies = Array.empty } {-| Get the position and orientation of the body as Frame3d. Useful to transform points and directions between world and body coordinates, e.g. for rendering. -} frame : Body -> Frame3d Meters WorldCoordinates { defines : BodyCoordinates } frame (Types.Body { transform3d, centerOfMassTransform3d }) = let bodyCoordinatesTransform3d = Transform3d.placeIn transform3d (Transform3d.inverse centerOfMassTransform3d) { m11, m21, m31, m12, m22, m32, m13, m23, m33 } = Transform3d.orientation bodyCoordinatesTransform3d in Frame3d.unsafe { originPoint = Point3d.fromMeters (Transform3d.originPoint bodyCoordinatesTransform3d) , xDirection = Direction3d.unsafe { x = m11, y = m21, z = m31 } , yDirection = Direction3d.unsafe { x = m12, y = m22, z = m32 } , zDirection = Direction3d.unsafe { x = m13, y = m23, z = m33 } } {-| Get the origin point of a body. -} originPoint : Body -> Point3d Meters WorldCoordinates originPoint (Types.Body { transform3d, centerOfMassTransform3d }) = let bodyCoordinatesTransform3d = Transform3d.placeIn transform3d (Transform3d.inverse centerOfMassTransform3d) in Point3d.fromMeters (Transform3d.originPoint bodyCoordinatesTransform3d) {-| Get the current linear velocity of a body. -} velocity : Body -> Vector3d MetersPerSecond WorldCoordinates velocity (Types.Body body) = Vector3d.unsafe body.velocity {-| Get the current angular velocity of a body. -} angularVelocity : Body -> Vector3d RadiansPerSecond WorldCoordinates angularVelocity (Types.Body body) = Vector3d.unsafe body.angularVelocity {-| Get the linear velocity of a point on a body. Takes into account both linear and angular velocities. -} velocityAt : Point3d Meters WorldCoordinates -> Body -> Vector3d MetersPerSecond WorldCoordinates velocityAt position (Types.Body body) = let origin = Transform3d.originPoint body.transform3d { x, y, z } = Point3d.toMeters position originToPoint = { x = x - origin.x , y = y - origin.y , z = z - origin.z } cross = Vec3.cross body.angularVelocity originToPoint in Vector3d.unsafe (Vec3.add cross body.velocity) {-| Get the center of mass of a body. Returns Nothing for static and kinematic bodies (which have infinite mass). -} centerOfMass : Body -> Maybe (Point3d Meters WorldCoordinates) centerOfMass (Types.Body body) = case body.kind of InternalBody.Dynamic -> Just (Point3d.fromMeters (Transform3d.originPoint body.transform3d)) _ -> Nothing {-| Get the mass of a body. Returns Nothing for static and kinematic bodies (which have infinite mass). -} mass : Body -> Maybe Mass mass (Types.Body body) = case body.kind of InternalBody.Dynamic -> Just (Mass.kilograms body.mass) _ -> Nothing {-| Find the closest intersection of a ray against a list of bodies. - point masses are always excluded because they are infinitely small - a plane only intersects when the ray is facing the plane’s normal -} raycast : Axis3d Meters WorldCoordinates -> List ( id, Body ) -> Maybe ( id, Body, { point : Point3d Meters WorldCoordinates, normal : Direction3d WorldCoordinates } ) raycast axis bodiesWithIds = let ray = { direction = Direction3d.unwrap (Axis3d.direction axis) , from = Point3d.toMeters (Axis3d.originPoint axis) } in List.foldl (\( id, (Types.Body body) as originalBody ) closest -> case InternalBody.raycast ray body of Just result -> case closest of Just { bestDist } -> if result.distance < bestDist then Just { closestId = id, closestBody = originalBody, closestResult = result, bestDist = result.distance } else closest Nothing -> Just { closestId = id, closestBody = originalBody, closestResult = result, bestDist = result.distance } Nothing -> closest ) Nothing bodiesWithIds |> Maybe.map (\{ closestId, closestBody, closestResult } -> ( closestId , closestBody , { point = Point3d.fromMeters closestResult.point , normal = Direction3d.unsafe (Vec3.normalize closestResult.normal) } ) ) {-| Apply a force at a point on a body. Keep applying the force every simulation step to accelerate. force = Vector3d.withLength (Force.newtons 50) Direction3d.positiveY pushedBox = box |> applyForce force pointOnBox -} applyForce : Vector3d Newtons WorldCoordinates -> Point3d Meters WorldCoordinates -> Body -> Body applyForce force position ((Types.Body body) as original) = case body.kind of InternalBody.Dynamic -> Types.Body (InternalBody.applyForce (Vector3d.unwrap force) (Point3d.toMeters position) body ) _ -> original {-| Apply an impulse at a point on a body, adding to its velocity and angular velocity. impulse = Vector3d.withLength (Quantity.times (Duration.seconds 0.005) (Force.newtons 50) ) Direction3d.positiveY hitCueBall = cueBall |> applyImpulse impulse hitPoint -} applyImpulse : Vector3d (Product Newtons Seconds) WorldCoordinates -> Point3d Meters WorldCoordinates -> Body -> Body applyImpulse impulse position ((Types.Body body) as original) = case body.kind of InternalBody.Dynamic -> Types.Body (InternalBody.applyImpulse (Vector3d.unwrap impulse) (Point3d.toMeters position) body ) _ -> original {-| Apply a pure torque to a body — spin without translation. Keep applying the torque every simulation step to spin up. torque = Vector3d.withLength (Torque.newtonMeters 5) Direction3d.positiveZ spinningTop = top |> applyTorque torque -} applyTorque : Vector3d NewtonMeters WorldCoordinates -> Body -> Body applyTorque torque ((Types.Body body) as original) = case body.kind of InternalBody.Dynamic -> Types.Body (InternalBody.applyTorque (Vector3d.unwrap torque) body) _ -> original {-| Apply an angular impulse to a body, adding to its angular velocity. angularImpulse = Vector3d.withLength (Quantity.times (Duration.seconds 0.005) (Torque.newtonMeters 50) ) Direction3d.positiveZ spunUp = body |> applyAngularImpulse angularImpulse -} applyAngularImpulse : Vector3d (Product NewtonMeters Seconds) WorldCoordinates -> Body -> Body applyAngularImpulse angularImpulse ((Types.Body body) as original) = case body.kind of InternalBody.Dynamic -> Types.Body (InternalBody.applyAngularImpulse (Vector3d.unwrap angularImpulse) body) _ -> original {-| Replace the linear velocity of a body. Works on both [dynamic](#dynamic) and [kinematic](#kinematic) bodies. For dynamic bodies, prefer [applyImpulse](#applyImpulse) — using `setVelocityTo` after `applyImpulse` silently discards the impulse: body |> applyImpulse impulse point -- erased by the next line |> setVelocityTo newVelocity For kinematic bodies, this is the primary way to drive motion — the engine integrates the position from the velocity each frame. Has no effect on static bodies. -} setVelocityTo : Vector3d MetersPerSecond WorldCoordinates -> Body -> Body setVelocityTo newVelocity ((Types.Body body) as original) = case body.kind of InternalBody.Static -> original _ -> Types.Body { body | velocity = Vector3d.unwrap newVelocity } {-| Replace the angular velocity of a body. See [setVelocityTo](#setVelocityTo) for guidance. Has no effect on static bodies. -} setAngularVelocityTo : Vector3d RadiansPerSecond WorldCoordinates -> Body -> Body setAngularVelocityTo newAngularVelocity ((Types.Body body) as original) = case body.kind of InternalBody.Static -> original _ -> Types.Body { body | angularVelocity = Vector3d.unwrap newAngularVelocity } {-| Scale a body to the given mass. The volume and center of mass are preserved. Has no effect on static or kinematic bodies. -} scaleMassTo : Mass -> Body -> Body scaleMassTo desiredMass ((Types.Body body) as original) = let newMass = Mass.inKilograms desiredMass in case body.kind of InternalBody.Dynamic -> if newMass > 0 then let scale = body.mass / newMass newInvInertia = { x = body.invInertia.x * scale , y = body.invInertia.y * scale , z = body.invInertia.z * scale } in Types.Body { body | mass = newMass , invMass = 1 / newMass , invInertia = newInvInertia , invInertiaWorld = Transform3d.invertedInertiaRotateIn body.transform3d newInvInertia } else original _ -> original {-| Set linear and angular damping, in order to decrease velocity over time. These parameters specify the proportion of velocity lost per second. Values are clamped to [0, 1]. Default: 0.01 for both. -} damp : { linear : Float, angular : Float } -> Body -> Body damp { linear, angular } (Types.Body body) = Types.Body { body | linearDamping = clamp 0 1 linear , angularDamping = clamp 0 1 angular } {-| Restrict a body’s degrees of freedom along world axes. The list fully describes the lock state — calling `lock` again replaces the previous masks. An empty list clears all locks. -- 2D-in-3D gameplay on the XY plane body |> lock [ Lock.translateZ, Lock.rotateX, Lock.rotateY ] -- character controller: slides freely, never tips body |> lock Lock.allRotation See [Physics.Lock](Physics-Lock) for the available tokens. Has no effect on static or kinematic bodies. -} lock : List Lock -> Body -> Body lock locks ((Types.Body body) as original) = if body.kind == InternalBody.Dynamic then Types.Body (InternalBody.lock locks body) else original {-| Apply the inverse inertia tensor of a body to a vector. Returns Vector3d.zero for static and kinematic bodies. For common cases, see [angularAccelerationFromTorque](#angularAccelerationFromTorque) and [angularVelocityDeltaFromAngularImpulse](#angularVelocityDeltaFromAngularImpulse). -} applyInverseInertia : Body -> Vector3d units WorldCoordinates -> Vector3d (Rate units (Product Kilograms SquareMeters)) WorldCoordinates applyInverseInertia (Types.Body body) vector = case body.kind of InternalBody.Dynamic -> let { x, y, z } = Vector3d.unwrap vector in Vector3d.unsafe { x = body.invInertiaWorld.m11 * x + body.invInertiaWorld.m12 * y + body.invInertiaWorld.m13 * z , y = body.invInertiaWorld.m21 * x + body.invInertiaWorld.m22 * y + body.invInertiaWorld.m23 * z , z = body.invInertiaWorld.m31 * x + body.invInertiaWorld.m32 * y + body.invInertiaWorld.m33 * z } _ -> Vector3d.zero {-| Compute angular acceleration from torque: α = I⁻¹τ How fast will this torque make the body spin up? -} angularAccelerationFromTorque : Body -> Vector3d NewtonMeters WorldCoordinates -> Vector3d RadiansPerSecondSquared WorldCoordinates angularAccelerationFromTorque body torque = Vector3d.unsafe (Vector3d.unwrap (applyInverseInertia body torque)) {-| Compute angular velocity change from an angular impulse: Δω = I⁻¹L How much does this impulse add to my current spin? -} angularVelocityDeltaFromAngularImpulse : Body -> Vector3d (Product NewtonMeters Seconds) WorldCoordinates -> Vector3d RadiansPerSecond WorldCoordinates angularVelocityDeltaFromAngularImpulse body angularImpulse = Vector3d.unsafe (Vector3d.unwrap (applyInverseInertia body angularImpulse)) contactPointsHelp : (id -> id -> Bool) -> Float -> Vec3.Vec3 -> List InternalContact.ContactGroup -> Array (SolverBody.SolverBody id) -> List ( id, id, List (Point3d Meters WorldCoordinates) ) -> List ( id, id, List (Point3d Meters WorldCoordinates) ) contactPointsHelp predicate dt gravity internalContactGroups solverBodies acc = case internalContactGroups of [] -> acc contactGroup :: remainingContactGroups -> case Array.get contactGroup.body1.id solverBodies of Just solverBody1 -> case Array.get contactGroup.body2.id solverBodies of Just solverBody2 -> if predicate solverBody1.extId solverBody2.extId || predicate solverBody2.extId solverBody1.extId then let newBody1 = SolverBody.toBody dt gravity solverBody1 -- Transform contact points from pre-sim body1 frame to post-sim body1 frame transform = Transform3d.atOrigin |> Transform3d.relativeTo contactGroup.body1.transform3d |> Transform3d.placeIn newBody1.transform3d points = List.map (\{ contact } -> Point3d.fromMeters (Transform3d.pointPlaceIn transform contact.pi) ) contactGroup.contacts in contactPointsHelp predicate dt gravity remainingContactGroups solverBodies (( solverBody1.extId, solverBody2.extId, points ) :: acc) else contactPointsHelp predicate dt gravity remainingContactGroups solverBodies acc Nothing -> contactPointsHelp predicate dt gravity remainingContactGroups solverBodies acc Nothing -> contactPointsHelp predicate dt gravity remainingContactGroups solverBodies acc outputBodiesHelp : Float -> Vec3.Vec3 -> Array (SolverBody.SolverBody id) -> List ( id, InternalBody.Body ) -> List ( id, Body ) -> List ( id, Body ) outputBodiesHelp dt gravity solverBodies bodies acc = case bodies of [] -> acc ( extId, body ) :: rest -> case Array.get body.id solverBodies of Just solverBody -> outputBodiesHelp dt gravity solverBodies rest (( extId, Types.Body (SolverBody.toBody dt gravity solverBody) ) :: acc) Nothing -> outputBodiesHelp dt gravity solverBodies rest (( extId, Types.Body body ) :: acc) ================================================ FILE: src/Shapes/Convex.elm ================================================ module Shapes.Convex exposing ( Convex , Face , computeNormal , expandBoundingSphereRadius , extendContour , foldFaceEdges , fromBlock , fromCylinder , fromTriangularMesh , placeIn , raycast ) import Array exposing (Array) import Dict exposing (Dict) import Internal.Matrix3 as Mat3 exposing (Mat3) import Internal.Transform3d as Transform3d exposing (Transform3d) import Internal.Vector3 as Vec3 exposing (Vec3) import Set exposing (Set) type alias Face = { vertices : List Vec3 , normal : Vec3 } type alias Convex = { faces : List Face , vertices : List Vec3 -- cached for performance , uniqueEdges : List Vec3 -- unique edges , uniqueNormals : List Vec3 -- unique face normals , position : Vec3 , inertia : Mat3 , volume : Float } placeIn : Transform3d coordinates defines -> Convex -> Convex placeIn transform3d { faces, vertices, uniqueEdges, uniqueNormals, position, volume, inertia } = { faces = facesPlaceInHelp transform3d faces [] , vertices = Transform3d.pointsPlaceIn transform3d vertices , uniqueEdges = Transform3d.directionsPlaceIn transform3d uniqueEdges , uniqueNormals = Transform3d.directionsPlaceIn transform3d uniqueNormals , volume = volume , position = Transform3d.pointPlaceIn transform3d position , inertia = Transform3d.inertiaRotateIn transform3d inertia } {-| Places faces into the frame. -} facesPlaceInHelp : Transform3d coordinates defines -> List Face -> List Face -> List Face facesPlaceInHelp transform3d faces result = case faces of { vertices, normal } :: remainingFaces -> facesPlaceInHelp transform3d remainingFaces ({ vertices = List.reverse (Transform3d.pointsPlaceIn transform3d vertices) , normal = Transform3d.directionPlaceIn transform3d normal } :: result ) [] -> result fromTriangularMesh : List ( Int, Int, Int ) -> Array Vec3 -> Convex fromTriangularMesh faceIndices vertices = let faces = initFaces faceIndices vertices allVertices = Array.toList vertices averageCenter = case allVertices of { x, y, z } :: rest -> averageCenterHelp rest 1 x y z [] -> Vec3.zero ( volume, position, inertia ) = convexMassProperties averageCenter faceIndices vertices 0 0 0 0 Mat3.zero in { faces = faces , vertices = allVertices , uniqueEdges = List.foldl (\face edges -> foldFaceEdges (\v1 v2 -> addDirectionIfDistinct (Vec3.direction v1 v2) ) edges face.vertices ) [] faces , uniqueNormals = List.foldl (\{ normal } -> addDirectionIfDistinct normal ) [] faces , position = position , volume = volume , inertia = inertia } convexMassProperties : Vec3 -> List ( Int, Int, Int ) -> Array Vec3 -> Float -> Float -> Float -> Float -> Mat3 -> ( Float, Vec3, Mat3 ) convexMassProperties center faceIndices vertices cX cY cZ totalVolume totalInertia = case faceIndices of ( i1, i2, i3 ) :: rest -> case ( Array.get i1 vertices, Array.get i2 vertices, Array.get i3 vertices ) of ( Just p1, Just p2, Just p3 ) -> let newX = (center.x + p1.x + p2.x + p3.x) / 4 newY = (center.y + p1.y + p2.y + p3.y) / 4 newZ = (center.z + p1.z + p2.z + p3.z) / 4 volume = Vec3.dot (Vec3.sub p1 center) (Vec3.cross (Vec3.sub p2 center) (Vec3.sub p3 center)) / 6 newInertia = Mat3.tetrahedronInertia volume center p1 p2 p3 in convexMassProperties center rest vertices (cX + newX * volume) (cY + newY * volume) (cZ + newZ * volume) (totalVolume + volume) (Mat3.add totalInertia newInertia) _ -> convexMassProperties center rest vertices cX cY cZ totalVolume totalInertia [] -> let centerOfMass = { x = cX / totalVolume , y = cY / totalVolume , z = cZ / totalVolume } pointInertia = Mat3.pointInertia totalVolume (centerOfMass.x - center.x) (centerOfMass.y - center.y) (centerOfMass.z - center.z) in ( totalVolume , centerOfMass -- inertia about origin = inertia about center of mass + point inertia about origin -- inertia about center of mass = inertia about origin - point inertia about origin , Mat3.sub totalInertia pointInertia ) averageCenterHelp : List Vec3 -> Float -> Float -> Float -> Float -> Vec3 averageCenterHelp vertices n cX cY cZ = case vertices of { x, y, z } :: rest -> averageCenterHelp rest (n + 1) (cX + x) (cY + y) (cZ + z) [] -> { x = cX / n, y = cY / n, z = cZ / n } initFaces : List ( Int, Int, Int ) -> Array Vec3 -> List Face initFaces vertexIndices convexVertices = let faceByEdgeIndex = List.foldl (\(( i1, i2, i3 ) as indices) dict -> case Array.get i1 convexVertices of Just p1 -> case Array.get i2 convexVertices of Just p2 -> case Array.get i3 convexVertices of Just p3 -> let face = { indices = indices , normal = computeNormal p1 p2 p3 } in dict |> Dict.insert ( i1, i2 ) face |> Dict.insert ( i2, i3 ) face |> Dict.insert ( i3, i1 ) face Nothing -> dict Nothing -> dict Nothing -> dict ) Dict.empty vertexIndices in case vertexIndices of (( i1, i2, i3 ) as indices) :: _ -> case Array.get i1 convexVertices of Just p1 -> case Array.get i2 convexVertices of Just p2 -> case Array.get i3 convexVertices of Just p3 -> initFacesHelp (Set.singleton indices) convexVertices faceByEdgeIndex [] [ ( i2, i1 ), ( i3, i2 ), ( i1, i3 ) ] (computeNormal p1 p2 p3) [ i1, i2, i3 ] [] Nothing -> [] Nothing -> [] Nothing -> [] [] -> [] initFacesHelp : Set ( Int, Int, Int ) -> Array Vec3 -> Dict ( Int, Int ) { indices : ( Int, Int, Int ), normal : Vec3 } -> List { indices : ( Int, Int, Int ), normal : Vec3 } -> List ( Int, Int ) -> Vec3 -> List Int -> List Face -> List Face initFacesHelp visited vertices faceByEdgeIndex facesToCheck edgesToCheck currentNormal currentContour result = let adjacentFaces = edgesToCheck |> List.filterMap (\edge -> Dict.get edge faceByEdgeIndex) |> List.filter (\{ indices } -> not (Set.member indices visited)) ( coplanar, nonCoplanar ) = List.partition (\{ normal } -> Vec3.almostZero (Vec3.sub normal currentNormal)) adjacentFaces newVisited = List.foldl (\{ indices } -> Set.insert indices) visited coplanar newEdgesToCheck = List.foldl (\{ indices } res -> let ( i1, i2, i3 ) = indices in ( i2, i1 ) :: ( i3, i2 ) :: ( i1, i3 ) :: res ) [] coplanar newFacesToCheck = nonCoplanar ++ facesToCheck newContour = List.foldl (\{ indices } -> extendContour indices) currentContour coplanar in case coplanar of _ :: _ -> -- grow the contour initFacesHelp newVisited vertices faceByEdgeIndex newFacesToCheck newEdgesToCheck currentNormal newContour result [] -> -- couldn’t grow the contour let faceToAdd = { normal = currentNormal , vertices = List.filterMap (\i -> Array.get i vertices) newContour } updatedFacesToCheck = List.filter (\{ indices } -> not (Set.member indices newVisited)) newFacesToCheck in case updatedFacesToCheck of -- pick a non coplanar face { indices, normal } :: remainingFacesToCheck -> let ( i1, i2, i3 ) = indices in initFacesHelp (Set.insert indices newVisited) vertices faceByEdgeIndex remainingFacesToCheck [ ( i2, i1 ), ( i3, i2 ), ( i1, i3 ) ] normal [ i1, i2, i3 ] (faceToAdd :: result) -- end the recursion [] -> faceToAdd :: result extendContour : ( Int, Int, Int ) -> List Int -> List Int extendContour triangle currentContour = case currentContour of i1 :: _ :: _ -> extendContourHelp triangle i1 currentContour [] _ -> currentContour extendContourHelp : ( Int, Int, Int ) -> Int -> List Int -> List Int -> List Int extendContourHelp (( ti1, ti2, ti3 ) as triangle) i1 currentContour result = case currentContour of ci1 :: rest1 -> case rest1 of ci2 :: _ -> if (ci1 - ti2 == 0) && (ci2 - ti1 == 0) then -- insert ti3 List.reverse result ++ (ci1 :: ti3 :: rest1) else if (ci1 - ti3 == 0) && (ci2 - ti2 == 0) then -- insert ti1 List.reverse result ++ (ci1 :: ti1 :: rest1) else if (ci1 - ti1 == 0) && (ci2 - ti3 == 0) then -- insert ti2 List.reverse result ++ (ci1 :: ti2 :: rest1) else extendContourHelp triangle i1 rest1 (ci1 :: result) [] -> if (ci1 - ti2 == 0) && (i1 - ti1 == 0) then -- insert ti3 List.reverse (ti3 :: ci1 :: result) else if (ci1 - ti3 == 0) && (i1 - ti2 == 0) then -- insert ti1 List.reverse (ti1 :: ci1 :: result) else if (ci1 - ti1 == 0) && (i1 - ti3 == 0) then -- insert ti2 List.reverse (ti2 :: ci1 :: result) else List.reverse result [] -> List.reverse result computeNormal : Vec3 -> Vec3 -> Vec3 -> Vec3 computeNormal v1 v2 v3 = Vec3.normalize (Vec3.cross (Vec3.sub v3 v2) (Vec3.sub v1 v2)) fromBlock : Float -> Float -> Float -> Convex fromBlock sizeX sizeY sizeZ = let x = sizeX / 2 y = sizeY / 2 z = sizeZ / 2 v0 = { x = -x, y = -y, z = -z } v1 = { x = x, y = -y, z = -z } v2 = { x = x, y = y, z = -z } v3 = { x = -x, y = y, z = -z } v4 = { x = -x, y = -y, z = z } v5 = { x = x, y = -y, z = z } v6 = { x = x, y = y, z = z } v7 = { x = -x, y = y, z = z } volume = sizeX * sizeY * sizeZ inertia = { m11 = volume / 12 * (sizeY * sizeY + sizeZ * sizeZ) , m21 = 0 , m31 = 0 , m12 = 0 , m22 = volume / 12 * (sizeX * sizeX + sizeZ * sizeZ) , m32 = 0 , m13 = 0 , m23 = 0 , m33 = volume / 12 * (sizeY * sizeY + sizeX * sizeX) } in { faces = [ { vertices = [ v3, v2, v1, v0 ], normal = Vec3.zNegative } , { vertices = [ v4, v5, v6, v7 ], normal = Vec3.zAxis } , { vertices = [ v5, v4, v0, v1 ], normal = Vec3.yNegative } , { vertices = [ v2, v3, v7, v6 ], normal = Vec3.yAxis } , { vertices = [ v0, v4, v7, v3 ], normal = Vec3.xNegative } , { vertices = [ v1, v2, v6, v5 ], normal = Vec3.xAxis } ] , vertices = [ v0, v1, v2, v3, v4, v5, v6, v7 ] , uniqueEdges = Vec3.basis , uniqueNormals = Vec3.basis , volume = volume , position = Vec3.zero , inertia = inertia } fromCylinder : Int -> Float -> Float -> Convex fromCylinder subdivisions radius length = let top = length * 0.5 bottom = length * -0.5 sides = List.map (\value -> let r0 = 2 * pi * (toFloat value - 0.5) / toFloat subdivisions r1 = 2 * pi * toFloat value / toFloat subdivisions r2 = 2 * pi * (toFloat value + 0.5) / toFloat subdivisions in { normal = { x = sin r1, y = cos r1, z = 0 } , v0 = { x = sin r0 * radius, y = cos r0 * radius, z = top } , v1 = { x = sin r2 * radius, y = cos r2 * radius, z = top } , v2 = { x = sin r2 * radius, y = cos r2 * radius, z = bottom } , v3 = { x = sin r0 * radius, y = cos r0 * radius, z = bottom } } ) (List.range 0 (subdivisions - 1)) volume = 2 * pi * length * radius ^ 2 cap z = List.map (\value -> let r0 = 2 * pi * (toFloat value - 0.5) / toFloat subdivisions in { x = sin r0 * radius, y = cos r0 * radius, z = z } ) (List.range 0 (subdivisions - 1)) bottomCap = { vertices = cap bottom, normal = Vec3.zNegative } topCap = { vertices = List.reverse (cap top), normal = Vec3.zAxis } uniqueSideNormals = List.map .normal <| if modBy 2 subdivisions == 0 then List.take (subdivisions // 2) sides else sides in { faces = topCap :: bottomCap :: List.map (\{ v0, v1, v2, v3, normal } -> { vertices = [ v0, v1, v2, v3 ], normal = normal } ) sides , vertices = topCap.vertices ++ bottomCap.vertices , uniqueEdges = Vec3.zAxis -- Rotate normals by 90 degrees to get edge directions :: List.map (\{ x, y } -> { x = -y, y = x, z = 0 }) uniqueSideNormals , uniqueNormals = Vec3.zAxis :: uniqueSideNormals , position = Vec3.zero , inertia = Mat3.cylinderInertia volume radius length , volume = volume } {-| Add a candidate direction to a set if it is not a near duplicate or near opposite of a direction already in the set. -} addDirectionIfDistinct : Vec3 -> List Vec3 -> List Vec3 addDirectionIfDistinct currentNormal uniques = if List.any (\direction -> Vec3.almostZero (Vec3.cross direction currentNormal ) ) uniques then uniques else currentNormal :: uniques expandBoundingSphereRadius : Convex -> Float -> Float expandBoundingSphereRadius { vertices } boundingSphereRadius = vertices |> List.foldl (\vertex -> max (Vec3.lengthSquared vertex) ) (boundingSphereRadius * boundingSphereRadius) |> sqrt raycast : { from : Vec3, direction : Vec3 } -> Convex -> Maybe { distance : Float, point : Vec3, normal : Vec3 } raycast { direction, from } convex = List.foldl (\{ normal, vertices } maybeHit -> let dot = Vec3.dot direction normal point = case vertices of first :: _ -> first [] -> Vec3.zero in if dot < 0 then let pointToFrom = Vec3.sub point from scalar = Vec3.dot normal pointToFrom / dot in if scalar >= 0 then let intersectionPoint = { x = direction.x * scalar + from.x , y = direction.y * scalar + from.y , z = direction.z * scalar + from.z } isInsidePolygon = foldFaceEdges (\p1 p2 result -> result && (Vec3.dot (Vec3.sub intersectionPoint p1) (Vec3.cross normal (Vec3.sub p2 p1)) > 0 ) ) True vertices in if isInsidePolygon then case maybeHit of Just { distance } -> if scalar - distance < 0 then Just { distance = scalar , point = intersectionPoint , normal = normal } else maybeHit Nothing -> Just { distance = scalar , point = intersectionPoint , normal = normal } else maybeHit else maybeHit else maybeHit ) Nothing convex.faces {-| Map the function to pairs of consecutive vertices, starting with the pair (first, second), and so on, until (last, first). -} foldFaceEdges : (Vec3 -> Vec3 -> b -> b) -> b -> List Vec3 -> b foldFaceEdges fn resultSeed vertices = case vertices of first :: _ :: _ -> foldFaceEdgesHelp fn first resultSeed vertices _ -> -- The list is empty or contains one element. resultSeed foldFaceEdgesHelp : (Vec3 -> Vec3 -> b -> b) -> Vec3 -> b -> List Vec3 -> b foldFaceEdgesHelp fn seed resultSeed vertices = case vertices of el1 :: rest1 -> case rest1 of [] -> fn el1 seed resultSeed el2 :: _ -> foldFaceEdgesHelp fn seed (fn el1 el2 resultSeed) rest1 [] -> resultSeed ================================================ FILE: src/Shapes/Plane.elm ================================================ module Shapes.Plane exposing (Plane, placeIn, raycast) import Internal.Transform3d as Transform3d exposing (Transform3d) import Internal.Vector3 as Vec3 exposing (Vec3) type alias Plane = { normal : Vec3 , position : Vec3 } placeIn : Transform3d coordinates defines -> Plane -> Plane placeIn transform3d { normal, position } = { normal = Transform3d.directionPlaceIn transform3d normal , position = Transform3d.pointPlaceIn transform3d position } raycast : { from : Vec3, direction : Vec3 } -> Plane -> Maybe { distance : Float, point : Vec3, normal : Vec3 } raycast { from, direction } { normal, position } = let dot = Vec3.dot direction normal in if dot < 0 then let pointToFrom = Vec3.sub position from scalar = Vec3.dot normal pointToFrom / dot in if scalar >= 0 then Just { distance = scalar , point = { x = direction.x * scalar + from.x , y = direction.y * scalar + from.y , z = direction.z * scalar + from.z } , normal = normal } else Nothing else Nothing ================================================ FILE: src/Shapes/Sphere.elm ================================================ module Shapes.Sphere exposing ( Sphere , atOrigin , expandBoundingSphereRadius , placeIn , raycast ) import Internal.Matrix3 as Mat3 exposing (Mat3) import Internal.Transform3d as Transform3d exposing (Transform3d) import Internal.Vector3 as Vec3 exposing (Vec3) type alias Sphere = { radius : Float , position : Vec3 , volume : Float , inertia : Mat3 } atOrigin : Float -> Sphere atOrigin radius = let volume = 4 / 3 * pi * (radius ^ 3) in { radius = radius , position = Vec3.zero , volume = volume , inertia = Mat3.sphereInertia volume radius } placeIn : Transform3d coordinates defines -> Sphere -> Sphere placeIn transform3d { radius, position, volume, inertia } = { radius = radius , volume = volume , inertia = inertia , position = Transform3d.pointPlaceIn transform3d position } expandBoundingSphereRadius : Sphere -> Float -> Float expandBoundingSphereRadius { radius, position } boundingSphereRadius = max (Vec3.length position + radius) boundingSphereRadius raycast : { from : Vec3, direction : Vec3 } -> Sphere -> Maybe { distance : Float, point : Vec3, normal : Vec3 } raycast { from, direction } { position, radius } = let b = 2 * (direction.x * (from.x - position.x) + direction.y * (from.y - position.y) + direction.z * (from.z - position.z)) c = (from.x - position.x) * (from.x - position.x) + (from.y - position.y) * (from.y - position.y) + (from.z - position.z) * (from.z - position.z) - radius * radius delta = b * b - 4 * c in if delta < 0 then Nothing else let distance = (-b - sqrt delta) / 2 in if distance >= 0 then let point = { x = from.x + direction.x * distance , y = from.y + direction.y * distance , z = from.z + direction.z * distance } in Just { distance = distance , point = point -- normalized at the top level , normal = Vec3.sub point position } else Nothing ================================================ FILE: tests/BodyTest.elm ================================================ module BodyTest exposing (boundingSphereRadius, updateMassProperties, volume) import Expect import Extra.Expect as Expect import Fixtures.Convex as Convex import Internal.Body as Body import Internal.Const as Const import Internal.Material as Material import Internal.Shape as Shape exposing (Shape) import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 import Physics exposing (BodyCoordinates) import Shapes.Convex as Convex import Test exposing (Test, describe, test) boundingSphereRadius : Test boundingSphereRadius = describe "Body.boundingSphereRadius" [ test "is set to zero by default" <| \_ -> Expect.equal 0 (Body.compound Body.Dynamic [] |> .boundingSphereRadius) , test "addShape computes the bounding sphere radius" <| \_ -> Body.compound Body.Dynamic [ ( box 2 2 2, Material.wood, 1 ) ] |> .boundingSphereRadius |> Expect.within (Expect.Absolute 0.00001) (Vec3.length { x = 1, y = 1, z = 1 }) , test "addShape expands the bounding sphere radius" <| \_ -> Body.compound Body.Dynamic [ ( box 2 2 2, Material.wood, 1 ), ( box 4 4 4, Material.wood, 1 ) ] |> .boundingSphereRadius |> Expect.within (Expect.Absolute 0.00001) (Vec3.length { x = 2, y = 2, z = 2 }) , test "addShape sets the bounding sphere radius to maxNumber for a plane shape" <| \_ -> Body.compound Body.Dynamic [ ( plane, Material.wood, 1 ) ] |> .boundingSphereRadius |> Expect.atLeast Const.maxNumber ] updateMassProperties : Test updateMassProperties = describe "Body.compound inertia" [ test "compound body out of two cubes has the same invInertia as a block with twice the length" <| \_ -> let mat = { friction = 0, bounciness = 0, density = 700 } body1 = Body.compound Body.Dynamic [ ( Convex.fromBlock 2 2 2 |> Convex.placeIn (Transform3d.atPoint { x = -1, y = 0, z = 0 }) |> Shape.Convex , mat , 1 ) , ( Convex.fromBlock 2 2 2 |> Convex.placeIn (Transform3d.atPoint { x = 1, y = 0, z = 0 }) |> Shape.Convex , mat , 1 ) ] body2 = Body.compound Body.Dynamic [ ( box 4 2 2, mat, 1 ) ] in Expect.vec3 body1.invInertia body2.invInertia , test "cube box body should have the same invInertia as a compound body out of tetrahedrons" <| \_ -> let mat = { friction = 0, bounciness = 0, density = 700 } body1 = Body.compound Body.Dynamic (Convex.blockOfTetrahedrons 2 3 1 |> List.map (\s -> ( Shape.Convex s, mat, 1 )) ) body2 = Body.compound Body.Dynamic [ ( Shape.Convex (Convex.block Transform3d.atOrigin 2 3 1), mat, 1 ) ] in Expect.vec3 body1.invInertia body2.invInertia ] volume : Test volume = describe "Body.volume" [ test "solid box has volume equal to its dimensions" <| \_ -> Body.compound Body.Dynamic [ ( box 2 3 4, Material.wood, 1 ) ] |> .volume |> Expect.within (Expect.Absolute 0.00001) (2 * 3 * 4) , test "hollow crate has correct mass (outer minus inner)" <| \_ -> -- mass = density × net_volume = 700 × (1 - 0.8³) Body.compound Body.Dynamic [ ( box 1 1 1, Material.wood, 1 ) , ( box 0.8 0.8 0.8, Material.wood, -1 ) ] |> .mass |> Expect.within (Expect.Absolute 0.001) (700 * (1 - 0.8 ^ 3)) , test "hollow crate subtracts void volume" <| \_ -> -- outer 1×1×1, inner void 0.8×0.8×0.8 -- net volume = 1 - 0.512 = 0.488 Body.compound Body.Dynamic [ ( box 1 1 1, Material.wood, 1 ) , ( box 0.8 0.8 0.8, Material.wood, -1 ) ] |> .volume |> Expect.within (Expect.Absolute 0.00001) (1 - 0.8 ^ 3) , test "hollow crate has correct inertia (outer minus inner)" <| \_ -> -- For a uniform box a×a×a with density ρ: -- mass = ρ·a³, Ixx = mass/12·(a²+a²) = ρ·a⁵/6 -- invIxx = 6/(ρ·a⁵) -- Hollow: invIxx_net = 1/(Ixx_outer - Ixx_inner) -- = 1/(ρ/6·(1 - 0.8⁵)) = 6/(ρ·(1-0.8⁵)) let rho = 700 expectedInvIxx = 6 / (rho * (1 - 0.8 ^ 5)) hollow = Body.compound Body.Dynamic [ ( box 1 1 1, Material.wood, 1 ) , ( box 0.8 0.8 0.8, Material.wood, -1 ) ] in hollow.invInertia.x |> Expect.within (Expect.Relative 0.0001) expectedInvIxx ] box : Float -> Float -> Float -> Shape BodyCoordinates box sizeX sizeY sizeZ = Shape.Convex (Convex.fromBlock sizeX sizeY sizeZ) plane : Shape BodyCoordinates plane = Shape.Plane { position = Vec3.zero, normal = Vec3.zAxis } ================================================ FILE: tests/Collision/ConvexConvexTest.elm ================================================ module Collision.ConvexConvexTest exposing ( addContacts , findSeparatingAxis , project , testSeparatingAxis ) import Collision.ConvexConvex import Expect import Internal.Const as Const import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 import Shapes.Convex as Convex import Test exposing (Test, describe, test) addContacts : Test addContacts = describe "Collision.ConvexConvex.addContacts" [ test "should return 4 results" <| \_ -> let convex = Convex.fromBlock 2 2 2 t1 = -- going slightly into another box Transform3d.atPoint { x = 0, y = 0, z = 2.1 } |> Transform3d.rotateAroundOwn Vec3.yAxis (pi / 2) t2 = Transform3d.atPoint { x = 0, y = 0, z = 4 } |> Transform3d.rotateAroundOwn Vec3.yAxis (pi / 2) in Collision.ConvexConvex.addContacts "" (Convex.placeIn t1 convex) (Convex.placeIn t2 convex) [] |> List.length |> Expect.equal 4 , test "should return 2 results" <| \_ -> let convex1 = Convex.fromBlock 1.2 1.2 1.2 convex2 = Convex.fromBlock 1 1 1 transform3d1 = Transform3d.atPoint { x = -0.5, y = 0, z = 0 } |> Transform3d.rotateAroundOwn Vec3.zAxis (pi / 2) transform3d2 = Transform3d.atPoint { x = 0.5, y = 0, z = 0 } |> Transform3d.rotateAroundOwn Vec3.zAxis (pi / 4) in Collision.ConvexConvex.addContacts "" (Convex.placeIn transform3d1 convex1) (Convex.placeIn transform3d2 convex2) [] |> List.length |> Expect.equal 2 ] testSeparatingAxis : Test testSeparatingAxis = describe "Collision.ConvexConvex.testSeparatingAxis" [ test "returns Just depth" <| \_ -> let convex1 = Convex.fromBlock 1 1 1 |> Convex.placeIn (Transform3d.atPoint { x = -0.2, y = 0, z = 0 }) convex2 = Convex.fromBlock 1 1 1 |> Convex.placeIn (Transform3d.atPoint { x = 0.2, y = 0, z = 0 }) in Expect.equal (Collision.ConvexConvex.testSeparatingAxis convex1 convex2 Vec3.xAxis) (Just 0.6) , test "returns Nothing" <| \_ -> let convex1 = Convex.fromBlock 1 1 1 |> Convex.placeIn (Transform3d.atPoint { x = -5.2, y = 0, z = 0 }) convex2 = Convex.fromBlock 1 1 1 |> Convex.placeIn (Transform3d.atPoint { x = 0.2, y = 0, z = 0 }) in Expect.equal (Collision.ConvexConvex.testSeparatingAxis convex1 convex2 Vec3.xAxis) Nothing , test "works with rotation" <| \_ -> case let convex1 = Convex.fromBlock 1 1 1 |> Convex.placeIn (Transform3d.atPoint { x = 1, y = 0, z = 0 }) convex2 = Convex.fromBlock 1 1 1 |> Convex.placeIn (Transform3d.atPoint { x = 0.2, y = 0, z = 0 } |> Transform3d.rotateAroundOwn Vec3.zAxis (pi / 4) ) in Collision.ConvexConvex.testSeparatingAxis convex1 convex2 Vec3.xAxis of Nothing -> Expect.fail "expected depth" Just value -> Expect.within (Expect.Absolute 0.00001) 0.4071067 value ] findSeparatingAxis : Test findSeparatingAxis = describe "Collision.ConvexConvex.findSeparatingAxis" [ test "works for offset" <| \_ -> let convex1 = Convex.fromBlock 1 1 1 |> Convex.placeIn (Transform3d.atPoint { x = -0.2, y = 0, z = 0 }) convex2 = Convex.fromBlock 1 1 1 |> Convex.placeIn (Transform3d.atPoint { x = 0.2, y = 0, z = 0 }) in Expect.equal (Collision.ConvexConvex.findSeparatingAxis convex1 convex2) (Just { x = -1, y = 0, z = 0 }) , test "works for rotation" <| \_ -> let convex1 = Convex.fromBlock 1 1 1 |> Convex.placeIn (Transform3d.atPoint { x = -0.2, y = 0, z = 0 }) convex2 = Convex.fromBlock 1 1 1 |> Convex.placeIn (Transform3d.atPoint { x = 0.2, y = 0, z = 0 } |> Transform3d.rotateAroundOwn Vec3.zAxis (pi / 4) ) in Expect.equal (Collision.ConvexConvex.findSeparatingAxis convex1 convex2) (Just { x = -1, y = 0, z = 0 }) ] project : Test project = describe "Collision.ConvexConvex.project" [ test "works for the positive x axis" <| \_ -> Expect.equal (Collision.ConvexConvex.project Vec3.xAxis Const.maxNumber -Const.maxNumber (Convex.fromBlock 1 1 1).vertices ) { min = -0.5, max = 0.5 } , test "works for the negative x axis" <| \_ -> Expect.equal (Collision.ConvexConvex.project { x = -1, y = 0, z = 0 } Const.maxNumber -Const.maxNumber (Convex.fromBlock 1 1 1).vertices ) { min = -0.5, max = 0.5 } , test "works for the positive y axis" <| \_ -> Expect.equal (Collision.ConvexConvex.project Vec3.yAxis Const.maxNumber -Const.maxNumber (Convex.fromBlock 1 1 1).vertices ) { min = -0.5, max = 0.5 } , test "works for the offset" <| \_ -> Expect.equal (Collision.ConvexConvex.project Vec3.yAxis Const.maxNumber -Const.maxNumber (Convex.fromBlock 1 1 1 |> Convex.placeIn (Transform3d.atPoint { x = 0, y = 1, z = 0 }) ).vertices ) { min = 0.5, max = 1.5 } , test "works for the rotation and offset" <| \_ -> Collision.ConvexConvex.project Vec3.yAxis Const.maxNumber -Const.maxNumber (Convex.fromBlock 1 1 1 |> Convex.placeIn (Transform3d.atPoint { x = 0, y = 1, z = 0 } |> Transform3d.rotateAroundOwn Vec3.xAxis (pi / 2) ) ).vertices |> Expect.all [ .min >> Expect.within (Expect.Absolute 0.00001) 0.5 , .max >> Expect.within (Expect.Absolute 0.00001) 1.5 ] ] ================================================ FILE: tests/Collision/PlaneSphereTest.elm ================================================ module Collision.PlaneSphereTest exposing (addContacts) import Collision.PlaneSphere import Extra.Expect as Expect import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 import Shapes.Sphere as Sphere import Test exposing (Test, describe, test) addContacts : Test addContacts = let radius = 1 plane = { position = Vec3.zero, normal = Vec3.zAxis } sphere = Sphere.atOrigin radius |> Sphere.placeIn (Transform3d.atPoint { x = 0, y = 0, z = radius }) delta = 0.3 overlappingSphere = Sphere.atOrigin radius |> Sphere.placeIn (Transform3d.atPoint { x = 0, y = 0, z = radius - delta }) nonCollidingSphere = Sphere.atOrigin radius |> Sphere.placeIn (Transform3d.atPoint { x = 0, y = 0, z = radius + delta }) in describe "Collision.PlaneSphere.addContacts" [ test "exact collision" <| \_ -> Collision.PlaneSphere.addContacts "" identity plane sphere [] |> Expect.contacts [ { id = "" , ni = { x = 0, y = 0, z = 1 } , pi = { x = 0, y = 0, z = 0 } , pj = { x = 0, y = 0, z = 0 } } ] , test "overlapping collision" <| \_ -> Collision.PlaneSphere.addContacts "" identity plane overlappingSphere [] |> Expect.contacts [ { id = "" , ni = { x = 0, y = 0, z = 1 } , pi = { x = 0, y = 0, z = 0 } , pj = { x = 0, y = 0, z = -delta } } ] , test "no collision" <| \_ -> Collision.PlaneSphere.addContacts "" identity plane nonCollidingSphere [] |> Expect.contacts [] ] ================================================ FILE: tests/Collision/SphereConvexTest.elm ================================================ module Collision.SphereConvexTest exposing (addContacts) import Collision.SphereConvex import Expect import Extra.Expect as Expect import Fixtures.Convex import Fixtures.NarrowPhase import Internal.Transform3d as Transform3d import Shapes.Convex as Convex import Shapes.Sphere as Sphere import Test exposing (Test, describe, test) addContacts : Test addContacts = let center = { x = 0, y = 0, z = 7 } sphereRadius = 5 boxSize = 6 octoHalfExtent = 1 placeInWithCorrectWinding position convex = Convex.placeIn Transform3d.atOrigin (Convex.placeIn (Transform3d.atPoint position) convex) in describe "Collision.SphereConvex.addContacts" [ test "for a box" (Fixtures.NarrowPhase.sphereContactBoxPositions center sphereRadius boxSize |> List.map (\( position, expectedContacts ) -> \_ -> Collision.SphereConvex.addContacts "" identity (Sphere.placeIn (Transform3d.atPoint center) (Sphere.atOrigin sphereRadius)) (Convex.placeIn (Transform3d.atPoint position) (Convex.fromBlock boxSize boxSize boxSize)) [] |> Expect.contacts expectedContacts ) |> Expect.all ) , test "fail for a far box" <| \_ -> Fixtures.NarrowPhase.sphereContactBoxPositions center (sphereRadius * 2) boxSize |> List.concatMap (\( position, _ ) -> Collision.SphereConvex.addContacts "" identity (Sphere.placeIn (Transform3d.atPoint center) (Sphere.atOrigin sphereRadius)) (placeInWithCorrectWinding position (Convex.fromBlock boxSize boxSize boxSize)) [] ) |> Expect.equal [] , test "for an octohedron" (Fixtures.NarrowPhase.sphereContactOctohedronPositions center sphereRadius octoHalfExtent |> List.map (\( position, expectedContacts ) -> \_ -> Collision.SphereConvex.addContacts "" identity (Sphere.placeIn (Transform3d.atPoint center) (Sphere.atOrigin sphereRadius)) (placeInWithCorrectWinding position (Fixtures.Convex.octoHull octoHalfExtent)) [] |> Expect.contacts expectedContacts ) |> Expect.all ) , test "fail for a far octohedron" <| \_ -> Fixtures.NarrowPhase.sphereContactOctohedronPositions center (sphereRadius * 2) octoHalfExtent |> List.concatMap (\( position, _ ) -> Collision.SphereConvex.addContacts "" identity (Sphere.placeIn (Transform3d.atPoint center) (Sphere.atOrigin sphereRadius)) (placeInWithCorrectWinding position (Fixtures.Convex.octoHull octoHalfExtent)) [] ) |> Expect.equal [] ] ================================================ FILE: tests/EigenDecompositionTest.elm ================================================ module EigenDecompositionTest exposing (eigenDecomposition) import Expect exposing (FloatingPointTolerance(..)) import Extra.Expect as Expect import Fuzz exposing (Fuzzer) import Internal.Body as Body import Internal.Matrix3 as Mat3 exposing (Mat3) import Internal.Shape as Shape import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 exposing (Vec3) import Shapes.Convex as Convex import Test exposing (Test, describe, fuzz, test) eigenDecomposition : Test eigenDecomposition = describe "Mat3.eigenDecomposition" [ describe "diagonal matrices" [ test "identity matrix" <| \_ -> let result = Mat3.eigenDecomposition { m11 = 1 , m21 = 0 , m31 = 0 , m12 = 0 , m22 = 1 , m32 = 0 , m13 = 0 , m23 = 0 , m33 = 1 } in Expect.vec3 { x = 1, y = 1, z = 1 } result.eigenvalues , test "distinct diagonal entries preserve axis pairing" <| \_ -> let result = Mat3.eigenDecomposition { m11 = 3 , m21 = 0 , m31 = 0 , m12 = 0 , m22 = 1 , m32 = 0 , m13 = 0 , m23 = 0 , m33 = 2 } in Expect.vec3 { x = 3, y = 1, z = 2 } result.eigenvalues ] , describe "symmetric matrices with off-diagonal terms" [ test "eigenvalues of a known symmetric matrix" <| \_ -> let result = Mat3.eigenDecomposition { m11 = 2 , m21 = 1 , m31 = 0 , m12 = 1 , m22 = 3 , m32 = 1 , m13 = 0 , m23 = 1 , m33 = 2 } in expectEigenvalues [ 4, 2, 1 ] result.eigenvalues , test "eigenvalues of a matrix with all off-diagonal terms" <| \_ -> let result = Mat3.eigenDecomposition { m11 = 2 , m21 = -1 , m31 = 0 , m12 = -1 , m22 = 2 , m32 = -1 , m13 = 0 , m23 = -1 , m33 = 2 } in expectEigenvalues [ 2 + sqrt 2, 2, 2 - sqrt 2 ] result.eigenvalues ] , describe "degenerate eigenvalues" [ test "axis-aligned cylinder: two equal eigenvalues, unique axis is z" <| \_ -> let m = Mat3.cylinderInertia 5.0 1.0 3.0 { eigenvalues, v1, v2, v3 } = Mat3.eigenDecomposition m in Expect.all [ \_ -> Expect.mat3 m (reconstruct (Mat3.eigenDecomposition m)) , \_ -> expectOrthonormal v1 v2 v3 , \_ -> Expect.within (Absolute 0.0001) eigenvalues.x eigenvalues.y |> Expect.onFail "radial eigenvalues should be equal" ] () , test "rotated cylinder: unique eigenvector aligns with cylinder axis" <| \_ -> let localInertia = Mat3.cylinderInertia 5.0 1.0 3.0 rotation = Transform3d.rotateAroundOwn { x = 1, y = 1, z = 0 } (pi / 3) Transform3d.atOrigin m = Transform3d.inertiaRotateIn rotation localInertia { eigenvalues, v1, v2, v3 } = Mat3.eigenDecomposition m cylinderAxis = Transform3d.directionPlaceIn rotation { x = 0, y = 0, z = 1 } uniqueEigenvector = uniqueEigenvectorOf eigenvalues v1 v2 v3 in Expect.all [ \_ -> Expect.mat3 m (reconstruct (Mat3.eigenDecomposition m)) , \_ -> expectOrthonormal v1 v2 v3 , \_ -> abs (Vec3.dot uniqueEigenvector cylinderAxis) |> Expect.within (Absolute 0.0001) 1.0 ] () ] , describe "reconstruction R * diag(d) * R^T == original" [ test "reconstructs identity" <| \_ -> let m = { m11 = 1 , m21 = 0 , m31 = 0 , m12 = 0 , m22 = 1 , m32 = 0 , m13 = 0 , m23 = 0 , m33 = 1 } in Expect.mat3 m (reconstruct (Mat3.eigenDecomposition m)) , test "reconstructs diagonal matrix" <| \_ -> let m = { m11 = 5 , m21 = 0 , m31 = 0 , m12 = 0 , m22 = 3 , m32 = 0 , m13 = 0 , m23 = 0 , m33 = 1 } in Expect.mat3 m (reconstruct (Mat3.eigenDecomposition m)) , test "reconstructs symmetric matrix with off-diagonal terms" <| \_ -> let m = { m11 = 2 , m21 = 1 , m31 = 0 , m12 = 1 , m22 = 3 , m32 = 1 , m13 = 0 , m23 = 1 , m33 = 2 } in Expect.mat3 m (reconstruct (Mat3.eigenDecomposition m)) , test "reconstructs matrix with all off-diagonal terms" <| \_ -> let m = { m11 = 4 , m21 = 2 , m31 = 1 , m12 = 2 , m22 = 5 , m32 = 3 , m13 = 1 , m23 = 3 , m33 = 6 } in Expect.mat3 m (reconstruct (Mat3.eigenDecomposition m)) , test "reconstructs inertia from pointInertia" <| \_ -> let m = Mat3.pointInertia 2.5 1.0 2.0 3.0 in Expect.mat3 m (reconstruct (Mat3.eigenDecomposition m)) , test "reconstructs inertia from tetrahedronInertia" <| \_ -> let m = Mat3.tetrahedronInertia 1.0 { x = 0, y = 0, z = 0 } { x = 1, y = 0, z = 0 } { x = 0, y = 2, z = 0 } { x = 0, y = 0, z = 3 } in Expect.mat3 m (reconstruct (Mat3.eigenDecomposition m)) , test "L-shaped compound body has positive invInertia components" <| \_ -> let mat = { friction = 0, bounciness = 0, density = 700 } body = Body.compound Body.Dynamic [ ( Convex.fromBlock 2 1 1 |> Convex.placeIn (Transform3d.atPoint { x = 1, y = 0, z = 0 }) |> Shape.Convex , mat , 1 ) , ( Convex.fromBlock 1 2 1 |> Convex.placeIn (Transform3d.atPoint { x = 0, y = 1, z = 0 }) |> Shape.Convex , mat , 1 ) ] in Expect.all [ \_ -> body.invInertia.x |> Expect.greaterThan 0 , \_ -> body.invInertia.y |> Expect.greaterThan 0 , \_ -> body.invInertia.z |> Expect.greaterThan 0 ] () ] , describe "eigenvectors form an orthonormal basis" [ test "columns are orthonormal for off-diagonal matrix" <| \_ -> let { v1, v2, v3 } = Mat3.eigenDecomposition { m11 = 4 , m21 = 2 , m31 = 1 , m12 = 2 , m22 = 5 , m32 = 3 , m13 = 1 , m23 = 3 , m33 = 6 } in expectOrthonormal v1 v2 v3 , test "columns are orthonormal for inertia tensor" <| \_ -> let { v1, v2, v3 } = Mat3.eigenDecomposition (Mat3.pointInertia 2.5 1.0 2.0 3.0) in expectOrthonormal v1 v2 v3 ] , describe "eigenvectors satisfy A*v = λ*v" [ test "for symmetric matrix with off-diagonal terms" <| \_ -> let m = { m11 = 4 , m21 = 2 , m31 = 1 , m12 = 2 , m22 = 5 , m32 = 3 , m13 = 1 , m23 = 3 , m33 = 6 } { eigenvalues, v1, v2, v3 } = Mat3.eigenDecomposition m in Expect.all [ \_ -> Expect.vec3 (Vec3.scale eigenvalues.x v1) (mulVec m v1) , \_ -> Expect.vec3 (Vec3.scale eigenvalues.y v2) (mulVec m v2) , \_ -> Expect.vec3 (Vec3.scale eigenvalues.z v3) (mulVec m v3) ] () ] , describe "eigenvectors form a proper rotation (determinant +1)" [ test "for off-diagonal matrix" <| \_ -> let { v1, v2, v3 } = Mat3.eigenDecomposition { m11 = 4 , m21 = 2 , m31 = 1 , m12 = 2 , m22 = 5 , m32 = 3 , m13 = 1 , m23 = 3 , m33 = 6 } in Vec3.dot v1 (Vec3.cross v2 v3) |> Expect.within (Absolute 0.0001) 1.0 , test "for inertia tensor" <| \_ -> let { v1, v2, v3 } = Mat3.eigenDecomposition (Mat3.pointInertia 2.5 1.0 2.0 3.0) in Vec3.dot v1 (Vec3.cross v2 v3) |> Expect.within (Absolute 0.0001) 1.0 ] , describe "fuzz" [ fuzz rotatedCylinderInertia "reconstructs randomly rotated cylinder inertia" <| \{ inertia } -> Expect.mat3 inertia (reconstruct (Mat3.eigenDecomposition inertia)) , fuzz rotatedCylinderInertia "eigenvectors are orthonormal for randomly rotated cylinder" <| \{ inertia } -> let { v1, v2, v3 } = Mat3.eigenDecomposition inertia in expectOrthonormal v1 v2 v3 , fuzz rotatedCylinderInertia "A*v = λ*v for randomly rotated cylinder" <| \{ inertia } -> let { eigenvalues, v1, v2, v3 } = Mat3.eigenDecomposition inertia in Expect.all [ \_ -> Expect.vec3 (Vec3.scale eigenvalues.x v1) (mulVec inertia v1) , \_ -> Expect.vec3 (Vec3.scale eigenvalues.y v2) (mulVec inertia v2) , \_ -> Expect.vec3 (Vec3.scale eigenvalues.z v3) (mulVec inertia v3) ] () , fuzz rotatedCylinderInertia "unique eigenvector aligns with cylinder axis" <| \{ inertia, cylinderAxis } -> let { eigenvalues, v1, v2, v3 } = Mat3.eigenDecomposition inertia uniqueEigenvector = uniqueEigenvectorOf eigenvalues v1 v2 v3 in abs (Vec3.dot uniqueEigenvector cylinderAxis) |> Expect.within (Absolute 0.0001) 1.0 ] ] reconstruct : { eigenvalues : Vec3, v1 : Vec3, v2 : Vec3, v3 : Vec3 } -> Mat3 reconstruct { eigenvalues, v1, v2, v3 } = let r = { m11 = v1.x , m12 = v2.x , m13 = v3.x , m21 = v1.y , m22 = v2.y , m23 = v3.y , m31 = v1.z , m32 = v2.z , m33 = v3.z } rt = Mat3.transpose r in Mat3.mul r (Mat3.mul (diag eigenvalues) rt) diag : Vec3 -> Mat3 diag { x, y, z } = { m11 = x , m21 = 0 , m31 = 0 , m12 = 0 , m22 = y , m32 = 0 , m13 = 0 , m23 = 0 , m33 = z } mulVec : Mat3 -> Vec3 -> Vec3 mulVec m v = { x = m.m11 * v.x + m.m12 * v.y + m.m13 * v.z , y = m.m21 * v.x + m.m22 * v.y + m.m23 * v.z , z = m.m31 * v.x + m.m32 * v.y + m.m33 * v.z } rotatedCylinderInertia : Fuzzer { inertia : Mat3, cylinderAxis : Vec3 } rotatedCylinderInertia = Fuzz.map2 (\axisAngle rotAngle -> let axis = Vec3.normalize { x = cos axisAngle , y = sin axisAngle , z = cos (axisAngle * 1.7) } rotation = Transform3d.rotateAroundOwn axis rotAngle Transform3d.atOrigin in { inertia = Transform3d.inertiaRotateIn rotation (Mat3.cylinderInertia 5.0 1.0 3.0) , cylinderAxis = Transform3d.directionPlaceIn rotation { x = 0, y = 0, z = 1 } } ) (Fuzz.floatRange 0 (2 * pi)) (Fuzz.floatRange 0 (2 * pi)) uniqueEigenvectorOf : Vec3 -> Vec3 -> Vec3 -> Vec3 -> Vec3 uniqueEigenvectorOf eigenvalues ev1 ev2 ev3 = let d12 = abs (eigenvalues.x - eigenvalues.y) d13 = abs (eigenvalues.x - eigenvalues.z) d23 = abs (eigenvalues.y - eigenvalues.z) in if d23 <= d12 && d23 <= d13 then ev1 else if d13 <= d12 && d13 <= d23 then ev2 else ev3 expectEigenvalues : List Float -> Vec3 -> Expect.Expectation expectEigenvalues expected actual = Expect.all (List.map2 (\e a -> \_ -> Expect.within (Absolute 1.0e-14) e a) (List.sort expected) (List.sort [ actual.x, actual.y, actual.z ]) ) () expectOrthonormal : Vec3 -> Vec3 -> Vec3 -> Expect.Expectation expectOrthonormal c1 c2 c3 = Expect.all [ \_ -> Vec3.length c1 |> Expect.within (Absolute 0.0001) 1.0 , \_ -> Vec3.length c2 |> Expect.within (Absolute 0.0001) 1.0 , \_ -> Vec3.length c3 |> Expect.within (Absolute 0.0001) 1.0 , \_ -> Vec3.dot c1 c2 |> Expect.within (Absolute 0.0001) 0.0 , \_ -> Vec3.dot c1 c3 |> Expect.within (Absolute 0.0001) 0.0 , \_ -> Vec3.dot c2 c3 |> Expect.within (Absolute 0.0001) 0.0 ] () ================================================ FILE: tests/Extra/Expect.elm ================================================ module Extra.Expect exposing ( contacts , frame3d , mat3 , vec3 ) import Direction3d import Expect exposing (Expectation, FloatingPointTolerance(..)) import Frame3d exposing (Frame3d) import Internal.Contact exposing (Contact) import Internal.Matrix3 exposing (Mat3) import Internal.Vector3 exposing (Vec3) import Point3d contacts : List Contact -> List Contact -> Expectation contacts = expectList contact contact : Contact -> Contact -> Expectation contact = expectAll (List.map (Tuple.pair vec3) [ .pi, .pj, .ni ]) vec3 : Vec3 -> Vec3 -> Expectation vec3 = expectAll (List.map (Tuple.pair (Expect.within tolerance)) [ .x, .y, .z ]) mat3 : Mat3 -> Mat3 -> Expectation mat3 = expectAll (List.map (Tuple.pair (Expect.within tolerance)) [ .m11, .m21, .m31, .m12, .m22, .m32, .m13, .m23, .m33 ] ) frame3d : Frame3d units coords define -> Frame3d units coords define -> Expectation frame3d = expectAll (List.map (Tuple.pair vec3) [ Frame3d.originPoint >> Point3d.unwrap , Frame3d.xDirection >> Direction3d.unwrap , Frame3d.yDirection >> Direction3d.unwrap , Frame3d.zDirection >> Direction3d.unwrap ] ) expectList : (a -> a -> Expectation) -> List a -> List a -> Expectation expectList fn l1 l2 = Expect.all ((\_ -> Expect.equal (List.length l1) (List.length l2) |> Expect.onFail "List sizes do not match" ) :: List.map2 (\a b -> \_ -> fn a b) l1 l2 ) () expectAll : List ( b -> b -> Expectation, a -> b ) -> a -> a -> Expectation expectAll expectations a b = Expect.all (List.map (\( expectation, getter ) -> \_ -> expectation (getter a) (getter b) ) expectations ) () |> Expect.onFail ("Expected " ++ Debug.toString a ++ ", got " ++ Debug.toString b) tolerance : FloatingPointTolerance tolerance = Absolute 0.0001 ================================================ FILE: tests/Fixtures/Convex.elm ================================================ module Fixtures.Convex exposing ( askewSquarePyramid , block , blockOfTetrahedrons , hugeConvex , nonSquareQuadPyramid , octoHull , squarePyramid ) import Array import Internal.Const as Const import Internal.Transform3d as Transform3d exposing (Transform3d) import Shapes.Convex as Convex exposing (Convex) -- Test data generators block : Transform3d coord define -> Float -> Float -> Float -> Convex.Convex block transform sizeX sizeY sizeZ = let halfExtentX = sizeX / 2 halfExtentY = sizeY / 2 halfExtentZ = sizeZ / 2 t p = Transform3d.pointPlaceIn transform p in Convex.fromTriangularMesh [ ( 1, 7, 5 ) , ( 1, 2, 7 ) , ( 4, 7, 6 ) , ( 4, 5, 7 ) , ( 6, 2, 3 ) , ( 6, 7, 2 ) , ( 3, 4, 6 ) , ( 3, 0, 4 ) , ( 0, 5, 4 ) , ( 0, 1, 5 ) , ( 3, 1, 0 ) , ( 3, 2, 1 ) ] (Array.fromList [ t { x = halfExtentX, y = -halfExtentY, z = -halfExtentZ } , t { x = halfExtentX, y = halfExtentY, z = -halfExtentZ } , t { x = -halfExtentX, y = halfExtentY, z = -halfExtentZ } , t { x = -halfExtentX, y = -halfExtentY, z = -halfExtentZ } , t { x = halfExtentX, y = -halfExtentY, z = halfExtentZ } , t { x = halfExtentX, y = halfExtentY, z = halfExtentZ } , t { x = -halfExtentX, y = -halfExtentY, z = halfExtentZ } , t { x = -halfExtentX, y = halfExtentY, z = halfExtentZ } ] ) blockOfTetrahedrons : Float -> Float -> Float -> List Convex.Convex blockOfTetrahedrons sizeX sizeY sizeZ = let lX = sizeX / 2 lY = sizeY / 2 lZ = sizeZ / 2 p0 = { x = 0, y = 0, z = 0 } in List.map (\( p1, p2, p3 ) -> Convex.fromTriangularMesh [ ( 1, 0, 2 ), ( 2, 0, 3 ), ( 3, 0, 1 ), ( 3, 1, 2 ) ] (Array.fromList [ p0, p1, p2, p3 ]) ) [ ( { x = lX, y = lY, z = -lZ }, { x = -lX, y = lY, z = lZ }, { x = lX, y = lY, z = lZ } ) , ( { x = lX, y = lY, z = -lZ }, { x = -lX, y = lY, z = -lZ }, { x = -lX, y = lY, z = lZ } ) , ( { x = lX, y = -lY, z = lZ }, { x = -lX, y = lY, z = lZ }, { x = -lX, y = -lY, z = lZ } ) , ( { x = lX, y = -lY, z = lZ }, { x = lX, y = lY, z = lZ }, { x = -lX, y = lY, z = lZ } ) , ( { x = -lX, y = -lY, z = lZ }, { x = -lX, y = lY, z = -lZ }, { x = -lX, y = -lY, z = -lZ } ) , ( { x = -lX, y = -lY, z = lZ }, { x = -lX, y = lY, z = lZ }, { x = -lX, y = lY, z = -lZ } ) , ( { x = -lX, y = -lY, z = -lZ }, { x = lX, y = -lY, z = lZ }, { x = -lX, y = -lY, z = lZ } ) , ( { x = -lX, y = -lY, z = -lZ }, { x = lX, y = -lY, z = -lZ }, { x = lX, y = -lY, z = lZ } ) , ( { x = lX, y = -lY, z = -lZ }, { x = lX, y = lY, z = lZ }, { x = lX, y = -lY, z = lZ } ) , ( { x = lX, y = -lY, z = -lZ }, { x = lX, y = lY, z = -lZ }, { x = lX, y = lY, z = lZ } ) , ( { x = -lX, y = -lY, z = -lZ }, { x = lX, y = lY, z = -lZ }, { x = lX, y = -lY, z = -lZ } ) , ( { x = -lX, y = -lY, z = -lZ }, { x = -lX, y = lY, z = -lZ }, { x = lX, y = lY, z = -lZ } ) ] octoHull : Float -> Convex.Convex octoHull halfExtent = Convex.fromTriangularMesh [ ( 2, 1, 0 ) , ( 0, 5, 2 ) , ( 1, 2, 4 ) , ( 3, 0, 1 ) , ( 2, 5, 4 ) , ( 4, 3, 1 ) , ( 5, 0, 3 ) , ( 3, 4, 5 ) ] (Array.fromList [ { x = 0, y = 0, z = halfExtent } , { x = 0, y = halfExtent, z = 0 } , { x = halfExtent, y = 0, z = 0 } , { x = -halfExtent, y = 0, z = 0 } , { x = 0, y = 0, z = -halfExtent } , { x = 0, y = -halfExtent, z = 0 } ] ) squarePyramid : Convex squarePyramid = -- Specify 0 for exact precision squareLikePyramid 0.0 askewSquarePyramid : Convex askewSquarePyramid = -- Use an insignificant epsilon for an approximately square base squareLikePyramid (Const.precision / 3.0) nonSquareQuadPyramid : Convex nonSquareQuadPyramid = -- Use a significant epsilon for a not even approximately square base squareLikePyramid (Const.precision * 3.0) squareLikePyramid : Float -> Convex squareLikePyramid epsilon = let x = 1 y = 1 z = 1 -- zOffset is the height of the pyramid's center of gravity above its -- base -- the cube root of 1/2. -- It serves to keep the object vertically centered. zOffset = z * (0.5 ^ (1.0 / 3.0)) vertexIndices = [ ( 3, 2, 1 ) , ( 3, 1, 0 ) , ( 0, 1, 4 ) , ( 1, 2, 4 ) , ( 2, 3, 4 ) , ( 3, 0, 4 ) ] vertices = Array.fromList [ { x = -x, y = -y, z = -zOffset } , { x = x, y = -y, z = -zOffset } -- An optional adjustment of one base corner controls -- the number (0 or 2) of edge pairs that are exactly -- parallel OR approximately parallel. , { x = x + epsilon, y = y + epsilon, z = -zOffset } , { x = -x, y = y, z = -zOffset } , { x = 0, y = 0, z = z - zOffset } ] in Convex.fromTriangularMesh vertexIndices vertices hugeConvex : Convex hugeConvex = Convex.fromTriangularMesh [ ( 32, 58, 33 ) , ( 61, 56, 44 ) , ( 55, 52, 7 ) , ( 0, 3, 25 ) , ( 0, 2, 3 ) , ( 48, 9, 12 ) , ( 37, 15, 25 ) , ( 15, 0, 25 ) , ( 33, 45, 41 ) , ( 33, 58, 45 ) , ( 2, 0, 17 ) , ( 17, 21, 18 ) , ( 18, 21, 12 ) , ( 7, 19, 5 ) , ( 6, 22, 31 ) , ( 4, 22, 6 ) , ( 40, 26, 37 ) , ( 25, 28, 29 ) , ( 24, 26, 22 ) , ( 27, 29, 41 ) , ( 26, 31, 22 ) , ( 35, 34, 29 ) , ( 35, 23, 36 ) , ( 34, 35, 36 ) , ( 27, 38, 25 ) , ( 30, 40, 44 ) , ( 42, 40, 37 ) , ( 34, 41, 29 ) , ( 41, 34, 33 ) , ( 41, 45, 43 ) , ( 44, 42, 39 ) , ( 43, 39, 42 ) , ( 13, 36, 23 ) , ( 46, 48, 49 ) , ( 48, 51, 49 ) , ( 47, 49, 50 ) , ( 50, 54, 53 ) , ( 54, 51, 52 ) , ( 58, 59, 60 ) , ( 61, 58, 60 ) , ( 45, 39, 43 ) , ( 61, 44, 39 ) , ( 61, 60, 56 ) , ( 55, 7, 6 ) , ( 52, 19, 7 ) , ( 52, 51, 19 ) , ( 51, 48, 16 ) , ( 18, 12, 10 ) , ( 58, 32, 57 ) , ( 58, 61, 45 ) , ( 61, 39, 45 ) , ( 54, 52, 55 ) , ( 3, 28, 25 ) , ( 38, 37, 25 ) , ( 1, 14, 0 ) , ( 9, 8, 10 ) , ( 8, 13, 10 ) , ( 8, 32, 13 ) , ( 32, 36, 13 ) , ( 26, 24, 37 ) , ( 53, 54, 56 ) , ( 54, 55, 56 ) , ( 60, 62, 56 ) , ( 62, 53, 56 ) , ( 27, 25, 29 ) , ( 46, 8, 9 ) , ( 14, 17, 0 ) , ( 46, 47, 8 ) , ( 47, 32, 8 ) , ( 47, 57, 32 ) , ( 50, 59, 47 ) , ( 35, 29, 28 ) , ( 43, 27, 41 ) , ( 50, 53, 59 ) , ( 49, 47, 46 ) , ( 36, 32, 34 ) , ( 32, 33, 34 ) , ( 56, 30, 44 ) , ( 56, 31, 30 ) , ( 4, 6, 5 ) , ( 6, 7, 5 ) , ( 16, 21, 20 ) , ( 43, 38, 27 ) , ( 43, 42, 38 ) , ( 42, 37, 38 ) , ( 11, 3, 2 ) , ( 23, 3, 11 ) , ( 18, 2, 17 ) , ( 42, 44, 40 ) , ( 51, 16, 19 ) , ( 16, 48, 12 ) , ( 48, 46, 9 ) , ( 59, 57, 47 ) , ( 53, 62, 59 ) , ( 62, 60, 59 ) , ( 50, 49, 54 ) , ( 49, 51, 54 ) , ( 24, 15, 37 ) , ( 24, 22, 15 ) , ( 22, 4, 15 ) , ( 4, 1, 15 ) , ( 1, 0, 15 ) , ( 23, 35, 3 ) , ( 35, 28, 3 ) , ( 30, 26, 40 ) , ( 30, 31, 26 ) , ( 5, 1, 4 ) , ( 5, 14, 1 ) , ( 14, 20, 17 ) , ( 20, 21, 17 ) , ( 18, 11, 2 ) , ( 5, 20, 14 ) , ( 18, 10, 11 ) , ( 10, 13, 11 ) , ( 13, 23, 11 ) , ( 56, 6, 31 ) , ( 56, 55, 6 ) , ( 19, 20, 5 ) , ( 19, 16, 20 ) , ( 16, 12, 21 ) , ( 12, 9, 10 ) , ( 57, 59, 58 ) ] (Array.fromList [ { x = -0.11459, y = -0.298243, z = 0.100413 } , { x = 0.436854, y = -0.300164, z = 0.1143 } , { x = -0.409218, y = -0.296382, z = 0.186322 } , { x = -0.441714, y = -0.072783, z = 0.146552 } , { x = 0.733418, y = -0.149194, z = 0.183468 } , { x = 0.602326, y = -0.395949, z = 0.281944 } , { x = 0.818608, y = -0.167292, z = 0.42114 } , { x = 0.660817, y = -0.392449, z = 0.485555 } , { x = -0.547257, y = -0.088449, z = 0.899761 } , { x = -0.536566, y = -0.269735, z = 0.747375 } , { x = -0.608158, y = -0.285728, z = 0.559957 } , { x = -0.57747, y = -0.2151, z = 0.335446 } , { x = -0.149689, y = -0.515712, z = 0.480973 } , { x = -0.679758, y = -0.06998, z = 0.585114 } , { x = 0.151798, y = -0.364164, z = 0.1141 } , { x = 0.55179, y = -0.114214, z = 0.100309 } , { x = 0.09126, y = -0.527181, z = 0.558675 } , { x = -0.130679, y = -0.456457, z = 0.235531 } , { x = -0.317681, y = -0.457769, z = 0.366178 } , { x = 0.46511, y = -0.477899, z = 0.427776 } , { x = 0.282, y = -0.514911, z = 0.349703 } , { x = 0.0303, y = -0.524675, z = 0.354574 } , { x = 0.759308, y = 0.10619, z = 0.182734 } , { x = -0.64683, y = 0.01928, z = 0.341592 } , { x = 0.570919, y = 0.095929, z = 0.100917 } , { x = -0.25119, y = 0.30186, z = 0.099294 } , { x = 0.555386, y = 0.302365, z = 0.114663 } , { x = -0.227101, y = 0.481723, z = 0.183816 } , { x = -0.441233, y = 0.186976, z = 0.144447 } , { x = -0.45535, y = 0.385063, z = 0.236628 } , { x = 0.646226, y = 0.465931, z = 0.415905 } , { x = 0.806278, y = 0.239899, z = 0.353877 } , { x = -0.617107, y = 0.209783, z = 0.81899 } , { x = -0.46719, y = 0.480974, z = 0.553544 } , { x = -0.589689, y = 0.355858, z = 0.437199 } , { x = -0.592654, y = 0.202361, z = 0.279119 } , { x = -0.68018, y = 0.178183, z = 0.582133 } , { x = 0.254849, y = 0.379992, z = 0.099383 } , { x = -0.032588, y = 0.439714, z = 0.11387 } , { x = 0.099846, y = 0.607803, z = 0.480417 } , { x = 0.465132, y = 0.51505, z = 0.29225 } , { x = -0.323879, y = 0.526379, z = 0.37056 } , { x = 0.237499, y = 0.576722, z = 0.294563 } , { x = -0.068028, y = 0.572111, z = 0.301029 } , { x = 0.461069, y = 0.549594, z = 0.497648 } , { x = -0.199928, y = 0.554805, z = 0.570804 } , { x = 0.004558, y = -0.167478, z = 1.44564 } , { x = 0.084454, y = -0.020208, z = 1.57338 } , { x = 0.206856, y = -0.363481, z = 1.3067 } , { x = 0.20283, y = -0.233369, z = 1.52392 } , { x = 0.280317, y = -0.054322, z = 1.62761 } , { x = 0.376109, y = -0.323103, z = 1.41943 } , { x = 0.531913, y = -0.304605, z = 1.29392 } , { x = 0.507305, y = 0.021512, z = 1.59939 } , { x = 0.560087, y = -0.21699, z = 1.47327 } , { x = 0.934426, y = 0.045556, z = 1.25358 } , { x = 0.955972, y = 0.229201, z = 1.36005 } , { x = -0.010552, y = 0.166565, z = 1.46812 } , { x = 0.09539, y = 0.353323, z = 1.39163 } , { x = 0.208829, y = 0.198483, z = 1.59289 } , { x = 0.279242, y = 0.362635, z = 1.48749 } , { x = 0.312043, y = 0.451888, z = 1.27797 } , { x = 0.499772, y = 0.253275, z = 1.55213 } ] ) ================================================ FILE: tests/Fixtures/NarrowPhase.elm ================================================ module Fixtures.NarrowPhase exposing ( sphereContactBoxPositions , sphereContactOctohedronPositions ) import Internal.Const as Const import Internal.Contact exposing (Contact) import Internal.Vector3 as Vec3 exposing (Vec3) sphereContactBoxPositions : Vec3 -> Float -> Float -> List ( Vec3, List Contact ) sphereContactBoxPositions center radius boxSize = let boxHalfExtent = boxSize / 2 delta = 3 * Const.precision nearEdgeOffset = boxHalfExtent - delta -- Reposition the box so that it contacts the sphere at each: -- vertex -- edge (midpoint) -- face (center) -- face (at a point near a vertex) -- face (at a point near an edge midpoint). -- The adjustment of -Const.precision represents a minimum -- penetration value. vertexDimension = boxHalfExtent + radius / sqrt 3 - Const.precision offDiagonalFactor = (vertexDimension + delta) / vertexDimension edgeDimension = boxHalfExtent + radius / sqrt 2 - Const.precision faceDimension = boxHalfExtent + radius - Const.precision ceq vectors = ( vectors.cj, completeSphereContactEquation radius vectors ) invSqrt3 = 1 / sqrt 3 invSqrt2 = 1 / sqrt 2 in -- Box positions and their resulting contacts: [ -- the 8 vertex contacts ceq { cj = { x = vertexDimension, y = vertexDimension, z = vertexDimension } |> Vec3.add center, ci = center, ni = { x = invSqrt3, y = invSqrt3, z = invSqrt3 }, rj = { x = -boxHalfExtent, y = -boxHalfExtent, z = -boxHalfExtent } } , ceq { cj = { x = -vertexDimension, y = vertexDimension, z = vertexDimension } |> Vec3.add center, ci = center, ni = { x = -invSqrt3, y = invSqrt3, z = invSqrt3 }, rj = { x = boxHalfExtent, y = -boxHalfExtent, z = -boxHalfExtent } } , ceq { cj = { x = vertexDimension, y = -vertexDimension, z = vertexDimension } |> Vec3.add center, ci = center, ni = { x = invSqrt3, y = -invSqrt3, z = invSqrt3 }, rj = { x = -boxHalfExtent, y = boxHalfExtent, z = -boxHalfExtent } } , ceq { cj = { x = -vertexDimension, y = -vertexDimension, z = vertexDimension } |> Vec3.add center, ci = center, ni = { x = -invSqrt3, y = -invSqrt3, z = invSqrt3 }, rj = { x = boxHalfExtent, y = boxHalfExtent, z = -boxHalfExtent } } , ceq { cj = { x = vertexDimension, y = vertexDimension, z = -vertexDimension } |> Vec3.add center, ci = center, ni = { x = invSqrt3, y = invSqrt3, z = -invSqrt3 }, rj = { x = -boxHalfExtent, y = -boxHalfExtent, z = boxHalfExtent } } , ceq { cj = { x = -vertexDimension, y = vertexDimension, z = -vertexDimension } |> Vec3.add center, ci = center, ni = { x = -invSqrt3, y = invSqrt3, z = -invSqrt3 }, rj = { x = boxHalfExtent, y = -boxHalfExtent, z = boxHalfExtent } } , ceq { cj = { x = vertexDimension, y = -vertexDimension, z = -vertexDimension } |> Vec3.add center, ci = center, ni = { x = invSqrt3, y = -invSqrt3, z = -invSqrt3 }, rj = { x = -boxHalfExtent, y = boxHalfExtent, z = boxHalfExtent } } , ceq { cj = { x = -vertexDimension, y = -vertexDimension, z = -vertexDimension } |> Vec3.add center, ci = center, ni = { x = -invSqrt3, y = -invSqrt3, z = -invSqrt3 }, rj = { x = boxHalfExtent, y = boxHalfExtent, z = boxHalfExtent } } -- the 12 edge (midpoint) contacts , ceq { cj = { x = edgeDimension, y = edgeDimension, z = 0 } |> Vec3.add center, ci = center, ni = { x = invSqrt2, y = invSqrt2, z = 0 }, rj = { x = -boxHalfExtent, y = -boxHalfExtent, z = 0 } } , ceq { cj = { x = 0, y = edgeDimension, z = edgeDimension } |> Vec3.add center, ci = center, ni = { x = 0, y = invSqrt2, z = invSqrt2 }, rj = { x = 0, y = -boxHalfExtent, z = -boxHalfExtent } } , ceq { cj = { x = edgeDimension, y = 0, z = edgeDimension } |> Vec3.add center, ci = center, ni = { x = invSqrt2, y = 0, z = invSqrt2 }, rj = { x = -boxHalfExtent, y = 0, z = -boxHalfExtent } } , ceq { cj = { x = -edgeDimension, y = edgeDimension, z = 0 } |> Vec3.add center, ci = center, ni = { x = -invSqrt2, y = invSqrt2, z = 0 }, rj = { x = boxHalfExtent, y = -boxHalfExtent, z = 0 } } , ceq { cj = { x = 0, y = -edgeDimension, z = edgeDimension } |> Vec3.add center, ci = center, ni = { x = 0, y = -invSqrt2, z = invSqrt2 }, rj = { x = 0, y = boxHalfExtent, z = -boxHalfExtent } } , ceq { cj = { x = edgeDimension, y = 0, z = -edgeDimension } |> Vec3.add center, ci = center, ni = { x = invSqrt2, y = 0, z = -invSqrt2 }, rj = { x = -boxHalfExtent, y = 0, z = boxHalfExtent } } , ceq { cj = { x = edgeDimension, y = -edgeDimension, z = 0 } |> Vec3.add center, ci = center, ni = { x = invSqrt2, y = -invSqrt2, z = 0 }, rj = { x = -boxHalfExtent, y = boxHalfExtent, z = 0 } } , ceq { cj = { x = 0, y = edgeDimension, z = -edgeDimension } |> Vec3.add center, ci = center, ni = { x = 0, y = invSqrt2, z = -invSqrt2 }, rj = { x = 0, y = -boxHalfExtent, z = boxHalfExtent } } , ceq { cj = { x = -edgeDimension, y = 0, z = edgeDimension } |> Vec3.add center, ci = center, ni = { x = -invSqrt2, y = 0, z = invSqrt2 }, rj = { x = boxHalfExtent, y = 0, z = -boxHalfExtent } } , ceq { cj = { x = -edgeDimension, y = -edgeDimension, z = 0 } |> Vec3.add center, ci = center, ni = { x = -invSqrt2, y = -invSqrt2, z = 0 }, rj = { x = boxHalfExtent, y = boxHalfExtent, z = 0 } } , ceq { cj = { x = 0, y = -edgeDimension, z = -edgeDimension } |> Vec3.add center, ci = center, ni = { x = 0, y = -invSqrt2, z = -invSqrt2 }, rj = { x = 0, y = boxHalfExtent, z = boxHalfExtent } } , ceq { cj = { x = -edgeDimension, y = 0, z = -edgeDimension } |> Vec3.add center, ci = center, ni = { x = -invSqrt2, y = 0, z = -invSqrt2 }, rj = { x = boxHalfExtent, y = 0, z = boxHalfExtent } } -- the 6 face (center) contacts , ceq { cj = { x = faceDimension, y = 0, z = 0 } |> Vec3.add center, ci = center, ni = Vec3.xAxis, rj = { x = -boxHalfExtent, y = 0, z = 0 } } , ceq { cj = { x = 0, y = faceDimension, z = 0 } |> Vec3.add center, ci = center, ni = Vec3.yAxis, rj = { x = 0, y = -boxHalfExtent, z = 0 } } , ceq { cj = { x = 0, y = 0, z = faceDimension } |> Vec3.add center, ci = center, ni = Vec3.zAxis, rj = { x = 0, y = 0, z = -boxHalfExtent } } , ceq { cj = { x = -faceDimension, y = 0, z = 0 } |> Vec3.add center, ci = center, ni = Vec3.xNegative, rj = { x = boxHalfExtent, y = 0, z = 0 } } , ceq { cj = { x = 0, y = -faceDimension, z = 0 } |> Vec3.add center, ci = center, ni = Vec3.yNegative, rj = { x = 0, y = boxHalfExtent, z = 0 } } , ceq { cj = { x = 0, y = 0, z = -faceDimension } |> Vec3.add center, ci = center, ni = Vec3.zNegative, rj = { x = 0, y = 0, z = boxHalfExtent } } -- 3 sample face contacts very near a vertex , ceq { cj = { x = nearEdgeOffset, y = faceDimension, z = nearEdgeOffset } |> Vec3.add center, ci = center, ni = Vec3.yAxis, rj = { x = -nearEdgeOffset, y = -boxHalfExtent, z = -nearEdgeOffset } } , ceq { cj = { x = -faceDimension, y = nearEdgeOffset, z = nearEdgeOffset } |> Vec3.add center, ci = center, ni = Vec3.xNegative, rj = { x = boxHalfExtent, y = -nearEdgeOffset, z = -nearEdgeOffset } } , ceq { cj = { x = nearEdgeOffset, y = nearEdgeOffset, z = -faceDimension } |> Vec3.add center, ci = center, ni = Vec3.zNegative, rj = { x = -nearEdgeOffset, y = -nearEdgeOffset, z = boxHalfExtent } } -- 3 sample face contacts very near an edge (midpoint) , ceq { cj = { x = faceDimension, y = nearEdgeOffset, z = 0 } |> Vec3.add center, ci = center, ni = Vec3.xAxis, rj = { x = -boxHalfExtent, y = -nearEdgeOffset, z = 0 } } , ceq { cj = { x = nearEdgeOffset, y = 0, z = faceDimension } |> Vec3.add center, ci = center, ni = Vec3.zAxis, rj = { x = -nearEdgeOffset, y = 0, z = -boxHalfExtent } } , ceq { cj = { x = 0, y = -faceDimension, z = nearEdgeOffset } |> Vec3.add center, ci = center, ni = Vec3.yNegative, rj = { x = 0, y = boxHalfExtent, z = -nearEdgeOffset } } -- 3 sample edge contacts very near a vertex , ceq { cj = { x = edgeDimension, y = edgeDimension, z = nearEdgeOffset } |> Vec3.add center, ci = center, ni = { x = invSqrt2, y = invSqrt2, z = 0 }, rj = { x = -boxHalfExtent, y = -boxHalfExtent, z = -nearEdgeOffset } } , ceq { cj = { x = nearEdgeOffset, y = edgeDimension, z = -edgeDimension } |> Vec3.add center, ci = center, ni = { x = 0, y = invSqrt2, z = -invSqrt2 }, rj = { x = -nearEdgeOffset, y = -boxHalfExtent, z = boxHalfExtent } } , ceq { cj = { x = -edgeDimension, y = -nearEdgeOffset, z = edgeDimension } |> Vec3.add center, ci = center, ni = { x = -invSqrt2, y = 0, z = invSqrt2 }, rj = { x = boxHalfExtent, y = nearEdgeOffset, z = -boxHalfExtent } } -- 3 sample off-diagonal vertex contacts , ceq { cj = { x = vertexDimension, y = vertexDimension * offDiagonalFactor, z = vertexDimension / offDiagonalFactor } |> Vec3.add center, ci = center, ni = { x = invSqrt3, y = invSqrt3, z = invSqrt3 }, rj = { x = -boxHalfExtent, y = -boxHalfExtent, z = -boxHalfExtent } } , ceq { cj = { x = -vertexDimension / offDiagonalFactor, y = -vertexDimension, z = vertexDimension * offDiagonalFactor } |> Vec3.add center, ci = center, ni = { x = -invSqrt3, y = -invSqrt3, z = invSqrt3 }, rj = { x = boxHalfExtent, y = boxHalfExtent, z = -boxHalfExtent } } , ceq { cj = { x = vertexDimension / offDiagonalFactor, y = -vertexDimension * offDiagonalFactor, z = -vertexDimension } |> Vec3.add center, ci = center, ni = { x = invSqrt3, y = -invSqrt3, z = -invSqrt3 }, rj = { x = -boxHalfExtent, y = boxHalfExtent, z = boxHalfExtent } } ] sphereContactOctohedronPositions : Vec3 -> Float -> Float -> List ( Vec3, List Contact ) sphereContactOctohedronPositions center radius octoHalfExtent = let delta = 3 * Const.precision -- Reposition the octohedron so that it contacts the sphere at each: -- vertex -- edge (midpoint) -- face (center) -- face (at a point near a vertex) -- face (at a point near an edge midpoint) -- The adjustment of -Const.precision represents a minimum -- penetration value. vertexDimension = octoHalfExtent + radius - Const.precision edgeDimension = octoHalfExtent / 2 + radius / sqrt 2 - Const.precision faceDimension = octoHalfExtent / 3 + radius / sqrt 3 - Const.precision ceq vectors = ( vectors.cj, completeSphereContactEquation radius vectors ) invSqrt3 = 1 / sqrt 3 invSqrt2 = 1 / sqrt 2 in [ -- Octohedron positions and their contacts -- 6 vertex contacts ceq { ci = center, cj = { x = vertexDimension, y = 0, z = 0 } |> Vec3.add center, ni = Vec3.xAxis, rj = { x = -octoHalfExtent, y = 0, z = 0 } } , ceq { ci = center, cj = { x = 0, y = vertexDimension, z = 0 } |> Vec3.add center, ni = Vec3.yAxis, rj = { x = 0, y = -octoHalfExtent, z = 0 } } , ceq { ci = center, cj = { x = 0, y = 0, z = vertexDimension } |> Vec3.add center, ni = Vec3.zAxis, rj = { x = 0, y = 0, z = -octoHalfExtent } } , ceq { ci = center, cj = { x = 0, y = 0, z = -vertexDimension } |> Vec3.add center, ni = Vec3.zNegative, rj = { x = 0, y = 0, z = octoHalfExtent } } , ceq { ci = center, cj = { x = 0, y = -vertexDimension, z = 0 } |> Vec3.add center, ni = Vec3.yNegative, rj = { x = 0, y = octoHalfExtent, z = 0 } } , ceq { ci = center, cj = { x = -vertexDimension, y = 0, z = 0 } |> Vec3.add center, ni = Vec3.xNegative, rj = { x = octoHalfExtent, y = 0, z = 0 } } -- 12 edge (midpoint) contacts , ceq { ci = center, cj = { x = edgeDimension, y = edgeDimension, z = 0 } |> Vec3.add center, ni = { x = invSqrt2, y = invSqrt2, z = 0 }, rj = { x = -octoHalfExtent / 2, y = -octoHalfExtent / 2, z = 0 } } , ceq { ci = center, cj = { x = 0, y = edgeDimension, z = edgeDimension } |> Vec3.add center, ni = { x = 0, y = invSqrt2, z = invSqrt2 }, rj = { x = 0, y = -octoHalfExtent / 2, z = -octoHalfExtent / 2 } } , ceq { ci = center, cj = { x = edgeDimension, y = 0, z = edgeDimension } |> Vec3.add center, ni = { x = invSqrt2, y = 0, z = invSqrt2 }, rj = { x = -octoHalfExtent / 2, y = 0, z = -octoHalfExtent / 2 } } , ceq { ci = center, cj = { x = -edgeDimension, y = edgeDimension, z = 0 } |> Vec3.add center, ni = { x = -invSqrt2, y = invSqrt2, z = 0 }, rj = { x = octoHalfExtent / 2, y = -octoHalfExtent / 2, z = 0 } } , ceq { ci = center, cj = { x = 0, y = -edgeDimension, z = edgeDimension } |> Vec3.add center, ni = { x = 0, y = -invSqrt2, z = invSqrt2 }, rj = { x = 0, y = octoHalfExtent / 2, z = -octoHalfExtent / 2 } } , ceq { ci = center, cj = { x = edgeDimension, y = 0, z = -edgeDimension } |> Vec3.add center, ni = { x = invSqrt2, y = 0, z = -invSqrt2 }, rj = { x = -octoHalfExtent / 2, y = 0, z = octoHalfExtent / 2 } } , ceq { ci = center, cj = { x = edgeDimension, y = -edgeDimension, z = 0 } |> Vec3.add center, ni = { x = invSqrt2, y = -invSqrt2, z = 0 }, rj = { x = -octoHalfExtent / 2, y = octoHalfExtent / 2, z = 0 } } , ceq { ci = center, cj = { x = 0, y = edgeDimension, z = -edgeDimension } |> Vec3.add center, ni = { x = 0, y = invSqrt2, z = -invSqrt2 }, rj = { x = 0, y = -octoHalfExtent / 2, z = octoHalfExtent / 2 } } , ceq { ci = center, cj = { x = -edgeDimension, y = 0, z = edgeDimension } |> Vec3.add center, ni = { x = -invSqrt2, y = 0, z = invSqrt2 }, rj = { x = octoHalfExtent / 2, y = 0, z = -octoHalfExtent / 2 } } , ceq { ci = center, cj = { x = -edgeDimension, y = -edgeDimension, z = 0 } |> Vec3.add center, ni = { x = -invSqrt2, y = -invSqrt2, z = 0 }, rj = { x = octoHalfExtent / 2, y = octoHalfExtent / 2, z = 0 } } , ceq { ci = center, cj = { x = 0, y = -edgeDimension, z = -edgeDimension } |> Vec3.add center, ni = { x = 0, y = -invSqrt2, z = -invSqrt2 }, rj = { x = 0, y = octoHalfExtent / 2, z = octoHalfExtent / 2 } } , ceq { ci = center, cj = { x = -edgeDimension, y = 0, z = -edgeDimension } |> Vec3.add center, ni = { x = -invSqrt2, y = 0, z = -invSqrt2 }, rj = { x = octoHalfExtent / 2, y = 0, z = octoHalfExtent / 2 } } -- 8 face center contacts , ceq { ci = center, cj = { x = faceDimension, y = faceDimension, z = faceDimension } |> Vec3.add center, ni = { x = invSqrt3, y = invSqrt3, z = invSqrt3 }, rj = { x = -octoHalfExtent / 3, y = -octoHalfExtent / 3, z = -octoHalfExtent / 3 } } , ceq { ci = center, cj = { x = faceDimension, y = faceDimension, z = -faceDimension } |> Vec3.add center, ni = { x = invSqrt3, y = invSqrt3, z = -invSqrt3 }, rj = { x = -octoHalfExtent / 3, y = -octoHalfExtent / 3, z = octoHalfExtent / 3 } } , ceq { ci = center, cj = { x = faceDimension, y = -faceDimension, z = faceDimension } |> Vec3.add center, ni = { x = invSqrt3, y = -invSqrt3, z = invSqrt3 }, rj = { x = -octoHalfExtent / 3, y = octoHalfExtent / 3, z = -octoHalfExtent / 3 } } , ceq { ci = center, cj = { x = faceDimension, y = -faceDimension, z = -faceDimension } |> Vec3.add center, ni = { x = invSqrt3, y = -invSqrt3, z = -invSqrt3 }, rj = { x = -octoHalfExtent / 3, y = octoHalfExtent / 3, z = octoHalfExtent / 3 } } , ceq { ci = center, cj = { x = -faceDimension, y = faceDimension, z = faceDimension } |> Vec3.add center, ni = { x = -invSqrt3, y = invSqrt3, z = invSqrt3 }, rj = { x = octoHalfExtent / 3, y = -octoHalfExtent / 3, z = -octoHalfExtent / 3 } } , ceq { ci = center, cj = { x = -faceDimension, y = faceDimension, z = -faceDimension } |> Vec3.add center, ni = { x = -invSqrt3, y = invSqrt3, z = -invSqrt3 }, rj = { x = octoHalfExtent / 3, y = -octoHalfExtent / 3, z = octoHalfExtent / 3 } } , ceq { ci = center, cj = { x = -faceDimension, y = -faceDimension, z = faceDimension } |> Vec3.add center, ni = { x = -invSqrt3, y = -invSqrt3, z = invSqrt3 }, rj = { x = octoHalfExtent / 3, y = octoHalfExtent / 3, z = -octoHalfExtent / 3 } } , ceq { ci = center, cj = { x = -faceDimension, y = -faceDimension, z = -faceDimension } |> Vec3.add center, ni = { x = -invSqrt3, y = -invSqrt3, z = -invSqrt3 }, rj = { x = octoHalfExtent / 3, y = octoHalfExtent / 3, z = octoHalfExtent / 3 } } -- 3 face (near vertex) contacts , ceq { ci = center, cj = { x = vertexDimension - delta, y = delta, z = delta } |> Vec3.add center, ni = { x = 1, y = delta, z = delta }, rj = { x = -octoHalfExtent, y = 0, z = 0 } } , ceq { ci = center, cj = { x = delta, y = delta, z = vertexDimension - delta } |> Vec3.add center, ni = { x = delta, y = delta, z = 1 }, rj = { x = 0, y = 0, z = -octoHalfExtent } } , ceq { ci = center, cj = { x = delta - vertexDimension, y = delta, z = delta } |> Vec3.add center, ni = { x = -1, y = delta, z = delta }, rj = { x = octoHalfExtent, y = 0, z = 0 } } -- 3 face (near edge) contacts , ceq { ci = center, cj = { x = edgeDimension - delta, y = edgeDimension - delta, z = delta } |> Vec3.add center, ni = { x = invSqrt2, y = invSqrt2, z = delta }, rj = { x = delta - octoHalfExtent / 2, y = -octoHalfExtent / 2, z = 0 } } , ceq { ci = center, cj = { x = delta, y = edgeDimension - delta, z = edgeDimension - delta } |> Vec3.add center, ni = { x = delta, y = invSqrt2, z = invSqrt2 }, rj = { x = 0, y = -octoHalfExtent / 2, z = delta - octoHalfExtent / 2 } } , ceq { ci = center, cj = { x = delta - edgeDimension, y = -delta, z = delta - edgeDimension } |> Vec3.add center, ni = { x = -invSqrt2, y = -delta, z = -invSqrt2 }, rj = { x = octoHalfExtent / 2, y = 0, z = octoHalfExtent / 2 } } ] completeSphereContactEquation : Float -> { ni : Vec3, rj : Vec3, ci : Vec3, cj : Vec3 } -> List Contact completeSphereContactEquation radius { ni, rj, ci, cj } = [ { id = "" , ni = ni , pi = Vec3.add ci (Vec3.scale radius ni) , pj = Vec3.add cj rj } ] ================================================ FILE: tests/KinematicTest.elm ================================================ module KinematicTest exposing (integration) {-| Tests that a kinematic body's transform advances by velocity × dt each simulation step, and that contacts with dynamic bodies use the kinematic's velocity for friction (so dynamic bodies riding a moving kinematic platform don't slide off). -} import Expect import Length import Physics exposing (onEarth) import Physics.Material as Material import Physics.Shape as Shape import Plane3d import Point3d import Sphere3d import Test exposing (Test, describe, test) import Vector3d {-| Kinematic bodies translate by velocity × dt and rotate by angularVelocity × dt each simulation step, regardless of gravity or contacts. The user-set velocity is preserved across the step. -} integration : Test integration = describe "Physics.kinematic" [ test "translates by velocity * dt each step" <| \_ -> let floor = Physics.plane Plane3d.xy Material.wood platform = Physics.kinematic [ ( Shape.sphere (Sphere3d.atOrigin (Length.meters 0.5)) , Material.wood ) ] |> Physics.moveTo (Point3d.meters 0 0 5) |> Physics.setVelocityTo (Vector3d.metersPerSecond 1 0 0) ( simulated, _ ) = Physics.simulate onEarth [ ( "floor", floor ), ( "platform", platform ) ] platformAfter = simulated |> List.filter (\( id, _ ) -> id == "platform") |> List.head |> Maybe.map Tuple.second in case platformAfter of Just b -> let origin = Point3d.toMeters (Physics.originPoint b) in Expect.all [ \_ -> origin.x |> Expect.within (Expect.Absolute 0.0001) (1 / 60) , \_ -> origin.z |> Expect.within (Expect.Absolute 0.0001) 5 ] () Nothing -> Expect.fail "platform missing from simulation output" , test "preserves velocity (gravity does not affect a kinematic body)" <| \_ -> let platform = Physics.kinematic [ ( Shape.sphere (Sphere3d.atOrigin (Length.meters 0.5)) , Material.wood ) ] |> Physics.setVelocityTo (Vector3d.metersPerSecond 1 0 0) ( simulated, _ ) = Physics.simulate onEarth [ ( "platform", platform ) ] platformAfter = simulated |> List.head |> Maybe.map Tuple.second in case platformAfter of Just b -> let v = Vector3d.unwrap (Physics.velocity b) in Expect.all [ \_ -> v.x |> Expect.within (Expect.Absolute 0.0001) 1 , \_ -> v.y |> Expect.within (Expect.Absolute 0.0001) 0 , \_ -> v.z |> Expect.within (Expect.Absolute 0.0001) 0 ] () Nothing -> Expect.fail "platform missing from simulation output" , test "moveTo does not affect velocity (teleport keeps the kinematic moving)" <| \_ -> let platform = Physics.kinematic [ ( Shape.sphere (Sphere3d.atOrigin (Length.meters 0.5)) , Material.wood ) ] |> Physics.setVelocityTo (Vector3d.metersPerSecond 2 0 0) |> Physics.moveTo (Point3d.meters 100 0 5) v = Vector3d.unwrap (Physics.velocity platform) in v.x |> Expect.within (Expect.Absolute 0.0001) 2 , test "static bodies still ignore setVelocityTo" <| \_ -> let wall = Physics.static [ ( Shape.sphere (Sphere3d.atOrigin (Length.meters 0.5)) , Material.wood ) ] |> Physics.setVelocityTo (Vector3d.metersPerSecond 5 0 0) v = Vector3d.unwrap (Physics.velocity wall) in v.x |> Expect.within (Expect.Absolute 0.0001) 0 ] ================================================ FILE: tests/Matrix3Test.elm ================================================ module Matrix3Test exposing (inverse) import Extra.Expect as Expect import Internal.Matrix3 as Mat3 exposing (Mat3) import Test exposing (Test, describe, test) inverse : Test inverse = describe "Mat3.inverse" [ test "works for identity" <| \_ -> Expect.mat3 identity (Mat3.inverse identity) , test "works for arbitrary" <| \_ -> Expect.mat3 { m11 = -24, m21 = 20, m31 = -5, m12 = 18, m22 = -15, m32 = 4, m13 = 5, m23 = -4, m33 = 1 } (Mat3.inverse { m11 = 1, m21 = 0, m31 = 5, m12 = 2, m22 = 1, m32 = 6, m13 = 3, m23 = 4, m33 = 0 }) , test "matrix multiplied by its inverse should result in the indentity matrix" <| \_ -> Expect.mat3 identity (Mat3.mul { m11 = -24, m21 = 20, m31 = -5, m12 = 18, m22 = -15, m32 = 4, m13 = 5, m23 = -4, m33 = 1 } (Mat3.inverse { m11 = -24, m21 = 20, m31 = -5, m12 = 18, m22 = -15, m32 = 4, m13 = 5, m23 = -4, m33 = 1 }) ) , test "inverse of inverse is the matrix itself" <| \_ -> Expect.mat3 { m11 = 1, m21 = 0, m31 = 5, m12 = 2, m22 = 1, m32 = 6, m13 = 3, m23 = 4, m33 = 0 } (Mat3.inverse (Mat3.inverse { m11 = 1, m21 = 0, m31 = 5, m12 = 2, m22 = 1, m32 = 6, m13 = 3, m23 = 4, m33 = 0 })) ] identity : Mat3 identity = { m11 = 1 , m21 = 0 , m31 = 0 , m12 = 0 , m22 = 1 , m32 = 0 , m13 = 0 , m23 = 0 , m33 = 1 } ================================================ FILE: tests/PlaceInTest.elm ================================================ module PlaceInTest exposing (placeTests) import Angle import Axis3d import Block3d import Extra.Expect as Expect import Frame3d import Physics import Physics.Material as Material import Point3d import Test exposing (Test, describe, test) unitBlock : Physics.Body unitBlock = Physics.block (Block3d.from (Point3d.meters -0.5 -0.5 -0.5) (Point3d.meters 0.5 0.5 0.5)) Material.wood placeTests : Test placeTests = describe "Physics.place" [ test "place atOrigin is identity" <| \_ -> unitBlock |> Physics.place Frame3d.atOrigin |> Physics.frame |> Expect.frame3d Frame3d.atOrigin , test "place sets position" <| \_ -> let target = Frame3d.atPoint (Point3d.meters 1 2 3) in unitBlock |> Physics.place target |> Physics.frame |> Expect.frame3d target , test "place sets orientation" <| \_ -> let target = Frame3d.atOrigin |> Frame3d.rotateAround Axis3d.z (Angle.degrees 90) in unitBlock |> Physics.place target |> Physics.frame |> Expect.frame3d target , test "place sets position and orientation" <| \_ -> let target = Frame3d.atPoint (Point3d.meters 3 4 5) |> Frame3d.rotateAround Axis3d.x (Angle.degrees 45) in unitBlock |> Physics.place target |> Physics.frame |> Expect.frame3d target , test "place overwrites previous moveTo" <| \_ -> let target = Frame3d.atPoint (Point3d.meters 10 0 0) in unitBlock |> Physics.moveTo (Point3d.meters 1 2 3) |> Physics.place target |> Physics.frame |> Expect.frame3d target , test "place overwrites previous rotateAround" <| \_ -> let target = Frame3d.atOrigin |> Frame3d.rotateAround Axis3d.y (Angle.degrees 30) in unitBlock |> Physics.rotateAround Axis3d.z (Angle.degrees 90) |> Physics.place target |> Physics.frame |> Expect.frame3d target , test "place roundtrips with frame" <| \_ -> let moved = unitBlock |> Physics.moveTo (Point3d.meters 1 2 3) |> Physics.rotateAround Axis3d.z (Angle.degrees 45) bodyFrame = Physics.frame moved in unitBlock |> Physics.place bodyFrame |> Physics.frame |> Expect.frame3d bodyFrame , test "place with mirrored frame ignores mirroring" <| \_ -> let -- reverseX makes a left-handed frame mirrored = Frame3d.atPoint (Point3d.meters 1 2 3) |> Frame3d.rotateAround Axis3d.z (Angle.degrees 45) |> Frame3d.reverseX -- x and y are preserved, z is recomputed as x cross y expected = Frame3d.atPoint (Point3d.meters 1 2 3) |> Frame3d.rotateAround Axis3d.z (Angle.degrees 45) |> Frame3d.reverseX |> Frame3d.reverseZ in unitBlock |> Physics.place mirrored |> Physics.frame |> Expect.frame3d expected ] ================================================ FILE: tests/Shapes/ConvexTest.elm ================================================ module Shapes.ConvexTest exposing ( centerOfMass , extendContour , faces , inertia , uniqeNormals , uniqueEdges , volume ) import Expect import Extra.Expect as Expect import Fixtures.Convex import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 import Shapes.Convex as Convex import Test exposing (Test, describe, test) inertia : Test inertia = describe "inertia" [ test "inertia of a Convex.fromBlock is the same as Convex.fromTriangularMesh" <| \_ -> (Fixtures.Convex.block Transform3d.atOrigin 2 3 5).inertia |> Expect.mat3 (Convex.fromBlock 2 3 5).inertia , test "inertia of transformed geometry is the same as transformed inertia of original geometry" <| \_ -> let transform3d = Transform3d.atPoint { x = 12, y = 2, z = -3 } |> Transform3d.rotateAroundOwn Vec3.zAxis (pi / 5) |> Transform3d.rotateAroundOwn Vec3.xAxis (pi / 5) in (Fixtures.Convex.block transform3d 2 3 5).inertia |> Expect.mat3 (Transform3d.inertiaRotateIn transform3d (Convex.fromBlock 2 3 5).inertia) ] centerOfMass : Test centerOfMass = describe "centerOfMass" [ test "centerOfMass of transformed geometry is the same as transformed centerOfMass" <| \_ -> let transform3d = Transform3d.atPoint { x = 2, y = 1, z = -2 } |> Transform3d.rotateAroundOwn Vec3.zAxis (pi / 5) |> Transform3d.rotateAroundOwn Vec3.xAxis (pi / 5) in (Fixtures.Convex.block transform3d 2 3 5).position |> Expect.vec3 (Convex.placeIn transform3d (Convex.fromBlock 2 3 5)).position ] volume : Test volume = describe ".volume" [ test "volume of a Convex.fromBlock is the same as Convex.fromTriangularMesh" <| \_ -> (Fixtures.Convex.block Transform3d.atOrigin 2 3 1).volume |> Expect.equal (Convex.fromBlock 2 3 1).volume ] extendContour : Test extendContour = describe "Convex.extendContour" [ test "works for the first point" <| \_ -> Convex.extendContour ( 666, 4, 3 ) [ 1, 2, 3, 4, 5, 6 ] |> Expect.equal [ 1, 2, 3, 666, 4, 5, 6 ] , test "works for the second point" <| \_ -> Convex.extendContour ( 3, 666, 4 ) [ 1, 2, 3, 4, 5, 6 ] |> Expect.equal [ 1, 2, 3, 666, 4, 5, 6 ] , test "works for the third point" <| \_ -> Convex.extendContour ( 4, 3, 666 ) [ 1, 2, 3, 4, 5, 6 ] |> Expect.equal [ 1, 2, 3, 666, 4, 5, 6 ] , test "works for the first point at the end" <| \_ -> Convex.extendContour ( 666, 1, 6 ) [ 1, 2, 3, 4, 5, 6 ] |> Expect.equal [ 1, 2, 3, 4, 5, 6, 666 ] , test "works for the second point at the end" <| \_ -> Convex.extendContour ( 6, 666, 1 ) [ 1, 2, 3, 4, 5, 6 ] |> Expect.equal [ 1, 2, 3, 4, 5, 6, 666 ] , test "works for the third point at the end" <| \_ -> Convex.extendContour ( 1, 6, 666 ) [ 1, 2, 3, 4, 5, 6 ] |> Expect.equal [ 1, 2, 3, 4, 5, 6, 666 ] ] faces : Test faces = let normalsPointOutside convex = Expect.all (List.map (\{ normal, vertices } -> \_ -> Expect.equal True (List.all (\v -> let pointsOutside = Vec3.dot (Vec3.sub v convex.position) normal > 0 in pointsOutside ) vertices ) ) convex.faces ) () hasCorrectWindingOrder convex = Expect.all (List.map (\{ vertices, normal } -> case vertices of p1 :: p2 :: p3 :: rest -> \_ -> faceWindingOrderHelp normal p1 (p2 :: p3 :: rest) [] _ -> \_ -> Expect.fail "face with wrong number of vertices" ) convex.faces ) () faceWindingOrderHelp normal firstVertex vertices expectations = case vertices of p1 :: p2 :: restVertices -> faceWindingOrderHelp normal firstVertex (p2 :: restVertices) (Expect.vec3 normal (Convex.computeNormal firstVertex p1 p2) :: expectations) _ -> Expect.all (List.map always expectations) () in describe ".faces" [ test "block faces have correct normals" <| \_ -> List.map .normal (Convex.fromBlock 2 2 2).faces |> Expect.equal [ Vec3.zNegative , Vec3.zAxis , Vec3.yNegative , Vec3.yAxis , Vec3.xNegative , Vec3.xAxis ] , test "block faces have correct winding order" <| \_ -> hasCorrectWindingOrder (Convex.fromBlock 2 2 2) , test "block face normals point outside" <| \_ -> normalsPointOutside (Convex.fromBlock 2 2 2) , test "square pyramid face normals point outside" <| \_ -> normalsPointOutside Fixtures.Convex.squarePyramid , test "cylinder face normals point outside" <| \_ -> normalsPointOutside (Convex.fromCylinder 6 4 5) , test "cylinder faces have correct winding order" <| \_ -> hasCorrectWindingOrder (Convex.fromCylinder 6 4 5) , test "convex block face normals point outside" <| \_ -> normalsPointOutside (Fixtures.Convex.block Transform3d.atOrigin 4 4 4) , test "convex block face have correct winding order" <| \_ -> hasCorrectWindingOrder (Fixtures.Convex.block Transform3d.atOrigin 4 4 4) , test "huge convex face normals point outside" <| \_ -> normalsPointOutside Fixtures.Convex.hugeConvex , test "huge convex faces have correct winding order" <| \_ -> hasCorrectWindingOrder Fixtures.Convex.hugeConvex ] uniqeNormals : Test uniqeNormals = describe ".uniqeNormals" [ test "works for a block" <| \_ -> (Convex.fromBlock 2 2 2).uniqueNormals |> Expect.equal Vec3.basis , test "works for a square pyramid" <| \_ -> List.length Fixtures.Convex.squarePyramid.uniqueNormals |> Expect.equal 5 ] uniqueEdges : Test uniqueEdges = describe ".uniqueEdges" [ test "works for a block" <| \_ -> (Convex.fromBlock 2 2 2).uniqueEdges |> Expect.equal Vec3.basis , test "works for a square pyramid" <| \_ -> List.length Fixtures.Convex.squarePyramid.uniqueEdges |> Expect.equal 6 , test "works for an off-square pyramid" <| \_ -> List.length Fixtures.Convex.askewSquarePyramid.uniqueEdges |> Expect.equal 6 , test "works for a non-square-quad-based pyramid" <| \_ -> List.length Fixtures.Convex.nonSquareQuadPyramid.uniqueEdges -- all edges unique, none parallel |> Expect.equal 8 ] ================================================ FILE: tests/SimulateTest.elm ================================================ module SimulateTest exposing (assignIds) {-| Tests for the ID assignment logic in Physics.simulate. Bodies carry an internal id field. New bodies start with id = -1. On each simulate call: - bodies with id = -1 are assigned fresh IDs, filling gaps first - bodies with existing IDs keep them unchanged - the order of bodies in the output matches the input -} import Expect import Physics exposing (onEarth) import Physics.Material as Material import Physics.Types as Types import Plane3d import Test exposing (Test, describe, test) import Vector3d {-| Run one simulation step with no forces, just to trigger ID assignment. -} step : List ( id, Physics.Body ) -> ( List ( id, Physics.Body ), Physics.Contacts id ) step bodies = Physics.simulate { onEarth | gravity = Vector3d.zero } bodies {-| Extract both the external (user) id and the internal body id from a body entry. -} ids : ( id, Physics.Body ) -> ( id, Int ) ids ( extId, Types.Body body ) = ( extId, body.id ) {-| Manually set the internal id on a body, to simulate a body that has already been through a simulation step. -} withInternalId : Int -> Physics.Body -> Physics.Body withInternalId newId (Types.Body body) = Types.Body { body | id = newId } assignIds : Test assignIds = describe "Physics.simulate ID assignment" [ test "empty input produces empty output" <| \_ -> let ( result, _ ) = step [] in Expect.equal [] (List.map ids result) , test "single new body: gets internal id 0, external id preserved, order kept" <| \_ -> let ( result, _ ) = step [ ( "a", Physics.plane Plane3d.xy Material.wood ) ] in Expect.equal [ ( "a", 0 ) ] (List.map ids result) , test "multiple new bodies: get consecutive internal ids, external ids and order preserved" <| \_ -> let ( result, _ ) = step [ ( "a", Physics.plane Plane3d.xy Material.wood ) , ( "b", Physics.plane Plane3d.xy Material.wood ) , ( "c", Physics.plane Plane3d.xy Material.wood ) ] in -- foldl assigns ids left-to-right: "a"->0, "b"->1, "c"->2 Expect.equal [ ( "a", 0 ), ( "b", 1 ), ( "c", 2 ) ] (List.map ids result) , test "internal and external ids are preserved across simulation steps" <| \_ -> let ( step1, _ ) = step [ ( "a", Physics.plane Plane3d.xy Material.wood ) , ( "b", Physics.plane Plane3d.xy Material.wood ) ] ( step2, _ ) = step step1 in Expect.equal (List.map ids step1) (List.map ids step2) , test "new body added to existing sim: existing ids unchanged, new body gets gap id, order kept" <| \_ -> let ( step1, _ ) = step [ ( "a", Physics.plane Plane3d.xy Material.wood ) , ( "b", Physics.plane Plane3d.xy Material.wood ) ] -- step1 produces: [("a", 0), ("b", 1)] -- add "c" at the end; next available id is 2 ( step2, _ ) = step (step1 ++ [ ( "c", Physics.plane Plane3d.xy Material.wood ) ]) in Expect.equal [ ( "a", 0 ), ( "b", 1 ), ( "c", 2 ) ] (List.map ids step2) , test "new body fills gap left by a removed body, order kept" <| \_ -> let -- Simulate [b1(id=0), b2(id=2)] — gap at 1 existing = [ ( "a", withInternalId 0 (Physics.plane Plane3d.xy Material.wood) ) , ( "b", withInternalId 2 (Physics.plane Plane3d.xy Material.wood) ) ] ( result, _ ) = step (existing ++ [ ( "c", Physics.plane Plane3d.xy Material.wood ) ]) in -- "c" fills the gap at id=1; array stays compact [0,1,2] Expect.equal [ ( "a", 0 ), ( "b", 2 ), ( "c", 1 ) ] (List.map ids result) , test "body re-added with :: gets a new id, original keeps its id, order kept" <| \_ -> let ( step1, _ ) = step [ ( "a", Physics.plane Plane3d.xy Material.wood ) ] -- "a" now has some internal id; prepend it again as "b" using :: bodyA = step1 |> List.head |> Maybe.map Tuple.second |> Maybe.withDefault (Physics.plane Plane3d.xy Material.wood) ( result, _ ) = step (( "b", bodyA ) :: step1) in -- "b" is prepended with :: so it is the first occurrence and gets a fresh id (1); -- "a" is the last occurrence and keeps its id (0) Expect.equal [ ( "b", 1 ), ( "a", 0 ) ] (List.map ids result) , test "when two bodies share an id, the last in the list keeps it and the first gets a fresh one" <| \_ -> let -- "three" is prepended (::) with the same id=0 as "one". -- "three" is the first occurrence so gets a fresh id; "one" keeps id=0. bodies = [ ( "three", withInternalId 0 (Physics.plane Plane3d.xy Material.wood) ) , ( "two", withInternalId 1 (Physics.plane Plane3d.xy Material.wood) ) , ( "one", withInternalId 0 (Physics.plane Plane3d.xy Material.wood) ) ] ( result, _ ) = step bodies in Expect.equal [ ( "three", 2 ), ( "two", 1 ), ( "one", 0 ) ] (List.map ids result) , test "existing ids with gaps are preserved unchanged, order kept" <| \_ -> let bodies = [ ( "a", withInternalId 0 (Physics.plane Plane3d.xy Material.wood) ) , ( "b", withInternalId 5 (Physics.plane Plane3d.xy Material.wood) ) ] ( result, _ ) = step bodies in Expect.equal [ ( "a", 0 ), ( "b", 5 ) ] (List.map ids result) ] ================================================ FILE: tests/Transform3dFromFrame3dTest.elm ================================================ module Transform3dFromFrame3dTest exposing (conversion) import Angle import Axis3d import Direction3d import Extra.Expect as Expect import Frame3d exposing (Frame3d) import Internal.Transform3d as Transform3d exposing (Transform3d) import Length exposing (Meters) import Point3d import Test exposing (Test, describe, test) toFrame3d : Transform3d coords defines -> Frame3d Meters coords defines toFrame3d transform3d = let t = Transform3d.orientation transform3d in Frame3d.unsafe { originPoint = Point3d.unsafe (Transform3d.originPoint transform3d) , xDirection = Direction3d.unsafe { x = t.m11, y = t.m21, z = t.m31 } , yDirection = Direction3d.unsafe { x = t.m12, y = t.m22, z = t.m32 } , zDirection = Direction3d.unsafe { x = t.m13, y = t.m23, z = t.m33 } } fromFrame3d : Frame3d Meters coords defines -> Transform3d coords defines fromFrame3d frame3d = let origin = Point3d.unwrap (Frame3d.originPoint frame3d) x = Direction3d.unwrap (Frame3d.xDirection frame3d) y = Direction3d.unwrap (Frame3d.yDirection frame3d) z = Direction3d.unwrap (Frame3d.zDirection frame3d) in Transform3d.fromOriginAndBasis origin x y z conversion : Test conversion = let -- note this has to be right handed frame, use -- Frame3d.isRightHanded to check and reverse one of the axis frame3d = Frame3d.atPoint (Point3d.meters 0.5 0.6 0.7) |> Frame3d.rotateAround Axis3d.x (Angle.radians (pi / 5)) |> Frame3d.rotateAround Axis3d.y (Angle.radians (pi / 5)) point3d = Point3d.meters 0.234 0.234 0.234 in describe "Transform3d.fromOriginAndBasis" [ test "creates the correct frame3d for 180 rotation" <| \_ -> frame3d |> fromFrame3d |> toFrame3d |> Expect.frame3d frame3d , test "transforms the point exactly the same for 180 rotation" <| \_ -> Expect.vec3 (Point3d.unwrap (Point3d.placeIn frame3d point3d)) (Transform3d.pointPlaceIn (frame3d |> fromFrame3d) (Point3d.toMeters point3d)) ] ================================================ FILE: tests/Transform3dTest.elm ================================================ module Transform3dTest exposing (directionRelativeTo, inverse, pointRelativeTo, relativeTo) import Extra.Expect as Expect import Internal.Transform3d as Transform3d import Internal.Vector3 as Vec3 import Test exposing (Test, describe, test) pointRelativeTo : Test pointRelativeTo = let transform3d = Transform3d.atPoint { x = 0.5, y = 0.6, z = 0.7 } |> Transform3d.rotateAroundOwn Vec3.zAxis (pi / 5) |> Transform3d.rotateAroundOwn Vec3.xAxis (pi / 5) point = { x = 0.4, y = 0.6, z = 0.8 } in describe "Transform3d.pointRelativeTo" [ test "transforms the point back to its original value" <| \_ -> point |> Transform3d.pointPlaceIn transform3d |> Transform3d.pointRelativeTo transform3d |> Expect.vec3 point ] directionRelativeTo : Test directionRelativeTo = let transform3d = Transform3d.atPoint { x = 0.5, y = 0.6, z = 0.7 } |> Transform3d.rotateAroundOwn Vec3.zAxis (pi / 5) |> Transform3d.rotateAroundOwn Vec3.xAxis (pi / 5) direction = { x = 0.4, y = 0.6, z = 0.8 } in describe "Transform3d.directionRelativeTo" [ test "transforms the direction back to its original value" <| \_ -> direction |> Transform3d.directionPlaceIn transform3d |> Transform3d.directionRelativeTo transform3d |> Expect.vec3 direction ] relativeTo : Test relativeTo = let transform3d = Transform3d.atPoint { x = 0.5, y = 0.6, z = 0.7 } |> Transform3d.rotateAroundOwn Vec3.zAxis (pi / 5) |> Transform3d.rotateAroundOwn Vec3.xAxis (pi / 5) transform3dInverse = Transform3d.relativeTo transform3d Transform3d.atOrigin direction = { x = 0.4, y = 0.6, z = 0.8 } point = { x = 0.3, y = 0.5, z = 0.7 } in describe "Transform3d.relativeTo" [ test "transforms the direction back to its original value" <| \_ -> direction |> Transform3d.directionPlaceIn transform3d |> Transform3d.directionPlaceIn transform3dInverse |> Expect.vec3 direction , test "transforms the point back to its original value" <| \_ -> point |> Transform3d.pointPlaceIn transform3d |> Transform3d.pointPlaceIn transform3dInverse |> Expect.vec3 point ] inverse : Test inverse = let transform3d = Transform3d.atPoint { x = 0.5, y = 0.6, z = 0.7 } |> Transform3d.rotateAroundOwn Vec3.zAxis (pi / 5) |> Transform3d.rotateAroundOwn Vec3.xAxis (pi / 5) transform3dInverse = Transform3d.inverse transform3d direction = { x = 0.4, y = 0.6, z = 0.8 } point = { x = 0.3, y = 0.5, z = 0.7 } in describe "Transform3d.inverse" [ test "transforms the direction back to its original value" <| \_ -> direction |> Transform3d.directionPlaceIn transform3d |> Transform3d.directionPlaceIn transform3dInverse |> Expect.vec3 direction , test "transforms the point back to its original value" <| \_ -> point |> Transform3d.pointPlaceIn transform3d |> Transform3d.pointPlaceIn transform3dInverse |> Expect.vec3 point ]