Julia and Projective Geometric Algebra

experience the geometry superpowers of PGA

Greg Sepesi
192 min readJan 10, 2023

“As long as Algebra and Geometry were separated, their progress was slow and their use limited; but once these sciences were united, they lent each other mutual support and advanced rapidly together towards perfection. We owe to Descartes the application of Algebra to Geometry; this has become the key to the greatest discoveries in all fields of mathematics.” — Joseph-Louis Lagrange (1736–1813)

1. Why geometric algebra?

There are three compelling reasons for using geometric algebra instead of linear algebra:

  1. geometric algebra unifies many concepts and therefore makes them easier to remember and easier to implement,
  2. geometric algebra uses geometric objects (e.g., points, lines, planes) as fundamental abstractions that hide the coordinates and are easier to mentally manipulate than matrices of coordinates, and
  3. educational magic. Concretely, many geometry calculations are easier to understand (i.e., they more neatly fit within a consistent mental framework) when they are expressed in geometric algebra instead of linear algebra. The reason for this probably has more to do with the still mysterious workings of brains rather than some fault in linear algebra.

1.1 unifying concepts

Geometric algebra is good at unifying concepts. For example, in geometric algebra

More concise implementations in software result in faster development, fewer bugs, and less technical debt. For example, this video shows Dr. Todd Ell, senior technical fellow at Collins Aerospace (with 68,000 employees, the world’s largest supplier of aerospace components) describing how he is pushing to convert the highly regulated Collins Aerospace design and development tool chain currently based upon linear algebra to being based instead upon geometric algebra.

There are many “sub-algebras” within geometric algebra. Certain sub-algebras are particularly well-suited to solving certain types of problems. The sub-algebra called Projective Geometric Algebra (PGA) is particularly well-suited to translating and rotating and screwing (i.e., a combination of translating and rotating) solid objects. PGA is also known as Plane-based Geometric Algebra.

1.2 coordinate free

This essay introduces PGA through the following four animated examples. Most of them are implemented in both geometric algebra and linear algebra to simplify the comparison of those two algebras. In geometric algebra, the coordinates are embedded in the geometric objects and that abstraction can be very helpful when dealing with very complex geometry problems. However, as you will see, most of the following animation examples are simple enough that being coordinate free is not a big advantage, similar to how being object oriented is not a big advantage in a program that just prints “Hello, World!”

Animation of FABRIK (i.e., Forward And Backward Reaching Inverse Kinematics) algorithm with and without self-collision avoidance.
Animation of inverse kinematics applicable to robotics, based upon Steven De Keninck’s inverse kinematics example application in JavaScript, and ported to Julia and Makie in ikv().
Julia and GLMakie animation of 3D object slicing for 3D printing, based upon Steven De Keninck’s pga3d_slicing javascript example application.
Julia and GLMakie animation of 3D version of Separating Axis Theorem (SAT). The moving cube turns green only when a separating plane is detected between the two cubes, meaning the two cubes are not intersecting.
Julia and GLMakie animation of origami, applicable to art and engineering projects.

1.3 educational magic

Steven De Keninck’s PGA cheat sheet formula for the area of a planar polygon (“area of edge loop” about halfway down the third column of https://bivector.net/3DPGA.pdf) is one half of the ideal norm of the sum of the lines. The very next formula on that same PGA cheat sheet, the volume of a triangle mesh (“vol of triangle mesh”) is one sixth of the ideal norm of the sum of the faces. To me, both formulas initially seemed mysterious but they are simple applications of the PGA join operation.

With PGA, the join of two points results in the line segment spanning from the first point to the second point. The join of two line segments results in the parallelogram having those two line segments as adjacent edges (i.e., sharing a vertex). The join of three line segments results in the parallelepiped having those three line segments as adjacent edges (i.e., sharing a vertex).

To derive the “area of edge loop” formula, start at any vertex on the polygon and also pick some arbitrary “origin” vertex that can be outside, inside, or on the polygon. Then, until the polygon vertex traversal returns to that same starting polygon vertex,

  • traverse the polygon two adjacent vertices at a time,
  • use the join operation to calculate the two line segments spanning from the “origin” to each of those two adjacent vertices,
  • use the join operation again to convert those two line segments into a parallelogram, and
  • sum all those parallelogram areas to get twice the “area of edge loop” (because each parallelogram area is twice the sought triangle area enclosed by the two adjacent polygon vertices and the “origin”).

Similarly, to derive the “vol of triangle mesh” formula, pick some arbitrary “origin” vertex that can be outside, inside, or on the triangle mesh. Then for each triangle in the mesh,

  • use the join operation to calculate the three line segments spanning from the “origin” to each of the triangle’s three vertices,
  • use the join operation again to convert those three line segments into a parallelepiped, and
  • sum all those parallelepiped volumes to get six times the “vol of triangle mesh” (because each parallelepiped volume is six times the sought tetrahedron volume enclosed by the three triangle vertices and the “origin”).

Still learning some geometry fundamentals, it was initially not clear to me why a tetrahedron has exactly one sixth the volume of its parallelepiped until I implemented the following animation.

# tetra3x.jl
using GLMakie, GLMakie.FileIO
using Images # for RGBA
include("ripga3d.jl") # needed for satpga() not sat()

# partition tetrahedron
function tetra3x()

# initialize figure
fig = Figure(size = (800, 800))
ax3d = GLMakie.Axis3(fig[1,1],
elevation = pi/8,
azimuth = -pi/4,
viewmode = :fit,
aspect = (1,1,1),
limits = (-2.,2., -2.,2., -2.,2.),
titlesize = 28,
title = "partition of half parallelepiped into three tetrahedra")

# load meshes of three tetrahedra
T1 = load("tetra1.obj") # tetrahedron 1 (stationary)
T1C_obs = Observable(RGBA(1, 0, 0, 0.5))
T2 = load("tetra2.obj")
T2C_obs = Observable(RGBA(0, 1, 0, 0.5))
T3 = load("tetra3.obj")
T3C_obs = Observable(RGBA(0, 0, 1, 0.5))

# initialize plot containing observable data
mesh!(ax3d, T1, color = T1C_obs)
mesh!(ax3d, T2, color = T2C_obs)
mesh!(ax3d, T3, color = T3C_obs)
fig

# generate video
iState = 0
nFrame = 360
record(fig, "tetra3x.mp4", 1:nFrame) do iFrame
if mod(iFrame-1, 60) == 0
iState += 1
if iState == 4
T1C_obs[] = RGBA(1, 0, 0, 0)
elseif iState == 5
T1C_obs[] = RGBA(1, 0, 0, 0.5)
T2C_obs[] = RGBA(0, 1, 0, 0)
elseif iState == 6
T2C_obs[] = RGBA(0, 1, 0, 0.5)
T3C_obs[] = RGBA(0, 0, 1, 0)
end
end
ax3d.azimuth[] = -3pi/4 - 2pi*(iFrame-1)/nFrame
end # for each video frame
end # tetra3x()

# quick and dirty ffmpeg conversion from tetra3x.mp4 to tetra3x.gif:
# /tool/ffmpeg-2022-07/bin/ffmpeg.exe -i tetra3x.mp4 -r 24 -s 480x480 -loop 0 tetra3x.gif

To be clear, the areas of parallelograms and the volumes of parallelepipeds can also be calculated using linear algebra determinants. However, the linear algebra determinant seems like an arbitrary concept rather than being part of a consistent framework of concepts: with geometric algebra, the join operation creates line segments from points and then (applied again) the join operation creates areas or volumes from those line segments.

In his book “A Thousand Brains — a new theory of intelligence”, Jeff Hawkins writes

“[…] becoming an expert in a field of study requires discovering a good framework to represent the associated data and facts. There may not be a ‘correct’ reference frame, and two individuals might arrange the facts differently. Discovering a useful reference frame is the most difficult part of learning, even though most of the time we are not consciously aware of it.”

From that perspective, perhaps a good description of geometric algebra is that it is a good framework to represent the associated data and facts about geometry. As a non-mathematician, I started porting some of Steven De Keninck’s impressive collection of free example applications from JavaScript to Julia in order to get a practical introduction to PGA. A more mathematical introduction to PGA is in Charles Gunn’s SIGGRAPH 2019 PGA course notes or Leo Dorst & Steven De Keninck’s A Guided Tour to the Plane-Based Geometric Algebra PGA. (I think of Charles Gunn as the Original Gangster of PGA, and I think of Steven De Keninck as the Most Valuable Player in PGA application development.)

In addition to not being a mathematician, I am also not an expert in Julia programming. I just wanted a simple PGA library that works in Julia’s REPL (Read Evaluate Print Loop) to enable faster experimenting with PGA. So I ported Steven De Keninck’s C++ reference implementation of 3D PGA to Julia. The Julia community mostly disagrees with my implementation, referring to my approach as “type piracy” due to the way I overload several vector operations. I instead think of my implementation as “type squatting” because the vector operations I overload are currently unused (and in my opinion, wasted) in Julia.

Either way (“type piracy” or “type squatting”) this essay takes advantage of having access to a simple PGA library within the Julia REPL and describes each PGA application example by using a “REPL sandwich”:

  • an initial REPL session introduces key concepts used in the PGA example application,
  • a listing of the PGA example application, and
  • a final REPL session reviews details about the PGA example application.

While reading the REPL sandwiches, you will see PGA’s ability to avoid special cases. Perhaps while writing your own REPL sessions, PGA will reveal itself as being a good framework for you too to become enough of a geometry expert to write interesting (and perhaps even needed) geometry applications.

1.4 REPL introduction

To get started, the following Julia REPL session for 2D geometry includes the file ripga2d.jl which enables the Julia REPL to evaluate 2D PGA expressions. (The filename ripga2d.jl is an acronym for Reference Implementation of Projective Geometric Algebra in 2 Dimensions and the file is a Julia port of bivector.net’s C++ reference implementation of PGA.) Then, the REPL session creates a PGA expression defining a single point P at coordinate x=2 and y=3. The last thing this simple REPL session does is list all the 2D PGA basis vectors which will be described shortly.

julia> include("ripga2d.jl"); # REPL A

julia> P = point(2,3); println(toStr(P) * ": $P")
3*e01 + 2*e20 + e12: Float32[0.0, 0.0, 0.0, 0.0, 3.0, 2.0, 1.0, 0.0]

julia> basis
8-element Vector{String}:
"1"
"e0"
"e1"
"e2"
"e01"
"e20"
"e12"
"e012"

In that REPL session, the seemingly simple task of translating a coordinate to a PGA expression is conceptually tricky because most of us are in the habit of thinking of a coordinate pair as components of a vector to a point and we tend to think of coordinates and vectors and points as all the same thing. In contrast, coordinates and vectors and points are different concepts in PGA.

To help show the relationships between coordinates and vectors and points within PGA expressions, some commands in this essay’s REPL sessions are accompanied by println() statements that print the resulting PGA expressions in string form and in vector form. In the string form of the PGA expressions, there are a lot of ‘e’ terms (e.g., e12). They are the PGA basis vectors. Even though they may initially look unusual, they appear so often that they quickly become familiar. In the 2D space, there are eight PGA basis vectors.

Referring back to the 2D example of point P at coordinate (2,3), its PGA expression in string form is P = 3*e01 + 2*e20 + e12. There are a couple naming and numbering conventions worth mentioning:

  • A basis vector with two indices is called a bivector. A basis vector with three indices is called a trivector. In general, a basis vector with k indices is called a k-vector and the number k is said to be the grade of that basis vector.
  • Whenever a basis vector includes a zero index (e.g., e01), the zero refers to the added dimension beyond the 2D work space. That added dimension is unique in that it has a degenerative metric (i.e., the square of any vector in the direction of that added dimension is zero).

As seen in the REPL session above, the PGA expression of the 2D point P (= 3*e01 + 2*e20 + e12) is composed of three bivectors. The two bivectors including a zero index define the coordinates of the point. The presence of the e12 term in the PGA expression denotes that it defines some Euclidean point with finite coordinates. Without the e12 term, the PGA expression would instead define a direction to some “ideal point” an infinite distance away. This distinction between points and directions is an example of clearly defining variables.

Similarly, as shown in the REPL session below, the PGA expression of a 3D point P at coordinate x=2, y=3, z=4 is composed of four trivectors. The three trivectors including a zero index define the coordinates of the point, and it is now the presence of the e123 term in the PGA expression that denotes the definition of some Euclidean point with finite coordinates. Without the e123 term, the PGA expression would instead define a direction to some “ideal point” an infinite distance away. In 3D space, there are 16 PGA basis vectors.

julia> include("ripga3d.jl"); # REPL B

julia> P = point(2,3,4); println(toStr(P) * ": $P")
4*e021 + 3*e013 + 2*e032 + e123: Float32[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 3.0, 2.0, 1.0, 0.0]

julia> basis
16-element Vector{String}:
"1"
"e0"
"e1"
"e2"
"e3"
"e01"
"e02"
"e03"
"e12"
"e31"
"e23"
"e021"
"e013"
"e032"
"e123"
"e0123"

So there are 8 basis vectors for 2D geometry and 16 basis vectors for 3D geometry. In general, there are 2ⁿ⁺¹ basis vectors for nD geometry, where the +1 can be thought of as the extra dimension necessary to stay “above the fray” of special cases.

A final note about points, as shown in the following REPL session there are a couple convenience functions in ripga2d.jl and ripga3d.jl that convert between point coordinates and point PGA expressions: point() converts coordinates to PGA expressions and toCoord() converts PGA expressions back to coordinates.

julia> include("ripga2d.jl"); # REPL C

julia> VC = rand(Float32, 2, 7) # Vertex Coordinates
2×7 Matrix{Float32}:
0.985271 0.319659 0.889354 0.325182 0.313495 0.0471022 0.897992
0.382062 0.84916 0.694406 0.350365 0.248394 0.272515 0.327345

julia> V = point(VC) # Vertex PGA expressions
8×7 Matrix{Float32}:
0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.382062 0.84916 0.694406 0.350365 0.248394 0.272515 0.327345
0.985271 0.319659 0.889354 0.325182 0.313495 0.0471022 0.897992
1.0 1.0 1.0 1.0 1.0 1.0 1.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0

julia> VC2 = toCoord(V)
2×7 Matrix{Float32}:
0.985271 0.319659 0.889354 0.325182 0.313495 0.0471022 0.897992
0.382062 0.84916 0.694406 0.350365 0.248394 0.272515 0.327345

As you know, a line can be defined by two points and the following REPL session does that in 2D space. Point P1 is on the x-axis at x=1/4, point P2 is on the x-axis at x=5/4, and point P3 is a copy of P2 except rotated counterclockwise by an angle theta about P1. Line L1 is defined by using the regressive product (i.e., the '∨' operator in math syntax which is the ‘&’ operator in programming syntax) to connect points P1 and P2. Similarly, line L2 is defined by using the regressive product to connect points P1 and P3. Finally, in the rightmost column of the result is the inner product (i.e., the ‘·’ operator in math syntax which is the ‘|’ operator in programming syntax) of lines L1 and L2. In contrast to linear algebra’s scalar inner product, the PGA inner product is an entire multivector but you can access just the scalar value if you wish (e.g., result[1,7]).

julia> include("ripga2d.jl"); # REPL D

julia> theta = pi/3
1.0471975511965976

julia> P = point(Float32.(
[1/4 5/4 1/4+cos(theta);
0 0 sin(theta)]));

julia> L = [P[:,1]&P[:,2] P[:,1]&P[:,3]];

julia> result = [basis P L L[:,1]|L[:,2]]
8×7 Matrix{Any}:
"1" 0.0 0.0 0.0 0.0 0.0 0.5
"e0" 0.0 0.0 0.0 0.0 -0.216506 0.0
"e1" 0.0 0.0 0.0 0.0 0.866025 0.0
"e2" 0.0 0.0 0.0 -1.0 -0.5 0.0
"e01" 0.0 0.0 0.866025 0.0 0.0 0.0
"e20" 0.25 1.25 0.75 0.0 0.0 0.0
"e12" 1.0 1.0 1.0 0.0 0.0 0.0
"e012" 0.0 0.0 0.0 0.0 0.0 0.0

Given that the regressive product of two points creates a line, it probably is not much of a surprise that the regressive product of three points creates a plane. That is what the next REPL session (REPL E) does, using the same points P1, P2, P from the previous REPL session (REPL D) but extended to 3D.

julia> include("ripga3d.jl"); # REPL E

julia> theta = pi/3
1.0471975511965976

julia> P = point(Float32.(
[1/4 5/4 1/4+cos(theta);
0 0 sin(theta);
0 0 0]));

julia> result = [basis P P[:,1]&P[:,2]&P[:,3]]
16×5 Matrix{Any}:
"1" 0.0 0.0 0.0 0.0
"e0" 0.0 0.0 0.0 0.0
"e1" 0.0 0.0 0.0 0.0
"e2" 0.0 0.0 0.0 0.0
"e3" 0.0 0.0 0.0 -0.866025
"e01" 0.0 0.0 0.0 0.0
"e02" 0.0 0.0 0.0 0.0
"e03" 0.0 0.0 0.0 0.0
"e12" 0.0 0.0 0.0 0.0
"e31" 0.0 0.0 0.0 0.0
"e23" 0.0 0.0 0.0 0.0
"e021" 0.0 0.0 0.0 0.0
"e013" 0.0 0.0 0.866025 0.0
"e032" 0.25 1.25 0.75 0.0
"e123" 1.0 1.0 1.0 0.0
"e0123" 0.0 0.0 0.0 0.0

Up until now in this essay, rotations have been performed on coordinates. Rotations can also be performed on PGA expressions, as shown in the following REPL session (REPL F) where a couple rotations on coordinates are also done on PGA expressions and the results are the same. Rotations on PGA expressions are performed by a sequence of two PGA operations called a sandwich operation: rotor R rotating object A is written as R A ~R in math syntax which is R >>> A in programming syntax, where ‘>>>’ is the sandwich operator and ‘~’ is the reverse operator that reverses the order of subtasks (e.g., you put on your socks then shoes, but you take off your shoes then socks).

julia> include("ripga2d.jl"); # RIPL F

julia> theta = pi/3
1.0471975511965976

julia> phi = pi/2
1.5707963267948966

julia> ROT = [cos(phi) -sin(phi); sin(phi) cos(phi)];

julia> P = point(Float32.(ROT *
[1/4 5/4 1/4+cos(theta);
0 0 sin(theta)]))
8×3 Matrix{Float32}:
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.25 1.25 0.75
1.53081f-17 7.65404f-17 -0.866025
1.0 1.0 1.0
0.0 0.0 0.0

julia> PB = point(Float32.(
[1/4 5/4 5/4 0;
0 0 0 0]));

julia> rot = rotor(theta, PB[:,1]);

julia> PB[:,3] = rot >>> PB[:,3];

julia> rot2 = rotor(phi, PB[:,4]);

julia> PBR = rot2 >>> PB;

julia> PBR[:,1:end-1]
8×3 Matrix{Float32}:
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.25 1.25 0.75
0.0 0.0 -0.866025
1.0 1.0 1.0
0.0 0.0 0.0

Of course, there is much more to geometry than points and lines and planes and their rotations. The following PGA expression cheat sheets provided by bivector.net are a guide to exploring PGA beyond points and lines and planes and their rotations.

bivector.net 2D PGA expression cheat sheet
Cheat sheet of 3D projective geometric algebra expressions.
bivector.net 3D PGA expression cheat sheet
Also from bivector.net is this table that relates the operators in math syntax (which is used in the bivector.net PGA expression cheat sheets) to the operators in programming syntax (which is used in applications).

Heavily depending on the above PGA expression cheat sheets, the following REPL session

  1. includes “ripga3d.jl” so the REPL can evaluate 3D PGA expressions,
  2. defines three vertices of a triangle in 3D space,
  3. translates the vertices from Euclidean coordinates to PGA expressions,
  4. constructs three triangle sides by joining pairs of those vertices,
  5. calculates the lengths of the triangle sides,
  6. constructs a plane face by joining all three vertices,
  7. calculates the area of the triangle,
  8. defines a horizontal plane at z=1/2,
  9. cuts the triangle with that horizontal plane, and finally
  10. plots the cut triangle.
julia> include("ripga3d.jl"); # REPL G

julia> VC = Float32.([0 -1/2 1/2; 0 -1 -1; 1 0 0]) # Vertex Coordinates
3×3 Matrix{Float32}:
0.0 -0.5 0.5
0.0 -1.0 -1.0
1.0 0.0 0.0

julia> V = point(VC) # Vertex PGA expressions
16×3 Matrix{Float32}:
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
1.0 0.0 0.0
0.0 -1.0 -1.0
0.0 -0.5 0.5
1.0 1.0 1.0
0.0 0.0 0.0

julia> S = [V[:,1]&V[:,2] V[:,2]&V[:,3] V[:,3]&V[:,1]] # triangle Sides
16×3 Matrix{Float32}:
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
-1.0 0.0 1.0
0.5 0.0 0.5
0.0 -1.0 0.0
1.0 0.0 -1.0
1.0 0.0 -1.0
0.5 -1.0 0.5
0.0 0.0 0.0
0.0 -0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0

julia> side_lengths = norm(S)
1×3 Matrix{Float32}:
1.5 1.0 1.5

julia> F = V[:,1] & V[:,2] & V[:,3]
16-element Vector{Float32}:
0.0
1.0
0.0
1.0
-1.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0

julia> area = norm(F)/2
0.70710677f0

julia> zcut = e3 - 0.5*e0
16-element Vector{Float32}:
0.0
-0.5
0.0
0.0
1.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0

julia> P = S ^ zcut
16×3 Matrix{Float32}:
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.5 0.0 -0.5
-0.5 0.0 0.5
-0.25 -0.5 -0.25
1.0 0.0 -1.0
0.0 0.0 0.0

julia> C = toCoord(P)
3×2 Matrix{Float32}:
-0.25 0.25
-0.5 -0.5
0.5 0.5

julia>

julia> using GLMakie

julia> fig = Figure()

julia> ax3d = Axis3(fig[1,1], aspect = (1,1,1));

julia> lines!(ax3d, [VE VE[:,1]], color = :black);

julia> scatter!(ax3d, VE, markersize = 25, color = [:red, :green, :blue]);

julia> lines!(ax3d, C, linestyle = :dot, color = :gray);

The resulting GLMakie plot shows the triangle cut by the plane at the dotted line.

Before stepping through that REPL session line by line, take a moment to contemplate how you would typically go about calculating the area of that triangle and the lengths of the sides. Compare that approach to the following line by line description of the REPL session that just briefly mentions coordinates and instead focuses on operations on geometric objects like points, lines, and planes.

  1. The first line includes “ripga3d.jl” which enables the Julia REPL to evaluate 3D PGA expressions. In addition to needing this include statement when starting a new Julia REPL, it is also needed when switching the Julia REPL PGA capabilities from 2D geometry to 3D geometry.
  2. The REPL session’s second line defines the Euclidean coordinates of the triangle’s three vertices. Concretely, the coordinates of the first vertex are (0,0,1), the coordinates of the second vertex are (-1/2,-1,0), and the coordinates of the third vertex are (1/2,-1,0). Because many geometry problems involve more than one point, it is often convenient to store all the coordinates of all the points in a single matrix. The convention of storing all of a point’s coordinates in a single column of the matrix is efficient in Julia because the elements of a column are stored right next to each other. That convention is also convenient in Julia because Julia plot packages recognize the elements of a column as coordinates of a point.
  3. The REPL session’s third line translates the 3D Euclidean coordinates to PGA expressions. Again, each point has its own column. By the way, it is Julia’s multiple dispatch capabilities that allow the same “point” function name to operate on a list of single coordinates (as in this essay’s very first REPL session) or a single column of coordinates or a matrix of coordinates (as in this line of the REPL session).
  4. The REPL session’s fourth line joins pairs of vertices into lines representing the sides of the triangles. Referring to the bivector.net 3D PGA cheat sheet, two points are joined into a line by using the regressive product. In math syntax the regressive product operator is ‘∨’ (e.g., line = point0 ∨ point1) and in programming syntax the regressive product operator is ‘&’ (e.g., line = point0 & point1). Again, each PGA expression of a triangle side gets its own column.
  5. The REPL session’s fifth line calculates the length of each triangle side. Referring to the bivector.net 3D PGA cheat sheet, the “norm” of a line is its length.
  6. The REPL session’s sixth line joins all three of the triangle’s vertices into a plane representing the face of the triangle. As before with the triangle sides, the regressive product operator is ‘∨’ in math syntax and ‘&’ in programming syntax.
  7. The REPL session’s seventh line calculates the area of the triangle’s face. The norm function is used again but, referring to the bivector.net 3D PGA cheat sheet, the norm of a PGA expression representing a plane needs to be divided by 2 to calculate the plane’s area.
  8. The REPL session’s eighth line, referring to the 3D PGA cheat sheet, defines the horizontal plane at z=1/2.
  9. The REPL session’s ninth line meets (i.e., intersects) that horizontal plane with the triangle sides using the outer product. The outer product operator is ‘∧’ in math syntax (e.g., Point = line ∧ plane) and ‘^’ in programming syntax (e.g., Point = line ^ plane).
  10. The remainder of the REPL session plots the cut triangle. Although Julia offers several plotting packages, in this essay I consistently use GLMakie which is based upon OpenGL and is surprisingly fast in generating animations.

The same concepts in the above REPL session that cut a triangle get extended in Example 3.2 into a 3D slicer application that slices a chocolate bunny (modeled as a mesh of 5002 triangles) as shown below.

Note that in the 2D plot above that shows the cross sections of the slices, the slice circumference is calculated twice: once without using PGA and once with using PGA. Concerning complexity and speed, they are basically the same. In 2D and 3D geometry, there are many geometry calculations in which PGA offers no advantage. However, there are some geometry calculations that are significantly easier because of PGA. For example, to calculate an arbitrary polygon’s area, simply sum the PGA expressions of the polygon’s edges as demonstrated in the following REPL session that estimates the area of the unit circle by summing the edges of a polygon that approximates the shape of the unit circle.

julia> include("ripga2d.jl"); # REPL H

julia> r = 1.0;

julia> nTheta = 128;

julia> THETA = LinRange(0,2*pi,nTheta+1);

julia> VC = Float32.([ # Vertex Coordinates
r .* cos.(THETA');
r .* sin.(THETA')]);

julia> V = point(VC); # Vertex PGA expressions

julia> E = V[:,1:nTheta] & V[:,2:nTheta+1]; # Edges (CCW)

julia> area = normIdeal(sum(E, dims=2)[:]) / 2
3.1403282f0

That polygon area formula is listed in the bivector.net PGA expression cheat sheet. While referring to the bivector.net PGA cheat sheets, you may have noticed a reference to a “dual”. In math syntax the dual operator is ‘∗’ (e.g., P∗) and in programming syntax the dual operator is ‘!’ (e.g., !P). Calculating the dual of a PGA expression is very simple: just reverse the order of the expression’s vector form, as shown in the following REPL session.

julia> include("ripga2d.jl"); # REPL I

julia> P = point(2,3); println(toStr(P) * ": $P")
3*e01 + 2*e20 + e12: Float32[0.0, 0.0, 0.0, 0.0, 3.0, 2.0, 1.0, 0.0]

julia> PD = !P; println(toStr(PD) * ": $PD")
e0 + 2*e1 + 3*e2: Float32[0.0, 1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0]

The string form of the dual of a point’s PGA expression has a familiar look … provided we incorrectly interpret e1 as a unit vector along the x axis and incorrectly interpret e2 as a unit vector along the y axis. In fact, in many PGA applications that directly define points by combining scaled basis vectors instead of calling the function point(), the dual operator is often used (e.g., P = !(e0 + 2*e1 + 3*e2)). However, the dual operator offers much more than a mnemonic for correctly ordering a point’s coordinates in a PGA expression. In PGA, each geometric object is associated with a unique dual object. In 2D, the dual of a point is a line (and of course vice versa). Therefore, if you write a program concerning 2D points/lines you get a free program concerning 2D lines/points without doing any extra work.

2. Why Julia?

Although bivector.net lists reference implementations of PGA in several programming languages (e.g., JavaScript, C++, C#, Python, Rust), it does not currently list a Julia reference implementation. Julia is also obviously missing from the book Geometric Algebra for Computer Science given that the Julia language was created two years after the book’s publication. However, both geometric algebra and Julia are now at a point at which they can combine forces to efficiently implement advanced geometry applications. By the end of this essay, you will see that

  1. the Julia port (listed in this essay’s appendix) of bivector.net’s C++ reference implementation of PGA is fast enough for many advanced geometry applications, and
  2. Julia’s GLMakie plotting package, based upon OpenGL, is fast enough for most real time interactive graphics that demonstrate PGA. In many cases the GLMakie plotting package generates videos faster than it takes to play them.

The premise of this essay is that the Julia programming language is a great environment to learn PGA and develop PGA code due to Julia’s

  • execution speed,
  • metaprogramming capabilities,
  • plotting capabilities,
  • REPL (Read Execute Print Loop), and
  • developer community.

2.1 Execution speed

As mentioned in the abstract to Julia: A Fresh Approach to Numerical Computing, the authors (i.e., four people who started the Julia programming language) mention a long standing belief among many practitioners of numerical computing: one must prototype in one language and then rewrite in another language for speed or deployment. One of their design goals for Julia was to solve this two language problem by making Julia both good for prototyping and also fast for deployment.

In this essay, the example PGA applications demonstrate that Julia is both good for prototyping PGA code and also fast enough to illustrate that code in action.

2.2 Metaprogramming capabilities

Referring again to the slide at bivector.net from Steven De Keninck’s presentation to SIGBRAPI (an international conference annually promoted by the Special Interest Group on Computer Graphics and Image Processing (CEGRAPI) of the Brazilian Computer Society (SBC)). It shows the differences between the “standard” math syntax and the “standard” programming syntax when writing PGA expressions.

A slide from Steven De Keninck’s presentation to SIGBRAPI, showing the differences between the “standard” math syntax and the “standard” programming syntax when writing geometric algebra expressions.

Julia’s extensive metaprogramming capabilities offer a convenient bridge from PGA “standard” math syntax to PGA “standard” programming syntax. For example, the string macro called ga (for geometric algebra) translates the Unicode thinspace character (i.e., U+02009) that represents the geometric product operator in math syntax to ‘*’ that represents the geometric product operator in programming syntax. Using thinspace as the geometric product operator in math syntax significantly unclutters PGA expressions. The following listing shows how the string macro ga translates all operators in math syntax (Unicode) into operators in programming syntax (UTF-8). It is a function from the reference implementation of PGA listed in the appendix.

# convert GA math syntax string to GA programming syntax expression
macro ga_str(str)
C = collect(str)
n = length(C)
for i = 1:n
if C[i] == ' ' # \thinspace for geometric product
C[i] = '*'
elseif C[i] == '∧' # \wedge for outer product
C[i] = '^'
elseif C[i] == '∨' # \vee for regressive product
C[i] = '&'
elseif C[i] == '·' # \cdotp for inner product
C[i] = '|'
elseif C[i] == '∗' # \ast for dual (suffix)
j = i-1
if C[j] == ')'
nDepth = 1
C[j+1] = C[j]
j -= 1
# shift from suffix to prefix of parentheses
while (j > 0) && (nDepth > 0)
if C[j] == ')'
nDepth += 1
elseif C[j] == '('
nDepth -= 1
end
C[j+1] = C[j]
j -= 1
end
else
# shift from suffix to prefix of variable
while (j > 0) && (isletter(C[j]) || isnumeric(C[j]) || C[j]=='_')
C[j+1] = C[j]
j -= 1
end
end
C[j+1] = '!' # prefix '!'
end
end
return esc(Meta.parse(String(C)))
end

Unfortunately, the math syntax Unicode operators are not consistently displayed by all applications: they look good in this browser, they look less good in VS Code (because the \thinspace Unicode character representing the geometric product operator is rendered as a box), and they look dreadful in Notepad++ (because all the Unicode characters are rendered as boxes).

# translate distance along line
function xlator(line::Vector{Float32},dist::Number)
# return ga"1 - dist/2 (e0 normalize(line) e0∗)"
return 1 - dist/2*(e0*normalize(line)*!e0)
end

Perhaps in a couple years all applications will reasonably render the Unicode version of PGA operator symbols but, for now, I include both math syntax and programming syntax in my PGA code. I typically comment out the math syntax instead of the programming syntax because the expression execution time of the math syntax is about 3% slower. I use the math syntax “executable comment” as a type of safety signage, warning future code maintainers (including myself) to be extra careful around the PGA expressions.

2.3 Plotting capabilities

According to the Makie documentation,

“Makie is a data visualization ecosystem for the Julia programming language, with high performance and extensibility. It is available for Windows, Mac and Linux.”

Currently, the Makie backend package with interactive plotting capabilities is GLMakie which is based upon OpenGL and is surprisingly fast.

2.4 REPL (Read Execute Print Loop)

In the tools section of bivector.net, there is a PGA expression evaluator that enables the exploration of PGA expressions without writing a program. Julia’s REPL does that and more: in addition to evaluating PGA expressions, it can assign PGA expressions to variables, plot expressions, examine the underlying data structures, and offer a common perspective across the Julia community when troubleshooting.

A common phrase throughout Julia’s discourse community platform is MWE (i.e., Minimal Working Example). Even though programmers are often working on intricate Julia programs, when asking for help from the Julia community programmers are encouraged to isolate their problem with a tiny snippet of code that can easily be replicated by everyone in their own REPL. This is an extremely useful programming practice. More than once when I felt stuck on a problem and was about to ask the Julia community for help, I was able to answer the question myself because the solution revealed itself as I was writing the MWE.

2.5 Developer community

In the conclusion of Julia: A Fresh Approach to Numerical Computing, the authors wrote

“We built Julia to meet our needs for numerical computing, and it turns out that many others wanted exactly the same thing. At the time of writing, not a day goes by when we don’t learn that someone new has picked up Julia at universities and companies around the world, in fields as diverse as engineering, mathematics, physical and social sciences, finance, biotech, and many others. More than just a language, Julia has become a place for programmers, physical scientists, social scientists, computational scientists, mathematicians, and others to pool their collective knowledge in the form of online discussions and code.”

The significant overlap of the many fields interested in Julia and the many fields interested in geometric algebras (e.g., projective geometric algebra, spacetime geometric algebra, conformal geometric algebra) suggests that both communities would benefit from each other.

3. REPL sandwiches explaining PGA applications

The following PGA application examples are based upon PGA application examples at bivector.net’s free sample application collection called The CoffeeShop.

Example 3.1 FABRIK inverse kinematics

Animation of FABRIK (i.e., Forward And Backward Reaching Inverse Kinematics) algorithm with and without self-collision avoidance.
Animation of inverse kinematics, based upon Steven De Keninck’s inverse kinematics example application in JavaScript, and ported to Julia and Makie in ikv().
Step by step illustration of the quick convergence (just 4 passes) of the inverse kinematics algorithm implemented here using projective geometric algebra.

The above figure illustrates the fast conversion of the inverse kinematics algorithm listed below, which is a port of Steven De Kininck’s ganja.js example application of inverse kinematics. Also in the listing below is the function iik() which implements an interactive inverse kinematics demonstration of the same inverse kinematics algorithm. For an overview of inverse kinematics algorithms in general, see Inverse Kinematics Techniques in Computer Graphics: A Survey.

# fabrik2d.jl
# interactive graphical demonstration of Inverse Kinematics
# iterative solver algorithm called FABRIK defined by
# Andreas Aristidou and Joan Lasenby in their paper at
# http://www.andreasaristidou.com/publications/papers/FABRIK.pdf
using GLMakie
using Printf
include("ripga2d.jl")

# translate distance along line
function xlator(line::Vector{Float32},dist::Number)
# return ga"1 - dist/2 (e0 normalize(line) e0∗)"
return 1 - dist/2*(e0*normalize(line)*!e0)
end

# inverse kinematics algorithm; plot of convergence rate
# arguments:
# - coordinates of target
# - number of links in robot arm
function ik(target::Vector{Float64}=[2.5,2.,0.], nLink::Int64=6)
nEP = nLink + 1 # number of link endpoints
armLength = 3 # max reach of robot arm

# initialize figure
fig = Figure(size = (1800, 800))
C = ["red"; "green"; "blue"] # line colors
LIM = (-2,3, -2.5,2.5)
YTIC = (-2:1:2)
AX = [
Axis(fig[1,1], limits=LIM, yticks=YTIC, aspect=1,
title = "1. initial endpoints,\n" *
"the target is separate from robot arm");
Axis(fig[1,2], limits=LIM, yticks=YTIC, aspect=1,
title = "2. endpoints after 1st pass\n" *
"of backward relaxation");
Axis(fig[1,3], limits=LIM, yticks=YTIC, aspect=1,
title = "3. endpoints after 1st pass\n" *
"of forward relaxation");
Axis(fig[1,4], limits=LIM, yticks=YTIC, aspect=1,
title = "4. endpoints after 2nd pass\n" *
"of backward relaxation");
Axis(fig[1,5], limits=LIM, yticks=YTIC, aspect=1,
title = "5. endpoints after 2nd pass\n" *
"of forward relaxation");
Axis(fig[2,2], limits=LIM, yticks=YTIC, aspect=1,
title = "6. endpoints after 3rd pass\n" *
"of backward relaxation");
Axis(fig[2,3], limits=LIM, yticks=YTIC, aspect=1,
title = "7. endpoints after 3rd pass\n" *
"of forward relaxation");
Axis(fig[2,4], limits=LIM, yticks=YTIC, aspect=1,
title = "8. endpoints after 4th pass\n" *
"of backward relaxation");
Axis(fig[2,5], limits=LIM, yticks=YTIC, aspect=1,
title = "9. endpoints after 4th pass\n" *
"of forward relaxation");
]

# allocate endpoint PGA expressions
# (appended endpoint in last column is target)
PX = Matrix{Float32}(undef, (length(e0),nEP+1))

# define link endpoints
linkLength::Float32 = armLength / nLink
for iEP = 1:nEP
# PX[:,iEP] = ga"(e0 + (iEP linkLength - 1.5) e1)∗"
PX[:,iEP] = !(e0 + (iEP*linkLength - 1.5)*e1)
end
PX[:,nEP+1] = point(target[1], target[2], target[3])

# plot link endpoints and target point
P = toCoord(PX)
iAx = 1
scatterlines!(AX[iAx], P[1:3,1:nEP], color="black")
scatterlines!(AX[iAx], # target point is at end
[P[1,end]], [P[2,end]], [P[3,end]],
color="black")

# plot results of each relaxation loop
for iRelax = 1:4
# set tip to target, changing length of last link
PX[:,nEP] = PX[:,nEP+1]
P = toCoord(PX)

# restore link lengths from back to front
iAx += 1
scatterlines!(AX[iAx],
P[1:3,1:nEP], color = "light gray")
for jLink = 1:nEP-2
i = nEP - jLink
iColor = mod(jLink-1,3) + 1
# XL = xlator(ga"PX[:,i+1] ∨ PX[:,i]", linkLength)
# PX[:,i] = ga"XL PX[:,i+1] ~XL" # perform translation
XL = xlator( # define translation along line
PX[:,i+1] & PX[:,i], linkLength)
PX[:,i] = XL >>> PX[:,i+1] # perform translation
P = toCoord(PX)
if i > 2
scatterlines!(AX[iAx],
P[1:3,i:i+1], color = C[iColor])
else
scatterlines!(AX[iAx],
P[1:3,i-1:i+1], color = C[iColor])
end
end

# restore link lengths from front to back
iAx += 1
scatterlines!(AX[iAx],
P[1:3,1:nEP], color = "light gray")
for i = 2:nEP
iColor = mod(i-2,3) + 1
# XL = xlator(ga"PX[:,i-1] ∨ PX[:,i]", linkLength)
# PX[:,i] = ga"XL PX[:,i-1] ~XL" # perform translation
XL = xlator( # define translation along line
PX[:,i-1] & PX[:,i], linkLength)
PX[:,i] = XL >>> PX[:,i-1] # perform translation
P = toCoord(PX)
scatterlines!(AX[iAx],
P[1:3,i-1:i], color = C[iColor])
end
end
fig
end

function ik_solver(PX::Matrix{Float32},
linkLength::Float32,
SRC::Matrix{Float32},
nPass::Int64,
alpha::Float64,
pressure::Float64 = 0)
nEP = size(PX,2) - 1 # -1 because last column is target
nSRC = size(SRC,2)

# for each relaxation pass
for iPass = 1:nPass
# set tip to target, changing length of last link
PX[:,nEP] = PX[:,nEP+1]

# initially nudge inner pivot points away from point sources
if alpha > 0.15 && iPass == 1
NUDGE = zeros(Float32, size(PX,1))
for i = 2:nEP-1
for iSRC = 1:nSRC
L = SRC[:,iSRC] & PX[:,i]
d = sqrt(L[6]^2 + L[5]^2)
XL = xlator(L, d+pressure/(1+d^2))
PN = XL >>> PX[:,i]
NUDGE += (PN - PX[:,i])
end
PX[:,i] += NUDGE
end # for
end # if

# restore link lengths from back to front
for jLink = 1:nEP-2
i = nEP - jLink
# XL = xlator(ga"PX[:,i+1] ∨ PX[:,i]", linkLength)
# PX[:,i] = ga"XL PX[:,i+1] ~XL" # perform translation
XL = xlator( # define translation along line
PX[:,i+1] & PX[:,i], linkLength)
PX[:,i] = XL >>> PX[:,i+1] # perform translation
end

# restore link lengths from front to back
for i = 2:nEP
# XL = xlator(ga"PX[:,i-1] ∨ PX[:,i]", linkLength)
# PX[:,i] = ga"XL PX[:,i-1] ~XL" # perform translation
XL = xlator( # define translation along line
PX[:,i-1] & PX[:,i], linkLength)
PX[:,i] = XL >>> PX[:,i-1] # perform translation
end
end
end

# interactive inverse kinematics
# arguments:
# - coordinates of target of robot arm
# - number of links in robot arm
function iik(target::Vector{Float64}=[2.5,2.,0.], nLink::Int64=6)
nEP = nLink + 1 # number of link endpoints
armLength = 3 # max reach of robot arm

# initialize figure
fig = Figure(size = (800, 800))
LIM = (-2,3, -2.5,2.5)
YTIC = (-2:1:2)
ax1 = Axis(fig[1,1], limits=LIM, yticks=YTIC, aspect=1,
title = "Interactive demonstration of inverse kinematics algorithm.\n" *
"(The target point is initially separated from the robot arm.\n" *
"Drag that target point around to see how the robot arm reacts.)")

# allocate and define endpoint PGA expressions
# (appended endpoint in last column is target)
PX = Matrix{Float32}(undef, (length(e0),nEP+1))
linkLength::Float32 = armLength / nLink
ANCHOR = [-1; 0; 0]
for iEP = 1:nEP
# PX[:,iEP] = ga"(e0 + ((iEP-1) linkLength + ANCHOR[1]) e1)∗"
PX[:,iEP] = !(e0 + ((iEP-1)*linkLength + ANCHOR[1])*e1)
end
PX[:,nEP+1] = point(target[1], target[2], target[3])

# calculate inverse kinematics
ik_solver(PX, linkLength)
P = toCoord(PX) # convert PGA expressions to Euclidean coordinates

# define observables for plotting
RCOORD = Observable(P[1:3,1:nEP]) # robot coordinates
TCOORD = Observable([ANCHOR P[1:3,end]]) # target coordinates

# plot robot and target coordinates
scatterlines!(ax1, RCOORD, color="black")
scatter!(ax1, TCOORD, color="red")

deregister_interaction!(ax1, :rectanglezoom)
register_interaction!(ax1, :my_mouse_interaction) do event::MouseEvent, axis
if Makie.is_mouseinside(ax1.scene)
if event.type === MouseEventTypes.leftdrag
PX[:,end] = point(event.data[1], event.data[2], 0)
ik_solver(PX, linkLength)
P = toCoord(PX)
RCOORD[] = P[1:3,1:end-1] # update the plotted observables to
TCOORD[] = [ANCHOR P[1:3,end]] # automatically update the plot
end
end
end
fig
end

# collides: detects collisions of non-adjacent links
# arguments:
# 1. PX: robot arm pivot point expressions
# 2. LX: line segment expressions
function collides(PX::Matrix{Float32},LX::Matrix{Float32})::Bool
nLink = size(LX,2)
for iLink = 1:nLink-2
for jLink = iLink+2:nLink
# calculate areas that have negative product
# when the two points are on opposite side of line
A0 = (LX[:,iLink] & PX[:,jLink])[1]
A1 = (LX[:,iLink] & PX[:,jLink+1])[1]
if A0 * A1 < 0
# calculate areas that have negative product
# when the two points are on opposite side of line
A2 = (LX[:,jLink] & PX[:,iLink])[1]
A3 = (LX[:,jLink] & PX[:,iLink+1])[1]
if A2 * A3 < 0
return true
end
end
end
end
return false
end

# inverse kinematics video
# arguments:
# - number of relaxation passes in ik solver
# - number of links in robot arm
function ikv(nPass::Int64=8, nLink::Int64=12)
nEP = nLink + 1 # number of link endpoints
armLength = 3 # max reach of robot arm

# initialize figure
fig = Figure(size = (800, 800))
LIM = (-3,3, -3,3)
ax2d = GLMakie.Axis(fig[1,1],
limits=LIM,
aspect=1,
titlesize = 22,
title = "inverse kinematics using " *
"fast iterative algorithm called FABRIK\n" *
"(see http://www.andreasaristidou.com/publications/papers/FABRIK.pdf)")

# plot elliptical path of target
nFrame = 360 # sample points from ellipse
a = 2.5 # cos coefficient (1/2 major axis)
b = 1 # sin coefficient (1/2 minor axis)
x0 = 1 # ellipse x offset
y0 = 0 # ellipse y offset
phi = pi/4 # tilt of ellipse (major axis tilt)
PRESSURE = [ # collision avoidance pressure
0.0; 0.001]
THETA = LinRange(0, 2*pi,
nFrame+1) # +1 to avoid adjacent duplicate
# frames in repeating animated gif
T = zeros(Float32,2,nFrame+1) # Target (destination)
T[1,:] = (a*cos(phi)) .* cos.(THETA) -
(b*sin(phi)) .* sin.(THETA) .+ x0
T[2,:] = (a*sin(phi)) .* cos.(THETA) +
(b*cos(phi)) .* sin.(THETA) .+ y0
lines!(ax2d, T,
linestyle = :dot,
color = :gray)

# plot initial robot links
ANCHOR = [-1.5; 0]
# SRC = [point(x0, y0) point(ANCHOR[1], 0)]
SRC = Matrix{Float32}(undef, (length(e0),1))
SRC[:,1] = point(x0, y0)
println("SRC: $SRC")
UX = point(-1,0) & point(0,0)
UY = point(-1,0) & point(-1,1)
TCOORD = Observable([ANCHOR T[:,1]])
P = zeros(Float32, 2, nEP+1) # robot Pivots + 1 for target
P[1,1:nEP] = LinRange(ANCHOR[1],ANCHOR[1]+armLength,nEP)
PCOORD = Observable(P[:,1:nEP])
scatterlines!(ax2d, PCOORD, color="black")
scatter!(ax2d, TCOORD, color="red")
PRESSURELABEL = Observable("")
text!(-2.75, 2.8,
text = PRESSURELABEL,
align = (:left, :center),
fontsize = 25)

# pre-allocate for animation calculations
PX = Matrix{Float32}(undef, (length(e0),nEP+1))
linkLength::Float32 = armLength / nLink
for iEP = 1:nEP
# PX[:,iEP] = ga"(e0+((iEP-1) linkLength+ANCHOR[1]) e1)∗"
PX[:,iEP] = !(e0 + ((iEP-1)*linkLength + ANCHOR[1])*e1)
end
PX[:,end] = point(T[1,1], T[2,1])
fig
LX = Matrix{Float32}(undef, (length(e0),nLink))
nRev = 2
LINKANGLE = zeros(nFrame, nEP, nRev)

# record video of robot following target path
iRev = 0
fn = @sprintf("fabrik2dv%02d.mp4", nPass)
record(fig, fn, 1:2*nFrame) do jFrame
# check for beginning of ellipse (traversed twice)
iFrame = mod(jFrame-1, nFrame) + 1
alpha = iFrame / nFrame
if iFrame == 1
iRev += 1
PRESSURELABEL[] = @sprintf(
"collision avoidance nudge \"pressure\" = %.4f",
PRESSURE[iRev])
end

# move target T along ellipse
PX[:,end] = point(T[1,iFrame], T[2,iFrame])
ik_solver(PX, linkLength, SRC, nPass,
alpha, PRESSURE[iRev])
P = toCoord(PX)
PCOORD[] = P[:,1:end-1]
TCOORD[] = [ANCHOR P[:,end]] # automatically update the plot

# precalculate link lines for link angle calculations
for iLink = 1:nLink
LX[:,iLink] = PX[:,iLink] & PX[:,iLink+1]
end

# calculate link angles (for adjacent link collisions)
for iLink = 1:nLink
LINKANGLE[iFrame,iLink+1,iRev] = # +1 for column 1 of zeros
180/pi * atan(
(UY|LX[:,iLink])[1],
(UX|LX[:,iLink])[1])
end
end

# for each animated revolution
for iRev = 1:nRev
# unwrap wrapped link angles
isAdjacentLinkCollision = false
LAD = diff(LINKANGLE[:,:,iRev], dims=2) # Link Angle Diff
DELTA = diff(LAD, dims=1)
for iFrame = 1:nFrame-1
for iLink = 1:nLink
if DELTA[iFrame,iLink] < -0.9*360
LAD[iFrame+1:end,iLink] .+= 360
isAdjacentLinkCollision = true
elseif DELTA[iFrame,iLink] > 0.9*360
LAD[iFrame+1:end,iLink] .-= 360
isAdjacentLinkCollision = true
end
end
end

# plot link angles
fig2 = Figure(size = (800, 800))
strTitle = @sprintf("""link angles (unit: degrees)
top to bottom plots correspond to anchor to end links
%d relaxation passes
collision avoidance nudge \"pressure\" = %0.4f""",
nPass, PRESSURE[iRev])
for iLink = 1:nLink
if iLink == 1
ax = GLMakie.Axis(fig2[iLink,1],
xticks = (0:90:360),
titlesize = 22,
title = strTitle)
hidexdecorations!(ax,
grid = false,
ticks = false)
elseif iLink < nLink
ax = GLMakie.Axis(fig2[iLink,1],
xticks = (0:90:360))
hidexdecorations!(ax,
grid = false,
ticks = false)
else
strXlabel = "position along elliptical path " *
"(unit: degrees)"
ax = GLMakie.Axis(fig2[iLink,1],
xticks = (0:90:360),
xlabel = strXlabel)
end
lines!(ax, LAD[:,iLink], color=:black)
end
fn = @sprintf("fabrik2dang%02d_%d.png",
nPass, iRev)
save(fn, fig2)
fig2
end
end

# quick and dirty ffmpeg conversion from .mp4 to .gif:
# /tool/ffmpeg-2022-07/bin/ffmpeg.exe -i fabrik2dv.mp4 -r 24 -s 480x480 -loop 0 fabrik2dv.gif

Example 3.2 3D slicer (e.g., for 3D printing)

Animation of 3D object slicing, based upon Steven De Keninck’s pga3d_slicing example application ported to Julia and Makie.
# sl.jl
# 3D slicing application example
#
# The test tetrahedron has three faces (the base face
# is empty). All edges are of length 2.0. The peak is
# directly above (i.e., z offset) the origin. All the
# test tetrahedron's vertices and faces are defined,
# according to the wavefront 3D object file format,
# by seven lines of text (within the following
# multiline comment):
#=
v 1.0 -0.5773502692 0.0
v 0.0 1.15470053838 0.0
v -1.0 -0.5773502692 0.0
v 0.0 0.0 1.632993162
f 1 2 4
f 2 3 4
f 3 1 4
=#

using GLMakie, GLMakie.FileIO
using GeometryBasics # for normal_mesh()
include("ripga3d.jl")

function tetratest()
zmax = 1.5
zdel = 0.1
rmax = 1.0
rdel = 0.15
nTheta = 7
THETA = LinRange(-pi/6, 23*pi/6, nTheta)
open("tetratest.obj", "w") do io
for iTheta = 1:nTheta
r = rmax - (iTheta-1) * rdel
xTri = r*cos(THETA[iTheta])
yTri = r*sin(THETA[iTheta])
zTri = (iTheta < nTheta) ?
(iTheta - 1) * zdel : 0.15
println(io, "v $xTri $yTri $zTri")
end
println(io, "v 0 0 $zmax") # vertex 8
println(io, "v 0 0 1.48") # vertex 9
println(io, "v 0 0 1.46") # vertex 10
println(io, "v 0 0 1.44") # vertex 11
println(io, "v 0 0 1.42") # vertex 12
println(io, "v 0 0 1") # vertex 13
println(io, "f 1 2 8")
println(io, "f 2 3 9")
println(io, "f 3 4 10")
println(io, "f 4 5 11")
println(io, "f 5 6 12")
println(io, "f 6 7 13")
end
end

function zslice(zCut::Float32, F::GeometryBasics.Mesh,
TZ::Matrix{Float32}, TI::Matrix{Int32},
SEG::Matrix{Float32}, MEASURE::Vector{Float32})

# initialize
nCol = 0
MEASURE[:] .= 0
SUM1 = zeros(Float32, 16)
# iT0 = findfirst(x -> x>=zCut, TZ[:,1])

# for each triangle (face sorted by height) in 3D object
nF = length(F)
for iT = 1:nF
dz2 = TZ[iT,2] - TZ[iT,1]
dzc = zCut - TZ[iT,1]

# if triangle intersects with cut plane
if (dzc >= 0) && (dzc <= dz2)
iLo = TI[iT,1]
iHi = TI[iT,2]
iMid = xor(iLo, iHi)
iF = TI[iT,3]
P0 = F[iF][iLo][1:2]
P1 = F[iF][iMid][1:2]
P2 = F[iF][iHi][1:2]
dzm = F[iF][iMid][3] - TZ[iT,1]
PM = P0 + (dzm/dz2) .* (P2 - P0)

# if cut in lower section of triangle
if dzc <= dzm
if dzm == 0
nCol += 1
SEG[:,nCol] = F[iF][iLo][1:3]
nCol += 1
SEG[:,nCol] = F[iF][iMid][1:3]
MEASURE[1] += sqrt(
(SEG[1,nCol] - SEG[1,nCol-1])^2 +
(SEG[2,nCol] - SEG[2,nCol-1])^2)
MEASURE[2] += norm(
point(SEG[:,nCol-1]) &
point(SEG[:,nCol]))
P0X = point(SEG[:,nCol-1])
P1X = point(SEG[:,nCol])
X = TI[iT,5] * (P0X & P1X)
SUM1 += X
nCol += 1 # skip NaN32 column
else
s = dzc / dzm
nCol += 1
SEG[:,nCol] = [P0+s.*(PM-P0); zCut]
nCol += 1
SEG[:,nCol] = [P0+s.*(P1-P0); zCut]
MEASURE[1] += sqrt(
(SEG[1,nCol] - SEG[1,nCol-1])^2 +
(SEG[2,nCol] - SEG[2,nCol-1])^2)
MEASURE[2] += norm(
point(SEG[:,nCol-1]) &
point(SEG[:,nCol]))
P0X = point(SEG[:,nCol-1])
P1X = point(SEG[:,nCol])
X = TI[iT,5] * (P0X & P1X)
SUM1 += X
nCol += 1 # skip NaN32 column
end

# else cut in upper section of triangle
else
if dz2 - dzm == 0
nCol += 1
SEG[:,nCol] = F[iF][iHi][1:3]
nCol += 1
SEG[:,nCol] = F[iF][iMid][1:3]
MEASURE[1] += sqrt(
(SEG[1,nCol] - SEG[1,nCol-1])^2 +
(SEG[2,nCol] - SEG[2,nCol-1])^2)
MEASURE[2] += norm(
point(SEG[:,nCol-1]) &
point(SEG[:,nCol]))
P0X = point(SEG[:,nCol-1])
P1X = point(SEG[:,nCol])
X = TI[iT,5] * (P0X & P1X)
SUM1 += X
nCol += 1 # skip NaN32 column
else
s = (dzc - dzm) / (dz2 - dzm)
nCol += 1
SEG[:,nCol] = [PM+s.*(P2-PM); zCut]
nCol += 1
SEG[:,nCol] = [P1+s.*(P2-P1); zCut]
MEASURE[1] += sqrt(
(SEG[1,nCol] - SEG[1,nCol-1])^2 +
(SEG[2,nCol] - SEG[2,nCol-1])^2)
MEASURE[2] += norm(
point(SEG[:,nCol-1]) &
point(SEG[:,nCol]))
P0X = point(SEG[:,nCol-1])
P1X = point(SEG[:,nCol])
X = TI[iT,5] * (P0X & P1X)
SUM1 += X
nCol += 1 # skip NaN32 column
end
end # else cut in upper section of triangle
end # if cut slice intersects a triangle
end # for each triangle
MEASURE[3] = normIdeal(SUM1) / 2
return nCol
end

function sl()
# load and scale (by 13) faces of bunny object
#F = load("tetrahedron.obj")
#F = load("tetratest.obj")
F = load("xbunny.obj")
nF = length(F)

# initialize volume and surface area measurements
surface_area = 0
VOL = zeros(Float32, 16)

# sort triangles by height within object
# TZ (i.e., Triangle Z-values) has 2 columns:
# 1: min z value of triangle (in sorted order)
# 2: max z value of triangle (not necessarily in order)
TZ = Matrix{Float32}(undef, (nF,2))
# TI (i.e., Triangle Indices) has 4 columns:
# 1: in-triangle index of min z value
# 2: in-triangle index of max z value
# 3: index of face corresponding to sorted triangle
# 4: rank order of triangle's max z value
# 5: orientation of sorted triangle
TI = Matrix{Int32}(undef, (nF,5))
for iF = 1:nF
FACE =
point(F[iF][1][1],F[iF][1][2],F[iF][1][3]) &
point(F[iF][2][1],F[iF][2][2],F[iF][2][3]) &
point(F[iF][3][1],F[iF][3][2],F[iF][3][3])
surface_area += norm(FACE)
VOL += FACE

if F[iF][1][3] <= F[iF][2][3]
if F[iF][1][3] <= F[iF][3][3]
TZ[iF,1] = F[iF][1][3]
TI[iF,1] = 1
if F[iF][2][3] <= F[iF][3][3]
TZ[iF,2] = F[iF][3][3]
TI[iF,2] = 3
TI[iF,5] = 1
else
TZ[iF,2] = F[iF][2][3]
TI[iF,2] = 2
TI[iF,5] = -1
end
else
TZ[iF,1] = F[iF][3][3]
TI[iF,1] = 3
TZ[iF,2] = F[iF][2][3]
TI[iF,2] = 2
TI[iF,5] = 1
end
else
if F[iF][3][3] <= F[iF][2][3]
TZ[iF,1] = F[iF][3][3]
TI[iF,1] = 3
TZ[iF,2] = F[iF][1][3]
TI[iF,2] = 1
TI[iF,5] = -1
else
TZ[iF,1] = F[iF][2][3]
TI[iF,1] = 2
if F[iF][1][3] <= F[iF][3][3]
TZ[iF,2] = F[iF][3][3]
TI[iF,2] = 3
TI[iF,5] = -1
else
TZ[iF,2] = F[iF][1][3]
TI[iF,2] = 1
TI[iF,5] = 1
end
end
end
end
TI[:,4] = sortperm(TZ[:,2])
P = sortperm(TZ[:,1])
TZ = TZ[P,:]
TI = TI[P,:]
TI[:,3] = P
surface_area /= 2
volume = normIdeal(VOL, 3) / 3

# initialize figures
fig = Figure(resolution = (1000, 500))
ax3d = Axis3(fig[1,1],
elevation = pi/16,
azimuth = -5*pi/8,
viewmode = :fit,
zlabel = "z (cm)",
aspect = (1,1,1))
m = mesh!(ax3d, normal_mesh(F),
color = :chocolate4,
shading = true)
ax2d = Axis(fig[1,2],
limits = (-1,1, -1,1),
xlabel = "x (cm)",
ylabel = "y (cm)")

# plot initial observable data
SEG = fill(NaN32, 3, 3*nF) # line SEGment buffer
MEASURE = zeros(Float32, 3) # slice MEASUREments
zCut::Float32 = 1.395f0
nCol = zslice(zCut, F, TZ, TI, SEG, MEASURE)
SEG_obs = Observable(SEG)
slice2d = @lift @view $SEG_obs[1:2,1:nCol]
slice3d = @lift @view $SEG_obs[:,1:nCol]
strHeight = @sprintf("""
slice height = %.3f cm
circumference = %.2f cm
circumference = %.2f cm
area = %.3f sq cm
""",
zCut, MEASURE[1], MEASURE[2], MEASURE[3])
str_obs = Observable(strHeight)
strWholeObject = @sprintf("""
surface area = %.2f sq cm
volume = %.2f cc
""",
surface_area, volume)
lines!(ax3d, slice3d,
linewidth = 5,
color = :black)
lines!(ax2d, slice2d,
color = :black)
text!(ax2d, str_obs,
position = (0.05, 0.60),
space = :data)
text!(ax2d, strWholeObject,
position = (0.05, -0.98),
space = :data)
fig

# generate video of slicing
zMax = 2.0
nFrame = 800
record(fig, "sl.mp4", 1:nFrame) do iFrame
ax3d.azimuth[] = -3pi/4 - 2pi*(iFrame-1)/nFrame
zCut = zMax - abs(zMax - 2*zMax*(iFrame-1)/nFrame)
nCol = zslice(zCut, F, TZ, TI, SEG, MEASURE)
notify(SEG_obs)
str_obs[] = @sprintf("""
slice height = %.3f cm
circumference = %.2f cm
circumference = %.2f cm
area = %.3f sq cm
""",
zCut, MEASURE[1], MEASURE[2], MEASURE[3])
end # for each video frame
end # sl()

# quick and dirty ffmpeg conversion from sl.mp4 to sl.gif:
# ffmpeg -i sl.mp4 -r 24 -s 460x240 -loop 0 sl.gif
# ffmpeg -i sl.mp4 -r 24 -s 920x480 -loop 0 sl.gif

The shape of the 3D chocolate bunny is defined by the xbunny.obj file’s following mesh of 5002 triangles.

v 0.17408065 -0.3020772 1.2611313
v -0.84393656 -0.40479928 1.5494844
v -0.17865252 -0.03354842 1.1890984
v -0.10530782 -0.50755256 1.0244143
v -0.020861104 0.2955848 1.8865743
v 0.46629572 -0.43485922 1.1458083
v 0.2969486 -0.4437577 1.1912433
v 0.04331599 0.13673 1.6858933
v -0.22869858 -0.00650231 1.8077424
v -0.8776716 -0.06749649 0.99142027
v -0.3306225 0.03506974 1.8044794
v -0.60459626 0.1606344 1.6028492
v -0.78133774 0.3308382 1.7390113
v -0.72088766 -0.69098395 1.0671194
v 0.3871699 -0.517209 1.0446033
v -0.27373192 -0.037776798 1.7782843
v 0.28170922 -0.6155488 0.060482502
v -0.5249335 -0.6211869 0.098915696
v -0.3693053 -0.5978194 0.071879625
v -0.22048652 -0.04154199 1.2072983
v -0.4347369 -0.14139785 1.6175523
v -0.057406694 0.04173081 1.6323984
v -0.51573473 -0.12559246 1.6181633
v -0.4680143 -0.6194774 0.10412997
v -0.43107092 -0.5972851 0.12729728
v -0.1683214 -0.7016829 0.041990012
v 0.17234242 -0.6373095 0.12154871
v 0.28250936 -0.6136742 0.11274642
v -0.2897336 0.1357511 1.7603314
v -0.5213 -0.581351 0.15765232
v -0.31777072 -0.5669548 0.12725699
v -0.125723 -0.6851235 0.1524198
v -0.06585801 -0.71143943 0.139269
v -0.2625376 -0.03650319 1.7638803
v -0.6554028 0.1592681 1.6692405
v 0.8652943 -0.327786 0.48002893
v -0.42101276 -0.55448 0.17031693
v -0.3612622 -0.5741815 0.13634527
v -0.3495115 -0.5429919 0.1799798
v -0.07398301 -0.6937789 0.17164809
v 0.13368654 -0.6368064 0.15244192
v 0.22331387 -0.62402874 0.1511367
v 0.6607237 -0.3390089 1.0429783
v -0.12566972 0.08578521 1.9205303
v -0.009522498 -0.6540548 0.19050592
v 0.12739661 -0.6365815 0.17038578
v 0.29623398 -0.6572268 0.17392963
v 0.33672237 -0.6608616 0.1971398
v -0.2424851 -0.4749902 1.0287952
v -0.7693685 -0.6590858 1.3636754
v 0.6713122 -0.3956759 0.12796551
v -0.4661098 -0.5227756 0.19739592
v -0.39751273 -0.5279184 0.19690835
v -0.2877264 -0.54401237 0.16970718
v -0.12568268 -0.6604638 0.1885845
v -0.072342396 -0.67091453 0.19192809
v 0.044558793 -0.6417594 0.18957251
v 0.059897497 -0.6625685 0.21008521
v 0.21017568 -0.6580497 0.18048161
v 0.24526684 -0.6990426 0.21833241
v -0.07732791 -0.6251415 0.21546197
v -0.015614301 -0.629594 0.215904
v 0.11696516 -0.6821231 0.21917218
v 0.18351606 -0.7106984 0.2331875
v -0.6119711 0.2415646 1.6651843
v 0.640055 -0.4133884 0.1563133
v -0.9264269 -0.026733559 1.1915553
v -0.3211351 -0.06186567 1.7442763
v -0.619528 0.04726452 1.5615484
v -0.2275286 -0.516988 0.2237339
v -0.0799292 -0.5903145 0.2302391
v 0.031801894 -0.67850393 0.27453792
v 0.23631802 -0.710207 0.2469064
v 0.2655835 -0.7137651 0.2585531
v 0.32335395 -0.6923268 0.23743463
v -0.72624373 -0.6909566 1.2576993
v 0.7123194 -0.43399602 0.21428287
v 0.7188779 -0.4318575 0.19178247
v 0.4979585 -0.2955538 1.2002914
v -0.77584386 0.09691906 1.4945333
v -0.91571224 -0.01695431 1.1227074
v -0.34994698 -0.0497225 1.7539222
v -0.31006563 -0.3610439 1.1616163
v -0.3885076 -0.5079062 0.27639043
v -0.35451913 -0.5259814 0.23072922
v -0.2774603 -0.53019863 0.22476614
v -0.039972395 -0.619116 0.23787141
v -0.0014325976 -0.6492409 0.24093807
v 0.007173389 -0.67325974 0.27276862
v 0.08260849 -0.6947318 0.2648893
v 0.16239482 -0.7200779 0.2750488
v 0.3200115 -0.70502263 0.28263044
v 0.7118293 0.02237134 0.9305023
v -0.8086364 -0.7102109 1.2375364
v -0.818714 -0.68214387 1.3153284
v -0.5502731 -0.46763352 1.5221324
v -0.6355804 -0.515363 1.5120053
v -0.19695911 -0.27147782 1.1943634
v -0.68343085 0.100206494 1.6550055
v -0.36951852 -0.2841775 1.2485863
v -0.3260153 -0.5366271 0.29710847
v -0.1795794 -0.4962959 0.2743364
v -0.10382581 -0.5501874 0.25085187
v -0.062242687 -0.610341 0.30585098
v 0.21525867 -0.72960556 0.2759393
v -0.83329344 -0.69729024 1.1996024
v -0.17153761 0.04879566 1.8824143
v 0.6801132 -0.4279874 0.22281611
v 0.7815145 -0.44166732 0.2448628
v -0.6127875 -0.46752432 1.5511224
v -0.35994917 -0.53335893 0.3609814
v -0.28832567 -0.5412382 0.3209232
v -0.8097271 0.5185816 1.7827563
v 0.1582915 -0.7211491 0.31784737
v 0.19893184 -0.72947043 0.29400277
v 0.2859892 -0.7137755 0.3082053
v 0.21881495 -0.4710161 1.1680124
v 0.6548009 -0.3026492 1.0650523
v 0.72133493 -0.4247257 0.3008291
v -0.22860241 -0.5140539 0.27021927
v 0.107512206 -0.7382857 0.3490435
v 0.21268988 -0.7275542 0.32543552
v -0.96561015 -0.5411134 1.0627513
v 0.7484945 -0.43546113 0.27621877
v 0.8116927 -0.43516862 0.31320512
v 0.4830774 -0.3560519 1.1853023
v 0.034206897 -0.03627465 1.2267984
v 0.06257421 -0.70505124 0.3143829
v 0.26068184 -0.7403059 0.34474313
v 0.057275385 -0.45591792 1.1613823
v 0.3979924 -0.44180378 1.1688573
v -0.78264284 -0.33822238 1.5847402
v -0.1456052 -0.5063956 0.36095148
v 0.2073006 -0.74499243 0.3546868
v -0.2762396 -0.45059052 1.0654943
v 0.0074060857 -0.403771 1.1767483
v -0.11144382 -2.8308481e-5 1.6261714
v -0.66322875 -0.4123081 1.5760431
v 0.16240561 -0.7432205 0.36541313
v 0.17686643 -0.51781476 1.1155963
v 0.13483287 -0.4839017 1.1531143
v 0.76848197 -0.4202992 0.33525962
v 0.849043 -0.41378492 0.37012172
v 0.8737729 -0.4124563 0.35103512
v -0.29099977 -0.5572477 0.38693976
v -0.22942007 -0.533342 0.35711777
v 0.026714996 -0.7177717 0.41956198
v 0.25305176 -0.75409764 0.38207138
v 0.036522195 -0.4905395 1.1221093
v 0.4739826 -0.5073589 1.0276773
v -0.20535058 0.014469031 1.8418024
v 0.19044492 -0.6673161 0.8596575
v -0.50960517 -0.3823756 0.3707704
v -0.49278188 -0.4309878 0.39197212
v -0.44858974 -0.4279237 0.3537534
v -0.32731795 -0.5629937 0.4721613
v -0.1751295 -0.5130386 0.3791424
v 0.07037029 -0.733204 0.40862375
v 0.30882138 -0.74205834 0.4080726
v 0.7776093 -0.03260579 0.83695436
v 0.74667966 -0.40720558 0.367445
v 0.8187998 -0.4121742 0.37180263
v 0.706602 -0.3612155 0.9560603
v -0.8039161 0.34977138 1.6973073
v -0.5875376 0.03696384 1.4859663
v 0.14366326 -0.7495242 0.41832834
v 0.22171097 -0.7546709 0.42160684
v -0.048640788 -0.4483181 1.1243063
v 0.27451566 -0.3916797 1.2287093
v -0.3867318 -0.5345354 0.44644856
v -0.008054793 -0.7224335 0.4725448
v 0.020264387 -0.73433244 0.48880005
v 0.08473529 -0.7380348 0.46301454
v 0.21694542 -0.7548074 0.46771795
v -0.25360793 -0.418032 1.1109682
v -0.6958224 -0.2767896 0.39075267
v 0.7777133 -0.38377962 0.39631665
v 0.7752173 -0.3816294 0.67919934
v 0.4391738 -0.3173795 1.2199214
v -0.32916653 -0.03324695 1.2342343
v 0.43069258 -0.42715022 1.1597183
v -0.83802164 -0.3559414 1.5665274
v -0.20093971 0.307433 0.7134751
v 0.039337993 -0.73031276 0.48851526
v 0.2872215 -0.7429865 0.45104933
v 0.31846425 -0.7480786 0.50785416
v 0.26369277 -0.4991247 1.1273093
v 0.30490524 -0.52258193 1.0845393
v 0.8175388 -0.367787 0.43113202
v 0.89866793 -0.3869347 0.40322357
v -0.5862558 0.09477316 1.1655943
v 0.1352979 0.2776747 0.93515635
v -0.23942488 -0.5572295 0.45824873
v -0.2065557 -0.54088724 0.44745737
v -0.16177723 -0.53106695 0.47114074
v 0.15338114 -0.7588725 0.47248632
v 0.43387496 -0.4748199 1.1126972
v -0.94359326 -0.24705341 0.85428333
v 0.81276256 -0.3254109 0.47033346
v 0.9273966 -0.33800012 0.46132845
v -0.3664505 -0.5643496 0.49747622
v 0.16324177 -0.3550496 1.2459084
v -0.7420283 -0.7134154 1.1388793
v -0.74402773 -0.604205 1.4398683
v 0.873613 -0.3683395 0.44177377
v -0.48432672 0.013055671 1.6042793
v 0.5583383 -0.37427402 1.1288823
v 0.0788385 -0.16218941 1.2557104
v -0.3676114 0.2649815 0.7013656
v -0.28001872 -0.5632745 0.46194065
v -0.16190982 -0.5546932 0.54417086
v -0.3966326 -0.09823239 1.6854513
v 0.064155 -0.7505213 0.5183607
v 0.13555801 -0.772919 0.5053672
v 0.17949711 -0.7754436 0.5053945
v -0.1167413 0.3497766 0.7860879
v 0.0014092028 -0.5113616 1.0708892
v -0.8751912 -0.34077302 0.9751313
v -0.7474103 0.18267459 1.6933553
v -1.0074494 -0.2691898 1.2072723
v -0.2392793 -0.3005341 1.1926473
v -0.4250337 -0.5610268 0.52164316
v -0.21142161 -0.5558827 0.5634837
v 0.1233085 -0.77290213 0.52792615
v 0.21869704 -0.7693271 0.5224193
v 0.2729277 -0.7511531 0.51486886
v -0.7859163 -0.57986766 1.4605383
v 0.7523659 0.10797322 0.7203872
v 0.7048561 -0.02368649 0.9775753
v -0.4980352 -0.5644861 0.5379127
v -0.33720958 -0.5757233 0.5679102
v -0.6724926 -0.29224008 1.5988712
v 0.08914074 -0.7653218 0.55943155
v 0.17619003 -0.7825598 0.5512104
v 0.3928002 -0.6883514 0.7769295
v -0.73714423 0.1725021 0.7874541
v -0.74972045 -0.6782257 1.3229463
v -0.11765391 -0.3583061 1.1507484
v -0.22388473 0.029183079 1.8545942
v -0.76186377 0.32903898 1.7560544
v -0.2581462 -0.5653597 0.5431062
v -0.012928501 -0.7484036 0.57429194
v 0.0704899 -0.7693557 0.6053748
v 0.21705905 -0.7757491 0.56643724
v 0.25400883 -0.7566196 0.5953454
v 0.29079854 -0.7515236 0.5601336
v -0.5862337 0.22797179 1.5721173
v -0.725998 -0.6299658 1.3998284
v 0.42380908 -0.1913497 1.2441533
v 0.0662688 -0.68996733 0.83259547
v -0.5475756 -0.5777682 0.5750629
v -0.47216392 -0.5826237 0.57983243
v -0.44128108 -0.5800588 0.5569005
v -0.3978104 -0.5734613 0.54912525
v -0.3673098 -0.5697069 0.59132314
v -0.2953158 -0.5772118 0.5826328
v 0.020220205 -0.7594198 0.6452017
v 0.029161587 -0.7536816 0.56253994
v 0.1411852 -0.7728774 0.5944679
v 0.7017075 -0.2638169 1.0053433
v 0.24952161 -0.1746863 1.2781222
v -0.99896157 -0.2618773 1.0995933
v -0.6128044 -0.5663555 0.5890547
v -0.37148148 -0.5819373 0.63795817
v -0.25615853 -0.5803799 0.6219512
v -0.21376291 -0.5633486 0.63643706
v -0.1819948 -0.5626453 0.60786057
v 0.19166094 -0.7674694 0.6199492
v 0.3248155 -0.7391385 0.616802
v 0.050018802 -0.5252326 1.0741394
v -0.7966466 -0.697051 1.1727183
v -0.1271387 0.03528242 1.5907724
v -0.8537178 0.010937192 1.0781043
v -0.13958752 -0.6393336 0.60651374
v 0.23473631 -0.7660732 0.65696406
v 0.2983738 -0.7532955 0.6512519
v -0.5932186 -0.5939103 0.65943015
v -0.5598606 -0.5818723 0.6139978
v -0.49968487 -0.6040724 0.63296485
v -0.4835922 -0.5990895 0.6143606
v -0.43193805 -0.5947046 0.6240182
v -0.32753628 -0.5712006 0.6210594
v -0.2932397 -0.58479995 0.65417945
v -0.90168655 -0.2564537 0.97598934
v -0.908986 -0.06546823 0.8658949
v -0.072965115 -0.7419491 0.6496568
v -0.0024986118 -0.76708066 0.6401967
v 0.057135 -0.7637514 0.6748651
v 0.12163827 -0.52995944 1.0871134
v 0.1905254 -0.5709458 1.0156003
v -0.7659236 -0.70940495 1.1949223
v -0.7833241 -0.7012812 1.2821263
v -0.57616 -0.311991 1.5910323
v -0.2695875 -0.2184417 1.2237043
v 0.19174129 -0.6124067 0.8791263
v -0.6264856 -0.5928313 0.66613305
v -0.533793 -0.593688 0.65824854
v -0.3754413 -0.59334356 0.6718153
v -0.23688084 -0.5771364 0.6445179
v -0.17135301 -0.5879563 0.6772013
v -0.93718696 -0.14368416 0.8566896
v -0.030345902 -0.7630962 0.6521437
v -0.86695313 -0.029122699 0.96118236
v -0.6930534 -0.7045546 1.0997233
v -0.24342892 0.044009056 1.8543992
v -0.70239526 -0.71184367 1.1600432
v 0.8051524 -0.10030784 0.7841704
v -0.47860408 -0.6085925 0.6764797
v -0.4185974 -0.608846 0.68785346
v -0.2592707 -0.5862377 0.6860595
v 0.07706919 -0.7559215 0.7217196
v 0.16842774 -0.76284397 0.6692127
v -0.41884834 -0.42982823 1.0972402
v -0.3394742 -0.417655 1.1056513
v -0.5314855 -0.1880503 1.5899923
v -0.1119391 -0.020945178 1.1883963
v 0.46557158 -0.2504958 1.2226772
v -0.5550688 -0.6056909 0.7219628
v -0.52169 -0.6002777 0.71229076
v 0.26490828 -0.112981796 1.2750413
v 0.0015301108 -0.7569056 0.7014384
v 0.107937954 -0.7681376 0.6588478
v 0.26401466 -0.7608979 0.6893848
v -0.1507701 0.08274035 1.9161623
v -0.77694243 -0.68863094 1.0845914
v 0.45071518 -0.6525377 0.7486532
v -0.30603433 -0.44125262 1.0840193
v 0.29525042 -0.2379781 1.2752883
v -0.89669585 -0.018659389 0.7590896
v -0.43642557 -0.606714 0.72195244
v -0.35923028 -0.5844788 0.7064798
v -0.3283202 -0.5722198 0.6942209
v -0.21134752 -0.5725552 0.6873828
v -0.16173172 -0.5943458 0.74286556
v 0.12875967 -0.7624618 0.66459894
v 0.13233921 -0.7587074 0.74236894
v 0.20224217 -0.75444734 0.7251829
v 0.3407118 -0.7240533 0.74123657
v 0.7409454 0.0067931805 0.89707935
v -0.90253925 -0.0480026 1.0576552
v -0.94502854 -0.2043224 0.81019247
v -0.6543056 0.061371606 1.6091933
v -0.4697498 -0.5986657 0.74884677
v -0.2592707 -0.5823273 0.72250104
v 0.0412737 -0.7560372 0.73421407
v 0.5217953 -0.3426333 1.1729133
v -0.2632864 -0.02388162 1.2199342
v -0.73964417 -0.2059474 1.5966613
v -0.6342882 -0.5924673 0.7505225
v -0.5485532 -0.6086289 0.7633704
v -0.2136927 -0.587331 0.718428
v -0.19544202 -0.5935255 0.7463703
v -0.0418756 -0.7419322 0.7113886
v 0.072633594 -0.7486389 0.7677345
v 0.7268222 -0.1901199 0.97475433
v 0.59119713 -0.33836672 1.1217192
v -0.6778096 0.006360803 1.6018353
v -0.59558326 -0.60544914 0.751621
v 0.6141928 -0.2899534 1.1122034
v 0.020278692 -0.7448936 0.7606534
v 0.1453248 -0.7458868 0.77902627
v 0.28079987 -0.73056763 0.75848365
v -0.66925824 -0.5703231 0.7678697
v -0.5046951 -0.604985 0.76540744
v -0.45630002 -0.5904588 0.8079201
v -0.41610402 -0.5972071 0.75106204
v -0.3719378 -0.5815564 0.74999857
v -0.36027682 -0.5779125 0.8001565
v -0.29418093 -0.5805827 0.7317401
v -0.2449356 -0.5943705 0.7709975
v 0.21682101 -0.7394492 0.77858686
v 0.34204897 -0.6811078 0.808262
v 0.4971551 -0.6438342 0.75458634
v 0.10163361 0.27561548 1.7674034
v -0.7308353 -0.3107209 1.5952444
v -0.6234722 0.0072125606 1.5574794
v -0.68337744 -0.566947 0.8051276
v -0.5631093 -0.5936594 0.80915904
v -0.1617291 -0.6019456 0.7927464
v -0.1268852 -0.6039502 0.77973485
v 0.031290993 -0.7211855 0.7966101
v 0.10311392 -0.727068 0.8071753
v 0.20256768 -0.7188767 0.81759346
v 0.2710357 -0.7074484 0.8125975
v 0.15858778 -0.45440602 1.1826763
v -0.80126154 -0.64755225 1.3776114
v 0.35848278 -0.2057121 1.2672673
v -0.5176951 -0.58695275 0.81915987
v -0.25347272 -0.58581394 0.81101406
v -0.0985257 -0.59691846 0.81323445
v 0.3603145 -0.407411 1.2093393
v 0.14220348 -0.7049381 0.83252394
v 0.4390685 -0.6360017 0.829357
v -0.655603 -0.5599907 0.85623205
v -0.49651158 -0.5776876 0.8623017
v -0.413621 -0.59365034 0.82085633
v -0.501384 -0.2293591 1.5744963
v -1.0067525 -0.3256605 1.1539333
v 0.390572 -0.6530226 0.8245679
v 0.4867239 -0.629893 0.82280374
v 0.31387162 -0.4869242 1.1397372
v 0.042992294 -0.096867904 1.2416832
v -0.04302481 -0.3259179 1.1925564
v -0.68428755 0.2045731 1.7093585
v -0.613951 -0.5726189 0.83887434
v -0.4575402 -0.5745858 0.86317265
v -0.35346872 -0.5702542 0.8595028
v -0.29818362 -0.54673725 0.8949733
v -0.24869654 -0.5696354 0.86450124
v -0.18286583 -0.5997382 0.82686245
v -0.15003431 -0.58816695 0.83814645
v -0.11468083 -0.5940117 0.8502455
v -0.88966286 -0.33826008 1.0070333
v 0.3119298 -0.64872354 0.8521447
v 0.34387493 -0.63512033 0.8811153
v 0.3937453 -0.63617593 0.8769033
v 0.31901973 -0.171149 1.2761463
v 0.22893065 -0.5414488 1.0793003
v -0.7260708 0.21749249 1.7207983
v 0.14560077 -0.1559793 1.2669424
v -0.1235351 -0.245379 1.1944284
v -0.40026224 -0.5698083 0.87752736
v -0.32057744 -0.5623918 0.8480537
v -0.21815428 -0.548864 0.91687834
v -0.07889959 -0.58535236 0.9039823
v -0.07229298 -0.6012267 0.8436701
v -0.02690351 -0.5976309 0.8536229
v -0.7102251 -0.5140604 0.5754268
v -0.7306013 -0.7087198 1.1044424
v -0.67165023 0.08909774 1.3217244
v -0.7524764 0.09835024 0.96916425
v -0.626054 0.14446889 1.6277313
v -0.8076991 -0.4762447 1.5290743
v -0.74506253 -0.5176601 0.8597238
v -0.6975267 -0.53659326 0.87981534
v -0.5493514 -0.5746963 0.8723403
v -0.42102188 -0.5431882 0.9257313
v -0.16055259 -0.57350034 0.8926333
v 0.067616895 -0.5937621 0.8809854
v 0.28529853 -0.60976905 0.88827837
v -0.4510311 -0.5437836 0.9315423
v -0.04442358 -0.5873856 0.89484334
v 0.27368367 -0.5725968 0.9350523
v 0.4372394 -0.6080049 0.91084635
v -0.1151813 -0.078468755 1.2001222
v -0.71943295 -0.50707155 0.9367423
v -0.62638676 -0.5476914 0.91527927
v -0.5700695 -0.5510532 0.9277073
v -0.50060403 -0.5589871 0.9144733
v -0.35674864 -0.55669004 0.92309237
v 0.016740099 -0.5772222 0.92366433
v 0.11919284 -0.5875078 0.90655637
v 0.116952024 -0.5890015 0.93412936
v 0.34472513 -0.5861194 0.9350263
v 0.61231816 -0.392175 1.0689392
v -0.39904672 -0.5414228 0.94961226
v -0.28831273 -0.5164901 0.9557613
v -0.117887914 -0.56796753 0.9224423
v -0.016339704 -0.5757064 0.9324783
v 0.021072999 -0.56982523 0.9719722
v 0.16773704 -0.5918836 0.9405774
v 0.21876165 -0.58835673 0.9250423
v 0.3741309 -0.6094219 0.9194393
v 0.413478 -0.562826 0.9614682
v 0.5627583 -0.28444532 1.1569103
v -0.2525198 -0.5216537 0.94941735
v -0.056235403 -0.5485689 0.9881704
v 0.051755607 -0.5843241 0.92639434
v 0.28711528 -0.561045 0.9896523
v 0.33463782 -0.5554342 0.9593493
v -0.4555031 -0.11211769 1.6550314
v -0.48620522 -0.472242 1.0556923
v 0.39035487 -0.27523482 1.2515243
v -0.6835582 -0.50687134 0.9795253
v -0.66156995 -0.5135092 0.96240425
v -0.61075824 -0.5078074 1.0018203
v -0.44132918 -0.5171388 0.98770237
v 0.09629593 -0.58417976 0.9635353
v 0.23370112 -0.5738864 0.98819625
v 0.34583938 -0.5385602 0.99749136
v 0.48011988 -0.54347944 0.9622352
v 0.18217835 0.272697 0.9420463
v -0.4965467 -0.5317898 0.9676173
v -0.3791541 -0.5230733 0.9846343
v -0.33180547 -0.5322032 0.9628203
v -0.1996826 -0.5146051 0.98351634
v -0.18159959 -0.4464344 1.0746204
v 0.0675012 -0.5714489 0.9948523
v -0.16415489 0.3048447 0.7781371
v -0.8221538 0.103187665 0.7640139
v -0.42895323 -0.47580272 1.0414443
v -0.3499977 -0.094297424 1.6718533
v -0.53534913 -0.5132388 0.9895873
v -0.32097262 -0.48562548 1.0261953
v -0.105674386 -0.46250108 1.0740353
v -0.053049088 -0.511619 1.0502063
v 0.12023739 -0.5657861 1.0208653
v 0.26738036 -0.5587011 1.0282364
v -0.5983718 0.100815415 1.5416063
v -0.551135 -0.4903211 1.0386753
v -0.44561535 0.26363212 0.7875776
v -0.74287724 0.17773849 0.66384494
v -0.7194876 0.08159986 1.3202033
v -0.033902705 0.25366238 1.7823143
v -0.5034835 0.006212732 1.5046864
v 0.43536347 -0.5782778 0.047667086
v -0.4611919 -0.4582735 1.0977343
v -0.6812052 0.19504932 0.7764809
v 0.56394255 -0.4205527 1.0882833
v -0.0880529 -0.7231524 0.048451006
v 0.12250537 -0.65535223 0.07382053
v 0.3904641 -0.59778947 0.06686157
v 0.4717479 -0.5736043 0.09749222
v -0.6129877 -0.5195646 1.4772823
v -0.42612052 -0.6166902 0.036225796
v -0.3447951 -0.5918212 0.0386945
v -0.15920061 -0.12017301 1.1992903
v 0.19586697 -0.6336877 0.08016974
v -0.1892423 0.04149057 1.5640574
v 0.2519816 0.248608 0.96415937
v -0.5200169 -0.43189391 1.4538822
v -0.5156229 -0.6279248 0.056851596
v -0.4441294 -0.62098926 0.06503123
v -0.44433218 -0.377875 1.3202293
v -0.1150032 -0.7233682 0.07216042
v -0.07463431 -0.72108805 0.09497672
v -0.010621011 -0.7005142 0.07281691
v -0.037839085 0.15000299 1.7547543
v 0.5275543 -0.6217849 0.80486894
v 0.4100889 -0.5979039 0.12229228
v 0.5004012 -0.5502732 0.0890708
v -0.6144606 0.22585149 0.7392099
v 0.48256257 -0.61415654 0.8724313
v -0.255112 0.2866798 0.7841704
v 0.023657396 -0.6875285 0.120399475
v -0.5269459 -0.5422795 1.1711843
v -0.48583084 -0.5077632 1.1708983
v 0.5125406 -0.5510298 0.13405341
v 0.5929495 -0.4373201 0.13055116
v -0.3375216 -0.086891055 1.6115463
v 0.33831447 -0.60501623 0.14059758
v 0.4140396 -0.58859074 0.16179669
v -0.6104319 0.2080272 0.89961433
v -0.22450218 0.159961 1.6663413
v -0.85007644 -0.23432381 0.54990387
v -0.6434792 -0.6772767 1.1707942
v 0.016781703 -0.677759 0.15565163
v 0.464477 -0.5783974 0.14221615
v 0.5540964 -0.5168736 0.16921711
v 0.57290614 -0.4892525 0.14609271
v -0.3705039 -0.11291796 1.6073341
v 0.5285384 -0.53118527 0.94712925
v 0.49963808 -0.5482972 0.19262624
v 0.7876232 -0.4267342 0.20141935
v 0.8433854 -0.3854215 0.20358127
v 0.90303326 -0.2948869 0.18672812
v -0.4669795 -0.5931316 0.14836901
v -0.1725178 0.063407004 1.8105502
v -0.23019102 -0.040725976 1.5751464
v 0.3565991 -0.62340605 0.17422467
v 0.43607718 -0.5870918 0.19667828
v 0.5931653 -0.5902313 0.76470554
v -0.5607693 -0.5492969 1.2213773
v -0.2593643 -0.07369059 1.6212444
v -0.27409723 -0.022479178 1.6729324
v -0.0682084 0.137965 1.9522893
v -0.0983268 0.1085356 1.9195554
v -0.36505693 -0.11865722 1.5238613
v 0.5693714 -0.523046 0.23410273
v 0.8389784 -0.4196349 0.2527343
v 0.8905624 -0.33319402 0.2021175
v -0.65914416 -0.6437393 1.0521042
v 0.00706549 0.1067338 1.6510403
v -0.61262894 0.5102252 1.7499833
v -0.32323593 -0.0782965 1.5164642
v -0.2392286 0.015583001 1.7111394
v 0.7555574 -0.3348541 0.86042845
v 0.4011306 -0.6509907 0.24978203
v 0.47088858 -0.6016557 0.2341482
v 0.526227 -0.5529629 0.23613852
v 0.6637683 -0.44512272 0.24207169
v 0.9458579 -0.3208401 0.25788754
v -0.300781 -0.040092357 1.5069354
v -0.3924414 -0.3700815 1.2050754
v -0.1417962 0.3955795 0.73802817
v 0.46807277 -0.6396573 0.28413582
v 0.502931 -0.6056025 0.2680301
v 0.6251349 -0.4991468 0.2542007
v 0.8250086 -0.4349684 0.2783209
v 0.89212114 -0.382321 0.2595684
v -0.16203073 0.0080813505 1.6652234
v -0.6746974 -0.5788108 1.4472264
v -0.5104983 -0.40754882 1.5170622
v 0.79974437 -0.1755911 0.7994064
v -0.32504034 -0.10318266 1.5926442
v -0.67038786 -0.7009627 1.1645802
v -0.6150794 0.2162198 0.80860007
v -0.020567313 0.2035032 1.9776523
v 0.3711032 -0.6941533 0.3037892
v -0.5534672 -0.5463966 1.1267372
v 0.9866103 -0.2827813 0.32041234
v 0.7845331 -0.2443611 0.83303356
v -0.12338042 -0.01253015 1.6422133
v 0.4121754 -0.6630664 0.2995265
v 0.53413355 -0.5793984 0.2814331
v 0.5947968 -0.544327 0.2965989
v -0.42518842 -0.4126409 1.2504063
v 0.7491354 -0.3918227 0.80418396
v 0.4494854 -0.6545111 0.32414073
v 0.6548724 -0.517313 0.32767022
v 0.691418 -0.4560895 0.30792576
v -0.616252 -0.6433779 1.1586783
v -0.6321055 -0.556287 1.3884403
v 0.8788299 -0.4092882 0.3119766
v 0.9551451 -0.3350647 0.30759174
v -0.4231357 -0.16583331 1.5057262
v -0.6884748 -0.6467176 1.3145483
v 0.4723953 -0.65075934 0.3470207
v 0.56959367 -0.5841772 0.3463577
v 0.9682452 -0.3338934 0.3650803
v -0.52150154 -0.53181577 1.2080262
v 0.5235139 -0.45525488 1.0822903
v -0.53946877 -0.44745752 1.3720863
v 0.30239093 -0.73140997 0.35083747
v 0.35369098 -0.70156723 0.33608252
v 0.5198037 -0.6155943 0.3294785
v 0.6479382 -0.5364282 0.3700307
v -0.6181136 -0.6257226 1.2086242
v -0.52299523 -0.4937037 1.0700313
v 0.9262838 -0.378759 0.32542902
v -0.17333421 0.07639909 1.7784402
v -0.2635893 -0.04019545 1.6452031
v -0.5100134 -0.43488783 1.3520143
v -0.6613815 -0.58955526 1.4065104
v 0.3482982 -0.7213896 0.38253015
v 0.4071925 -0.67578167 0.364949
v 0.6090851 -0.5593654 0.37517613
v 0.70616263 -0.466028 0.36078644
v 0.919685 -0.3867605 0.36506212
v 0.9869717 -0.2995734 0.38764572
v -0.0983502 0.1330848 1.8671005
v 0.046118796 0.1642952 1.7285073
v 0.7282561 -0.2851226 0.9539803
v -0.015184 0.2160742 1.9171112
v 0.50314415 -0.63394123 0.35835934
v 0.6778564 -0.508148 0.36816132
v -0.5894798 -0.496383 1.3349452
v 0.95708084 -0.34798932 0.39495564
v -0.20798701 -0.04782164 1.6403022
v 0.3641703 -0.7210386 0.43256462
v 0.3863678 -0.7010043 0.40161806
v 0.4800627 -0.6422261 0.39703816
v 0.6860464 -0.5104282 0.43367606
v 0.7372027 -0.4311971 0.40366817
v -0.64871824 -0.6105217 1.2843494
v -0.32240522 -0.06525711 1.6441112
v 0.6479928 -0.4341793 0.9665903
v 0.5391503 -0.6069298 0.3949998
v 0.5578261 -0.5900051 0.45107013
v 0.6521437 -0.5357535 0.4386837
v 0.7763184 -0.3702011 0.43417144
v 0.929708 -0.37000608 0.40803486
v 0.0035113096 0.10094777 1.6892214
v -0.014177814 0.067924514 1.6617653
v 0.7529483 0.03241371 0.8343582
v 0.5224414 -0.6304338 0.47795933
v 0.7137507 -0.47599253 0.42079055
v 0.3503006 -0.4706157 1.1557922
v 0.9775142 -0.3050035 0.4346069
v 0.716339 0.1689544 0.66610956
v 0.6620562 -0.5079452 0.82867324
v -0.18921113 -0.04413744 1.6222582
v -0.53896046 -0.44099778 1.3999063
v -0.6502106 -0.68864256 1.1206663
v -0.666757 -0.68423563 1.0810943
v 0.43380088 -0.68239874 0.42847997
v 0.6001307 -0.5690322 0.43159866
v 0.6997666 0.13249199 0.78831613
v 0.9517781 -0.3308709 0.44655
v -0.57843107 -0.50206137 1.4545712
v 0.3274479 -0.5365738 1.0406773
v 0.37190402 -0.72439647 0.48894423
v 0.6050551 -0.5607707 0.48538357
v 0.6902402 -0.5017598 0.49284047
v 0.73710907 -0.44463 0.48080367
v -0.59750736 -0.4960918 1.3581113
v 0.012812793 0.2232346 1.8426085
v -0.981084 -0.2404962 1.0587473
v 0.20606768 -0.5950413 0.88531435
v -0.60985476 -0.5133584 1.0326173
v -0.57583505 -0.53477204 1.0881923
v 0.49130768 -0.6563155 0.46833277
v 0.55197346 -0.5982887 0.48412782
v 0.6471348 -0.5455945 0.5224804
v 0.7664423 -0.38740662 0.4733001
v -0.5033067 -0.4950505 1.2512643
v -0.3120169 -0.06795344 1.7036643
v 0.13710254 -0.595456 0.88279235
v 0.07558459 0.1893488 1.7277274
v -0.17615521 0.050008822 1.7052374
v 0.5578547 -0.56180286 0.87854135
v -0.63438576 -0.5694131 1.0398062
v 0.5930041 -0.5686201 0.8242364
v 0.4168359 -0.70459616 0.497406
v 0.5951842 -0.5840836 0.5301218
v 0.8017555 -0.3202876 0.5486884
v -0.7985146 0.01325522 1.5120964
v -0.38082075 -0.13630861 1.5555553
v -0.462384 -0.2654211 1.4757482
v -0.06228429 0.069940686 1.6162133
v 0.4417114 -0.6930158 0.5168319
v 0.5133505 -0.6603325 0.5439109
v 0.679549 -0.5195399 0.5169684
v -0.94483745 -0.3450695 1.3661714
v -0.4734041 -0.44798923 1.2858313
v -0.27268544 -0.07608104 1.5887182
v -0.95872927 -0.30111778 1.3235184
v -0.6053528 -0.5609787 1.2617943
v 0.4013789 -0.7241482 0.5634434
v 0.4538404 -0.7016179 0.55245715
v 0.7671417 -0.3987036 0.5921707
v -0.4590937 -0.2859039 1.4103583
v 0.5962333 -0.4587454 1.0164193
v -0.49309647 -0.39601648 1.4217074
v 0.5551598 -0.6226871 0.5489393
v 0.6252974 -0.5743583 0.5537675
v 0.71821356 -0.4749278 0.55041355
v 0.8209851 -0.2578759 0.5300048
v -0.4011228 -0.35107678 1.2670463
v 0.0695968 0.235569 1.7940145
v -0.2580006 -0.015608288 1.7301843
v -0.3880825 -0.1206947 1.6362853
v -0.37399572 -0.092738464 1.4732392
v 0.7571187 0.077693105 0.7677176
v -0.6173232 -0.5385628 1.3172393
v -0.5762627 -0.5251767 1.2801502
v -0.40355253 -0.3941016 1.1549343
v -0.42556536 -0.4323892 1.1908402
v -0.7063472 -0.64137197 1.3632724
v 0.3498937 -0.7376084 0.5610566
v 0.4510792 -0.7051435 0.600687
v 0.5010447 -0.6748002 0.61023045
v 0.5469438 -0.6436158 0.590395
v 0.619918 -0.5833244 0.5939648
v -0.5933317 -0.4998254 1.3979952
v -0.5897333 0.21262139 1.6036553
v 0.48695788 -0.5783311 0.9211552
v -0.60842335 -0.5727099 1.0712663
v 0.40592498 -0.7180928 0.6080828
v 0.5925439 -0.59812623 0.64016414
v 0.7962643 -0.3384577 0.63098097
v 0.810459 -0.2753804 0.6503705
v -0.4272424 -0.4373929 1.1352652
v -0.16089192 0.053843427 1.8575714
v -0.5382273 -0.4992131 1.2922013
v 0.7253753 0.08186908 0.8291179
v -0.49302238 -0.5061382 1.1362404
v 0.3898713 -0.7091254 0.66171694
v -0.04062891 0.11060025 1.7106323
v 0.4804033 -0.4647969 1.1074322
v -0.5077657 -0.5035655 1.0976303
v 0.45300192 -0.6859854 0.6540729
v -0.6104293 0.6327541 1.6355574
v -0.022445813 0.20229548 1.8618872
v 0.5919602 -0.5137276 0.91378427
v 0.756795 -0.2347957 0.9039173
v 0.5357651 -0.6002062 0.84119105
v 0.49044058 -0.66190034 0.68901944
v 0.61419797 -0.57841426 0.6714461
v 0.64780426 -0.5605497 0.6561321
v 0.6990893 -0.50284916 0.60536575
v -0.79196644 -0.645298 1.0432383
v 0.598663 -0.49494392 0.95171833
v -0.47160363 -0.38673452 1.3550823
v -0.60046476 -0.6073315 1.1269063
v -0.6317649 -0.6528328 1.0947312
v 0.65227365 -0.3805153 1.0265982
v 0.71778977 -0.4766867 0.70434
v -0.5868837 0.7171501 1.7092674
v -0.07898149 0.1553343 1.8280351
v 0.7873736 0.027151443 0.7160413
v 0.7292025 -0.3774265 0.8833513
v 0.3605836 -0.7307951 0.6853262
v 0.53182614 -0.64706993 0.6653751
v 0.6962722 -0.4461601 0.8565128
v -0.45682782 -0.46891272 1.2005513
v -0.19215429 0.055246 1.7304053
v 0.31774068 -0.7488027 0.6905248
v 0.436436 -0.676069 0.71935236
v 0.6656728 -0.54673594 0.697099
v 0.6882824 -0.5124497 0.66468096
v 0.75477743 -0.05405878 0.9139143
v 0.5403723 -0.4967392 1.0075144
v -0.0754078 0.0596621 1.6795232
v 0.0507949 0.27485108 1.8912413
v -0.105795294 0.1280161 1.7810144
v -0.4116788 -0.12802994 1.4597193
v 0.38136148 -0.7133998 0.7316921
v 0.5547607 -0.6257889 0.71724117
v 0.64424753 -0.5680715 0.7235695
v -0.0850538 0.022151124 1.6604133
v -0.985322 -0.3677077 1.1196653
v -0.7476795 0.06743844 1.3559924
v -0.9840766 -0.3996006 1.1440663
v -0.7875049 0.1644668 1.6163304
v -0.9893819 -0.4940716 1.1668813
v 0.5879159 -0.59431463 0.7111156
v 0.6766851 -0.5286607 0.7478497
v 0.4233008 -0.66848093 0.7786181
v 0.7226245 -0.4420326 0.8013772
v -0.5812326 -0.5790851 1.1755394
v -0.66747326 -0.671207 1.2243023
v -0.049604118 0.1666183 1.9378593
v 0.4151966 -0.500686 1.0712663
v -0.44092488 -0.1728871 1.5528902
v 0.6106061 -0.5315311 0.87330234
v -0.9181315 -0.07167612 0.8080747
v -0.5617222 -0.48011607 1.4239564
v -0.5434299 -0.45878443 1.4698074
v 0.63976383 -0.5508426 0.7855406
v -0.76851845 0.1996773 1.5166723
v 0.4601688 0.09354687 0.025792003
v 0.4421404 0.1716376 0.025343508
v 0.79465747 -0.3065375 0.74760664
v 0.7167797 -0.05948953 0.07177174
v 0.5069207 0.007925091 0.045050174
v 0.473681 0.1433301 0.0640133
v 0.5262803 -0.50220436 0.08388382
v 0.82542586 -0.10461435 0.11902022
v 0.4994873 0.04185808 0.05651492
v 0.4347148 0.23923367 0.06352061
v -0.95488906 -0.5096326 1.0445904
v -0.9593077 -0.1528684 1.0454743
v -0.5946291 0.7324134 1.8653064
v 0.07421829 0.3186832 1.9059443
v -0.59100735 0.2742557 1.5455583
v -0.3311854 0.27404118 0.8017452
v 0.78122723 -0.046094716 0.111204624
v 0.7875478 -0.0028607491 0.14587039
v 0.500474 0.10374835 0.0925262
v 0.0730249 0.1907021 1.7054713
v -0.5924308 0.3576923 1.5576613
v 0.098176904 0.34824258 1.8713384
v 0.7576192 0.099777505 0.6785935
v -0.83808005 -0.4019991 0.9812933
v 0.8628971 -0.208849 0.13657802
v 0.907257 -0.08481601 0.18769145
v 0.50412047 0.1697006 0.124090195
v 0.4589871 0.20350449 0.0647946
v -0.23789352 -0.01754373 1.5434133
v 0.8521513 -0.04920055 0.15541631
v 0.6332807 0.170803 0.86528265
v 0.03352049 0.1542683 1.6687331
v -0.41421384 -0.10775307 1.3399762
v 0.09382463 0.3498104 1.8323643
v 0.90483767 -0.15483011 0.17198998
v 0.8870524 -0.032791298 0.19727892
v 0.5292586 0.2512249 0.8412534
v 0.5377853 0.1214004 0.14066261
v 0.4830514 0.2466619 0.13458252
v -0.06605822 0.2367689 1.0083332
v -0.48140043 -0.3451358 1.5145924
v 0.9218976 -0.246562 0.1880216
v 0.9559108 -0.1574223 0.22367543
v 0.8156564 0.031060413 0.19277573
v -0.87121844 -0.3900885 1.0111543
v 0.5981144 0.09133634 0.18382388
v 0.5883267 0.1477436 0.1918475
v 0.5646979 0.19429009 0.18586361
v 0.5049343 0.2086005 0.15187377
v 0.46697432 0.26822108 0.1801514
v 0.0132443905 0.2891433 0.87646127
v -0.5942716 0.6669753 1.8046744
v -0.5920798 0.080310255 1.4838864
v 0.8111896 -0.1898833 0.71294343
v -0.4240808 -0.13591678 1.4238263
v -0.85749173 -0.59414953 1.0180702
v -0.4597476 -0.2461031 1.5187523
v -0.60860413 0.24294388 1.4911532
v -0.4509921 -0.3268786 1.3605032
v -0.5813158 0.7689421 1.8442333
v 0.9532367 -0.2216085 0.2186054
v 0.93676573 -0.06095138 0.23538321
v 0.8725483 0.02514619 0.2531945
v -0.5964531 0.5750107 1.7437043
v 0.9531743 -0.27849782 0.23231
v 0.958217 -0.11356978 0.2374866
v 0.9055176 -0.011200899 0.2416544
v -0.2875613 0.3582838 0.0059474707
v 0.6050421 0.1626117 0.22119242
v -0.67593765 0.048417624 0.023501396
v 0.98315096 -0.2616927 0.2885207
v -0.121674806 0.03888615 1.1672713
v -0.475033 -0.33466822 1.4391273
v 0.9747192 -0.2166412 0.25364822
v 0.9957883 -0.1927251 0.3058939
v 0.9275617 -0.010669459 0.29555112
v 0.56649446 0.24377331 0.23547554
v 0.10536447 0.3279444 1.8148792
v -0.7831018 -0.3625363 0.50254226
v -0.60149574 0.5391034 1.6336465
v 0.6826001 0.09772129 0.2960503
v 0.6422182 0.12381059 0.2651688
v 0.60503304 0.19165501 0.2499705
v 0.9885135 -0.14216003 0.29968768
v 0.9648964 -0.071589276 0.2951299
v 0.596232 0.2152773 0.2847091
v -0.005504191 0.3978389 0.8047546
v -0.89843524 -0.11882712 0.9929153
v 1.0061467 -0.2219608 0.3687749
v 0.99232376 -0.0951861 0.35353112
v 0.9633975 -0.04382232 0.34625113
v 0.7231952 0.06425825 0.3448887
v 0.6170294 0.19546919 0.31187648
v -0.14249432 0.09792878 1.1358763
v -0.9281168 -0.24688701 0.90786934
v 1.0085374 -0.155302 0.3683771
v 0.6853093 0.1362945 0.3458845
v 0.5336877 0.2960424 0.30091882
v -0.58699423 0.6648056 1.7594602
v -0.9588398 -0.229103 1.0328513
v -0.93168664 -0.1840996 1.0131303
v 0.992667 -0.09658229 0.38726223
v 0.6206382 0.21645509 0.3537066
v -0.9980724 -0.3600429 1.1886693
v -0.9930544 -0.5121481 1.1295583
v 0.09842807 0.2953001 1.8178563
v -0.8442005 -0.3223468 0.5661682
v -0.2624934 0.007213991 1.5252783
v 0.9996298 -0.2602029 0.42098677
v 0.9962966 -0.12995121 0.4304729
v 0.7475936 0.05342639 0.40515018
v 0.67010975 0.1711371 0.38936818
v -0.5860231 0.3162119 1.6470234
v -0.7765784 0.1484131 1.5618083
v 0.7138573 0.12557599 0.40952474
v 0.6332339 0.22088419 0.4075123
v -0.8626579 -0.05925527 1.4746304
v -0.91331375 -0.14621644 0.94013524
v -0.7104149 0.7019518 1.9206603
v -0.89480436 0.016266543 1.1869143
v 1.00789 -0.2009424 0.4224571
v 0.7372378 0.10513766 0.45320344
v 0.6731764 0.1846116 0.44760168
v -0.975273 -0.4932318 1.2241203
v -0.59311724 0.6264049 1.6946292
v -0.70678395 0.3394117 1.4831973
v -0.43632942 -0.04465393 1.3500254
v -0.4379921 -0.23106211 1.4077582
v 0.99694014 -0.22950082 0.45764166
v -0.579878 0.7381841 1.8119154
v 0.5432921 0.2871179 0.4236362
v -0.75467205 0.34151772 1.5160743
v -0.5905211 -0.2851109 0.35041887
v -0.5876117 -0.5902157 0.040626317
v 0.97728276 -0.18921511 0.4923945
v -0.251212 0.08853626 1.1642424
v 0.5982275 0.24617177 0.44902396
v -0.32344908 0.07198128 1.2017343
v -0.74653286 0.4778539 1.7037814
v 0.091213584 0.3025671 1.7676764
v -0.94610107 -0.06506783 1.1371503
v -0.59164166 0.508842 1.6952143
v 0.78410536 0.015138529 0.75672996
v -0.4112342 -0.19145499 1.3551213
v -0.94265866 -0.41523442 1.3418612
v -0.74657965 0.3079023 1.6018224
v 0.70433486 0.14499019 0.5171855
v 0.5605964 0.29115307 0.48009914
v -0.4058938 -0.07451818 1.4424292
v -0.97784185 -0.48522514 1.0764662
v -0.7085585 -0.4868774 0.52765834
v -0.58835787 0.41992983 1.6076853
v 0.0922931 0.23241258 1.7430284
v 0.7818603 0.009725982 0.49876058
v 0.7614555 0.060804803 0.47135532
v 0.6539832 0.20218241 0.5135
v 0.604058 0.25739992 0.49655968
v -0.16456959 -0.0037808893 1.5885103
v -0.9047402 -0.09703678 0.9292934
v -0.49542612 -0.388977 1.4730703
v 0.7930624 0.008251652 0.6746273
v 0.7808502 0.03122707 0.5525078
v 0.38867402 0.3612374 0.75161576
v -0.6534866 0.20726801 0.7518511
v -0.96226907 -0.09856805 1.1629683
v 0.811473 -0.05316607 0.53786844
v -0.9911045 -0.1650884 1.1304033
v 0.79867584 -0.2510873 0.7732972
v 0.07224879 0.2178604 1.7064462
v 0.8100586 -0.06979515 0.6973617
v 0.8196981 -0.10915305 0.572832
v 0.74207383 0.1014753 0.51125365
v 0.7802496 -0.08446929 0.8602984
v -0.4459949 -0.2049035 1.4545972
v 0.799422 -0.04847671 0.77192307
v 0.7829055 -0.3382692 0.76115644
v 0.7481812 -0.116866335 0.9390824
v -0.8945559 -0.010623179 0.8185736
v 0.7477925 0.08749901 0.5871527
v 0.6804083 0.16838239 0.5557903
v -0.9871615 -0.4442751 1.1364744
v -0.5912075 0.1714751 1.5386293
v 0.8028956 -0.02721547 0.61922514
v 0.7100041 0.15349479 0.60304785
v 0.6586515 0.20995249 0.5895084
v -0.9651629 -0.3017548 1.4129584
v -0.7436442 0.68164194 1.8833764
v -0.5845801 0.4233319 1.6558113
v -0.42830318 -0.08808875 1.4015962
v 0.7522632 -0.4078218 0.74296045
v -0.72950023 -0.26359978 0.41271746
v -0.9639331 -0.2450943 1.3733733
v 0.77584 -0.14244916 0.8791653
v -0.72350854 0.6766902 1.8563623
v -0.7678047 0.0980017 1.5262402
v -0.8900944 -0.03738628 0.9017854
v -0.78291976 0.040748533 1.0485553
v 0.82006466 -0.21149191 0.5900973
v 0.7658027 0.06742817 0.62690294
v 0.6932237 0.1917148 0.6328634
v -0.2680496 0.29567838 0.15754181
v -0.88852274 -0.05152105 1.3824862
v -0.60465986 0.4083663 1.7124002
v -0.5822752 0.3180228 1.6055794
v 0.81535345 -0.12877719 0.69844186
v 0.7986173 -0.030016448 0.6612905
v -0.3645447 -0.04151924 1.4687933
v -0.6152016 0.7442252 1.9009392
v -0.65350085 0.7167718 1.9164224
v -0.62390643 0.70721287 1.8920603
v -0.61841 0.62066406 1.8154771
v -0.6965465 0.67197376 1.9162273
v -0.76008785 -0.4661333 1.5472482
v 0.0068627 -0.23615682 1.2367563
v -0.5890963 -0.2537172 1.5950754
v -0.5097274 -0.3490709 1.5665922
v -0.2661997 -0.008008489 1.8145545
v -0.74565005 0.6410624 1.9053204
v -0.80910695 -0.2161134 1.5868335
v -0.72415847 -0.4039244 1.5767064
v -0.6621836 0.63561803 1.8717282
v 0.12046202 -0.6366231 0.86974025
v -0.5560698 -0.36212292 1.5783834
v -0.78756213 -0.420905 1.5620294
v -0.84104407 -0.2753349 1.5784743
v -0.72048986 0.051244993 1.6335423
v 0.0402025 0.31287348 1.9708273
v -0.34663594 -0.31201568 1.1856143
v -0.67993516 -0.04055204 1.5897064
v -0.7403123 -0.04980921 1.5870805
v -0.6680882 0.5704867 1.8341453
v -0.56227344 -0.44680882 1.5534103
v -0.08656049 -0.2352494 1.2069733
v -0.7276906 0.6074444 1.8880563
v -0.77732974 0.5932653 1.8748872
v 0.3991962 -0.3457026 1.2297754
v -0.9043646 -0.1750178 0.9848163
v -0.18925142 -0.3568943 1.1453012
v -0.4199468 -0.270352 1.3436682
v -0.33968478 -0.13978091 1.2476633
v -0.6307522 -0.031150179 1.5677102
v -0.2704715 -0.3353455 1.1819092
v 0.3420149 -0.288565 1.2624183
v 0.19504315 -0.2319591 1.2722203
v 0.2655142 -0.29446182 1.2669554
v -0.98635817 -0.1738335 1.0882182
v -0.6362031 -0.35749882 1.5904603
v -0.9068917 -0.12889484 1.4193673
v -0.7010965 -0.10904567 1.5910714
v 0.11332008 -0.2386099 1.2609364
v -0.35730892 -0.2896076 1.2058293
v -0.69447696 -0.4966157 1.5356263
v 0.020673886 0.2674736 1.9672394
v -0.0061060935 0.24043879 1.9979193
v -0.86788 -0.119118705 1.5502383
v -0.9272809 -0.09309439 0.8349978
v -0.9307766 -0.1575991 1.3738413
v -0.66273355 -0.2048619 1.5968564
v 0.13278902 -0.4122132 1.2103922
v -0.9352876 -0.11219114 1.3382993
v -0.78527415 0.22737248 1.6001713
v -0.99288154 -0.3229812 1.1037014
v -0.08717671 -0.1622297 1.2143313
v -0.64669275 0.52839136 1.7992144
v -0.680355 0.48790288 1.8091853
v 0.9617569 -0.12366988 0.4913714
v -0.7616687 0.661956 1.9008353
v -0.19536138 -0.1494442 1.2100542
v 0.10155756 -0.3056184 1.2507702
v 0.6646497 0.00985845 1.0221003
v -0.8399819 0.07009953 0.82927525
v -0.084471405 0.213729 1.9491303
v -0.58292913 -0.1635674 1.5926313
v -0.9363302 -0.2949311 0.7432542
v 0.97726333 -0.09369187 0.45185012
v -0.3411798 -0.2382199 1.2290342
v 0.6192771 0.022051152 1.0690173
v -0.9337939 -0.12240888 0.80042684
v -0.9738833 -0.4197922 1.0892713
v -0.71867645 0.5258239 1.8419454
v -0.68561476 -0.45952412 1.5570762
v 0.9994361 -0.1681785 0.45265222
v -0.30264127 -0.2741636 1.2086633
v 0.64507174 -0.07706618 1.0821993
v -0.72828853 0.1842411 1.4533494
v -0.89009964 -0.3220517 0.9387053
v -0.77025396 0.06226873 1.0104653
v -0.9065771 -0.35565022 1.5370953
v 0.9449635 -0.15797481 0.51050085
v -0.49193823 -0.1832312 1.5849614
v 0.69590294 -0.06954178 1.0121553
v 0.6279377 -0.04118163 1.0912473
v -0.8053565 0.118539095 0.8050952
v -0.94389355 -0.42129242 1.4328353
v -0.053171307 0.1762162 1.9843605
v -0.70281374 0.10725197 1.6647162
v -0.7035444 0.4066581 1.7947423
v -0.18161392 -0.2350024 1.2003044
v -0.48949158 -0.31183758 1.5556724
v 0.19821164 -0.3992977 1.2250823
v 0.5971108 -0.09872274 1.1305333
v 0.55284965 -0.01880005 1.1429224
v -0.942734 -0.1937053 0.8610823
v -0.7621705 0.4081687 1.7893994
v -0.153335 -0.4078166 1.1141273
v 0.4923204 -0.03356753 1.1916853
v -0.9807174 -0.5548921 1.1031553
v 0.24973598 -0.6294653 0.8691553
v -0.51774585 -0.404447 1.5521493
v -0.5260294 -0.2704612 1.5827773
v -0.25546432 -0.3789878 1.1529584
v -0.74499357 0.4745584 1.8232124
v 0.8883953 -0.1843531 0.5224284
v -0.640835 -0.11230034 1.5861962
v 0.687739 -0.1556634 1.0315642
v 0.4357106 -0.022173028 1.2166584
v -0.8315697 0.00063664094 1.4261533
v -0.9744358 -0.25054908 1.4304693
v -0.91031075 -0.2210482 1.5534753
v -0.63709235 0.3779931 1.7463303
v -0.79841185 0.4170659 1.7605913
v -0.72495925 -0.5615533 1.4848874
v 0.97813296 -0.2504048 0.4795817
v 0.9574318 -0.2420822 0.5031949
v 0.9323652 -0.2171768 0.5180721
v 0.83522266 -0.18086 0.52495944
v 0.3785652 -0.03660706 1.2444654
v -0.8188648 0.06688802 0.9126923
v -0.7781046 -0.12286219 1.5832453
v 0.5011279 -0.4206944 1.1423763
v -0.75997347 -0.52855796 1.5089893
v -0.11854181 0.1567643 1.9424355
v -0.66537637 0.3918875 1.7736952
v 0.4372173 -0.3888028 1.1902943
v 0.9206275 -0.262084 0.51446193
v 0.8752081 -0.22943969 0.521144
v -0.55615824 -0.12224287 1.5980003
v 0.5478785 -0.14903444 1.1756823
v 0.46882808 -0.091487736 1.2165933
v -0.9614476 -0.3573753 1.4160132
v -0.3867175 -0.2388348 1.2986754
v -0.09497669 0.1308124 1.9564884
v 0.33123273 -0.3593097 1.2432694
v 0.25080794 -0.3422823 1.2548392
v 0.9638993 -0.2942655 0.47024637
v -0.8908744 -0.2841879 1.5611584
v 0.6458179 -0.14011475 1.0868664
v 0.5336188 -0.08266385 1.1760854
v -0.40253985 -0.316632 1.2962313
v -0.6076304 0.317599 1.6901443
v -0.6474546 0.24945039 1.7090333
v -0.6721416 0.2922048 1.7421312
v -0.7274566 0.3319042 1.7725384
v 0.84510267 -0.24019979 0.51205313
v -0.13756862 -0.3008357 1.1691302
v -0.36759448 -0.1641654 1.2731433
v 0.59870327 -0.17916091 1.1344723
v 0.3962218 -0.10939562 1.2527204
v 0.32788718 -0.100850716 1.2688012
v 0.23100168 -0.05289567 1.2653302
v 0.2584556 -0.6762328 0.8449675
v -0.4630496 -0.18732621 1.5572842
v -0.6962631 0.3206059 1.7648814
v 0.9250852 -0.2972256 0.49604625
v 0.8055112 -0.3121691 0.49036652
v 0.4695782 -0.1661219 1.2229502
v -0.99424267 -0.19483499 1.2168143
v -0.91785336 -0.1966667 0.94419134
v -0.078657806 -0.6989165 0.7452264
v -0.5888792 -0.4201562 1.5698423
v -0.37765777 -0.378915 1.1364613
v 0.8785751 -0.2881126 0.5049889
v -0.9464818 -0.40028182 1.3832142
v -0.47478604 -0.2300507 1.5525262
v 0.020460695 -0.2993576 1.2290083
v 0.6560385 -0.2426269 1.0716172
v 0.5304169 -0.2210027 1.1879153
v -0.8757554 0.02332047 0.8378098
v -0.25249383 -0.14732195 1.2280463
v -0.2910518 -0.084720194 1.2346373
v -0.21935028 0.08706935 1.8776433
v -0.74993885 0.014470983 1.6013935
v -0.38940984 -0.12484664 1.2939303
v -0.8862022 -0.18377851 1.5588702
v 0.0771524 -0.3777502 1.2168924
v 0.1780168 -0.09262705 1.2664484
v -0.93547356 -0.293297 0.8284575
v -0.6241339 -0.22087921 0.015869081
v -0.6174909 -0.33480728 0.023094505
v -0.64903796 -0.4593122 0.03361669
v 0.4763135 0.2769467 0.8217169
v -0.2843035 0.1413814 1.6560974
v -0.7811583 0.071171135 1.2624183
v -0.7246031 -0.11065844 0.027162194
v -0.68130404 -0.1758173 0.027883679
v -0.6156774 -0.24920881 0.03368172
v -0.6500403 -0.45212188 0.05288142
v -0.59965485 -0.5744701 0.07477337
v -0.7143656 -0.1323267 0.045233488
v -0.6288152 -0.3566694 0.051734805
v -0.64468294 -0.4043547 0.0572325
v -0.6208501 -0.5316195 0.082289994
v -0.5634824 -0.5950894 0.102597296
v -0.5657262 -0.6074667 0.06673029
v -0.2594189 0.2910244 0.71959543
v -0.29501814 -0.57659173 0.055142105
v -0.717795 -0.070996225 0.055416405
v -0.7136038 -0.0974585 0.07996434
v -0.70674366 -0.12475473 0.078733206
v -0.67859083 -0.1592657 0.06705141
v -0.6558812 -0.195212 0.0628199
v -0.5762211 -0.2755117 0.058241308
v -0.29092312 -0.55845934 0.11825842
v -0.23848504 -0.6397951 0.06858802
v -0.1579487 -0.7122883 0.07768798
v -0.018497705 -0.6282927 0.83440113
v -0.6514989 0.09653062 1.1632284
v -0.61039686 -0.23498549 0.064222604
v -0.6189924 -0.3864355 0.1036607
v -0.8977189 -0.375665 0.82830155
v -0.26910132 -0.5951258 0.08985603
v -0.227929 -0.64880544 0.0989781
v -0.1868347 -0.68633246 0.090797186
v -0.6915519 -0.10623714 0.11392164
v -0.82651794 -0.45666412 0.83838046
v -0.12313861 -0.64790064 0.5761769
v -0.2691065 -0.5781192 0.1225484
v -0.12189451 -0.7123676 0.11945701
v -0.68687063 -0.06064354 0.1114282
v -0.65462154 -0.1524017 0.11261904
v -0.9178585 -0.3055352 0.8936473
v -0.092864215 -0.7152211 0.5913726
v -0.57331824 -0.57409054 0.13155484
v -0.18741968 -0.6664217 0.13698882
v -0.7660692 -0.48819298 0.9129913
v -0.5931159 -0.20967841 0.11148542
v -0.5752591 -0.1807547 0.14406991
v 0.5545293 0.2758534 0.73191047
v -0.790972 -0.48464012 0.63576496
v -0.56654394 -0.5404231 0.16394949
v -0.106353015 -0.7137443 0.6300801
v -0.22948769 -0.60976905 0.1463033
v -0.1695447 -0.6399797 0.17514122
v 0.0218582 -0.5977739 0.86606133
v -0.65657157 -0.118015796 0.14126968
v -0.53367597 -0.16308379 0.17074984
v -0.4551859 -0.042005442 1.6980095
v -0.49885035 -0.5200014 0.19596201
v -0.80662656 -0.6773677 1.1097333
v -0.13556531 -0.6420961 0.7108607
v -0.7635667 0.11643179 1.6439943
v -0.25155392 -0.5489017 0.17153764
v -0.11229661 -0.65434986 0.74578404
v -0.82224226 -0.6531825 1.0841234
v -0.4642638 -0.21242921 0.18548399
v -0.1981538 -0.5307095 0.22297466
v -0.1909284 -0.6075616 0.17706394
v -0.045766503 -0.68992054 0.7889662
v -0.719472 -0.5587024 0.7572057
v -0.4789616 -0.1778947 0.20862788
v -0.8528377 -0.661015 1.1512294
v -0.4283942 -0.5007523 0.21356916
v -0.2019043 -0.572082 0.1997177
v -0.0821847 -0.6349357 0.795678
v -0.90409935 -0.3678143 0.75935864
v -0.5318573 -0.11386463 0.20030272
v -0.9122021 -0.3427087 0.7228286
v -0.9806771 -0.5494945 1.1904763
v 0.0121966 -0.6710822 0.8282521
v -0.141947 -0.5826991 0.22349858
v -0.89853144 -0.5373174 1.4212134
v -0.75785315 -0.533823 0.73792815
v -0.9441874 -0.379305 1.4819492
v -0.4743362 -0.17176391 0.24200672
v -0.43378013 -0.2305902 0.2619812
v -0.899405 -0.472021 1.4741882
v -0.69077194 -0.5675541 0.685971
v -0.4367779 -0.4617562 0.24077821
v -0.86447406 -0.45790172 1.5118753
v -0.1608594 -0.5085549 0.24640983
v -0.93551505 -0.6245513 1.1198083
v -0.7916025 0.51995313 1.8248112
v -0.86602235 -0.4209492 0.7906275
v -0.5104645 -0.11510366 0.26208913
v -0.80774987 -0.5322903 1.4932073
v -0.66432345 -0.585121 0.7085949
v -0.35503262 0.2715816 0.7744633
v -0.9074533 -0.627449 1.2864553
v -0.9860929 -0.56045485 1.1378913
v -0.4431219 -0.2071954 0.25243402
v -0.4285112 -0.45638588 0.2981069
v -0.9486139 -0.578569 1.2446084
v -0.14629161 -0.4974997 0.2761343
v -0.117859304 -0.5286386 0.30393088
v -0.8697156 0.029221822 1.1564033
v -0.84050846 -0.6968001 1.2585573
v -0.45054752 -0.22597392 0.2933879
v -0.8033571 0.5866288 1.8343794
v -0.4694144 -0.2921192 0.30853164
v -0.47030753 -0.36002472 0.34117067
v -0.45928478 -0.36601248 0.29891026
v -0.4018755 -0.4964454 0.37224978
v -0.8676044 -0.37045333 0.62217605
v -0.020007014 -0.666189 0.3143413
v -0.8224944 -0.4689803 0.74459064
v -0.8711468 -0.3991729 0.68936145
v -0.50226283 -0.31851703 0.33943516
v -0.8505471 -0.5470713 1.4537653
v -0.096025795 -0.63912433 0.50646955
v -0.11117861 -0.54761857 0.38034487
v -0.9663043 -0.5927676 1.1636443
v -0.38057894 -0.028856719 1.7535324
v -0.92336786 -0.4834701 1.4326664
v -0.91246486 -0.5693845 1.3625704
v 0.014106289 -0.7000202 0.3576703
v -0.8710338 -0.6749965 1.1999145
v -0.5978141 0.1576002 1.0326173
v -0.8350265 -0.4496532 0.6680479
v -0.93291247 -0.60937643 1.0781564
v -0.04507619 -0.6460273 0.3764397
v 0.0543582 -0.7267833 0.3682263
v -0.69797266 -0.5471987 0.63060796
v -0.8720596 -0.65774554 1.3000662
v -0.5168709 -0.49516103 0.19559282
v 0.34769136 0.2338764 0.98653233
v -0.50865364 -0.6221593 0.033896208
v -0.7696299 0.2150043 1.6939404
v -0.87097144 -0.3837679 0.90476227
v -0.92886424 -0.6191069 1.2170223
v -0.81023407 -0.4359291 0.9475583
v -0.9282676 -0.327942 0.79528546
v -0.9400209 -0.50883436 1.3676533
v -0.03276521 -0.7189391 0.76458204
v -0.5390398 0.2530631 0.7490145
v -0.1331889 -0.5350073 0.43815857
v -0.05999893 0.2632525 1.9040723
v 0.11670099 0.2691441 0.9871043
v -0.0934349 -0.7175832 0.6849141
v -0.56787384 -0.07561694 1.5908113
v -0.9046453 -0.6486312 1.1488763
v -0.7446842 0.4046613 1.5153723
v -0.04913479 -0.7474507 0.6014137
v -0.8714745 -0.6028894 1.3775982
v -0.07972899 -0.7065215 0.030199021
v -0.9015838 -0.650602 1.2225993
v -0.63605875 0.16686009 0.9592453
v -0.060841322 -0.7064201 0.5314998
v -0.77509767 0.09819098 1.6126902
v -0.785395 -0.5057404 0.679705
v -0.8755474 -0.6755581 1.2511213
v -0.83746517 -0.4255954 0.6200129
v -0.84373903 -0.4214861 0.8887073
v -0.9028721 -0.3496494 0.8808553
v -0.83387196 -0.6349383 1.3744392
v -0.807395 -0.47993153 0.799773
v -0.73630965 -0.5420078 0.807335
v -0.46637237 0.2652519 0.71384037
v -0.7753161 -0.4579979 0.5746156
v -0.06487131 0.270981 0.9525113
v -0.93027866 -0.3578446 1.5165423
v -0.09461272 -0.6065892 0.4497584
v -0.041822314 -0.6896189 0.44537222
v -0.8680984 -0.6585775 1.1338093
v 0.027886286 0.31837508 1.8363163
v -0.1377337 -0.6529641 0.6603792
v -0.3267238 0.26837322 0.7169956
v -0.19360769 0.2713099 0.9009403
v -0.14415962 0.27753168 0.9242623
v -0.74086225 0.08323656 1.1291423
v -0.38120162 0.2466788 0.9328033
v -0.13795081 -0.5736082 0.51947343
v -0.3735147 0.2584516 0.018545806
v -0.36649597 0.2550014 0.074192286
v -0.3856008 0.2118531 0.045375198
v -0.9303515 -0.09496614 1.0440183
v 0.8911136 -0.13711162 0.51463497
v -0.69659466 -0.007155299 0.0538252
v -0.5735886 -0.3113657 0.06905466
v -0.7916441 0.5412627 1.7805982
v -0.35089862 0.07566433 1.7711861
v -0.7902206 0.6336043 1.8702981
v -0.30863166 0.336475 0.07248932
v -0.3563937 0.29600862 0.036680818
v -0.64784074 0.045964908 0.09256518
v -0.68173426 -0.021777568 0.10613459
v -0.7649161 0.3305405 1.6379232
v -0.601718 -0.3466308 0.08195591
v -0.72152597 0.5208059 1.5412424
v -0.31525648 0.29352298 0.106843114
v -0.39246482 0.1629497 0.07257247
v -0.945863 -0.078364745 1.2376924
v -0.7824218 0.01059204 1.5649934
v -0.69316906 0.6459868 1.7617092
v -0.6238909 -0.43028972 0.10938978
v -0.3295123 0.10501325 1.1802193
v -0.40649313 0.09002933 1.1910614
v -0.32954353 0.26034307 0.13044327
v -0.6566846 -0.03973434 0.1394133
v -0.7356128 0.36999938 1.5772133
v -0.5011734 -0.26803148 0.12514186
v -0.5624684 -0.3176629 0.10542476
v -0.63293755 -0.4783715 0.0991627
v -0.82245016 -0.02713734 1.5221194
v -0.3539939 0.21465199 0.112309635
v -0.36901152 0.1652468 0.11886549
v -0.86200917 0.023543421 0.9014733
v -0.63273335 -0.07851802 0.16013014
v -0.7621471 0.1678481 1.5431274
v -0.4897412 -0.3262338 0.16415882
v -0.5936203 -0.3896829 0.13535732
v -0.59841084 -0.4693677 0.1543464
v -0.6075823 -0.512932 0.1327833
v -0.5649059 0.034441058 0.14871609
v -0.60373306 -0.02556564 0.1591174
v -0.5908292 -0.112744555 0.1719926
v -0.9646338 -0.298861 1.5021123
v -0.5238103 -0.3685189 0.16492575
v -0.52618146 -0.40784 0.18186611
v -0.29534566 0.079208374 1.8271384
v -0.94692135 -0.46311343 1.3664184
v -0.9854052 -0.3466568 1.2594024
v -0.9812387 -0.4060421 1.2260442
v -0.22303188 0.3663126 0.10029757
v -0.9541805 -0.093051374 1.0824463
v -0.54229367 -0.02203198 0.18747818
v -0.71399385 0.26111138 1.4672333
v -0.4626544 -0.24919191 0.15780962
v -0.5539729 -0.46351123 0.18158525
v -0.5293548 -0.073377565 1.6310594
v -0.8340553 0.006976871 0.98744226
v -0.456339 -0.2717469 0.16627258
v -0.45537704 -0.30068102 0.19836438
v -0.7910422 0.19651961 1.6025114
v -0.2680015 0.2036449 0.18683857
v -0.8069763 -0.071108535 1.5620812
v -0.5308472 -0.07921092 0.22250283
v -0.4657172 -0.3968771 0.22262108
v -0.4705701 -0.4608488 0.2061683
v -0.7371208 0.5630234 1.7603183
v -0.4890561 0.057672974 0.18884581
v -0.4616521 -0.3375841 0.23108023
v -0.8102757 0.4423106 1.7427292
v 0.3407634 0.108508304 1.1725103
v -0.20689112 0.2269682 0.9835293
v -0.1863212 0.23301971 0.2118662
v 0.8163779 -0.05845629 0.47455984
v 0.65424454 0.07159324 0.9925773
v -0.4972006 0.032029692 0.25295013
v -0.5212597 0.0006447006 0.21249413
v -0.43536347 -0.2424124 0.205257
v -0.43932462 -0.26853332 0.21597421
v -0.45612323 -0.39244282 0.2601924
v -0.66148555 0.7017932 1.7163393
v -0.1751152 0.2105193 0.24435061
v 0.09999951 0.24779162 1.0153923
v -0.5234892 -0.049684152 0.2744885
v -0.20731622 0.1318875 1.8757582
v -0.15871179 0.2690154 0.2334202
v -0.18956733 0.1612584 0.24025822
v -0.50534904 0.02429352 0.30776197
v -0.44349504 -0.2760603 0.2665481
v -0.93696713 -0.2803958 1.5389934
v -0.78457594 0.6030088 1.8262024
v -0.80309844 0.3944667 1.7016492
v -0.1396837 0.30764621 0.26925212
v -0.48961508 -0.15351841 0.28661752
v -0.91312516 -0.03112093 1.2646282
v -0.95170796 -0.09625678 1.2931243
v -0.98061085 -0.21986781 1.2823733
v -0.41390705 0.04649895 1.7083964
v -0.48445153 0.07951987 0.34644222
v -0.5282303 -0.13270761 0.3168568
v -0.90991807 -0.078518674 1.3675622
v -0.9904505 -0.27822092 1.2689704
v -0.968253 -0.3402218 1.2975184
v 0.50200796 0.11092889 1.0971364
v -0.16699931 0.2577769 0.3223272
v -0.1883505 0.1677714 0.2846831
v -0.5486156 -0.05325304 0.3370211
v -0.9547291 -0.20320961 1.3318512
v -0.5819892 0.7825596 1.7880082
v -0.6404996 0.7431033 1.7194853
v -0.1006057 0.22418101 1.9077382
v 0.008700907 0.3192136 1.9458153
v -0.1821092 0.212585 0.3079453
v 0.3544489 0.317417 0.81731653
v -0.5289778 -0.0052480698 0.34086388
v -0.94049156 -0.3566564 1.3415362
v 0.27524185 0.26042628 0.9179183
v -0.956488 -0.4233451 1.2897962
v -0.9619272 -0.2865838 1.3681473
v -1.0066772 -0.30788952 1.2069733
v -0.9559667 -0.5150744 1.2725453
v -0.36766076 0.1311322 1.1553633
v 0.365937 0.2800446 0.84414077
v -0.29154968 0.26559508 0.85637116
v -0.6332105 0.7035339 1.6284204
v -0.78791046 0.2505398 1.6808364
v -0.413127 0.013562929 1.7365675
v -0.78158474 0.09875129 1.5626533
v -0.6623448 0.6824973 1.6124954
v 0.90619624 -0.012625439 0.41496783
v -0.1592994 0.3046666 0.3610646
v -0.1854567 0.2764943 0.412737
v 0.5336591 0.21880941 0.90157723
v -0.5027451 -0.03716255 1.6534972
v -0.64794475 0.7196851 1.6522884
v -0.79819095 0.2927261 1.6564872
v -0.019756109 0.1251691 1.1228893
v 0.500682 0.1767843 0.99129033
v -0.6705608 0.6854444 1.6443973
v -0.11237979 0.38197112 0.3496831
v -0.1266109 0.3923737 0.41306335
v -0.15112108 0.34393048 0.37556612
v -0.20546111 0.18161899 0.36341113
v -0.7029269 0.0841628 1.0764793
v -0.68873346 0.6231666 1.6389892
v -0.17581072 0.3402437 0.4470024
v -0.21029317 0.2197415 0.42971885
v 0.3943485 0.2612609 0.9001733
v 0.7957235 -0.03158945 0.47383702
v 0.1898143 0.23629308 1.0250382
v -0.71795756 -0.00987672 0.43857706
v -0.8046819 0.5360965 1.8177783
v -0.863343 -0.074037045 1.5263443
v -0.91829526 -0.050437108 1.3057082
v 0.560937 0.2817021 0.6662799
v -0.7553065 0.01044059 0.48242217
v -0.4910789 0.2680131 0.75728893
v -0.7337395 0.44487548 1.5470402
v -0.75075 0.598893 1.8054023
v -0.15443611 0.3786626 0.45465678
v 0.44426072 0.2600701 0.8989513
v -0.8379332 -0.111596525 0.5162989
v -0.7563569 0.2273985 1.5699854
v -0.9718214 -0.1575666 1.2824903
v 0.20769475 0.39746192 0.8041177
v -0.7906314 0.049972553 0.5289596
v -0.8197514 -0.058816258 0.5127863
v -0.8427446 -0.1643721 0.5220891
v -0.7394179 0.24380189 1.4891514
v -0.96861446 -0.23078129 1.4972893
v -0.96263826 -0.211787 1.4682993
v -0.6726577 0.7241727 1.8436613
v -0.135889 0.2573661 0.96414626
v 0.4971798 0.01884171 1.1625652
v -0.8093827 -0.008282269 0.52538717
v -0.8484125 -0.04217015 0.55488944
v -0.87016284 -0.1357318 0.56500477
v -0.95426106 -0.1887263 1.5101854
v 0.22943933 0.20611231 1.0840713
v -0.7510945 0.353154 1.5382912
v -0.1597245 0.41433847 0.5149911
v -0.18697903 0.3751487 0.5288348
v -0.20547283 0.3039958 0.5023941
v -0.829075 0.019573223 0.5628727
v -0.94497 -0.18162051 1.4380093
v -0.78763616 0.0373359 1.4723034
v -0.72923756 0.4241522 1.6255214
v 0.31116343 0.2192228 1.0578372
v -0.23012081 0.2715803 0.55666137
v -0.87216604 -0.049106818 0.6172348
v -0.8702135 -0.07994529 0.5710952
v -0.90224683 -0.15623021 0.6190704
v -0.90038645 -0.2070004 0.60607564
v -0.84164333 -0.3667249 0.5803304
v 0.5650736 0.1173535 1.0318893
v -0.7701252 0.2591965 1.6031742
v -0.9517443 -0.2240551 1.5278002
v -0.7066215 0.5704165 1.6905344
v -0.21333 0.3409392 0.56632286
v -0.7340073 0.1594787 0.60676074
v -0.81230366 0.07189314 0.6076057
v -0.9000355 -0.2733251 0.6413654
v -0.8779472 -0.3076165 0.6164601
v -0.7091565 0.5291103 1.6114943
v -0.73771757 0.4394792 1.6626754
v 0.4359368 0.20199 0.9958273
v -0.18431142 0.4073445 0.568243
v -0.5922566 0.78004795 1.8477952
v -0.010229707 0.204395 1.0608404
v -0.85218906 0.011980571 0.61836314
v -0.8031126 0.0405703 1.3934844
v -0.1576913 0.4363774 0.59719014
v -0.1875211 0.3990999 0.61234164
v -0.2139501 0.33362928 0.6020534
v -0.3626532 0.1721667 1.0998663
v -0.8179951 0.09462118 0.6619184
v -0.8871356 -0.01826068 0.65660274
v -0.9096801 -0.180717 0.663455
v -0.9083581 -0.21653591 0.6416162
v -0.8879689 -0.3531022 0.6742672
v -0.78227246 0.1295878 0.645138
v -0.8576802 0.037523102 0.65157306
v -0.9019321 -0.1020007 0.64040864
v -0.9666773 -0.2230307 1.4057432
v -0.7518914 0.073685974 1.4387764
v -0.8664175 0.0026651602 1.3081002
v -0.7670481 0.2391856 1.5344563
v -0.7556003 0.23289621 1.5524614
v -0.4616742 0.1211053 1.1477193
v -0.18112901 0.3773171 0.67753005
v -0.20526484 0.3590326 0.63222635
v -0.90549946 -0.03118671 0.6971978
v 0.05588959 0.2714126 0.94944334
v -0.9332037 -0.14768401 1.4657384
v -0.894513 -0.10504542 1.4795964
v -0.9192326 -0.15125251 1.5249664
v -0.5630625 -0.60306877 0.029126525
v -0.9292816 -0.124607965 0.69626963
v -0.9361508 -0.2042145 0.7079228
v -0.92560136 -0.22033711 0.6931145
v -0.9124713 -0.310743 0.6907524
v -0.75151706 0.1649712 1.4815853
v -0.2327897 0.2966599 0.659438
v -0.8861307 0.019818403 0.7364838
v -0.9267752 -0.08230688 0.70761204
v -0.9709635 -0.30887362 1.4575872
v -1.0085322 -0.2520259 1.1508783
v -0.1607255 0.4183984 0.6770855
v -0.1788384 0.3388891 0.719108
v -0.7926048 0.1327624 0.7089536
v -0.8310549 0.08213402 0.7136259
v -0.86412954 0.05418377 0.73016834
v -0.94377005 -0.1706615 0.73097825
v -0.94766617 -0.1983424 1.3832403
v -0.78434074 0.0532999 1.5366273
v -0.4461652 0.015362654 1.6956043
v -0.90534604 5.3331256e-5 1.2267333
v -0.6864598 0.6331831 1.5807753
v -0.9160346 -0.0739467 0.7644013
v -0.9397075 -0.1566787 0.7678735
v -0.9465469 -0.2508299 0.75776994
v -0.8571094 -0.03555783 1.4125682
v -0.72562623 0.123562306 1.4348243
v -0.79139054 0.4588349 1.7264013
v 0.022397697 0.4268549 0.78719544
v -0.70533186 0.57585055 1.5789683
v -0.98190176 -0.13833636 1.1950783
v -0.77019405 0.15809159 0.7504562
v -0.8598434 0.061143186 0.77976346
v 0.08306739 0.4199337 0.7966713
v -0.1628913 0.1867098 1.0588773
v -0.26542488 0.177979 1.0742173
v 0.056824297 0.2904225 0.9041513
v 0.3048755 0.4090254 0.71284986
v 0.13505712 0.4333588 0.77436197
v 0.4238156 0.12913279 1.1265293
v 0.5967481 0.18571919 0.8991463
v 0.7654621 0.01375689 0.39221913
v 0.5918133 0.20819488 0.84023297
v -0.041425794 0.28483772 0.9031503
v 0.65714216 0.22292651 0.65434456
v -0.024501115 0.2439631 1.9966192
v 0.30181438 0.1924051 1.0996974
v 0.95038056 -0.0537525 0.44798
v 0.1487222 0.2357029 1.0345933
v 0.8134152 -0.0034540705 0.40714574
v 0.08723129 0.3412382 0.8439561
v -0.5675475 0.1283398 1.0951992
v 0.3865537 0.16246219 1.1123203
v 0.41647708 0.2984123 0.801684
v -0.1831947 0.305132 0.18328834
v -0.012547612 0.2803709 1.9813704
v 0.0489593 0.2428256 0.9973483
v -0.7072715 -0.029308338 0.06888312
v 0.4511754 0.21121089 0.9485723
v -0.21869254 0.1357875 1.1062233
v -0.4303754 0.1679625 1.0994502
v 0.13147146 0.1804009 1.0941073
v 0.017543495 0.30307668 1.9846592
v -0.1794377 0.3723394 0.06147179
v 0.046549097 0.33848348 1.9586072
v -0.40375924 0.20799729 1.0181223
v 0.048125997 0.4573009 0.7470592
v -0.60211194 -0.05791952 1.5722733
v -0.49840438 0.2104751 1.0036793
v 0.0741546 0.3452825 1.9332705
v -0.12698272 0.3592094 0.018016696
v 0.13209742 0.2864809 0.89585733
v -0.5225402 -0.027101718 1.6133924
v 0.4664088 0.30430257 0.7612891
v 0.3935633 0.06959982 1.1828582
v -0.06408349 0.2967197 0.8463143
v -0.0044356138 0.3047459 0.8417188
v -0.5446623 -0.010715869 1.5348203
v 0.26474643 0.03880412 1.2264082
v 0.10743226 0.2972943 0.8659469
v 0.060177 0.3585256 1.9232345
v -0.576069 -0.032267787 1.5566473
v -0.1660048 0.15851149 1.0897783
v 0.6153043 0.087148786 1.0257273
v -0.1938703 0.379297 0.02342081
v 0.040428698 0.3463732 1.8958824
v 0.3694834 0.19374411 1.0683413
v -0.5875038 0.0019162297 1.5206243
v 0.695305 0.10876167 0.8799193
v 0.8490781 -0.09727651 0.50034785
v 0.21235721 0.15766779 1.1248523
v 0.4361266 0.33338362 0.73845327
v 0.3091951 0.3031339 0.83746266
v -0.34353542 0.1337621 0.16839814
v 0.10978175 0.21784739 1.0455394
v 0.05780579 0.1950597 1.0693552
v 0.82726145 -0.1510835 0.5335746
v 0.5475626 0.1572661 0.9880923
v -0.33356047 0.19117399 1.0479963
v 0.9690928 -0.053430878 0.40103054
v -0.5578235 0.10416369 0.041936696
v 0.17671108 0.2605407 0.9920443
v 0.5696821 0.2705273 0.36446542
v 0.31075224 0.256733 0.89182734
v -0.2785211 0.075964615 1.1874863
v 0.5556525 0.08758207 1.0719033
v -0.57846105 0.101911575 0.007673919
v -0.5546541 0.00528102 1.4836913
v 0.3549988 0.2681535 0.8783723
v 0.6484699 0.1469636 0.90251327
v 0.3811444 0.239638 0.9287733
v 0.518557 0.2694691 0.7889544
v 0.6286332 0.21738589 0.7692723
v 0.299953 0.2705897 0.86580646
v -0.3135106 0.2139838 0.16655469
v 0.6787092 0.136847 0.84342575
v 0.34078968 0.1489214 1.1428183
v 0.9169706 -0.107683 0.50332624
v 0.5514769 0.04616004 1.1114624
v -0.2775409 0.33035198 0.11903709
v -0.14834821 0.21475469 1.0121683
v -0.5648838 0.030008193 1.4310542
v 0.46563268 0.23206028 0.9191663
v 0.3164937 0.3920903 0.75883996
v 0.12642395 0.1284672 1.1581843
v -0.029204518 0.33926868 0.8222786
v -0.0019811988 0.266769 0.9539803
v 0.20966308 0.27788138 0.88255835
v 0.1011799 0.082015604 1.1956112
v -0.6673576 0.745338 1.9077642
v -0.088389605 0.1832362 1.0637653
v -0.5593926 0.067139834 0.123921156
v 0.90902627 0.018239941 0.33413255
v -0.26170042 0.36701462 0.049367517
v -0.5123833 0.017165493 1.4050672
v 0.6954597 0.08423795 0.9154353
v -0.2480933 0.11905649 1.1321713
v 0.09677264 0.47619247 0.6774794
v -0.5632419 0.102658436 1.1323533
v -0.29954338 0.1311439 1.1472642
v 0.5866016 0.1448342 0.97721124
v 0.19405945 0.20393218 1.0741783
v 0.72083956 0.1438761 0.7389642
v 0.19584604 0.4267665 0.76614594
v 0.8684234 0.02577487 0.36792862
v 0.94457996 -0.028244808 0.3848039
v 0.4844125 0.1510794 1.0639733
v 0.021257594 0.16878149 1.1007243
v -0.7027605 -0.4599063 0.49660397
v 0.513175 0.3047615 0.6928494
v 0.6780774 0.203147 0.70367694
v 0.4576247 0.08057545 1.1458863
v -0.1121237 0.3603924 0.10544431
v 0.11170237 0.38125739 0.82507753
v -0.5201261 0.1258919 0.007332027
v -0.23123881 0.3333277 0.14865625
v -0.3048591 0.1584933 1.1045463
v 0.431444 0.1821897 1.0492703
v 0.7726719 0.025688812 0.35733885
v -0.44052064 0.1416804 0.034138024
v 0.31389382 0.3666116 0.7955961
v -0.037774116 0.4310097 0.77489626
v 0.061334 0.10698716 1.1554674
v 0.8528897 -0.041308768 0.45692265
v 0.17828238 0.3369716 0.8462038
v -0.4731714 0.0022846516 1.3444483
v -0.3963583 -0.04125183 1.2641734
v 0.2764602 0.1618382 1.1326913
v 0.18740787 0.115702495 1.1647363
v -0.10441989 0.3078373 0.81312525
v -0.3314467 0.3350268 0.038766026
v -0.48863488 0.032626525 1.3096082
v -0.43859923 -0.0051361388 1.3007424
v 0.2522654 0.35002232 0.8272264
v 0.116800964 0.0059895217 1.2337273
v 0.3030582 -0.03134453 1.2572963
v 0.67007065 0.18423979 0.76653457
v -0.6628271 0.737213 1.9185283
v -0.21988723 0.21574919 1.0166793
v 0.2764325 0.10330883 1.1825204
v -0.08539963 0.3971434 0.7810776
v -0.41835427 -0.0020717792 1.2544492
v 0.20825167 0.022967003 1.2339483
v 0.25086153 0.4178992 0.73512006
v 0.3153323 0.23493591 1.0225554
v 0.59269726 0.24554259 0.7520162
v -0.4782583 0.07624152 1.1838464
v -0.4022538 0.14729899 1.1353433
v -0.03659761 0.104864016 1.1411673
v -0.62720317 0.7575918 1.9068544
v -0.4537416 0.1336542 0.056036502
v -0.4592029 0.034273103 1.2478453
v -0.3709355 -0.039834827 1.2428663
v 0.17880043 -0.0053755995 1.2465063
v 0.31218147 0.048425812 1.2208314
v 0.3701139 0.02213617 1.2205974
v 0.9081462 -0.05994882 0.47318572
v -0.60086256 0.7658468 1.8824015
v 0.5850754 0.24980399 0.40405822
v 0.66561556 0.113653705 0.93236136
v 0.6210906 0.1244892 0.96969724
v -0.4183842 0.18829711 1.0594363
v -0.20826912 0.057544794 1.1660364
v -0.4418947 0.043368157 1.2201164
v -0.2405091 0.021735638 1.1981853
v 0.9523319 -0.083219215 0.47634077
v 0.0072878003 0.028179612 1.1935573
v -0.002202198 0.23535317 1.0125453
v -0.48473233 0.1178111 0.08412695
v -0.22606349 0.2706495 0.18465328
v 0.3965013 0.3242901 0.787856
v 0.22985299 0.2973541 0.857025
v -0.3855605 0.044226293 1.2210913
v -0.32414854 0.0349946 1.2163593
v -0.054593503 0.06027713 1.1677393
v 0.060716495 0.048340265 1.2014093
v 0.16144387 0.07107377 1.2102753
v 0.2367244 0.28317758 0.019137293
v 0.025412396 0.31966472 0.022893012
v -0.05978051 0.34419438 0.019307613
v 0.37453258 0.2632954 0.034476012
v 0.26813695 0.2911453 0.036650896
v -0.44640183 0.26796627 0.8230001
v -0.49415988 0.2592602 0.8423039
v -0.14034802 0.21694519 1.7743454
v 0.3920917 0.2731819 0.07887751
v 0.27550808 0.2949439 0.06973851
v 0.12939134 0.3080323 0.04074979
v 0.045769095 0.3238234 0.057066083
v -0.4013334 0.1536456 0.036487103
v -0.37425828 0.22508709 0.97796535
v -0.4328311 0.2212885 0.99578834
v -0.6365528 0.07442491 0.041074812
v -0.08651242 0.22542769 1.7541823
v 0.1587196 0.3110977 0.06623107
v -0.10599029 0.3597151 0.053705603
v 0.5955144 -0.015698379 0.05262789
v 0.11510356 0.3175769 0.10633999
v -3.6388636e-5 0.33908018 0.08377069
v -0.62861633 0.08063318 1.3914564
v -0.3936283 0.1350374 0.09608042
v -0.67170477 0.04515462 0.054181367
v 0.70366144 -0.008109499 0.0896948
v 0.6779617 0.017227631 0.10331231
v 0.2857799 0.29358408 0.10694325
v 0.4610021 0.2683849 0.87097526
v -0.5682274 0.2022799 0.9642633
v -0.59245294 0.07698226 0.088870645
v 0.54935396 0.033056818 0.080262005
v 0.21544535 0.3073472 0.131105
v 0.11299807 0.32055128 0.1316796
v -0.007298216 0.3403776 0.12905747
v -0.12500411 0.3474561 0.14773333
v 0.26789683 0.24337941 0.98973036
v -0.5386082 0.24873799 0.81094646
v -0.6411782 0.016530443 0.117408216
v -0.57406574 0.22793409 0.7912203
v 0.6259929 0.0414985 0.12141216
v -0.2176187 -0.65744 0.039425075
v 0.44269937 0.28060362 0.14333409
v 0.3567031 0.3047706 0.1410721
v 0.275409 0.328987 0.16245836
v 0.111154675 0.35046428 0.15390444
v 0.013683796 0.3431414 0.152542
v 0.5952869 0.061151903 0.14804399
v 0.32460532 0.3080531 0.15266031
v 0.20589517 0.33573788 0.15688789
v 0.19626957 0.3698057 0.18862092
v -0.1915524 0.3457622 0.14859653
v -0.21864182 0.12405369 1.6068661
v -0.4413526 0.11097309 0.1273961
v 0.6979947 0.060104225 0.18256289
v 0.5430958 0.07195087 0.12135112
v 0.5377242 0.23647639 0.1957761
v 0.399945 0.30191448 0.1595009
v 0.39230227 0.32589948 0.19916523
v 0.26691458 0.358068 0.1926353
v 0.1040819 0.3691908 0.178711
v 0.039613605 0.39989552 0.24121761
v -0.05719742 0.3440969 0.17351359
v 0.7344285 0.03958633 0.16053307
v 0.6555692 0.06380598 0.20030141
v -0.63116425 0.19939129 0.8578012
v 0.23514153 0.3753762 0.22195935
v 0.11207624 0.3846582 0.21496671
v -0.004984215 0.3673747 0.19370782
v -0.120230496 0.3206735 0.18697381
v -0.88329804 -0.53261924 1.0071633
v -0.4487067 0.08383365 0.19449037
v -0.8541715 0.041565318 1.2264863
v 0.7210411 0.06771326 0.2390284
v -0.76494855 0.1357888 0.8596965
v 0.48211148 0.3043702 0.2345109
v 0.2956798 0.3592822 0.22966844
v -0.11345491 0.3302311 0.23611641
v -0.24390212 0.147975 0.20917004
v -0.338234 0.11511749 1.6755972
v -0.6343038 0.7672911 1.8635123
v -0.2255513 0.1537938 1.8345354
v 0.7861646 0.05852655 0.21907991
v -0.4360382 0.048236016 1.5879772
v 0.5494476 0.262713 0.26935738
v 0.3503604 0.3414878 0.20733178
v -0.06814861 0.3724928 0.26392734
v -0.20896852 0.1222831 0.23878008
v -0.27056643 0.1318693 0.21092373
v -0.3113448 0.1274506 0.2076776
v -0.38459456 0.111848 0.184353
v 0.7965646 0.063056275 0.2597803
v 0.6500858 0.074827254 0.2302391
v 0.4222309 0.32390139 0.23217219
v 0.3072572 0.3780243 0.2821039
v 0.24626511 0.3880772 0.2885077
v 0.22612004 0.3774094 0.25425398
v 0.17098717 0.3896827 0.24126834
v 0.09589372 0.418232 0.28716093
v -0.032515615 0.42168608 0.32389373
v -0.5756829 0.074653566 1.3071773
v -0.3446144 0.1289573 0.2905097
v -0.5756335 0.7502273 1.7553654
v 0.8368841 0.047420394 0.25523806
v -0.267826 0.2178929 1.0165752
v 0.47805548 0.3331366 0.27429742
v 0.28858024 0.3824794 0.32594514
v 0.0805662 0.4149729 0.26380903
v -0.23656493 0.11524619 0.24728209
v -0.38571262 0.10851975 0.23392719
v -0.6648772 0.10436949 1.0611393
v -0.7052136 0.56156087 1.5379143
v 0.14183804 0.2956485 0.019249111
v 0.4373603 0.3484298 0.28919542
v 0.357656 0.3677075 0.2779101
v 0.20303816 0.41442168 0.3403725
v 0.15038933 0.4076526 0.2909283
v -0.4537624 0.076991364 0.24909168
v -0.4657302 0.09716697 0.34754074
v -0.7253688 0.16837849 0.83788633
v 0.79471993 0.059201512 0.29260528
v 0.7184372 0.06109925 0.2811237
v 0.72331476 0.04909024 0.31847918
v 0.09879298 0.42841232 0.34060264
v -0.24000078 0.1178163 0.285675
v -0.27996022 0.1224989 0.2755168
v -0.012044504 -0.6981586 0.038734794
v 0.8684975 0.040805336 0.32221675
v 0.818155 0.04283048 0.3375957
v 0.4148469 0.3531254 0.3446287
v 0.3524742 0.37066758 0.3155958
v 0.13865358 0.4317754 0.35030973
v 0.058276385 0.4392205 0.3391245
v 0.030343294 0.45998672 0.38773924
v -0.3880409 0.1384954 0.3449043
v -0.5170243 0.07418141 1.1894103
v -0.8116745 0.054498244 1.1341733
v -0.8129394 0.060258668 1.1998622
v 0.508833 0.3189133 0.32736987
v 0.31756452 0.3854954 0.37424916
v 0.2614452 0.4029843 0.35290974
v -0.00014171004 0.4478109 0.36354887
v -0.2039817 0.1380911 0.3105232
v -0.22499359 0.1504203 0.36603713
v -0.22224802 0.12434879 0.3083002
v -0.2836938 0.1303054 0.32491422
v -0.36669618 0.1548052 0.38238198
v -0.521235 0.05388191 1.3088022
v -0.36468768 0.09348108 1.6139514
v -0.8035104 -0.17280911 0.472511
v -0.09007701 0.2906032 0.8911773
v -0.1471847 0.2872713 0.8581625
v -0.4325841 0.02807561 1.5166723
v -0.21121618 0.28527448 0.8144331
v 0.4721782 0.3355325 0.3738137
v -0.2868281 0.2490682 0.91039133
v -0.4179747 0.1136017 0.2986256
v -0.483496 -0.004848579 1.4518024
v -0.3734848 0.2632135 0.84761167
v -0.44343 0.2617822 0.8766693
v 0.5206084 0.30635402 0.38327384
v 0.34504962 0.3855682 0.3883295
v 0.20715317 0.42911166 0.40624613
v 0.1075698 0.4467865 0.39899343
v -0.41145647 0.15011871 0.4092946
v -0.46181458 0.23125559 0.94689524
v -0.5000359 0.23939228 0.8951423
v -0.549133 0.23062901 0.8543899
v -0.5335954 0.22156408 0.9233264
v -0.1340625 0.22173569 1.8201573
v 0.515229 0.3225975 0.43904507
v 0.4071613 0.3688541 0.40671277
v 0.36753988 0.38244557 0.44552952
v -0.012199208 0.47027487 0.4283617
v -0.62736446 0.085899875 1.3157442
v -0.76736784 0.056247007 1.4187692
v -0.3192969 0.1473094 0.3682562
v -0.5115097 0.055533428 0.37195212
v -0.5892263 0.03949338 0.3839615
v -0.2663895 0.15788229 1.7053542
v -0.0392327 0.209647 1.7132063
v 0.30015844 0.404049 0.41738456
v 0.14138734 0.4478655 0.4706741
v 0.08141118 0.45545226 0.45096087
v -0.24160114 0.1813837 0.43559754
v -0.4835155 0.101961374 0.40727437
v -0.6116669 0.0709413 0.41941118
v -0.6125587 0.09187299 1.2276174
v -0.13181609 -0.7128434 0.037364572
v 0.47345608 0.3493593 0.4380675
v 0.3779906 0.39092162 0.476112
v 0.28895763 0.40622652 0.45419538
v 0.16110353 0.4340426 0.43302095
v -0.06643391 0.4556954 0.43985373
v -0.37494862 0.07559022 1.5776684
v -0.45334256 0.13829519 0.4360304
v -0.53695464 0.11130589 0.43102932
v -0.5224869 0.0098538995 1.4275184
v -0.8300513 0.03844714 1.2725583
v -0.5166629 0.061349105 1.2375883
v 0.44926828 0.3629911 0.4787718
v 0.30288765 0.4165589 0.487266
v 0.22827102 0.4287438 0.48261982
v 0.046346307 0.4736575 0.46777767
v -0.6576674 0.094221815 1.1126323
v -0.2771405 0.16050829 0.4010812
v 0.2424986 0.23805588 1.0364133
v -0.7689474 -0.254804 0.45318907
v 0.088387 0.47032818 0.51170087
v -0.015276298 0.4805007 0.48495072
v -0.081054986 0.4756738 0.5130918
v -0.12114832 0.44159952 0.49227875
v -0.6725472 0.08219059 1.3777933
v -0.24000597 0.2148847 0.4908514
v -0.32184356 0.19879329 0.47403336
v -0.4683991 0.17397499 0.480857
v -0.6523114 0.069252715 0.44627833
v 0.08523449 0.3647357 1.8934252
v -0.76013076 0.085044205 1.2153842
v -0.91726565 -0.5659135 1.0292504
v 0.3321669 0.4149066 0.5328921
v -0.6168305 0.7222695 1.6528213
v -0.358189 0.21309718 0.5208099
v -0.3765008 0.18780439 0.46976018
v -0.40503323 0.190121 0.5100303
v -0.5332808 0.1681445 0.47096527
v -0.7185178 0.077858716 0.4969679
v 0.54956067 0.30493048 0.53019977
v 0.4227691 0.3674514 0.52580446
v 0.2810912 0.4299125 0.5259032
v 0.18912582 0.4472636 0.5279482
v 0.12242763 0.46463943 0.53936607
v -0.044515908 0.48830587 0.569209
v -0.22379759 0.25143158 0.50428826
v -0.2820012 0.2082586 0.51389
v -0.6186907 0.1800889 0.51748323
v -0.6198361 0.1401334 0.4794075
v -0.091692924 0.1692846 1.6683562
v 0.5057702 0.33043128 0.51197517
v 0.42033422 0.3656197 0.5626023
v 0.36887628 0.38872072 0.5401591
v 0.026902199 0.48110914 0.5763004
v 0.010168582 0.48596722 0.5327257
v -0.44593382 0.1918903 0.51690596
v -0.5354505 0.20851989 0.52920395
v -0.6864962 0.122877195 0.50716895
v -0.7695363 0.06598036 1.0898043
v -0.27542713 0.2404154 0.96466625
v 0.3574636 0.39685482 0.60553086
v 0.14894958 0.46792972 0.58441114
v -0.120653 0.45887262 0.55745816
v -0.29068 0.11827649 1.6104283
v -0.63451576 0.19848129 0.5531513
v -0.6938958 0.1606396 0.5505942
v -0.7473752 0.10232927 0.53948045
v -0.6061627 0.7790886 1.8010733
v 0.6173167 0.25084528 0.5514209
v 0.469378 0.341402 0.59420395
v 0.10662327 0.47349763 0.59837437
v -0.083188295 0.4797545 0.5623618
v -0.2771184 0.22310719 0.5600426
v -0.35659128 0.2280758 0.5766046
v -0.4449185 0.2171597 0.5511376
v -0.6993233 0.09464796 1.2562953
v 0.58433306 0.27235252 0.5911815
v 0.3172338 0.4190198 0.599638
v 0.2827661 0.42805737 0.58132493
v 0.012253791 0.49014276 0.6221241
v -0.022114292 0.48786908 0.650442
v -0.81819797 0.03465673 1.3358684
v -0.1843296 0.1918656 1.7294822
v -0.2463344 0.23259199 0.5711992
v -0.41786814 0.23521411 0.59509456
v -0.49744368 0.23755148 0.5821309
v 0.526591 0.31158128 0.61009645
v 0.3777163 0.3807608 0.6519084
v 0.24252787 0.43363053 0.6679777
v 0.21570158 0.44589728 0.5932095
v 0.21286069 0.4536791 0.6452875
v 0.16108093 0.4678036 0.6432998
v -0.21321693 0.24914879 0.94215035
v -0.291278 0.248153 0.6269444
v -0.5662072 0.23261148 0.60393715
v -0.32462037 0.2515577 0.9185424
v 0.072761 0.48109478 0.6422442
v -0.08294651 0.47900957 0.6289023
v -0.1356186 0.4567991 0.6143943
v -0.2426996 0.27096668 0.60952055
v -0.3648216 0.24688551 0.632944
v -0.4400097 0.2584867 0.6369935
v -0.5033808 0.2583294 0.63203
v -0.92675185 -0.25997928 1.0145863
v -0.7180563 -0.6089682 1.0109982
v -0.21526441 0.181567 1.7752032
v -0.65049404 -0.2997684 0.3751878
v 0.6179108 0.2412565 0.63256836
v 0.42932892 0.357106 0.69121647
v -0.6897098 0.1737514 0.8705983
v -0.61145765 0.22654569 0.6291311
v -0.6644794 0.20339659 0.6087432
v -0.3763643 0.08704464 1.6912754
v 0.37087047 0.3819113 0.7111118
v 0.31374824 0.4075122 0.6754813
v 0.17374955 0.454723 0.69820404
v -0.13212031 0.4534321 0.66563773
v -0.25678122 0.2742206 0.6760819
v -0.28587133 0.2656588 0.6749964
v -0.53138673 0.25500792 0.6883292
v -0.5951504 0.22993091 0.69009984
v -0.7097259 0.09245746 1.1879153
v 0.41083378 0.2776877 0.85450304
v -0.0567047 0.26643232 1.8409183
v 0.025698394 0.47885227 0.7017257
v -0.084302425 0.4490836 0.73005533
v -0.45233107 0.261543 0.6644585
v -0.68484 0.20430921 0.6805059
v -0.7035106 0.1301858 0.9650953
v 0.6103019 0.24603528 0.68866324
v -0.50410235 0.171128 1.0707723
v 0.11313757 0.45590857 0.7248826
v -0.83726895 -0.007791129 1.0236343
v -0.025625601 0.46162212 0.7348653
v -0.0558922 0.47276828 0.69261134
v -0.117115706 0.43679988 0.7225361
v -0.41838938 0.2643536 0.74648476
v -0.24796069 0.3817176 0.01905927
v -0.21272421 0.36711472 0.0038557947
v -0.11911899 0.3229043 0.002302289
v 0.0010855049 0.3008602 0.003922105
v 0.14888081 0.27453518 0.006748289
v 0.30270955 0.2518437 0.012780309
v 0.39976692 0.2226977 0.020411313
v -0.333645 0.3351178 0.011429608
v -0.2137213 0.3280822 0.0034956932
v 0.30007052 0.2246646 0.011126697
v -0.35704237 0.2656172 0.004599422
v -0.08931652 0.2825614 0.004174322
v 0.068151206 0.2544359 0.0076114833
v 0.19188468 0.2320343 0.009671986
v 0.24093407 0.19663268 0.014947385
v -0.13669631 0.25832808 0.0078026056
v 0.0445887 0.22803809 0.013672113
v 0.34306648 0.1812433 0.011664897
v 0.023653492 0.2146312 0.0333879
v 0.1878964 0.18781611 0.033711612
v 0.24772058 0.172922 0.040320814
v -0.23917529 0.24733791 0.0068288743
v -0.120568484 0.2329976 0.033913106
v 0.32597578 0.14516309 0.03035891
v 0.3545698 0.1271477 0.01862511
v 0.40113318 0.16917409 0.015255481
v -0.2622035 0.1758314 0.030816495
v 0.18627764 0.164797 0.06628573
v -0.29485172 0.2876925 0.0040079057
v -0.31577653 0.1366975 0.009049296
v -0.1922544 0.23038459 0.029671192
v -0.042708904 0.20460041 0.06180978
v 0.073941395 0.1841332 0.06474519
v 0.27629238 0.14918008 0.05713892
v 0.4247295 0.019296452 0.0143818855
v -0.40392298 0.1811679 0.010743201
v -0.3993678 0.1471898 0.0040664077
v -0.21588191 0.1907866 0.055472285
v -0.14365649 0.2044652 0.0662272
v 0.1078281 0.1497404 0.07434827
v 0.30233124 0.11287369 0.063118905
v 0.31480423 0.01988184 0.017540902
v 0.3739736 0.05817269 0.013331503
v 0.4838054 0.02987897 0.029350102
v -0.03234142 0.1653638 0.07088506
v 0.20360586 0.11568819 0.076269686
v 0.26800695 0.08580549 0.0742898
v 0.31618366 0.068628594 0.044505507
v 0.4779983 0.0054103713 0.019943297
v -0.36734617 0.1435875 0.0037778318
v -0.1813994 0.1633904 0.06928867
v -0.45108438 0.1378025 0.005449593
v -0.09042671 0.1249429 0.068585396
v 0.2700681 0.03008255 0.06368309
v 0.34254402 -0.0415806 0.011995137
v 0.5965388 -0.06559017 0.032371283
v -0.5091346 0.099350065 0.0028600097
v -0.035392493 0.039794855 0.06753892
v 0.4311385 -0.049103178 0.014103711
v 0.49563408 -0.092647195 0.017083287
v -0.29730088 0.076147005 0.037233293
v 0.033140898 0.03666952 0.069395304
v 0.24865398 -0.0494898 0.0264979
v 0.40059888 -0.069812566 0.031120718
v 0.4176094 -0.06688705 0.038087368
v 0.5272033 -0.07112089 0.01769039
v 0.6392126 -0.11059019 0.032579303
v -0.6098937 0.08481528 0.006071031
v -0.61705667 0.047359165 0.0032837987
v -0.2764528 0.07573348 0.06771308
v 0.09581156 0.07803331 0.072511375
v 0.26999414 -0.08395085 0.011573881
v 0.34099337 -0.07812763 0.035366505
v 0.60658 -0.1471241 0.021712601
v -0.46707827 0.00428275 0.002443999
v -0.31811 0.00517104 0.007091522
v 0.17797649 0.016862072 0.07327193
v 0.29926533 -0.10441065 0.023714602
v 0.4900857 -0.13347371 0.03415361
v 0.681291 -0.1468719 0.04181713
v -0.6536049 0.060924795 0.0068926215
v 0.20786479 -0.08130795 0.0534859
v 0.25935298 -0.1721786 0.010600179
v 0.2823366 -0.13843593 0.008933604
v 0.54511344 -0.1624338 0.016081005
v 0.58564484 -0.22748709 0.022574484
v -0.65301603 -0.3740777 0.41386926
v -0.3845296 -0.007643449 0.0038388968
v -0.21271119 -0.012442399 0.06351152
v -0.19855163 -0.013795829 0.06517547
v -0.063592106 -0.077901036 0.06442928
v 0.52951336 -0.2498796 0.01958841
v 0.7737613 -0.13538808 0.08667362
v -0.5434403 -0.016453288 0.0019799173
v -0.26667422 -0.07871211 0.0042003095
v 0.082900986 -0.10025051 0.06732309
v 0.20034702 -0.050426446 0.06943947
v 0.21440315 -0.12665936 0.027983785
v 0.2000973 -0.1875498 0.037481576
v 0.5077007 -0.20100611 0.0425542
v -0.7040905 -0.009081379 0.0134654045
v -0.67944753 -0.016701069 0.005300075
v -0.62431985 -0.055959247 0.002548039
v -0.39336312 -0.06561032 0.002496004
v 0.17037305 -0.1888784 0.0632177
v 0.6535984 -0.230741 0.040786207
v 0.7026578 -0.21187541 0.0546
v -0.37537372 -0.10286039 0.005118102
v -0.28389788 -0.13042845 0.00815621
v 0.09482966 -0.2216891 0.065555125
v 0.49791038 -0.25547218 0.03246358
v 0.8018075 -0.2273779 0.102974296
v -0.72374904 -0.03946238 0.03829801
v -0.7110624 -0.07390445 0.014860302
v -0.1428687 -0.122458935 0.061789006
v 0.0031797886 -0.15207541 0.064695805
v 0.32376957 -0.25501978 0.019929022
v 0.5250765 -0.3486601 0.03354913
v 0.7548099 -0.28904468 0.09457761
v -0.4491487 -0.12369822 0.0043654144
v -0.3939429 -0.12431507 0.029114783
v 0.20007844 -0.24003601 0.047969997
v 0.23033582 -0.22252111 0.019415528
v 0.25596726 -0.2643174 0.017243207
v 0.3910426 -0.2977781 0.01390481
v 0.4046081 -0.2999816 0.014502823
v 0.4457843 -0.3058017 0.017373174
v 0.6067035 -0.2776086 0.038205683
v -0.29147297 -0.1595387 0.0392119
v -0.17557931 -0.21045321 0.00891152
v 0.47946858 -0.3835144 0.024216413
v 0.54327 -0.4018548 0.06233111
v 0.6550063 -0.31734312 0.064922005
v 0.8556171 -0.28784353 0.14642811
v -0.538525 -0.47492388 0.43286616
v -0.6568874 -0.1327162 0.0069745183
v -0.23258689 -0.1809718 0.0067444146
v -0.16902861 -0.23186031 0.012702286
v 0.24054393 -0.2946048 0.039013
v 0.80519396 -0.3509845 0.14710152
v -0.67698145 -0.5263298 0.9998573
v -0.5314348 -0.2017939 0.0040664077
v -0.47332352 -0.201101 0.0035801828
v -0.24583781 -0.2208077 0.033562094
v -0.1567865 -0.24482 0.027128398
v -0.12713349 -0.2234389 0.057625115
v 0.20414628 -0.284821 0.062892705
v 0.67403305 -0.3698852 0.099632025
v -0.68749845 -0.1466496 0.015195668
v -0.48383403 -0.225294 0.0
v 0.27667484 -0.3410889 0.04950273
v 0.30500013 -0.33537668 0.024632424
v 0.32739592 -0.33201492 0.018704385
v 0.7238933 -0.3439658 0.10040033
v 0.18403852 -0.3459769 0.0704236
v 0.26030198 -0.3559544 0.070180476
v 0.33543444 -0.38953862 0.046553016
v 0.389974 -0.430473 0.019836724
v 0.5082038 -0.449583 0.048664182
v 0.5947422 -0.3733224 0.076051295
v -0.9226426 -0.5021459 1.0205534
v -0.013903514 -0.2824316 0.063622
v 0.34954917 -0.41996253 0.048591375
v 0.76826876 -0.39680168 0.1576367
v -0.878024 -0.62394553 1.0474113
v -0.7409311 -0.5290936 0.9833993
v -0.4477499 -0.46462402 0.40208083
v -0.58469325 -0.23897782 0.00920397
v -0.2582333 -0.318686 0.025925905
v 0.024320394 -0.4011879 0.06933683
v -0.8332155 -0.5121026 0.9927074
v -0.94928074 -0.4462589 1.0473073
v -0.6871085 -0.4239327 0.46044052
v -0.8014344 -0.5720027 0.99551535
v -0.76730025 -0.50194436 0.9703083
v -0.5062148 -0.3660372 0.010636598
v -0.2792218 -0.3362178 0.027548313
v -0.24026993 -0.31495368 0.0113087
v -0.116962284 -0.2790295 0.05921632
v 0.30819657 -0.4034005 0.07402718
v 0.41925 -0.5000737 0.020052493
v 0.46707958 -0.49561992 0.030299127
v -0.60923207 -0.4330353 0.42559272
v 0.7332754 -0.4091712 0.1511848
v -0.825413 -0.2272089 0.49910247
v -0.4859972 -0.49943283 0.44216645
v -0.7320196 0.35446048 1.4971073
v -0.548587 -0.5548323 0.5257408
v -0.5553561 0.01160994 0.3604952
v -0.5978804 -0.3248428 0.016729712
v -0.3262285 -0.36949 0.004659176
v 0.33516365 -0.45998043 0.056283534
v 0.32928714 -0.4907163 0.020515293
v -0.9490377 -0.5753008 1.0617243
v -0.63367724 -0.5284553 0.53456646
v -0.62196165 -0.02220176 0.3693729
v 0.28665897 -0.4517956 0.080563605
v 0.4816669 -0.5305236 0.048402905
v -0.6526702 0.0034720711 0.38977897
v -0.6198244 -0.07431616 0.3554408
v -0.52544177 -0.08319855 0.3079778
v -0.7483034 -0.42786 0.5086523
v -0.587834 -0.4337529 0.01343292
v -0.3909074 -0.4159403 0.01474461
v -0.0521729 -0.3815592 0.06618431
v 0.4321252 -0.555923 0.030227602
v -0.6797206 -0.60843134 1.0236213
v -0.71513784 0.4310435 1.4989403
v -0.68559146 -0.06575566 0.3869437
v -0.8848177 -0.23327601 0.58606863
v -0.9670415 -0.3404415 1.0735283
v -0.9363483 -0.3296268 1.0436673
v -0.7085234 -0.6620485 1.0392734
v -0.4949867 -0.52403665 0.48064125
v -0.78530264 -0.47086787 0.9600383
v -0.5777304 -0.12446158 0.33406746
v -0.63809985 -0.4016845 0.02229762
v -0.53414917 -0.49072543 0.014426112
v -0.45560712 -0.4695276 0.01508522
v -0.23443681 -0.4687866 0.018349499
v -0.1843881 -0.4403413 0.06346601
v 0.15391597 -0.49639863 0.07871503
v 0.3773276 -0.5733378 0.02427879
v -0.75526357 -0.6793645 1.0566282
v -0.82768536 -0.6256342 1.0435114
v -0.7449858 -0.3737072 0.47158933
v -0.8086208 -0.3994953 0.55938214
v -0.6304493 -0.4579459 0.019098312
v -0.2191202 -0.4991533 0.0134446025
v 0.28252703 -0.58427733 0.019563705
v 0.3659292 -0.5997499 0.03774029
v -0.5689879 -0.5217694 0.49197978
v -0.9383205 -0.389068 1.0555884
v -0.67265254 -0.1565214 0.36114776
v -0.85683 -0.6500105 1.0813413
v -0.32645863 -0.45087 0.0150514245
v 0.0716456 -0.5242433 0.0808509
v 0.18167253 -0.6122182 0.020480186
v -0.62088263 -0.1890721 0.34026462
v -0.54314524 -0.1938535 0.3183908
v -0.08474702 -0.5194229 0.076174796
v -0.0176657 -0.5315077 0.07816124
v 0.004284784 -0.5871711 0.019537717
v 0.26119158 -0.6175898 0.032806814
v -0.8895718 -0.41468972 1.0230623
v -0.89496815 -0.4643224 1.0140923
v -0.7504822 -0.08030929 0.43382037
v -0.706056 -0.13366415 0.38274086
v -0.5765292 -0.23428221 0.33181852
v -0.39144695 -0.5358861 0.017124891
v -0.31147093 -0.54567254 0.017657906
v -0.12378731 -0.5844255 0.017525315
v 0.17702541 -0.6416203 0.037960023
v -0.82081354 -0.46282738 0.9861423
v -0.5578456 -0.3935296 0.38812017
v -0.44688278 -0.5054479 0.45177603
v -0.43323284 -0.532523 0.48910165
v -0.7278583 -0.2169467 0.40416998
v -0.6722326 -0.21887329 0.36276108
v -0.29304862 -0.5716738 0.027693897
v 0.11271532 -0.6368012 0.0220792
v -0.6406361 -0.4814577 0.4796064
v -0.6734208 0.5013969 1.5159702
v -0.7139743 -0.3437474 0.43827033
v -0.75808203 -0.1524433 0.4322123
v -0.62849283 -0.5363463 0.0342654
v -0.26126882 -0.6015946 0.0339911
v 0.095198475 -0.6619146 0.03468007
v -0.888875 -0.64508224 1.0888553
v -0.6053502 -0.3526888 0.38324517
v -0.6478199 -0.2545856 0.3599934
v -0.5377658 -0.3131493 0.34677893
v -0.61634564 -0.5314947 0.021875113
v -0.47612238 -0.5754412 0.016251296
v -0.015996516 -0.6597124 0.020151317
v 0.0696878 -0.67285407 0.056301683
v -0.7587359 -0.62646365 1.0170302
v -0.7847762 -0.08505727 0.47471452
v -0.72800523 -0.1764283 0.4039451
v -0.79587305 -0.2964573 0.48990887
v -0.5780112 -0.5652037 0.017946482
v -0.4888351 -0.6026398 0.022066236
v -0.3763929 -0.5850053 0.024662286
v -0.22140822 -0.6297227 0.02416572
v -0.15413582 -0.67641217 0.023055494
v 0.019429788 -0.67100936 0.025174469
v -0.63027376 0.6289867 1.5939834
v -0.64566976 0.39126092 1.5002663
v -0.588835 0.7397467 1.6974504
v -0.60416067 0.3996121 1.5489123
v -0.35041112 0.008593161 1.4875133
v -0.63564676 0.22740108 1.4599793
v -0.6488651 0.61899227 1.5636153
v 0.08213659 0.2634293 1.7338502
v -0.5972655 0.75290793 1.6986463
v -0.69695866 0.09069803 1.4037673
v -0.58148223 0.062285107 1.3918593
v 0.0695305 0.329715 1.8056493
v -0.61009127 0.7620768 1.7219813
v -0.14843142 0.12372349 1.6155243
v -0.021713912 0.1259335 1.6427722
v -0.64869606 0.5691932 1.5477552
v -0.4302402 0.0049284603 1.4741882
v -0.19043183 0.08057299 1.5768363
v -0.6584071 0.6514546 1.5829723
v 0.076627195 0.3609319 1.8613803
v -0.7186738 0.49118668 1.5124993
v -0.6165874 0.31742612 1.5010854
v -0.6013228 0.07811001 1.4313793
v -0.47115117 -0.013106959 1.3989964
v -0.29208916 0.06679495 1.5436473
v -0.6239467 0.45114148 1.5443493
v -0.6187909 0.6860632 1.6254432
v -0.64190227 0.10262619 1.4153504
v -0.6623552 0.2840629 1.4656473
v -0.6230978 0.5067724 1.5702844
v -0.09703982 0.08279209 1.6063724
v 0.014011383 0.1938858 1.6875703
v -0.7031271 0.17454049 1.4395692
v -0.35803038 0.05464774 1.5317652
v -0.5992454 0.1510118 1.4772953
v -0.61040723 0.1288741 1.4427283
v -0.66837686 0.17552459 1.4377884
v -0.43511522 -0.0389689 1.4307293
v -0.1525498 0.172948 1.6740763
v -0.63712615 0.14434019 1.4336543
v -0.5119166 0.03360659 1.3576432
v -0.6768567 0.3543487 1.4810002
v -0.5741372 0.7716422 1.7772443
v 0.037771493 0.2653936 1.7493072
v -0.68395597 0.5597305 1.5328052
v -0.4971044 -0.2401517 0.31199735
f 1578 1069 1647
f 1058 909 939
f 421 1176 238
f 1042 1055 1101
f 238 1059 1126
f 30 1261 1254
f 1 1065 1071
f 1120 1037 1130
f 2381 1585 1570
f 2502 2473 2434
f 1654 1646 1632
f 1144 1166 669
f 305 1202 1440
f 1 1071 1090
f 1570 1584 1555
f 1184 1174 404
f 65 432 12
f 1032 1085 574
f 1789 2207 2223
f 1154 1118 1184
f 1086 1154 1141
f 99 1117 342
f 1174 419 404
f 2000 1998 489
f 1118 1174 1184
f 1196 403 136
f 717 1490 1495
f 402 1207 1804
f 1398 891 2272
f 1002 804 1100
f 1596 1595 2381
f 420 1207 208
f 208 1207 402
f 1935 1925 1455
f 238 1176 1059
f 1040 348 1150
f 1537 2051 1957
f 1189 939 1124
f 1207 1823 1804
f 1109 1381 1300
f 1182 383 384
f 1085 1086 1141
f 132 1040 1046
f 1495 1188 220
f 420 261 1207
f 1065 261 420
f 1101 1055 1133
f 1054 421 403
f 2 182 1109
f 1207 320 1181
f 545 1570 1561
f 432 35 342
f 574 1141 1024
f 432 342 12
f 1081 1547 1489
f 1181 320 1805
f 1507 1516 1683
f 1117 1047 357
f 1561 1570 1555
f 1090 1196 1206
f 1047 1203 1051
f 1165 202 1121
f 341 301 1099
f 1174 240 419
f 921 833 922
f 1121 1080 385
f 21 1183 815
f 35 99 342
f 1083 398 262
f 94 1317 106
f 292 1317 94
f 292 95 1317
f 1033 940 1039
f 433 1300 1306
f 212 471 21
f 1131 1037 1120
f 688 833 921
f 342 1117 357
f 94 106 271
f 386 227 1375
f 1044 1053 1130
f 419 240 219
f 32 1255 1244
f 1081 1489 1557
f 2120 2109 2062
f 2110 430 2034
f 23 315 1111
f 291 94 271
f 292 94 291
f 50 386 95
f 734 665 964
f 1611 1616 1585
f 1084 402 445
f 574 1085 1141
f 341 1653 1654
f 220 1188 1640
f 12 342 69
f 417 261 328
f 50 95 292
f 204 227 386
f 204 386 50
f 1471 1311 1276
f 1206 1196 136
f 1033 1055 1042
f 1037 1044 1130
f 320 417 1180
f 1121 202 1080
f 203 271 325
f 76 292 291
f 237 50 292
f 2159 1696 1767
f 929 850 583
f 1585 1616 1584
f 1495 1490 1188
f 1557 1489 1660
f 1078 1069 1494
f 1992 1971 1972
f 1226 2000 183
f 429 203 325
f 237 292 76
f 1143 1152 227
f 1489 1488 1412
f 1646 1653 1638
f 1869 2468 1947
f 306 291 203
f 76 291 306
f 248 50 237
f 1143 227 204
f 14 429 2395
f 1502 881 2500
f 1 1090 202
f 1653 1099 1652
f 2117 1863 2496
f 248 204 50
f 792 994 160
f 884 888 857
f 2117 2496 544
f 202 1090 1206
f 879 2492 2463
f 306 203 429
f 188 418 498
f 884 857 865
f 998 1014 994
f 884 897 888
f 1795 948 1802
f 1071 208 1035
f 1066 1065 1
f 435 1377 377
f 304 429 14
f 306 429 304
f 74 73 60
f 592 204 248
f 829 846 2264
f 897 912 906
f 1004 991 992
f 1422 1421 1233
f 10 303 980
f 922 909 1058
f 2449 2418 2436
f 394 435 377
f 475 446 435
f 475 474 446
f 361 336 337
f 235 372 338
f 624 148 129
f 596 812 306
f 992 1019 1726
f 1514 1511 945
f 1627 1628 1069
f 1823 1181 1812
f 169 1165 1121
f 447 475 435
f 2458 901 2487
f 59 46 42
f 401 7 187
f 970 797 1010
f 1513 220 1640
f 2491 2462 2474
f 1014 594 307
f 398 1513 1640
f 307 594 1026
f 2381 1570 545
f 403 421 238
f 402 127 445
f 1611 1631 1616
f 1805 1180 1148
f 447 435 394
f 2341 2413 2376
f 75 74 60
f 47 42 541
f 47 59 42
f 541 42 28
f 931 1103 917
f 897 906 883
f 2068 779 2484
f 888 883 857
f 328 261 1065
f 349 363 1307
f 394 377 363
f 747 464 444
f 338 362 323
f 92 116 74
f 97 592 634
f 982 1027 1004
f 1020 982 1004
f 1035 1084 1054
f 208 402 1084
f 421 1119 1176
f 1207 1181 1823
f 1179 1187 1160
f 296 1343 263
f 1307 1298 296
f 349 1307 296
f 405 363 349
f 405 394 363
f 447 394 405
f 372 384 362
f 372 362 338
f 983 1004 987
f 134 139 122
f 415 440 414
f 92 74 75
f 246 226 186
f 796 787 700
f 1176 1119 1059
f 122 114 91
f 624 129 116
f 641 558 631
f 1318 1487 1311
f 1162 1170 100
f 341 1099 1653
f 1316 1983 273
f 277 296 263
f 358 349 296
f 436 447 405
f 570 109 554
f 504 1385 2501
f 115 122 91
f 2460 779 2068
f 43 777 163
f 378 405 349
f 358 378 349
f 448 447 436
f 448 476 447
f 77 108 78
f 75 60 47
f 1764 2481 1795
f 714 1512 717
f 717 1501 1490
f 168 238 1126
f 1878 1866 826
f 2367 2025 2360
f 278 263 251
f 278 277 263
f 318 296 277
f 318 358 296
f 350 358 318
f 436 405 378
f 1182 384 372
f 454 440 415
f 1004 992 987
f 493 476 448
f 788 338 323
f 136 403 238
f 1565 1503 1474
f 297 277 278
f 318 277 297
f 350 378 358
f 388 436 378
f 500 476 493
f 73 105 60
f 323 337 312
f 953 1573 2358
f 142 161 119
f 454 443 440
f 1871 1405 1862
f 319 318 297
f 560 47 541
f 170 1323 111
f 357 1047 1050
f 1119 98 1059
f 1877 1900 1838
f 230 251 2359
f 364 378 350
f 449 448 436
f 493 448 449
f 186 226 185
f 469 479 443
f 874 165 2480
f 464 463 444
f 105 91 64
f 440 1129 1182
f 1958 1651 2502
f 2034 191 1238
f 279 278 251
f 279 297 278
f 388 378 364
f 483 493 449
f 148 139 134
f 268 259 244
f 942 930 910
f 105 115 91
f 24 30 18
f 1059 1132 487
f 2021 1869 1947
f 2494 2463 2497
f 230 2359 2385
f 280 251 230
f 280 279 251
f 308 297 279
f 308 319 297
f 364 318 319
f 364 350 318
f 395 436 388
f 449 436 395
f 472 500 493
f 129 134 122
f 125 142 124
f 400 393 373
f 557 30 24
f 2264 2278 2251
f 1269 1261 30
f 1877 1730 1862
f 252 280 230
f 343 364 319
f 388 364 343
f 64 91 63
f 393 416 399
f 444 463 416
f 162 189 142
f 373 326 768
f 189 661 177
f 199 661 189
f 887 864 847
f 747 444 533
f 1744 1022 1418
f 1170 524 729
f 121 1342 128
f 1236 1244 26
f 281 279 280
f 308 279 281
f 343 319 308
f 365 388 343
f 395 388 365
f 406 395 365
f 406 449 395
f 477 493 483
f 491 472 477
f 472 493 477
f 109 77 78
f 174 196 166
f 150 814 481
f 64 63 59
f 373 393 326
f 260 43 643
f 253 252 230
f 441 483 449
f 477 483 441
f 463 415 416
f 246 245 226
f 464 470 454
f 362 337 323
f 1283 52 37
f 281 252 253
f 281 280 252
f 309 308 281
f 330 343 308
f 366 365 343
f 441 449 406
f 814 15 464
f 906 887 883
f 362 371 337
f 498 290 479
f 247 746 1003
f 37 557 25
f 930 669 640
f 2499 2459 2486
f 309 330 308
f 366 343 330
f 477 441 437
f 498 418 290
f 124 119 108
f 124 108 77
f 589 125 109
f 570 589 109
f 162 142 125
f 1034 1045 433
f 261 320 1207
f 2004 2474 2495
f 1215 1228 2285
f 396 406 365
f 422 406 396
f 437 441 422
f 422 441 406
f 60 59 47
f 78 66 51
f 383 361 371
f 215 214 196
f 463 454 415
f 27 41 535
f 53 1283 37
f 84 1299 1283
f 320 1180 1805
f 254 253 222
f 281 253 254
f 366 330 309
f 396 365 366
f 456 477 437
f 491 477 484
f 2493 2480 2485
f 188 187 418
f 85 1283 53
f 85 84 1283
f 1065 420 1071
f 264 281 254
f 298 309 281
f 368 366 367
f 368 396 366
f 1639 1564 1139
f 560 48 47
f 212 82 471
f 38 37 25
f 202 1206 1080
f 264 298 281
f 331 309 298
f 331 366 309
f 367 366 331
f 422 396 368
f 456 437 422
f 1192 313 491
f 1710 1699 2064
f 443 479 462
f 384 371 362
f 2476 2464 2502
f 384 383 371
f 732 212 21
f 1571 1629 1627
f 39 53 38
f 53 37 38
f 85 53 39
f 1173 1184 404
f 2142 1674 1006
f 255 254 201
f 255 264 254
f 407 422 368
f 450 456 422
f 484 456 450
f 484 477 456
f 314 1192 491
f 2027 2501 2489
f 2471 2488 2475
f 492 732 551
f 481 814 464
f 1081 1494 1547
f 231 255 201
f 450 422 407
f 494 491 484
f 327 491 494
f 327 314 491
f 797 995 876
f 847 856 829
f 143 162 125
f 148 134 129
f 1564 1571 1627
f 261 417 320
f 328 1065 1066
f 156 201 170
f 231 201 156
f 282 255 231
f 282 264 255
f 485 484 450
f 494 484 485
f 2486 2479 2463
f 185 167 159
f 68 212 492
f 492 212 732
f 68 82 212
f 1296 1311 1471
f 156 111 101
f 332 264 282
f 332 298 264
f 332 331 298
f 367 331 332
f 450 407 423
f 485 450 423
f 1443 804 1002
f 779 946 2484
f 443 462 689
f 440 689 1129
f 167 174 166
f 39 38 31
f 112 145 101
f 145 156 101
f 256 231 156
f 423 368 332
f 368 367 332
f 423 407 368
f 779 920 946
f 1261 1449 1432
f 461 478 453
f 464 15 470
f 54 39 31
f 85 39 54
f 101 85 86
f 210 156 145
f 283 332 282
f 369 332 283
f 369 423 332
f 408 485 423
f 876 965 854
f 78 108 66
f 440 443 689
f 961 374 2465
f 519 979 929
f 86 85 54
f 241 256 156
f 256 282 231
f 283 282 256
f 389 423 369
f 408 423 389
f 457 485 408
f 49 485 457
f 49 494 485
f 135 327 494
f 175 83 314
f 1167 1140 1483
f 215 196 174
f 16 68 697
f 16 1038 82
f 117 141 140
f 1654 1653 1646
f 54 31 1234
f 112 101 86
f 241 156 210
f 923 917 911
f 34 16 697
f 193 210 145
f 265 283 256
f 310 283 265
f 310 369 283
f 344 369 310
f 370 369 344
f 370 389 369
f 409 408 389
f 466 408 409
f 466 457 408
f 49 457 466
f 49 135 494
f 225 215 174
f 1014 766 602
f 2215 826 2220
f 1078 1494 1081
f 70 86 1273
f 120 112 86
f 146 145 112
f 193 145 146
f 265 256 241
f 223 265 241
f 486 49 466
f 175 327 135
f 122 115 105
f 15 681 480
f 225 234 215
f 731 34 697
f 1273 86 54
f 120 86 70
f 241 210 193
f 299 310 265
f 333 344 310
f 370 344 351
f 424 466 409
f 175 135 49
f 215 234 214
f 48 75 47
f 9 1038 34
f 1038 16 34
f 291 271 203
f 558 754 9
f 397 1120 1195
f 146 112 120
f 194 193 146
f 266 265 223
f 266 299 265
f 333 310 299
f 351 344 333
f 383 392 382
f 416 415 399
f 333 299 266
f 352 370 351
f 486 466 424
f 487 175 49
f 7 117 187
f 414 440 1182
f 46 41 42
f 289 497 290
f 2464 2473 2502
f 399 414 372
f 1585 1584 1570
f 1066 1 1165
f 1165 1 202
f 102 120 70
f 157 146 120
f 223 193 194
f 223 241 193
f 379 370 352
f 379 389 370
f 410 409 389
f 1409 1958 2478
f 806 945 1002
f 157 194 146
f 267 266 223
f 333 266 267
f 410 389 379
f 438 409 410
f 438 424 409
f 190 205 143
f 371 361 337
f 830 826 2215
f 1646 1638 1631
f 102 157 120
f 195 194 157
f 195 223 194
f 211 223 195
f 267 223 211
f 300 333 267
f 334 351 300
f 351 333 300
f 334 352 351
f 411 438 410
f 486 424 438
f 487 49 486
f 594 989 875
f 108 581 66
f 245 244 225
f 312 336 335
f 754 107 151
f 1386 300 274
f 379 352 334
f 1729 1096 923
f 245 268 244
f 464 454 463
f 415 414 399
f 15 480 470
f 1647 1069 1078
f 922 833 909
f 417 328 387
f 133 157 102
f 1314 133 102
f 195 157 133
f 1148 1179 1160
f 182 1046 1167
f 411 410 379
f 229 792 339
f 391 7 668
f 226 174 185
f 290 497 461
f 504 2501 2027
f 1196 1054 403
f 1019 752 728
f 2483 2461 2459
f 55 1291 1264
f 1356 195 133
f 211 195 1356
f 412 438 411
f 4 486 438
f 458 4 438
f 4 487 486
f 1720 1572 1771
f 275 268 245
f 2059 1869 2021
f 399 372 235
f 105 64 60
f 879 836 2492
f 1315 133 1314
f 1382 1356 1331
f 1310 926 1128
f 7 1121 117
f 161 611 119
f 380 379 334
f 411 379 380
f 467 4 458
f 495 487 4
f 1126 487 495
f 533 416 400
f 469 498 479
f 74 116 73
f 497 478 461
f 416 393 400
f 61 1291 55
f 505 1999 2474
f 1999 2491 2474
f 36 199 189
f 1164 1165 169
f 1179 387 249
f 390 411 380
f 412 411 390
f 458 438 412
f 168 1126 495
f 480 469 470
f 116 122 105
f 187 140 418
f 185 174 167
f 167 166 148
f 470 469 443
f 40 55 32
f 71 1291 61
f 71 103 1291
f 1154 1184 1173
f 514 97 634
f 425 458 412
f 923 931 917
f 2489 853 2472
f 641 567 754
f 1163 44 567
f 470 443 454
f 40 32 1249
f 33 40 1249
f 56 55 40
f 56 61 55
f 439 451 1265
f 1180 417 1179
f 301 1077 1099
f 1189 1058 939
f 221 1132 1059
f 1075 598 1074
f 425 412 426
f 186 185 650
f 244 259 234
f 245 225 226
f 1030 1033 1042
f 836 247 2492
f 169 1121 7
f 1322 1482 1462
f 467 458 425
f 496 4 467
f 2480 1751 2468
f 418 140 290
f 326 789 762
f 177 161 142
f 2480 165 1751
f 87 103 71
f 104 103 87
f 1180 1179 1148
f 417 387 1179
f 2081 2060 2031
f 1141 1154 1173
f 181 131 197
f 442 425 426
f 144 143 614
f 797 876 1010
f 45 56 40
f 61 56 45
f 87 71 61
f 1437 1590 1563
f 1121 385 117
f 1137 1148 1160
f 1459 1439 1449
f 2462 929 1028
f 459 425 442
f 459 467 425
f 168 495 4
f 496 168 4
f 1403 1444 1763
f 187 117 140
f 244 234 225
f 740 269 246
f 414 1182 372
f 45 40 547
f 62 61 45
f 62 87 61
f 88 104 87
f 1054 1084 517
f 387 328 1064
f 2497 2485 2467
f 302 286 1363
f 205 189 162
f 140 289 290
f 234 224 214
f 393 399 809
f 315 1131 397
f 321 353 302
f 1164 169 391
f 459 442 427
f 217 496 467
f 217 168 496
f 2074 978 969
f 383 382 361
f 269 276 245
f 305 1440 11
f 88 87 62
f 328 1066 1064
f 1066 1165 1164
f 287 302 242
f 302 1363 242
f 321 302 287
f 1179 249 1187
f 1020 1004 983
f 481 464 747
f 788 323 276
f 269 245 246
f 89 1325 88
f 172 242 171
f 360 353 321
f 360 1354 353
f 1064 1164 1057
f 2184 2188 2183
f 460 459 451
f 460 467 459
f 149 168 217
f 136 168 149
f 129 122 116
f 109 124 77
f 159 167 148
f 42 41 28
f 88 62 57
f 62 45 57
f 1336 1325 89
f 72 1336 89
f 172 171 147
f 258 242 172
f 257 242 258
f 257 287 242
f 257 321 287
f 345 360 321
f 381 1354 360
f 1069 938 1655
f 387 473 249
f 270 217 467
f 130 136 149
f 851 847 829
f 983 987 975
f 189 177 142
f 72 89 88
f 258 172 184
f 288 321 257
f 451 459 1265
f 270 149 217
f 226 225 174
f 41 27 28
f 125 124 109
f 57 45 547
f 58 88 57
f 72 88 58
f 2484 2458 2476
f 184 172 147
f 213 258 184
f 243 257 258
f 288 257 243
f 345 321 288
f 391 169 7
f 468 460 451
f 488 460 468
f 270 467 460
f 488 270 460
f 1206 136 130
f 793 150 481
f 205 162 143
f 142 119 124
f 90 72 58
f 128 72 90
f 173 184 147
f 213 184 173
f 233 258 213
f 243 258 233
f 354 360 345
f 354 381 360
f 307 1026 991
f 268 312 259
f 1080 1206 130
f 116 105 73
f 148 166 139
f 275 312 268
f 401 187 188
f 2461 2479 2459
f 63 90 58
f 1064 1066 1164
f 387 1064 473
f 311 345 288
f 354 345 311
f 994 307 996
f 452 468 439
f 478 468 452
f 478 488 468
f 141 130 149
f 1563 1564 1639
f 57 547 41
f 2081 2107 2060
f 382 381 354
f 497 270 488
f 289 149 270
f 141 149 289
f 122 139 114
f 60 64 59
f 323 312 275
f 668 7 401
f 46 57 41
f 58 57 46
f 1269 1459 1345
f 158 1342 121
f 166 173 158
f 224 233 213
f 259 243 233
f 322 288 243
f 311 288 322
f 453 478 452
f 289 270 497
f 912 911 906
f 323 275 276
f 276 275 245
f 63 58 46
f 121 128 90
f 214 213 173
f 224 213 214
f 322 243 259
f 336 311 322
f 336 354 311
f 361 382 354
f 1043 439 1290
f 497 488 478
f 385 130 141
f 385 1080 130
f 190 143 144
f 41 547 535
f 166 158 121
f 335 336 322
f 361 354 336
f 1764 2004 2481
f 698 439 1043
f 140 141 289
f 1096 931 923
f 650 185 159
f 59 63 46
f 91 90 63
f 114 121 90
f 139 166 121
f 196 214 173
f 335 322 259
f 2502 2434 2478
f 337 336 312
f 91 114 90
f 139 121 114
f 196 173 166
f 234 233 224
f 259 233 234
f 312 335 259
f 916 1189 1124
f 542 541 530
f 479 290 462
f 783 276 269
f 813 567 641
f 783 788 276
f 82 1038 1333
f 816 701 703
f 137 603 672
f 635 624 625
f 2439 1973 2457
f 767 533 529
f 2480 2468 1869
f 662 190 639
f 720 719 711
f 630 639 614
f 161 654 638
f 781 991 982
f 1227 31 516
f 648 639 630
f 630 614 590
f 2098 544 1899
f 586 578 579
f 697 492 551
f 533 400 529
f 870 869 859
f 1732 924 914
f 1027 991 1004
f 801 591 603
f 676 651 636
f 876 949 965
f 1789 1859 2207
f 739 237 76
f 15 188 681
f 604 599 578
f 797 616 995
f 510 2035 1365
f 617 76 812
f 617 739 76
f 1468 93 1765
f 546 812 596
f 1305 1477 1457
f 760 197 150
f 773 765 671
f 609 604 586
f 591 700 632
f 476 2312 474
f 2027 2489 2084
f 582 590 571
f 1555 2449 1996
f 546 596 674
f 655 617 812
f 177 661 161
f 636 599 604
f 787 576 700
f 776 675 572
f 776 674 675
f 634 739 617
f 591 632 649
f 612 546 674
f 634 617 655
f 752 706 728
f 571 2311 2305
f 775 674 776
f 775 612 674
f 628 546 612
f 628 812 546
f 655 812 628
f 620 630 615
f 620 648 630
f 667 653 646
f 782 785 810
f 197 814 150
f 1517 2000 534
f 702 572 2378
f 748 776 572
f 613 634 655
f 911 917 905
f 679 662 648
f 727 771 713
f 807 799 750
f 639 190 144
f 679 200 662
f 748 572 702
f 775 776 748
f 718 655 628
f 658 645 626
f 778 790 791
f 811 628 612
f 514 634 613
f 1380 1756 1673
f 590 614 570
f 741 719 720
f 835 1074 795
f 639 144 614
f 811 612 775
f 735 655 718
f 735 613 655
f 798 338 788
f 652 676 636
f 590 555 571
f 730 687 528
f 690 702 2312
f 476 690 2312
f 718 628 811
f 721 778 727
f 748 702 690
f 686 613 735
f 2002 2127 1517
f 685 667 654
f 588 606 569
f 538 513 531
f 549 548 538
f 549 553 548
f 588 549 550
f 1903 869 870
f 691 775 748
f 600 775 691
f 811 775 600
f 563 718 811
f 736 718 563
f 736 735 718
f 647 735 736
f 647 686 735
f 745 613 686
f 745 514 613
f 606 605 569
f 654 667 638
f 857 847 851
f 588 569 549
f 691 748 690
f 680 514 745
f 2094 2127 2002
f 481 747 701
f 529 400 373
f 536 811 600
f 563 811 536
f 1152 1306 227
f 24 18 522
f 523 24 522
f 865 857 851
f 2031 2060 1540
f 701 747 767
f 618 652 609
f 652 636 609
f 573 22 710
f 730 642 699
f 1522 1518 2476
f 629 691 500
f 500 691 690
f 600 691 629
f 644 641 780
f 579 578 561
f 131 668 197
f 197 668 814
f 809 798 789
f 622 760 150
f 621 563 536
f 673 745 686
f 673 818 745
f 818 680 745
f 96 514 680
f 2462 1028 2495
f 583 575 1028
f 663 794 664
f 761 600 629
f 757 600 761
f 757 536 600
f 696 563 621
f 755 736 563
f 696 755 563
f 755 633 736
f 633 647 736
f 623 686 647
f 633 623 647
f 623 673 686
f 819 680 818
f 819 96 680
f 1677 1096 1729
f 1899 2471 2482
f 537 536 757
f 621 536 537
f 819 818 673
f 222 230 2428
f 25 24 523
f 25 557 24
f 38 25 19
f 710 22 272
f 663 759 794
f 1195 1120 878
f 696 621 537
f 633 755 696
f 822 2215 2220
f 96 1053 97
f 750 784 743
f 887 905 864
f 373 768 784
f 513 548 512
f 573 664 22
f 715 633 696
f 521 819 673
f 2454 2453 2445
f 883 887 847
f 812 76 306
f 642 528 759
f 809 235 798
f 792 998 994
f 626 586 587
f 1918 1937 1900
f 645 652 618
f 786 696 537
f 593 819 521
f 19 523 515
f 741 749 719
f 326 809 789
f 581 550 539
f 777 723 657
f 684 713 660
f 712 720 692
f 666 692 652
f 507 761 629
f 507 629 472
f 507 757 761
f 673 623 633
f 724 521 673
f 516 19 515
f 674 304 675
f 178 778 721
f 2358 947 1447
f 645 618 626
f 626 618 586
f 784 768 742
f 537 757 753
f 786 537 753
f 981 521 724
f 981 593 521
f 979 559 850
f 660 677 637
f 787 631 576
f 117 385 141
f 809 399 235
f 641 754 558
f 553 561 542
f 768 762 742
f 444 416 533
f 687 796 528
f 598 566 813
f 1501 1557 1490
f 753 757 507
f 715 696 786
f 724 673 633
f 2109 2090 2062
f 653 660 646
f 694 683 660
f 660 683 677
f 839 838 1872
f 30 1224 18
f 393 809 326
f 529 373 799
f 313 507 472
f 774 633 715
f 974 699 841
f 816 703 820
f 692 711 676
f 355 766 1014
f 875 752 1019
f 660 627 646
f 720 711 692
f 692 676 652
f 799 373 784
f 813 566 567
f 2482 2475 2462
f 764 644 780
f 1479 1924 1916
f 738 786 753
f 607 786 738
f 607 715 786
f 524 774 715
f 774 724 633
f 979 672 559
f 798 783 758
f 694 705 683
f 820 703 562
f 644 764 687
f 744 743 725
f 753 507 313
f 524 715 607
f 664 801 22
f 646 627 610
f 820 562 800
f 769 807 750
f 747 533 767
f 586 604 578
f 862 593 981
f 2382 1083 688
f 674 306 304
f 584 607 738
f 136 238 168
f 773 552 765
f 2464 2458 2473
f 773 793 552
f 619 658 626
f 1007 1139 1013
f 562 529 799
f 744 750 743
f 683 693 659
f 683 659 677
f 737 753 313
f 737 738 753
f 729 524 607
f 28 27 518
f 569 580 553
f 777 657 163
f 605 580 569
f 789 798 758
f 562 807 769
f 671 816 820
f 638 646 611
f 1074 598 644
f 799 784 750
f 1931 907 898
f 2487 2461 2483
f 584 738 737
f 1439 1438 1431
f 1213 544 2098
f 578 75 48
f 796 631 787
f 732 21 815
f 581 588 550
f 636 651 625
f 1011 810 778
f 705 725 693
f 683 705 693
f 1921 1966 236
f 729 607 584
f 1866 2227 2237
f 530 541 28
f 739 248 237
f 530 28 512
f 778 771 727
f 727 713 684
f 2220 826 2237
f 561 560 542
f 528 796 700
f 785 671 808
f 592 248 739
f 905 896 895
f 740 246 186
f 272 137 979
f 770 769 744
f 742 720 712
f 2026 544 1213
f 1888 1235 2438
f 555 554 2311
f 1192 737 313
f 1612 1611 1585
f 721 685 695
f 28 518 17
f 562 769 770
f 719 749 740
f 669 679 648
f 657 723 773
f 637 619 606
f 2072 2062 2042
f 619 626 606
f 569 553 549
f 161 638 611
f 942 910 917
f 1103 942 917
f 991 1026 992
f 137 672 979
f 163 657 785
f 2472 710 2488
f 119 611 581
f 671 820 808
f 1900 1870 1820
f 759 700 591
f 637 677 619
f 2490 2463 2494
f 765 816 671
f 687 764 780
f 1026 1019 992
f 987 1726 1719
f 771 694 713
f 2355 78 51
f 526 525 510
f 526 1249 525
f 33 1249 526
f 554 2335 2311
f 848 840 827
f 603 591 649
f 758 269 740
f 1595 1612 1586
f 1694 1048 1699
f 740 186 682
f 801 603 22
f 555 570 554
f 1053 110 97
f 601 615 582
f 668 188 814
f 744 725 705
f 759 528 700
f 640 648 620
f 703 701 562
f 892 582 886
f 631 731 576
f 1747 1087 1835
f 864 895 882
f 956 950 1103
f 1502 2500 2470
f 200 205 190
f 815 878 616
f 616 878 995
f 815 1183 878
f 1601 1827 881
f 535 526 527
f 2184 2183 2175
f 1125 1133 1142
f 235 338 798
f 339 792 160
f 599 92 75
f 598 1116 566
f 631 558 731
f 771 770 744
f 730 528 642
f 699 642 841
f 668 401 188
f 527 526 510
f 749 758 740
f 706 721 695
f 726 705 694
f 744 726 694
f 911 905 906
f 661 695 161
f 708 815 616
f 547 33 535
f 794 759 591
f 778 808 790
f 758 783 269
f 771 744 694
f 808 820 800
f 886 582 571
f 948 1010 854
f 906 905 887
f 651 635 625
f 534 2000 1226
f 1504 2016 2140
f 601 620 615
f 640 620 601
f 669 648 640
f 698 452 439
f 785 657 671
f 545 1561 2356
f 685 653 667
f 727 684 685
f 568 616 797
f 708 732 815
f 93 229 339
f 865 851 839
f 1103 950 942
f 614 125 589
f 610 627 606
f 951 834 873
f 599 625 92
f 1902 1878 830
f 2098 1899 2482
f 568 708 616
f 551 732 708
f 2487 2483 2434
f 665 160 964
f 2316 2391 2309
f 762 758 749
f 614 589 570
f 888 897 883
f 1517 1388 2000
f 721 727 685
f 610 606 588
f 685 684 653
f 651 650 635
f 1151 6 760
f 622 150 793
f 676 650 651
f 769 750 744
f 542 560 541
f 500 690 476
f 473 1064 1057
f 561 578 560
f 636 625 599
f 876 995 949
f 856 846 829
f 704 740 682
f 791 790 770
f 2466 2500 2460
f 587 586 579
f 1352 1208 1095
f 1479 1916 1684
f 609 636 604
f 751 721 706
f 608 782 810
f 603 649 672
f 476 475 447
f 794 591 801
f 682 186 650
f 808 800 790
f 598 813 644
f 719 740 704
f 608 810 1011
f 584 737 1192
f 687 780 796
f 2312 2337 474
f 667 646 638
f 728 706 1186
f 575 568 733
f 595 551 708
f 540 551 595
f 501 1852 1308
f 339 160 665
f 535 527 2447
f 558 9 731
f 723 793 773
f 713 694 660
f 693 725 666
f 767 529 562
f 550 538 531
f 2233 2267 2287
f 160 996 964
f 2470 2466 2068
f 711 719 704
f 762 749 741
f 606 626 605
f 548 542 530
f 878 709 995
f 1684 1916 1898
f 778 791 771
f 782 163 785
f 789 758 762
f 857 883 847
f 1028 733 970
f 838 829 825
f 511 535 2447
f 22 603 137
f 726 744 705
f 605 587 580
f 548 530 512
f 784 742 743
f 790 800 770
f 810 808 778
f 998 355 1014
f 595 708 568
f 656 697 551
f 540 656 551
f 143 125 614
f 1000 1020 983
f 1011 778 178
f 704 682 676
f 660 637 627
f 627 637 606
f 552 481 701
f 810 785 808
f 590 570 555
f 716 595 568
f 2335 554 2355
f 1729 911 912
f 1076 1456 1546
f 697 68 492
f 711 704 676
f 839 851 838
f 575 733 1028
f 844 982 1020
f 716 568 575
f 844 781 982
f 2156 2034 1238
f 580 561 553
f 580 579 561
f 461 453 452
f 578 48 560
f 564 540 595
f 632 656 540
f 564 632 540
f 578 599 75
f 27 535 518
f 518 535 511
f 798 788 783
f 642 759 663
f 742 741 720
f 605 626 587
f 580 587 579
f 725 712 666
f 701 767 562
f 1729 923 911
f 743 742 712
f 677 658 619
f 695 654 161
f 800 562 770
f 2084 2489 2472
f 559 716 575
f 564 595 716
f 695 685 654
f 2064 843 855
f 9 34 731
f 527 510 1973
f 723 622 793
f 987 992 1726
f 693 666 652
f 853 573 2472
f 159 148 624
f 657 773 671
f 681 188 498
f 733 797 970
f 632 565 656
f 565 697 656
f 731 697 565
f 951 920 1949
f 111 84 85
f 662 200 190
f 44 324 754
f 547 40 33
f 693 652 658
f 658 652 645
f 664 794 801
f 666 712 692
f 648 662 639
f 646 610 611
f 850 559 575
f 1106 1447 2490
f 1972 1955 1935
f 615 590 582
f 66 581 539
f 780 641 631
f 796 780 631
f 83 1049 1192
f 1348 13 1519
f 562 799 807
f 611 588 581
f 795 644 687
f 642 663 8
f 1972 1935 1936
f 682 650 676
f 615 630 590
f 795 687 730
f 742 762 741
f 553 542 548
f 1048 1692 1074
f 659 693 658
f 37 52 30
f 611 610 588
f 649 632 564
f 576 731 565
f 2138 922 1058
f 854 965 1204
f 725 743 712
f 644 813 641
f 684 660 653
f 791 770 771
f 1074 644 795
f 480 681 469
f 672 564 559
f 564 716 559
f 672 649 564
f 1378 2171 2161
f 476 474 475
f 765 701 816
f 765 552 701
f 538 548 513
f 324 107 754
f 618 609 586
f 19 25 523
f 677 659 658
f 689 452 698
f 1334 1115 1353
f 700 565 632
f 700 576 565
f 793 481 552
f 763 901 2458
f 550 549 538
f 964 996 781
f 1634 1595 1596
f 916 1124 198
f 198 1124 341
f 973 1025 842
f 842 1025 836
f 1009 1024 934
f 2472 573 710
f 1002 1100 971
f 1501 1081 1557
f 1225 1219 955
f 413 2138 284
f 522 955 1630
f 1124 301 341
f 2333 2376 2350
f 218 284 1107
f 925 1513 398
f 1442 1495 1513
f 1935 1455 1744
f 1723 1935 1744
f 1872 838 825
f 1496 1495 1442
f 963 1024 1009
f 966 1511 1514
f 1775 1729 912
f 262 1067 688
f 1007 1512 714
f 1732 914 919
f 2319 2331 2304
f 2407 2391 2400
f 2164 1780 1674
f 843 927 899
f 1188 1660 988
f 262 1640 1067
f 1109 1483 1381
f 1381 1483 1437
f 2495 1010 948
f 1313 1514 1289
f 961 899 374
f 1438 1430 1422
f 1095 1632 1634
f 2487 973 2461
f 1003 499 874
f 849 848 827
f 1462 1453 1430
f 2496 2084 2471
f 909 10 980
f 927 835 730
f 2031 1540 1536
f 831 849 2178
f 881 834 951
f 1841 1722 1803
f 670 1020 1005
f 1021 670 1005
f 2467 1869 2059
f 903 902 1939
f 1651 2476 2502
f 853 8 573
f 1850 831 2178
f 934 746 247
f 934 65 746
f 301 285 1077
f 977 968 944
f 1028 970 2495
f 374 974 2465
f 899 927 374
f 1898 1916 1882
f 1613 1634 1596
f 833 1396 909
f 247 1003 2492
f 919 914 1931
f 1299 1458 1459
f 1632 1633 1634
f 228 844 670
f 2494 2497 2467
f 901 973 2487
f 1772 734 228
f 1666 1701 1709
f 574 1024 963
f 847 864 856
f 1730 1736 2239
f 870 859 848
f 2111 2103 2074
f 1483 1140 1590
f 927 730 974
f 2074 2103 978
f 1745 1718 756
f 848 859 840
f 1482 1320 1296
f 51 66 2331
f 988 962 1067
f 833 1445 1396
f 1005 1000 1001
f 901 1009 973
f 1077 817 1099
f 944 936 933
f 952 958 1828
f 1660 986 988
f 1067 1445 833
f 1640 988 1067
f 413 284 218
f 180 347 1843
f 1846 1708 1798
f 2477 855 2469
f 1021 1005 1006
f 382 250 381
f 828 531 2369
f 1001 968 977
f 1949 779 2460
f 1115 1194 1441
f 1001 1000 968
f 1745 756 678
f 963 1009 901
f 2084 2472 2471
f 841 642 8
f 982 991 1027
f 670 844 1020
f 1514 945 1289
f 904 890 869
f 1115 1639 1161
f 849 823 2178
f 746 12 499
f 263 428 2366
f 1075 1692 1685
f 806 1002 926
f 1799 1755 216
f 968 993 944
f 993 943 944
f 31 38 19
f 550 531 828
f 1078 1081 1501
f 1149 431 1921
f 936 943 932
f 1489 1412 1660
f 980 285 301
f 918 902 903
f 890 868 869
f 890 903 867
f 746 499 1003
f 2500 951 1949
f 990 841 853
f 1634 1611 1595
f 374 927 974
f 1025 247 836
f 1653 1652 1638
f 1303 1545 1142
f 1631 1638 1616
f 1629 1546 1628
f 936 932 913
f 531 513 506
f 868 890 867
f 2330 2369 2353
f 924 918 914
f 907 914 904
f 1267 1258 1421
f 939 980 301
f 1482 1296 1472
f 868 867 859
f 313 472 491
f 2488 272 519
f 1472 1296 1471
f 1025 934 247
f 1634 1633 1611
f 1847 2177 2176
f 1289 806 1310
f 924 933 918
f 902 1969 1968
f 2128 2118 2107
f 1436 1287 1428
f 1139 1564 1617
f 572 2384 2378
f 841 8 853
f 2501 961 2465
f 1240 1408 1221
f 1578 1627 1069
f 1006 1005 1001
f 1564 1578 1617
f 539 550 828
f 1791 2168 2160
f 1739 1829 1718
f 902 1968 1939
f 1718 665 756
f 1388 1998 2000
f 545 2356 2451
f 997 1011 178
f 325 1270 1275
f 872 1666 1709
f 1959 1847 2176
f 944 943 936
f 518 511 2424
f 1067 962 1445
f 952 1828 2007
f 2061 2081 2052
f 539 828 2303
f 1699 1048 835
f 872 1709 1706
f 885 574 963
f 1320 1318 1296
f 859 867 1902
f 1452 1448 1421
f 993 976 943
f 1000 983 993
f 1010 876 854
f 986 962 988
f 2081 2031 2052
f 1732 1828 924
f 965 949 1060
f 228 734 781
f 1718 1765 665
f 943 976 932
f 1680 1794 1783
f 1471 1276 1448
f 1276 1267 1421
f 914 907 1931
f 781 996 991
f 1448 1276 1421
f 909 1396 10
f 860 849 831
f 1774 1523 1762
f 1828 937 924
f 994 1014 307
f 946 963 901
f 2103 977 978
f 1006 1001 977
f 1161 1639 1007
f 1294 1437 1639
f 1032 574 885
f 1381 1437 1294
f 733 568 797
f 229 1112 792
f 119 581 108
f 843 835 927
f 1889 860 831
f 2211 2216 2204
f 2431 2422 2400
f 2103 1006 977
f 840 1902 830
f 827 840 830
f 827 830 822
f 1003 874 2492
f 1432 1439 1431
f 734 964 781
f 1937 1936 1723
f 918 913 902
f 958 977 944
f 1850 2178 2177
f 1005 1020 1000
f 996 307 991
f 1445 340 1396
f 2179 1763 889
f 939 909 980
f 958 937 1828
f 978 977 958
f 1590 1571 1563
f 1949 920 779
f 1573 1551 1362
f 2142 1006 2103
f 920 885 963
f 920 963 946
f 1616 1583 1584
f 1453 1472 1452
f 1617 1578 1647
f 1564 1627 1578
f 1628 938 1069
f 869 868 859
f 993 983 976
f 1775 912 1762
f 752 751 706
f 1628 1546 938
f 228 781 844
f 859 1902 840
f 907 904 898
f 973 1009 1025
f 663 664 573
f 763 946 901
f 898 904 869
f 1763 2172 889
f 1128 926 971
f 860 848 849
f 904 903 890
f 2459 2479 2486
f 577 782 608
f 933 936 918
f 1847 1851 2177
f 1765 339 665
f 958 944 937
f 894 981 724
f 968 1000 993
f 2205 2192 2195
f 1099 817 1652
f 997 608 1011
f 577 608 997
f 163 782 577
f 1112 998 792
f 1851 1850 2177
f 1421 1258 1257
f 951 873 920
f 830 2215 822
f 2496 2471 1899
f 1668 1558 1773
f 914 903 904
f 932 1671 913
f 920 873 885
f 1617 1647 1013
f 873 1032 885
f 862 981 894
f 2469 855 961
f 913 1671 1969
f 2477 2064 855
f 918 936 913
f 860 870 848
f 937 944 933
f 1013 1647 1501
f 824 178 751
f 824 997 178
f 577 997 824
f 643 163 577
f 882 863 856
f 2128 2153 2134
f 880 722 774
f 722 894 774
f 905 895 864
f 850 575 583
f 914 918 903
f 937 933 924
f 1013 1501 717
f 1587 1324 928
f 1512 1013 717
f 602 577 824
f 766 643 577
f 709 862 894
f 878 862 709
f 976 975 932
f 1324 1596 928
f 1060 880 524
f 2434 2459 2499
f 1324 1613 1596
f 752 824 751
f 766 577 602
f 1014 602 594
f 1387 1226 2152
f 1387 2152 2153
f 950 669 930
f 1694 1699 1710
f 768 326 762
f 892 601 582
f 2465 974 990
f 625 624 116
f 835 795 730
f 2484 763 2458
f 989 602 824
f 1710 2064 2477
f 983 975 976
f 949 722 880
f 160 994 996
f 863 556 2305
f 863 886 556
f 910 640 601
f 829 2264 825
f 989 824 752
f 864 882 856
f 1595 1586 2381
f 1629 1628 1627
f 2180 2173 2174
f 2128 2134 2118
f 22 137 272
f 949 880 1060
f 995 894 722
f 995 709 894
f 894 724 774
f 895 892 886
f 930 640 910
f 871 870 860
f 856 863 846
f 1026 875 1019
f 851 829 838
f 1024 1171 934
f 205 36 189
f 882 886 863
f 895 886 882
f 594 875 1026
f 52 1459 1269
f 917 910 896
f 1009 934 1025
f 995 722 949
f 2152 1226 1636
f 896 892 895
f 910 601 892
f 942 950 930
f 875 989 752
f 594 602 989
f 355 643 766
f 355 260 643
f 917 896 905
f 965 1060 1162
f 896 910 892
f 1042 1101 1052
f 1029 1031 834
f 1101 1133 1118
f 342 357 376
f 516 515 2454
f 1656 2494 2467
f 1056 1303 1133
f 1120 1130 862
f 69 342 376
f 1055 1056 1133
f 499 69 165
f 101 111 85
f 834 1031 1032
f 1166 200 679
f 1031 1042 1032
f 1171 65 934
f 1204 1177 1822
f 956 1103 1096
f 96 97 514
f 956 1145 1144
f 1144 1185 1166
f 1145 1185 1144
f 1166 1185 200
f 375 132 1041
f 1153 1202 305
f 1249 32 1244
f 1087 956 1096
f 554 78 2355
f 1191 138 110
f 65 35 432
f 1110 956 1087
f 1110 1146 956
f 1146 1145 956
f 1146 1156 1145
f 1145 1156 1185
f 956 1144 950
f 2481 2495 948
f 1156 1193 1185
f 1050 1047 1051
f 107 239 151
f 1185 1193 36
f 1747 1110 1087
f 1134 1146 1110
f 1146 1157 1156
f 1156 1157 1193
f 1041 1045 1034
f 1397 1134 1110
f 1134 1157 1146
f 1157 1175 1193
f 36 1193 199
f 1090 1035 1196
f 1150 1051 1456
f 1193 1175 199
f 1186 695 199
f 1175 1186 199
f 1157 1134 1175
f 1175 728 1186
f 6 197 760
f 1130 593 862
f 182 1167 1109
f 1115 1161 1194
f 1928 1504 2140
f 2138 921 922
f 1147 1134 1397
f 1147 1397 1719
f 1134 1147 1175
f 1147 728 1175
f 1208 341 1654
f 754 151 9
f 2138 1058 284
f 1188 1557 1660
f 1053 1191 110
f 284 1189 916
f 284 1058 1189
f 2094 1465 2127
f 1726 1019 1147
f 1019 728 1147
f 1130 96 593
f 239 305 1038
f 315 1036 1131
f 397 1131 1120
f 1130 1053 96
f 2467 2485 1869
f 517 1089 421
f 1827 1029 834
f 1117 419 1047
f 1034 433 1306
f 1862 1730 2239
f 1462 1472 1453
f 1408 1422 1399
f 471 23 1111
f 1150 1456 1205
f 1040 1150 1205
f 1036 293 1131
f 293 1068 1044
f 375 1041 138
f 1046 1205 1140
f 1046 1040 1205
f 1167 1046 1140
f 1104 1049 83
f 1032 1052 1085
f 1044 1068 1191
f 1167 1483 1109
f 1035 208 1084
f 375 1040 132
f 1834 20 3
f 1070 1050 1051
f 1133 1125 1174
f 11 1440 1401
f 1071 420 208
f 1079 1094 1135
f 1086 1101 1118
f 1029 1030 1031
f 1200 1061 294
f 1068 138 1191
f 1171 1141 65
f 1141 1172 65
f 1172 35 65
f 1172 404 35
f 35 404 99
f 221 1104 1063
f 802 398 1083
f 20 1089 3
f 1699 835 2064
f 1042 1052 1032
f 1261 1432 1433
f 1323 2338 155
f 1205 1456 1076
f 1056 1088 1402
f 348 1070 1150
f 1200 1089 20
f 1162 100 1097
f 834 1032 873
f 21 471 1111
f 294 1097 1104
f 1072 100 584
f 1151 760 622
f 1041 132 1045
f 1050 1070 1135
f 1039 940 1088
f 650 159 635
f 1170 729 100
f 100 729 584
f 1096 1103 931
f 1443 1513 925
f 138 1102 110
f 1152 1034 1306
f 1090 1071 1035
f 1097 100 1072
f 23 1158 315
f 1068 375 138
f 1612 1585 1586
f 1819 1030 1029
f 1102 1041 1034
f 232 375 1068
f 348 1079 1070
f 1061 1097 294
f 1443 1442 1513
f 1200 294 1119
f 1050 1062 376
f 1094 1036 315
f 1089 1200 1119
f 21 1111 1183
f 1044 1191 1053
f 689 698 295
f 1079 232 1036
f 404 1117 99
f 1496 717 1495
f 1119 294 98
f 1089 517 3
f 1063 83 1132
f 1132 83 175
f 132 1046 182
f 1183 1111 1195
f 1131 1044 1037
f 402 1804 127
f 219 1272 1047
f 1135 1094 1697
f 2140 1854 2117
f 1111 397 1195
f 1177 1162 1097
f 1061 1177 1097
f 1509 714 717
f 433 2 1300
f 290 461 462
f 98 294 221
f 294 1104 221
f 1161 1007 714
f 1073 1152 1143
f 1094 1360 1697
f 1223 1423 1218
f 842 836 2479
f 1097 1072 1049
f 375 348 1040
f 3 517 316
f 1061 1201 180
f 232 348 375
f 1432 1431 1415
f 1495 220 1513
f 1104 1097 1049
f 596 306 674
f 777 455 723
f 1641 2170 2151
f 419 219 1047
f 1102 1034 1073
f 1073 1034 1152
f 1196 1035 1054
f 1204 1162 1177
f 746 65 12
f 751 178 721
f 1054 517 421
f 1070 1051 1150
f 110 1102 1073
f 1136 355 998
f 566 1163 567
f 1111 315 397
f 1048 1074 835
f 1158 1094 315
f 1107 1252 1374
f 1112 1136 998
f 472 629 500
f 1136 260 355
f 118 43 260
f 1063 1104 83
f 357 1050 376
f 1142 1545 1463
f 1036 232 293
f 1031 1030 1042
f 232 1079 348
f 221 1063 1132
f 1079 1036 1094
f 1205 1076 1629
f 1136 1197 260
f 1197 118 260
f 965 1162 1204
f 293 232 1068
f 1590 1205 1629
f 1140 1205 1590
f 392 250 382
f 1296 1318 1311
f 347 1201 20
f 1201 1200 20
f 132 182 1045
f 1052 1101 1086
f 1033 1039 1055
f 138 1041 1102
f 2495 970 1010
f 43 455 777
f 2023 1992 1948
f 347 20 1834
f 1072 584 1049
f 1049 584 1192
f 1045 182 2
f 44 1163 324
f 1094 1158 1360
f 1158 1450 1360
f 1091 1112 229
f 455 509 723
f 207 509 455
f 1266 1251 1257
f 1489 1547 1488
f 1541 1875 2157
f 107 324 305
f 1045 2 433
f 1070 1079 1135
f 1168 1197 1136
f 1197 359 118
f 118 359 43
f 359 356 43
f 356 455 43
f 356 207 455
f 1240 1422 1408
f 1163 1153 324
f 1201 1061 1200
f 1052 1086 1085
f 1024 1141 1171
f 1105 1136 1112
f 1050 1135 1062
f 1105 1168 1136
f 1168 1178 1197
f 1178 359 1197
f 1173 404 1172
f 465 356 359
f 1174 1125 240
f 1431 1422 1240
f 1113 1105 1098
f 1098 1105 1112
f 1178 1168 1105
f 1178 465 359
f 1091 1098 1112
f 1118 1133 1174
f 98 221 1059
f 1132 175 487
f 980 1017 285
f 465 207 356
f 180 1201 347
f 1060 524 1170
f 445 127 316
f 1431 1438 1422
f 681 498 469
f 940 1807 1759
f 250 1290 381
f 1113 1122 1105
f 1122 1178 1105
f 207 1151 509
f 525 1236 2035
f 1131 293 1044
f 465 346 207
f 346 1151 207
f 1796 1204 1822
f 97 1143 204
f 1128 971 123
f 2153 2152 2134
f 346 126 1151
f 517 445 316
f 23 1450 1158
f 1458 1462 1430
f 1129 152 1182
f 1159 1178 1122
f 1198 465 1178
f 79 346 465
f 126 1155 1151
f 1155 6 1151
f 689 295 1129
f 97 1073 1143
f 1123 1113 1098
f 1123 1122 1113
f 1123 1169 1122
f 1159 1198 1178
f 1198 79 465
f 152 392 383
f 1822 1061 180
f 625 116 92
f 1089 1119 421
f 1129 295 152
f 110 1073 97
f 1141 1173 1172
f 1169 1159 1122
f 79 126 346
f 1155 181 6
f 926 1002 971
f 295 1043 152
f 1039 1088 1056
f 1436 1428 1266
f 404 419 1117
f 2479 836 879
f 2476 2458 2464
f 317 79 1198
f 1124 939 301
f 567 44 754
f 1055 1039 1056
f 1459 1458 1439
f 1660 1412 986
f 1169 1160 1159
f 179 1155 126
f 1155 131 181
f 1177 1061 1822
f 324 1153 305
f 175 314 327
f 1160 1187 1159
f 1187 1198 1159
f 1187 317 1198
f 79 179 126
f 1043 250 392
f 152 1043 392
f 593 96 819
f 1127 1169 1123
f 317 179 79
f 179 1057 1155
f 1155 391 131
f 131 391 668
f 1586 1585 2381
f 12 69 499
f 398 1640 262
f 2107 2118 2060
f 2130 2094 2002
f 1187 249 317
f 1057 391 1155
f 439 1265 1290
f 239 107 305
f 1127 1160 1169
f 317 473 179
f 473 1057 179
f 83 1192 314
f 250 1043 1290
f 1807 940 1030
f 1084 445 517
f 1057 1164 391
f 2493 2492 2480
f 43 163 643
f 1303 1056 1545
f 1069 1655 1023
f 249 473 317
f 1162 1060 1170
f 1086 1118 1154
f 16 82 68
f 1990 1536 1989
f 1633 1632 1611
f 1487 2372 1305
f 1069 1023 1494
f 1137 1160 1127
f 1166 679 669
f 426 390 1285
f 1972 1971 1955
f 1219 1223 2437
f 1254 1261 1223
f 1056 1319 1545
f 1328 2443 1320
f 1261 1433 1223
f 1254 1223 1219
f 254 222 2428
f 1265 1237 1290
f 1284 1273 1263
f 1291 1301 1277
f 1314 102 1301
f 363 377 1280
f 1353 1514 1313
f 468 451 439
f 1964 1956 1918
f 29 2140 2026
f 381 1279 1354
f 30 1254 1224
f 173 147 158
f 1253 274 1247
f 380 334 1271
f 2072 2042 2043
f 300 267 274
f 1392 211 1356
f 240 1142 13
f 1330 1392 1382
f 1323 155 1312
f 1125 1142 240
f 1573 1362 2358
f 1249 1244 1236
f 219 1348 1272
f 1274 380 1271
f 2034 1982 191
f 2052 1990 1992
f 462 452 689
f 2261 2262 2286
f 489 1642 183
f 2485 2480 1869
f 111 1323 84
f 1354 1190 353
f 435 446 434
f 171 1341 1336
f 430 2059 2021
f 878 1120 862
f 1263 1273 1248
f 1921 2144 1966
f 1323 1312 84
f 240 13 1348
f 1274 1271 1359
f 1247 1392 1330
f 1333 11 1520
f 1253 1247 1368
f 1279 1285 1190
f 2465 990 2489
f 1272 1519 805
f 1272 805 1369
f 95 1344 1317
f 1248 1234 1242
f 242 1363 1368
f 1262 1386 274
f 597 1886 532
f 2140 2117 2026
f 1247 274 1392
f 508 985 2162
f 1469 1965 1964
f 104 1331 1315
f 1382 1392 1356
f 1342 1336 128
f 427 426 1285
f 1224 1254 1219
f 1321 1320 1322
f 1321 1328 1320
f 153 2443 1328
f 1321 153 1328
f 1244 1243 1235
f 1224 1219 1225
f 353 1190 1359
f 1312 1473 1458
f 1342 147 1336
f 1038 305 1333
f 147 171 1336
f 31 19 516
f 2461 842 2479
f 1265 427 1237
f 1278 1284 1263
f 1827 834 881
f 1237 427 1285
f 1299 1312 1458
f 1285 1274 1190
f 1363 286 1253
f 2303 828 2330
f 427 442 426
f 2463 2492 2493
f 1285 380 1274
f 18 1225 522
f 2471 2472 2488
f 2338 154 1321
f 1423 1415 1218
f 18 1224 1225
f 286 1262 1253
f 353 1359 286
f 171 1368 1383
f 1273 54 1234
f 2447 527 1973
f 155 1321 1322
f 1203 1369 1413
f 1307 363 1298
f 1329 1364 1375
f 227 1306 1329
f 296 1298 1343
f 947 2499 1447
f 1047 1272 1203
f 1748 1123 1098
f 1348 1519 1272
f 1277 70 1273
f 1282 1337 1361
f 302 353 286
f 104 1315 103
f 435 434 1377
f 1345 1449 1261
f 1310 806 926
f 1263 1248 1242
f 508 597 985
f 1415 1222 1218
f 1325 104 88
f 156 170 111
f 1282 1361 1384
f 1262 274 1253
f 1317 1344 1371
f 1371 1366 1337
f 1345 1459 1449
f 171 1383 1341
f 1235 1227 2438
f 2118 2134 1582
f 428 1260 1379
f 1336 1341 1325
f 1235 1242 1227
f 1228 1687 2284
f 2140 2016 1854
f 1887 1873 1866
f 1298 1370 1343
f 1384 1361 2440
f 242 1368 171
f 1344 1309 1366
f 1371 1344 1366
f 1280 1377 1293
f 200 1185 205
f 1368 1330 1383
f 1264 1263 1255
f 1367 1876 543
f 1370 1260 1343
f 1293 1326 1370
f 1361 1302 2440
f 1282 1384 2406
f 271 1337 1282
f 170 2338 1323
f 1503 2470 1528
f 515 1347 2453
f 1998 1997 1705
f 1228 2284 2285
f 1229 1250 1228
f 1368 1247 1330
f 1619 2045 1919
f 1364 1335 1344
f 1222 1240 1221
f 1212 858 1741
f 1222 1221 2388
f 2470 2068 1528
f 501 1308 2171
f 1311 1487 1295
f 1655 2116 1619
f 1229 1228 1220
f 8 663 573
f 1343 1260 428
f 1337 1366 1361
f 1280 1293 1298
f 1269 1345 1261
f 381 1290 1279
f 1230 1229 1220
f 1245 1229 1230
f 1245 1250 1229
f 1234 31 1227
f 1361 1350 1302
f 1266 1428 1245
f 2023 2052 1992
f 2471 2475 2482
f 462 461 452
f 271 1282 1275
f 1991 1989 1934
f 1366 1309 1350
f 1344 1335 1309
f 974 730 699
f 1374 1252 1208
f 1912 597 508
f 1363 1253 1368
f 1271 300 1386
f 1218 1222 1211
f 1377 434 1376
f 2437 1211 2399
f 1291 1277 1284
f 1251 1245 1230
f 1266 1245 1251
f 1317 1371 1337
f 1286 1095 1288
f 1286 1352 1095
f 1241 1208 1352
f 1374 1208 1241
f 1291 1284 1278
f 267 211 1392
f 1375 1364 1344
f 929 583 1028
f 1366 1350 1361
f 1294 1639 1115
f 103 1301 1291
f 1231 1230 1220
f 1251 1230 1231
f 1248 1273 1234
f 55 1264 1255
f 1450 1702 1360
f 363 1280 1298
f 1272 1369 1203
f 1415 1240 1222
f 1231 1220 1216
f 1243 1263 1235
f 227 1329 1375
f 1264 1278 1263
f 855 899 961
f 1241 1352 1286
f 2128 2107 2081
f 1433 1423 1223
f 1312 155 1473
f 154 153 1321
f 1377 1376 1293
f 274 267 1392
f 1271 334 300
f 1955 1991 1934
f 1327 1288 1613
f 1286 1288 1327
f 1349 1374 1241
f 2370 2025 2367
f 1331 133 1315
f 446 1256 434
f 1251 1231 1232
f 1255 1243 1244
f 1304 1241 1286
f 1349 1107 1374
f 1359 1271 1386
f 1227 516 2431
f 240 1348 219
f 1270 271 1275
f 1255 1263 1243
f 29 2026 1926
f 2157 1212 1683
f 1376 1326 1293
f 32 55 1255
f 1325 1341 104
f 2475 519 2462
f 2154 2161 2137
f 434 1246 1376
f 434 1256 1246
f 1257 1251 1232
f 1359 1386 1262
f 2195 2192 2186
f 1308 534 1226
f 2026 2117 544
f 1327 1613 1324
f 1326 1286 1327
f 1304 1286 1326
f 1341 1331 104
f 880 774 524
f 837 1517 534
f 1567 1127 1123
f 1237 1285 1279
f 1381 1294 1297
f 1217 1232 1216
f 13 1142 1519
f 1287 1436 1267
f 1327 1324 1372
f 1246 1241 1304
f 1246 1349 1241
f 1373 1349 1246
f 286 1359 1262
f 1330 1382 1383
f 1284 1277 1273
f 1998 1799 489
f 1075 1675 1116
f 1317 1337 106
f 1311 1295 1281
f 1329 1292 1364
f 1364 1292 1335
f 1294 1115 1334
f 1297 1294 1334
f 1300 1381 1297
f 973 842 2461
f 1239 1232 1217
f 1239 1257 1232
f 1267 1436 1258
f 1190 1274 1359
f 1405 1877 1862
f 1339 1327 1372
f 1326 1327 1339
f 1351 1349 1373
f 1311 1281 1276
f 2386 1351 1256
f 2 1109 1300
f 1731 520 482
f 1604 2022 803
f 1223 1218 1211
f 1383 1382 1341
f 1298 1293 1370
f 1354 1279 1190
f 1372 1324 2398
f 1714 1700 2173
f 2000 489 183
f 1666 192 1701
f 1242 1234 1227
f 1332 1289 1310
f 2130 1517 2005
f 1382 1331 1341
f 1249 1236 525
f 1268 1450 23
f 1291 1278 1264
f 1281 1287 1267
f 1295 1305 1287
f 1281 1295 1287
f 1487 1305 1295
f 1605 2097 2058
f 1376 1304 1326
f 1376 1246 1304
f 1919 1984 1316
f 2500 1949 2460
f 1313 1289 1332
f 2177 2189 2181
f 1334 1353 1335
f 1297 1334 1292
f 1245 1428 1250
f 969 958 952
f 1233 1239 1217
f 1257 1239 1233
f 1338 1876 1367
f 1260 1372 1379
f 1260 1339 1372
f 1302 1310 1128
f 1332 1310 1302
f 1335 1353 1313
f 1292 1334 1335
f 1300 1297 1329
f 1290 1237 1279
f 103 1314 1301
f 1301 102 70
f 1333 1268 23
f 1285 390 380
f 325 1275 772
f 1315 1314 103
f 2458 2487 2473
f 1276 1281 1267
f 95 1375 1344
f 1771 1572 2053
f 1256 1373 1246
f 1256 1351 1373
f 1302 1128 1340
f 1350 1313 1332
f 1329 1297 1292
f 2473 2487 2434
f 106 1337 271
f 471 1333 23
f 509 622 723
f 2127 1388 1517
f 1990 1989 1991
f 1226 183 1636
f 2151 2133 1605
f 1370 1339 1260
f 1370 1326 1339
f 867 1894 1902
f 426 412 390
f 1263 1242 1235
f 1399 1422 1233
f 305 11 1333
f 1306 1300 1329
f 1350 1332 1302
f 1309 1313 1350
f 1309 1335 1313
f 2102 1502 2470
f 1599 1787 1531
f 1725 1691 1724
f 1927 1827 1601
f 1476 1678 1358
f 1823 1812 1846
f 1708 1805 1824
f 1797 1746 1676
f 429 325 2395
f 1826 1835 1677
f 1722 1507 1790
f 858 1526 1672
f 158 147 1342
f 1473 1322 1462
f 1414 1565 1474
f 1761 1900 1877
f 940 1759 1008
f 1015 1008 1565
f 1533 1933 1924
f 830 1878 826
f 1015 1565 1414
f 1088 1008 1402
f 1538 1532 1651
f 1008 1015 1552
f 1591 1474 1538
f 1474 1532 1538
f 1414 1474 1591
f 1402 1008 1484
f 1484 1008 1552
f 1015 1414 1460
f 1552 1015 1460
f 1289 945 806
f 1597 1538 1659
f 1319 1402 1484
f 1056 1402 1319
f 1591 1538 1597
f 960 1414 1591
f 1460 1414 960
f 1925 1466 1455
f 1484 1552 1400
f 1319 1484 1400
f 113 1319 1400
f 1580 1591 1597
f 1400 1552 1460
f 1441 966 1514
f 1597 1659 1409
f 113 1400 1657
f 1400 1460 1657
f 1288 1095 1634
f 1551 1597 1409
f 1598 1591 1580
f 960 1591 1598
f 1990 2031 1536
f 1657 1460 960
f 1809 1746 1797
f 1433 1432 1423
f 1362 1409 2478
f 1545 113 1463
f 1463 113 1657
f 1305 1457 1287
f 1746 1682 1716
f 1434 1761 1885
f 1139 1617 1013
f 1362 2478 2379
f 1420 1597 1551
f 1580 1597 1420
f 1712 1664 1808
f 2250 2231 2256
f 1551 1409 1362
f 2213 2196 2214
f 1777 1691 1725
f 1626 192 1666
f 1574 2058 1534
f 1600 1605 1574
f 1606 1605 1600
f 1606 1641 1605
f 1420 1551 1573
f 1463 1657 1485
f 1742 678 1806
f 1553 1574 1534
f 1575 1600 1574
f 585 1810 2170
f 1623 1641 1606
f 1657 960 1407
f 960 1598 1407
f 1142 1463 1485
f 1676 1716 1581
f 1733 1738 1743
f 2064 835 843
f 1575 1574 1539
f 1574 1553 1539
f 1592 1600 1575
f 1624 1606 1592
f 1606 1600 1592
f 1642 585 1641
f 1623 1642 1641
f 1142 1485 164
f 1743 1738 1516
f 1798 1809 1720
f 1535 1534 1533
f 1607 1624 1592
f 1624 1623 1606
f 1116 1163 566
f 1485 1657 1407
f 1449 1439 1432
f 1100 802 2382
f 1743 1516 1722
f 1676 1746 1716
f 1539 1534 1535
f 1539 1553 1534
f 1642 1623 1624
f 1208 1654 1095
f 1407 1598 967
f 1598 1580 967
f 1809 1797 1720
f 1524 1535 1924
f 1535 1533 1924
f 1576 1575 1539
f 216 585 1642
f 1529 1485 1407
f 164 1485 1529
f 1462 1482 1472
f 1415 1431 1240
f 1194 714 966
f 1182 152 383
f 474 2337 446
f 1757 1743 1841
f 1486 1524 1924
f 1525 1539 1535
f 1592 1575 1576
f 967 1580 1420
f 1288 1634 1613
f 1265 459 427
f 1393 1404 2179
f 1403 1800 1404
f 1410 1403 1404
f 1410 1749 1403
f 1351 218 1349
f 1498 1524 1486
f 1525 1535 1524
f 1636 1624 1607
f 183 1642 1624
f 1636 183 1624
f 218 1107 1349
f 845 218 1351
f 1142 164 1519
f 845 413 218
f 1576 1539 1525
f 1582 1592 1576
f 2134 1607 1592
f 2134 1636 1607
f 1491 1401 2147
f 1529 1407 1589
f 1519 164 1529
f 1444 1693 1763
f 1486 1924 1479
f 2134 1592 1582
f 499 165 874
f 1857 1959 2176
f 2368 2326 2327
f 821 953 2358
f 821 1573 953
f 1824 1704 1464
f 1678 1731 1358
f 1394 1410 1404
f 1418 1410 1394
f 1466 1479 1839
f 1498 1486 1479
f 1525 1524 1498
f 2080 1582 1576
f 1785 1684 1898
f 804 398 802
f 925 398 804
f 1562 2358 1447
f 821 2358 1562
f 1620 1573 821
f 1420 1573 1620
f 967 1420 1556
f 1394 1404 1393
f 2080 1576 1525
f 1621 1420 1620
f 1556 1420 1621
f 1589 1407 967
f 1357 1505 5
f 1258 1436 1266
f 1395 1394 1393
f 1848 2176 2175
f 1455 1466 1839
f 1540 2080 1525
f 2118 1582 2080
f 804 802 1100
f 1589 967 1556
f 1529 1589 1082
f 1093 1685 1357
f 1504 1093 1357
f 1425 1418 1394
f 1475 1479 1466
f 1506 1498 1479
f 1730 1789 1784
f 2501 2465 2489
f 1438 1458 1430
f 1473 1462 1458
f 805 1529 1454
f 1454 1529 1082
f 1519 1529 805
f 1425 1394 1395
f 1744 1418 1425
f 1475 1506 1479
f 2060 2080 1540
f 1082 1589 1556
f 1511 1443 945
f 1536 1498 1506
f 1536 1525 1498
f 1540 1525 1536
f 1670 852 1672
f 1388 1389 1998
f 966 1509 1511
f 966 714 1509
f 1496 1442 1443
f 821 1562 1635
f 155 1322 1473
f 1439 1458 1438
f 1426 1425 1395
f 1499 1506 1475
f 1776 1735 1588
f 2422 2454 2421
f 1432 1415 1423
f 1559 2101 2073
f 866 413 845
f 1429 1620 821
f 1429 1621 1620
f 1250 1687 1228
f 945 1443 1002
f 802 1083 2382
f 1411 1395 1859
f 1411 1426 1395
f 1426 1744 1425
f 1483 1590 1437
f 1480 1475 1466
f 1499 1475 1480
f 1510 1733 1743
f 1658 1663 1696
f 1453 1452 1430
f 1472 1471 1452
f 1452 1471 1448
f 1430 1452 1421
f 1430 1421 1422
f 1082 1556 1429
f 1556 1621 1429
f 2386 845 1351
f 1126 1059 487
f 1437 1563 1639
f 1093 1504 1928
f 1536 1506 1499
f 1727 1588 1770
f 1397 1110 1747
f 1776 1588 1531
f 1322 1320 1482
f 1590 1629 1571
f 1877 1838 1730
f 935 1082 1429
f 1454 1082 935
f 1443 925 804
f 1639 1139 1007
f 1480 1466 1925
f 1989 1480 1934
f 1989 1536 1499
f 1531 1727 1526
f 1614 502 1593
f 2455 2431 2400
f 1755 1680 908
f 1563 1571 1564
f 1647 1078 1501
f 1635 1106 2490
f 717 1496 1511
f 2431 516 2454
f 1153 1093 1478
f 1870 1426 1411
f 1723 1744 1426
f 986 1412 962
f 1509 717 1511
f 1824 1825 1704
f 2253 2225 2234
f 1490 1557 1188
f 80 821 1635
f 805 1454 935
f 1186 706 695
f 1194 1161 714
f 1007 1013 1512
f 592 97 204
f 1258 1266 1257
f 82 1333 471
f 1505 1694 1710
f 490 1661 1643
f 490 1114 1661
f 1518 2068 2484
f 1808 1664 1750
f 1635 2490 1656
f 1521 805 935
f 1076 1546 1629
f 1301 70 1277
f 1441 1194 966
f 1824 1148 1825
f 1609 1643 1614
f 1092 1921 1114
f 1770 1739 1670
f 1632 1646 1631
f 1016 1429 821
f 935 1429 1016
f 1095 1654 1632
f 1083 262 688
f 1725 1724 1686
f 1644 490 1643
f 1149 1921 1092
f 3 893 1832
f 1640 1188 988
f 1107 284 916
f 80 1635 1656
f 1016 821 80
f 1521 935 1016
f 1202 1153 1478
f 1401 1928 29
f 1478 1928 1440
f 1865 1849 1700
f 1611 1612 1595
f 1208 198 341
f 1464 1704 1746
f 984 1721 2143
f 1868 1848 1849
f 1662 1114 490
f 1682 1669 1787
f 1618 80 1656
f 916 198 1208
f 1440 1928 1401
f 1369 805 1521
f 1107 916 1252
f 1672 1745 678
f 1721 1703 1779
f 1808 1750 1465
f 1644 1643 1609
f 1092 1114 1662
f 1793 1826 1523
f 2261 2224 2262
f 1696 2166 1767
f 1648 1521 1016
f 1252 916 1208
f 688 1067 833
f 1794 1803 1558
f 512 28 17
f 1750 861 1566
f 1644 1609 1594
f 1645 490 1644
f 1662 490 1645
f 2224 2229 2262
f 1760 1602 861
f 1530 1777 1760
f 1673 872 1706
f 1696 1668 2166
f 1708 1809 1798
f 1581 1716 1814
f 1709 1794 1680
f 1421 1257 1233
f 1724 1476 1686
f 1481 1965 1469
f 1492 1965 1481
f 1559 2073 1549
f 1615 1644 1594
f 1706 1755 1799
f 1837 1725 1686
f 1720 1797 1572
f 2022 1618 2467
f 1579 80 1618
f 1648 1016 80
f 2152 1636 2134
f 1632 1631 1611
f 1434 1470 1761
f 1577 1594 1559
f 1603 1615 1594
f 1645 1644 1615
f 1637 1662 1645
f 1199 1092 1662
f 1199 1149 1092
f 1451 1108 1149
f 756 665 734
f 1714 1865 1700
f 1709 1841 1794
f 1579 1618 2022
f 1413 1369 1648
f 1369 1521 1648
f 11 1401 1520
f 1446 1470 1434
f 1754 1798 1691
f 2073 2063 1544
f 1549 2073 1544
f 1603 1594 1577
f 1637 1645 1615
f 1199 1662 1637
f 1427 1149 1199
f 2167 1108 1451
f 1997 1673 1705
f 1705 1706 1799
f 1709 1757 1841
f 1579 2022 1604
f 707 80 1579
f 707 1648 80
f 1520 1401 1491
f 1520 1491 1649
f 1435 1434 1885
f 1470 1469 1461
f 1508 2024 1481
f 1544 2063 2370
f 1568 1559 1549
f 1577 1559 1568
f 1610 1615 1603
f 1610 1637 1615
f 999 1199 1637
f 1451 1149 1427
f 1825 1148 1137
f 1705 1673 1706
f 1138 1604 2116
f 1138 1579 1604
f 1413 1648 707
f 2360 2024 1508
f 1075 1116 598
f 1468 229 93
f 1839 1479 1684
f 2224 2216 2229
f 1625 1637 1610
f 329 999 1637
f 1017 1427 1199
f 1017 303 1427
f 303 1451 1427
f 1792 1754 1777
f 2309 2391 2301
f 1655 1138 2116
f 707 1579 1138
f 1649 1491 206
f 1406 1885 1398
f 1419 1885 1406
f 1419 1435 1885
f 1435 1446 1434
f 1481 1469 1470
f 1583 1603 1577
f 1017 1199 999
f 67 941 81
f 67 1650 941
f 1259 1815 2164
f 1619 2116 2045
f 1424 707 1138
f 1702 1649 206
f 1687 1406 1398
f 1477 1481 1470
f 1569 1577 1568
f 1583 1577 1569
f 1610 1603 1583
f 329 1637 1625
f 340 273 2167
f 81 273 340
f 962 67 81
f 1547 1619 1488
f 1770 1830 1739
f 938 1424 1138
f 1424 1413 707
f 1527 1649 1702
f 1520 1649 1527
f 1268 1520 1527
f 1250 1406 1687
f 1353 1115 1441
f 1051 1203 1413
f 1250 1419 1406
f 2372 1481 1477
f 2372 1508 1481
f 1560 1568 2449
f 1568 1549 2449
f 1569 1568 1560
f 1584 1583 1569
f 1652 329 1625
f 817 999 329
f 285 1017 999
f 10 1451 303
f 10 2167 1451
f 1412 1650 67
f 1412 1488 1650
f 1023 1619 1547
f 1023 1655 1619
f 938 1138 1655
f 1456 1413 1424
f 1457 1470 1446
f 1457 1477 1470
f 1652 817 329
f 340 2167 10
f 1546 1424 938
f 1546 1456 1424
f 1779 1259 1548
f 2052 2031 1990
f 1202 1478 1440
f 1428 1419 1250
f 1428 1435 1419
f 1428 1446 1435
f 1955 1934 1935
f 1584 1569 1560
f 1638 1625 1610
f 1638 1652 1625
f 1077 999 817
f 1077 285 999
f 980 303 1017
f 1412 67 962
f 1494 1023 1547
f 271 1270 325
f 1511 1496 1443
f 1268 1527 1450
f 1353 1441 1514
f 1287 1446 1428
f 1287 1457 1446
f 2372 1477 1305
f 1992 1990 1991
f 1992 1991 1971
f 1971 1991 1955
f 2449 1549 2418
f 1616 1610 1583
f 1638 1610 1616
f 1396 340 10
f 1445 81 340
f 962 81 1445
f 1753 1790 984
f 1753 984 2148
f 1588 1713 1770
f 969 978 958
f 1703 1741 1779
f 1758 1846 1754
f 1819 1029 1827
f 1818 1530 1712
f 1750 1566 2127
f 2434 2483 2459
f 1798 1720 1771
f 1794 1841 1803
f 216 1755 1810
f 1735 1748 1098
f 1497 1748 1735
f 2102 1601 1502
f 1601 881 1502
f 1455 1839 1744
f 1709 1680 1706
f 1212 1741 1703
f 1671 1788 1969
f 1692 1075 1074
f 881 951 2500
f 2486 2463 2490
f 1781 1748 1497
f 984 1840 1721
f 1741 1815 1259
f 1756 1837 1626
f 975 987 1542
f 2236 2235 2230
f 678 734 1772
f 975 1542 1671
f 1806 1772 1780
f 1806 678 1772
f 2268 2218 2225
f 2007 1828 1732
f 1688 1531 1526
f 1752 1526 1554
f 1844 1818 1712
f 1804 1823 1846
f 1704 1781 1669
f 1721 1779 2143
f 1526 1770 1670
f 1669 1781 1497
f 1735 1098 1713
f 1741 1742 1815
f 1875 1526 858
f 1599 1531 1688
f 1558 1803 1790
f 1683 1703 1721
f 957 1832 1766
f 1542 1679 1671
f 1671 1679 1788
f 1819 1827 1927
f 1739 1718 1745
f 1839 1684 1022
f 1283 1299 1459
f 1418 1022 1410
f 2368 2393 2326
f 1669 1497 1776
f 1875 858 1212
f 852 1739 1745
f 1964 1918 1461
f 1331 1356 133
f 1829 1468 1765
f 1741 858 1742
f 1674 1021 1006
f 1936 1935 1723
f 1713 1098 1468
f 1724 1678 1476
f 1680 1783 908
f 1731 1543 520
f 1840 1683 1721
f 1542 1467 1679
f 1812 1708 1846
f 1788 1679 1975
f 1770 1713 1830
f 1803 1722 1790
f 2391 2349 2301
f 1588 1735 1713
f 1818 1836 1530
f 861 1837 1756
f 886 571 556
f 1181 1805 1812
f 1706 1680 1755
f 1677 1729 1775
f 1669 1776 1787
f 1526 1670 1672
f 1727 1770 1526
f 987 1467 1542
f 1704 1137 1567
f 1693 1865 1714
f 1762 912 897
f 1135 1697 1062
f 1062 1697 376
f 1678 1543 1731
f 1467 1793 1679
f 1760 1777 1602
f 1846 1798 1754
f 1835 1096 1677
f 1030 940 1033
f 1450 1527 1702
f 376 1697 1717
f 1697 1711 1717
f 376 1717 165
f 1790 1840 984
f 1746 1704 1669
f 1746 1669 1682
f 2301 2349 2308
f 1898 1882 1444
f 1730 1820 1789
f 1566 861 1380
f 2301 2308 2266
f 1691 1771 1543
f 1659 1651 1958
f 1697 1360 1711
f 1717 1711 1737
f 165 1717 1737
f 1558 1790 1753
f 1663 1668 1696
f 1360 1702 1711
f 1711 1702 1707
f 1711 1707 1737
f 165 1737 1751
f 1444 1782 1693
f 1716 1787 1599
f 1744 1839 1022
f 1785 1898 1444
f 1702 206 1707
f 1751 1764 2468
f 316 1844 893
f 893 1844 915
f 1845 1804 1758
f 861 1756 1380
f 1780 670 1021
f 1763 1714 2172
f 1783 1558 1663
f 1465 1750 2127
f 1691 1798 1771
f 1724 1691 1543
f 1910 839 1872
f 1751 1737 2044
f 1751 2044 1764
f 1701 482 1757
f 1777 1725 1602
f 1836 1845 1530
f 2102 2470 1503
f 544 2496 1899
f 946 763 2484
f 987 1719 1467
f 1845 1758 1792
f 1602 1725 1837
f 1873 1872 1866
f 1712 1530 1760
f 1799 216 489
f 1750 1760 861
f 2466 2460 2068
f 2168 1696 2159
f 377 1377 1280
f 1797 1676 1572
f 1572 1581 2053
f 1572 1676 1581
f 2468 1764 2498
f 2468 2498 1994
f 1861 1695 1860
f 2004 2495 2481
f 1826 1677 1523
f 1670 1739 852
f 2269 2253 2234
f 1678 1724 1543
f 1791 1658 2168
f 1397 1747 1719
f 1658 1696 2168
f 272 979 519
f 1679 1774 1975
f 932 975 1671
f 1716 1682 1787
f 1747 1835 1826
f 2469 961 2501
f 1810 908 1791
f 191 1982 1768
f 1825 1137 1704
f 1804 1846 1758
f 1737 2004 2044
f 913 1969 902
f 2498 1795 1801
f 915 1844 1712
f 1689 915 1712
f 1740 1752 1541
f 199 695 661
f 1693 1782 1865
f 1809 1824 1464
f 1829 1765 1718
f 1982 1816 1768
f 1816 1622 1768
f 1681 1622 2165
f 1768 1622 1681
f 1772 228 670
f 1283 1459 52
f 1749 1785 1444
f 1675 1075 1685
f 1704 1567 1781
f 1858 1857 1848
f 1752 1688 1526
f 1810 1791 2160
f 908 1658 1791
f 1773 1558 1813
f 1845 1792 1530
f 69 376 165
f 1834 3 1832
f 1722 1516 1507
f 1994 1801 1821
f 2046 1833 1982
f 2046 1821 1833
f 1982 1833 1816
f 1022 1785 1749
f 1810 2160 2170
f 1726 1147 1719
f 1507 1683 1840
f 1467 1719 1793
f 1801 1795 1802
f 1801 1802 1811
f 1801 1811 1821
f 1622 1690 2165
f 1934 1480 1925
f 1468 1091 229
f 1742 1780 2164
f 858 1672 1742
f 1833 1417 1816
f 1816 1417 1622
f 2165 1690 1831
f 1663 1558 1668
f 1719 1747 1826
f 1664 1760 1750
f 1622 1817 1690
f 1530 1792 1777
f 1802 948 1796
f 1802 1796 1811
f 1622 1515 1817
f 1831 1695 1861
f 1783 1663 1658
f 1022 1749 1410
f 948 854 1796
f 1811 1842 1833
f 1821 1811 1833
f 1833 1842 1417
f 1417 1515 1622
f 127 1804 1845
f 1837 1686 1626
f 1817 1608 1690
f 1523 1775 1762
f 127 1845 1836
f 1812 1805 1708
f 1523 1677 1775
f 1780 1772 670
f 1758 1754 1792
f 854 1204 1796
f 1811 1822 1842
f 1690 1608 1831
f 1811 1796 1822
f 1417 1842 1416
f 1417 1416 1515
f 1817 1515 1608
f 1831 1608 1728
f 908 1783 1658
f 127 1836 316
f 1805 1148 1824
f 852 1745 1672
f 1478 1093 1928
f 1842 1822 1843
f 1842 1843 959
f 1842 959 1416
f 1831 1728 1695
f 1695 1728 1860
f 2337 2346 446
f 1602 1837 861
f 1835 1087 1096
f 1708 1824 1809
f 1737 505 2004
f 1781 1567 1748
f 1543 1883 520
f 1712 1760 1664
f 128 1336 72
f 1543 2053 1883
f 1822 180 1843
f 1515 1786 1608
f 519 929 2462
f 506 512 2402
f 1683 1212 1703
f 1830 1829 1739
f 1771 2053 1543
f 1515 1416 1769
f 1515 1769 1786
f 1608 1786 1728
f 1689 1712 1808
f 1783 1794 1558
f 1497 1735 1776
f 1137 1127 1567
f 1567 1123 1748
f 1185 36 205
f 959 1734 1416
f 1733 1541 1738
f 1774 1762 1974
f 1541 1752 1554
f 1740 1688 1752
f 1554 1526 1875
f 1830 1468 1829
f 1755 908 1810
f 1814 1716 1599
f 1742 1806 1780
f 2349 2340 2308
f 1832 915 1689
f 1713 1468 1830
f 1814 1599 1346
f 1766 1832 1689
f 1022 1684 1785
f 1116 1093 1153
f 1672 678 1742
f 1675 1685 1093
f 1841 1743 1722
f 2053 1581 1814
f 1809 1464 1746
f 2497 2493 2485
f 1416 1734 1769
f 1786 1665 1728
f 1728 1665 1951
f 1728 1951 1860
f 1860 1951 2094
f 1844 1836 1818
f 316 1836 1844
f 1787 1776 1531
f 1719 1826 1793
f 1401 29 2147
f 2121 1548 2111
f 1741 1259 1779
f 1843 347 1834
f 959 1843 1734
f 1769 1734 1766
f 1734 957 1766
f 1769 1766 1786
f 1766 1689 1786
f 1786 1689 1665
f 1777 1754 1691
f 1790 1507 1840
f 1470 1461 1761
f 1793 1523 1679
f 1098 1091 1468
f 1838 1820 1730
f 1843 1834 1734
f 1665 1808 1951
f 1531 1588 1727
f 1832 893 915
f 1679 1523 1774
f 2488 710 272
f 1116 1675 1093
f 2348 2340 2349
f 1734 1834 1832
f 1734 1832 957
f 1951 1808 2094
f 1685 1692 1505
f 295 698 1043
f 2143 1779 2121
f 1665 1689 1808
f 1763 1693 1714
f 1738 2157 1516
f 1114 1921 236
f 1333 1520 1268
f 1108 431 1149
f 2144 1912 508
f 1537 1957 1108
f 1108 1957 431
f 2167 1018 1108
f 1957 1681 1338
f 1957 1338 2163
f 1983 1390 2093
f 37 30 557
f 2172 1714 2173
f 1983 1984 1390
f 1984 2065 1390
f 1762 897 884
f 1214 2065 1984
f 1974 1762 1950
f 1950 1762 884
f 1698 1861 2012
f 2116 803 1214
f 1938 1974 1950
f 1967 1974 1938
f 1461 1900 1761
f 1929 884 865
f 1950 884 1929
f 2062 2071 2042
f 1985 1732 919
f 502 2146 1593
f 1995 1213 2098
f 1522 2476 1651
f 1849 2175 2174
f 1989 1499 1480
f 1938 1950 1929
f 1574 1605 2058
f 1605 2133 2097
f 1912 2014 1886
f 2092 2082 2083
f 206 1930 505
f 2101 2100 2092
f 2101 2092 2073
f 1910 865 839
f 1901 1929 1910
f 1929 865 1910
f 1788 1975 1967
f 2073 2092 2063
f 1593 2100 2101
f 1876 1698 2015
f 2014 1853 1884
f 2165 1831 1698
f 81 1316 273
f 1920 1929 1901
f 1920 1938 1929
f 1968 1967 1920
f 1967 1938 1920
f 1700 1849 2174
f 2173 1700 2174
f 2091 2062 2072
f 2467 2059 803
f 2239 1736 2240
f 1685 1505 1357
f 1686 1476 1358
f 1788 1967 1968
f 1969 1788 1968
f 2065 2110 2156
f 1214 2110 2065
f 1214 503 2110
f 273 2093 1018
f 1983 2093 273
f 532 1886 2155
f 2021 1947 2034
f 216 1810 585
f 1912 543 2014
f 1390 2051 1537
f 1873 1910 1872
f 2045 1214 1984
f 597 1912 1886
f 1593 2146 2100
f 2090 2071 2062
f 2034 2046 1982
f 1947 2046 2034
f 2045 2116 1214
f 1887 1910 1873
f 1901 1910 1887
f 1562 1447 1106
f 431 1957 2163
f 1948 1972 1936
f 1948 1992 1972
f 2015 2013 2014
f 2014 2013 1853
f 1884 1853 1550
f 2468 1994 1947
f 1355 1550 2154
f 1884 1550 1355
f 2108 2128 2081
f 2024 1965 1492
f 2024 2032 1965
f 1604 803 2116
f 1911 1920 1901
f 1968 1920 1939
f 1939 1920 1911
f 1626 1666 872
f 2091 2120 2062
f 1759 1819 1927
f 1674 1780 1021
f 1673 1756 872
f 1550 501 2171
f 1550 2171 1378
f 2162 2145 2146
f 1358 482 192
f 2120 2119 2109
f 1866 1872 2227
f 2012 1860 1391
f 2161 2136 2137
f 1661 236 2162
f 1894 1901 1887
f 1911 1901 1894
f 1707 206 505
f 2137 2136 2120
f 2164 1674 2142
f 1861 1860 2012
f 1939 1911 1894
f 2118 2080 2060
f 236 508 2162
f 1815 1742 2164
f 2093 1537 1018
f 2154 1378 2161
f 2041 2098 2491
f 2043 2042 2032
f 1018 1537 1108
f 1808 1465 2094
f 1643 1661 502
f 1618 1656 2467
f 2136 2135 2119
f 2119 2108 2071
f 1183 1195 878
f 1594 1593 2101
f 2063 2033 2370
f 2098 2482 2491
f 1275 1282 2406
f 2003 1948 1956
f 2043 2032 2024
f 2025 2043 2024
f 1550 1378 2154
f 2498 1764 1795
f 1548 2164 2142
f 2431 2454 2422
f 2011 1993 1981
f 2349 2391 2362
f 502 2162 2146
f 2025 2024 2360
f 2129 2120 2091
f 2007 1732 1985
f 2171 1308 209
f 1930 1995 2041
f 1390 1238 2051
f 1878 1887 1866
f 1894 1887 1878
f 2032 2011 1965
f 2492 874 2480
f 2108 2069 2071
f 1358 1731 482
f 430 2021 2034
f 1965 2003 1964
f 1855 1889 831
f 1668 1773 2150
f 2156 1238 1390
f 1903 898 869
f 2391 2407 2362
f 2121 2111 2074
f 1259 2164 1548
f 2099 2129 2091
f 1853 501 1550
f 1853 1852 501
f 2017 969 952
f 2121 2074 2085
f 1391 2130 2006
f 1367 543 2144
f 2146 2099 2100
f 1545 1319 113
f 1922 898 1903
f 1931 898 1922
f 585 2170 1641
f 2017 952 2007
f 2074 969 2017
f 1558 1753 1813
f 2005 1517 837
f 2006 2130 2005
f 1474 1528 1532
f 2003 1981 1948
f 2071 2069 2070
f 919 1931 1922
f 2085 2074 2017
f 2104 2121 2085
f 2100 2099 2082
f 2110 2034 2156
f 505 2474 2004
f 1922 1903 871
f 1952 919 1922
f 1985 919 1952
f 2001 2007 1985
f 2036 2017 2001
f 2017 2007 2001
f 2085 2017 2036
f 2047 2085 2036
f 2075 2085 2047
f 2075 2104 2085
f 1993 2023 1948
f 2422 2407 2400
f 2011 2070 1993
f 2033 2043 2025
f 1698 2012 2015
f 1338 2165 1876
f 1940 1922 871
f 1976 2001 1985
f 2143 2121 2104
f 1051 1413 1456
f 1362 2379 2358
f 1870 1859 1789
f 2090 2109 2071
f 1885 1405 1398
f 1886 1884 1355
f 1960 1952 1922
f 1960 1985 1952
f 1976 1985 1960
f 1956 1948 1936
f 2135 209 2128
f 2157 1875 1212
f 2160 2168 2169
f 1461 1918 1900
f 2018 2036 2001
f 2086 2104 2075
f 2111 2142 2103
f 1956 1936 1937
f 2070 2061 2023
f 2135 2128 2108
f 2042 2071 2011
f 2383 2138 413
f 2072 2043 2033
f 1960 1922 1940
f 2069 2061 2070
f 2069 2108 2061
f 2119 2135 2108
f 1904 1889 1855
f 1904 871 1889
f 1940 871 1904
f 2018 2001 1976
f 2047 2036 2018
f 2122 2143 2104
f 489 216 1642
f 2148 984 2143
f 1975 1974 1967
f 1516 2157 1683
f 1594 1614 1593
f 2270 2276 2269
f 2147 29 1926
f 2082 2091 2072
f 2059 430 503
f 1905 1940 1904
f 1961 1960 1940
f 1976 1960 1961
f 2087 2086 2075
f 2065 2156 1390
f 1838 1900 1820
f 837 534 1308
f 273 1018 2167
f 1855 831 1850
f 2019 2037 2018
f 2037 2047 2018
f 2075 2047 2037
f 2095 2104 2086
f 2095 2122 2104
f 2148 2143 2122
f 1926 1213 1995
f 1885 1761 1405
f 2013 2012 2006
f 2216 2211 2233
f 1890 1904 1855
f 1905 1904 1895
f 1932 1940 1905
f 1977 1976 1961
f 1986 2018 1976
f 1518 2484 2476
f 1870 1411 1859
f 1548 2142 2111
f 1895 1904 1890
f 1932 1905 1895
f 1961 1940 1932
f 1986 1976 1977
f 2008 2018 1986
f 2019 2018 2008
f 2087 2075 2037
f 2095 2086 2087
f 1860 2094 1391
f 1853 2006 1852
f 2013 2006 1853
f 979 850 929
f 1874 1890 1855
f 2028 2019 2008
f 1993 2070 2023
f 1998 1705 1799
f 1491 2147 206
f 1856 1855 1851
f 1895 1890 1874
f 2038 2019 2028
f 2048 2037 2038
f 2038 2037 2019
f 2067 2087 2048
f 2048 2087 2037
f 2095 2087 2067
f 2149 2122 2095
f 2149 2148 2122
f 2005 837 1308
f 1308 1387 209
f 1927 1601 2102
f 201 254 170
f 1403 1763 1800
f 1346 1740 1510
f 871 1903 870
f 1650 1619 1919
f 1667 1753 2148
f 1923 1961 1932
f 1953 1986 1977
f 2112 2095 2067
f 2149 2095 2112
f 1667 2148 2149
f 2422 2421 2407
f 1926 2026 1213
f 2144 543 1912
f 1387 2153 2128
f 1510 1740 1733
f 2489 990 853
f 803 503 1214
f 431 2163 1921
f 2146 2145 2129
f 2163 2144 1921
f 1874 1855 1856
f 1923 1932 1895
f 1941 1961 1923
f 1941 1977 1961
f 2076 2067 2048
f 2113 2067 2076
f 2113 2112 2067
f 1937 1723 1900
f 1900 1723 1870
f 2163 1338 1367
f 520 1346 1510
f 1698 1831 1861
f 1919 2045 1984
f 1891 1923 1895
f 2028 2008 1986
f 1981 1993 1948
f 1883 1346 520
f 1883 1814 1346
f 206 2147 1930
f 1447 2499 2486
f 1906 1923 1891
f 1953 1941 1923
f 1953 1977 1941
f 1987 1986 1953
f 2123 2112 2113
f 2123 2149 2112
f 1308 1226 1387
f 1346 1599 1688
f 2093 1390 1537
f 2011 1981 2003
f 2028 1986 1987
f 2049 2048 2038
f 2076 2048 2049
f 1813 1667 2149
f 1813 2149 2123
f 1469 1964 1461
f 1757 1510 1743
f 1930 1999 505
f 1784 1789 2223
f 1532 1522 1651
f 1913 1923 1906
f 1943 1923 1913
f 1943 1942 1923
f 1942 1953 1923
f 1987 1953 1942
f 1852 2005 1308
f 2053 1814 1883
f 1740 1541 1733
f 1886 1355 2154
f 1474 1503 1528
f 1879 1895 1874
f 1891 1895 1879
f 2124 2113 2076
f 2124 2123 2113
f 1896 1891 1879
f 1906 1891 1896
f 1962 1987 1942
f 2009 2028 1962
f 2028 1987 1962
f 2038 2028 2009
f 2109 2119 2071
f 1918 1956 1937
f 1864 1856 1851
f 1897 1906 1896
f 1913 1906 1897
f 1962 1942 1943
f 2077 2076 2049
f 2125 2123 2124
f 2147 1926 1930
f 1902 1894 1878
f 482 1510 1757
f 2129 2137 2120
f 803 2059 503
f 1857 1851 1847
f 1857 1864 1851
f 2039 2038 2009
f 2049 2038 2039
f 2077 2124 2076
f 2150 1813 2123
f 482 520 1510
f 1994 1821 2046
f 2044 2004 1764
f 1867 1856 1864
f 1867 1874 1856
f 1944 1913 1897
f 1962 1943 1944
f 2126 2125 2124
f 2150 2123 2125
f 2146 2129 2099
f 1995 2098 2041
f 1641 2151 1605
f 1959 1857 1847
f 1879 1874 1867
f 1944 1943 1913
f 1963 1962 1944
f 2096 2124 2077
f 2126 2124 2096
f 2150 2125 2126
f 1650 1919 941
f 2136 209 2135
f 2014 1884 1886
f 2077 2049 2029
f 2127 1389 1388
f 2127 1566 1389
f 1926 1995 1930
f 941 1919 1316
f 503 430 2110
f 1880 1879 1867
f 1880 1896 1879
f 1944 1897 1907
f 1978 1962 1963
f 1978 2009 1962
f 2029 2049 2039
f 2078 2096 2077
f 827 822 823
f 2166 1668 2150
f 941 1316 81
f 2204 2216 2203
f 2071 2070 2011
f 1892 1896 1880
f 1907 1897 1892
f 1897 1896 1892
f 1914 1944 1907
f 2010 2009 1978
f 2039 2009 2010
f 1346 1688 1740
f 1820 1870 1789
f 1391 2094 2130
f 1945 1963 1944
f 2078 2077 2029
f 1767 2150 2126
f 2166 2150 1767
f 2022 2467 803
f 1927 2102 1503
f 1954 1944 1914
f 1954 1945 1944
f 1970 1978 1963
f 2105 2096 2078
f 2105 2126 2096
f 1965 2011 2003
f 1626 1358 192
f 1594 2101 1559
f 1930 2041 1999
f 2165 1698 1876
f 1398 1871 891
f 1681 2165 1338
f 2010 1978 1970
f 2030 2029 2010
f 2029 2039 2010
f 2055 2078 2030
f 2078 2029 2030
f 1849 1848 2175
f 1871 1862 891
f 543 2015 2014
f 1858 1864 1857
f 1867 1864 1858
f 1970 1963 1945
f 2088 2078 2055
f 2088 2105 2078
f 2131 2126 2105
f 1767 2126 2131
f 2063 2083 2033
f 2171 209 2161
f 2042 2011 2032
f 1773 1813 2150
f 1908 1954 1914
f 2010 1970 1979
f 2131 2105 2088
f 1876 2015 543
f 1692 1048 1694
f 1859 1395 2207
f 1395 1393 2207
f 1736 1730 1784
f 2470 2500 2466
f 1701 1757 1709
f 1979 1970 1945
f 2050 2055 2030
f 2286 2350 2317
f 2155 1886 2154
f 1889 871 860
f 2161 209 2136
f 2463 2493 2497
f 2204 2203 2190
f 1404 1800 2179
f 1385 2477 2469
f 1715 2477 1385
f 209 1387 2128
f 1868 1867 1858
f 1881 1880 1867
f 1893 1892 1880
f 1893 1880 1881
f 1907 1892 1893
f 1908 1914 1907
f 1979 1945 1954
f 1980 2010 1979
f 2159 1767 2131
f 1765 93 339
f 1405 1761 1877
f 515 523 1347
f 1738 1541 2157
f 2163 1367 2144
f 1566 1380 1389
f 2317 2392 2316
f 2498 1801 1994
f 1881 1867 1868
f 2050 2030 1980
f 2030 2010 1980
f 2089 2055 2050
f 2089 2088 2055
f 2114 2131 2088
f 1538 1651 1659
f 2145 2155 2129
f 1928 2140 29
f 2370 2033 2025
f 2252 2239 2240
f 2252 1862 2239
f 2316 2392 2391
f 1385 2469 2501
f 1715 1710 2477
f 1643 502 1614
f 2438 1227 2431
f 1915 1907 1893
f 1915 1908 1907
f 1979 1954 1908
f 1988 1979 1908
f 1988 1980 1979
f 2159 2131 2114
f 2155 2154 2129
f 1966 2144 508
f 1756 1626 872
f 1505 1710 1715
f 236 1966 508
f 2284 1398 2272
f 2325 2355 2319
f 1779 1548 2121
f 1532 1528 1522
f 2056 2050 1980
f 2056 2089 2050
f 2015 2012 2013
f 1964 2003 1956
f 2012 1391 2006
f 1565 1927 1503
f 2226 2244 2243
f 5 1715 1385
f 1868 1858 1848
f 1946 1908 1915
f 1946 1988 1908
f 2020 2056 1980
f 2115 2159 2114
f 2092 2083 2063
f 1687 1398 2284
f 2162 2155 2145
f 2475 2488 519
f 2158 5 1385
f 1505 1715 5
f 1692 1694 1505
f 2020 1980 1988
f 2169 2159 2115
f 2168 2159 2169
f 2083 2082 2072
f 1316 1984 1983
f 1488 1619 1650
f 2083 2072 2033
f 1210 1233 2361
f 1933 1946 1915
f 2079 2089 2056
f 2115 2114 2088
f 2099 2091 2082
f 532 2155 2162
f 2006 2005 1852
f 2061 2052 2023
f 2176 2184 2175
f 985 532 2162
f 1909 1893 1881
f 1909 1915 1893
f 2040 2020 1988
f 2040 2056 2020
f 2079 2088 2089
f 2115 2088 2079
f 1882 1782 1444
f 1216 1215 2320
f 867 1939 1894
f 903 1939 867
f 1379 1372 2398
f 1863 504 2027
f 2158 1385 504
f 1782 1881 1868
f 1933 1915 1909
f 2040 1988 1946
f 2024 1492 1481
f 2136 2119 2120
f 1528 1518 1522
f 1405 1871 1398
f 1408 1399 1221
f 1357 5 2158
f 1800 1763 2179
f 1782 1868 1865
f 1882 1881 1782
f 1882 1909 1881
f 2057 2056 2040
f 2106 2079 2056
f 2057 2106 2056
f 2132 2079 2106
f 2132 2115 2079
f 2169 2115 2132
f 985 597 532
f 2100 2082 2092
f 1221 1399 1210
f 1399 1233 1210
f 2130 2002 1517
f 1865 1868 1849
f 2040 1946 1933
f 52 1269 30
f 1813 1753 1667
f 1380 1673 1997
f 1088 940 1008
f 1947 1994 2046
f 1916 1909 1882
f 1924 1933 1909
f 1533 2040 1933
f 1534 2040 1533
f 2058 2040 1534
f 2058 2057 2040
f 1238 191 1768
f 1389 1380 1997
f 1541 1554 1875
f 1854 504 1863
f 2158 504 1854
f 1275 2406 2396
f 153 2426 2443
f 1916 1924 1909
f 1935 1934 1925
f 1723 1426 1870
f 2097 2057 2058
f 2097 2106 2057
f 2151 2169 2132
f 2160 2169 2151
f 1635 1562 1106
f 1768 1681 1957
f 2051 1768 1957
f 535 33 526
f 1609 1614 1594
f 2216 2233 2229
f 2496 2027 2084
f 1863 2027 2496
f 1854 1863 2117
f 2016 2158 1854
f 1504 1357 2016
f 1357 2158 2016
f 1114 236 1661
f 2154 2137 2129
f 2133 2106 2097
f 2041 2491 1999
f 1238 1768 2051
f 2108 2081 2061
f 2195 2186 2189
f 2348 2349 2362
f 192 482 1701
f 1707 505 1737
f 2133 2132 2106
f 2151 2132 2133
f 2170 2160 2151
f 1661 2162 502
f 1389 1997 1998
f 2352 2329 2297
f 2352 2364 2329
f 2364 2394 2414
f 2394 2364 2352
f 2402 512 2415
f 2254 2243 2255
f 1365 2456 2446
f 2271 2282 2298
f 846 2283 2264
f 2318 2293 2310
f 2295 2294 2254
f 2283 2290 2278
f 2293 2270 2294
f 2423 2455 2400
f 2267 2281 2287
f 2191 2204 2190
f 2282 2271 2263
f 2364 2334 2329
f 2424 2432 2409
f 2282 2263 2298
f 1409 1659 1958
f 2263 2302 2298
f 2297 2329 2296
f 446 2346 1256
f 1958 2502 2478
f 2444 2437 2399
f 263 2366 2359
f 849 827 823
f 2311 2325 2290
f 2379 2434 2499
f 2446 2456 2423
f 2358 2379 947
f 947 2379 2499
f 2212 2205 2195
f 2227 2245 2237
f 2237 2245 2256
f 2263 2271 2256
f 556 571 2305
f 1528 2068 1518
f 2424 2439 2432
f 2302 2352 2297
f 826 1866 2237
f 2211 2248 2242
f 2363 2334 2364
f 2235 2244 2226
f 2295 2254 2255
f 2329 2324 2296
f 2447 1973 2439
f 2329 2334 2324
f 2409 2432 2414
f 2318 2276 2293
f 2416 866 2425
f 1493 2372 1487
f 2237 2231 2230
f 512 17 2415
f 1236 26 2035
f 688 921 2138
f 2491 2482 2462
f 6 181 197
f 2481 948 1795
f 2383 2382 2138
f 2377 2394 2352
f 2377 506 2394
f 506 2402 2394
f 2402 2415 2401
f 2394 2402 2401
f 2326 2276 2318
f 2439 2457 2432
f 2298 2302 2297
f 2244 2249 2243
f 1100 2382 2404
f 2227 2238 2245
f 2245 2257 2256
f 2257 2263 2256
f 2334 2328 2324
f 2257 2289 2263
f 2289 2302 2263
f 2250 2236 2231
f 2382 688 2138
f 2404 2382 2383
f 1100 2404 2343
f 2302 2353 2352
f 2353 2377 2352
f 2220 2237 2230
f 2335 2355 2325
f 2340 2315 2308
f 2276 2253 2269
f 2311 2335 2325
f 2424 511 2439
f 2248 2268 2267
f 2404 2383 413
f 123 971 832
f 2269 2234 2243
f 2234 2225 2213
f 2225 2219 2213
f 2212 2195 2196
f 1549 1544 2418
f 866 2404 413
f 2416 2404 866
f 2404 2416 2417
f 2343 2404 2417
f 2415 2409 2401
f 2219 2212 2196
f 2248 2218 2268
f 2197 2206 2214
f 2332 2343 2417
f 832 2343 2332
f 2289 2330 2302
f 2330 2353 2302
f 2454 515 2453
f 2217 2218 2248
f 2218 2217 2205
f 2281 2268 2276
f 2178 2197 2177
f 2177 2197 2189
f 2066 832 2332
f 123 832 2066
f 2231 2236 2230
f 950 1144 669
f 2217 2211 2199
f 1217 1216 1209
f 2365 123 2066
f 2214 2230 2226
f 2290 2325 2304
f 2325 2319 2304
f 2211 2217 2248
f 2192 2199 2191
f 510 525 2035
f 2332 2417 1917
f 2066 2332 1917
f 2408 2413 2341
f 2242 2248 2267
f 2281 2326 2333
f 1340 2365 2066
f 2440 1302 1340
f 2230 2235 2226
f 1163 1116 1153
f 2438 2431 2455
f 2417 2416 2425
f 2474 2462 2495
f 2290 2304 2277
f 1872 825 2227
f 151 239 1038
f 151 1038 9
f 928 2381 545
f 1384 2440 2406
f 1596 2381 928
f 2186 2188 2185
f 26 1888 2456
f 2262 2287 2333
f 2342 2417 2425
f 1917 2417 2342
f 877 2066 1917
f 2336 1340 2066
f 2440 1340 2336
f 2351 2327 2328
f 825 2238 2227
f 2351 2368 2327
f 1211 1222 2388
f 678 756 734
f 263 1343 428
f 2191 2190 2188
f 2341 2376 2333
f 2336 2066 877
f 2278 2290 2277
f 634 592 739
f 675 304 14
f 675 14 2384
f 2199 2211 2204
f 2199 2204 2191
f 2322 2318 2310
f 2233 2287 2262
f 2185 2188 2184
f 2425 845 2386
f 572 675 2384
f 1128 123 2365
f 971 2343 832
f 2186 2191 2188
f 2185 2184 2176
f 2345 1917 2342
f 877 1917 2345
f 2406 2440 2336
f 971 1100 2343
f 2299 2289 2257
f 2299 2303 2289
f 2243 2249 2255
f 513 512 506
f 955 1219 2437
f 1324 1587 2398
f 2396 2336 877
f 2406 2336 2396
f 2479 879 2463
f 2376 2412 2350
f 2267 2268 2281
f 2303 2330 2289
f 635 159 624
f 2356 1561 1996
f 1996 2449 2436
f 2451 2356 2054
f 1587 928 2398
f 2262 2333 2350
f 2035 26 2456
f 2342 2425 2346
f 2345 2342 2346
f 2418 1544 2380
f 2412 2392 2350
f 509 1151 622
f 2054 1996 2436
f 928 545 2451
f 2326 2341 2333
f 2346 2425 2386
f 1365 2035 2456
f 2353 2369 2377
f 2369 506 2377
f 900 928 2451
f 2398 928 900
f 1244 1235 1888
f 2337 2345 2346
f 772 2396 877
f 1275 2396 772
f 2432 2446 2414
f 2310 2294 2295
f 2330 828 2369
f 2436 2418 2419
f 2429 2436 2450
f 2054 2436 2429
f 2490 2494 1656
f 155 2338 1321
f 2346 2386 1256
f 2448 877 2345
f 772 877 2448
f 2414 2446 2423
f 2363 2351 2334
f 2269 2243 2254
f 2418 2380 2419
f 2450 2436 2419
f 2264 2283 2278
f 823 822 2197
f 1008 1759 1565
f 2448 2345 2337
f 2293 2276 2270
f 2328 2323 2324
f 1012 2054 2429
f 2213 2226 2243
f 2395 325 772
f 2380 2370 2367
f 2451 2054 2435
f 2397 2451 2435
f 900 2451 2397
f 1975 1774 1974
f 2283 2305 2290
f 846 2305 2283
f 2320 1215 2285
f 2139 2448 2337
f 2395 772 2448
f 1232 1231 1216
f 2285 2284 2272
f 2380 2367 2371
f 2405 2380 2371
f 2419 2380 2405
f 2429 2450 2419
f 1012 2429 176
f 2373 900 2397
f 2373 2398 900
f 1379 2398 2373
f 1500 1508 2372
f 1133 1303 1142
f 2272 2252 2273
f 2272 891 2252
f 2429 2419 2405
f 2430 2429 2405
f 176 2429 2430
f 2189 2186 2181
f 2218 2212 2219
f 2312 2139 2337
f 2384 2448 2139
f 2384 2395 2448
f 855 843 899
f 2285 2272 2273
f 2331 2303 2299
f 2435 2054 176
f 2054 1012 176
f 2177 2185 2176
f 2225 2218 2219
f 1216 1220 1215
f 2378 2139 2312
f 14 2395 2384
f 2324 2295 2255
f 2273 2252 2240
f 2405 2371 2387
f 2430 2405 2410
f 176 2430 2442
f 2344 2397 2435
f 2373 2397 2344
f 1888 2455 2456
f 2233 2242 2267
f 2229 2233 2262
f 2378 2384 2139
f 2323 2310 2295
f 2322 2310 2323
f 2273 2240 2274
f 990 974 841
f 1447 2486 2490
f 2410 2405 2387
f 2141 176 2442
f 1778 2373 2344
f 972 1379 2373
f 972 2373 1778
f 428 1379 972
f 2437 1223 1211
f 1220 1228 1215
f 702 2378 2312
f 17 518 2415
f 26 1244 1888
f 2324 2323 2295
f 2305 2311 2290
f 2307 2285 2273
f 2307 2273 2274
f 2320 2285 2307
f 2369 531 506
f 2344 2435 2258
f 2324 2288 2296
f 1233 1217 2361
f 2367 2360 2371
f 2442 2430 2410
f 2258 176 2141
f 2258 2435 176
f 66 539 2331
f 2350 2392 2317
f 2268 2225 2253
f 2371 1508 1500
f 2371 2360 1508
f 2371 1500 2387
f 2366 428 972
f 1686 1358 1626
f 1759 1807 1819
f 2277 2257 2245
f 2277 2299 2257
f 1736 1784 2228
f 2240 1736 2265
f 1736 2228 2265
f 2274 2240 2265
f 1209 2320 2307
f 1209 1216 2320
f 1555 1584 1560
f 2387 1500 2372
f 2442 2410 2420
f 972 1778 2433
f 2366 972 2433
f 522 1225 955
f 2339 2307 2274
f 1493 2387 2372
f 2420 2410 2411
f 954 2442 2420
f 2141 2442 954
f 2433 1778 2344
f 2212 2218 2205
f 2334 2351 2328
f 2394 2401 2414
f 2271 2250 2256
f 2339 1209 2307
f 2328 2322 2323
f 2425 866 845
f 3 316 893
f 2410 2387 2411
f 2441 2141 954
f 2441 2258 2141
f 2433 2344 2354
f 2294 2270 2254
f 2270 2269 2254
f 863 2305 846
f 2354 2258 2441
f 2354 2344 2258
f 2355 51 2319
f 1784 2223 2228
f 2411 2387 1493
f 1555 1560 2449
f 2288 2324 2255
f 825 2251 2238
f 2238 2251 2245
f 84 1312 1299
f 2228 2246 2265
f 2313 2274 2265
f 2313 2339 2274
f 2251 2277 2245
f 2319 51 2331
f 891 1862 2252
f 2443 954 2420
f 2441 954 2443
f 511 2447 2439
f 2211 2242 2233
f 814 188 15
f 2426 2441 2443
f 2426 2354 2441
f 2403 2433 2306
f 2403 2366 2433
f 2331 539 2303
f 2228 2223 2246
f 1807 1030 1819
f 2306 2433 2354
f 2413 2412 2376
f 1888 2438 2455
f 1848 1857 2176
f 2223 2207 2208
f 2223 2208 2246
f 1217 1209 2339
f 2361 1217 2339
f 1221 1210 2388
f 554 109 78
f 386 1375 95
f 2327 2326 2318
f 1393 2179 2182
f 1393 2182 2208
f 2207 1393 2208
f 2399 2388 2361
f 1211 2388 2399
f 2306 2354 2426
f 2359 2366 2403
f 2213 2214 2226
f 2276 2268 2253
f 2179 889 2200
f 2182 2179 2200
f 2182 2200 2221
f 2208 2182 2221
f 2246 2314 2265
f 2314 2313 2265
f 2374 2361 2339
f 2478 2434 2379
f 2217 2199 2205
f 2208 2259 2246
f 2259 2275 2246
f 2313 2314 2321
f 2347 2339 2313
f 2347 2374 2339
f 2399 2361 2374
f 154 2426 153
f 154 2306 2426
f 2359 2403 2385
f 2259 2208 2221
f 2357 2403 2306
f 2385 2403 2357
f 2231 2237 2256
f 889 2172 2180
f 2200 889 2180
f 2200 2201 2221
f 2291 2314 2246
f 2444 2399 2374
f 571 555 2311
f 2205 2199 2192
f 2172 2173 2180
f 2275 2279 2246
f 2279 2291 2246
f 2291 2292 2314
f 2362 2313 2321
f 2362 2347 2313
f 2389 2374 2347
f 955 2437 2444
f 2279 2292 2291
f 2452 2444 2374
f 2356 1996 2054
f 2338 2306 154
f 2192 2191 2186
f 2193 2201 2200
f 2201 2259 2221
f 2201 2247 2259
f 955 2444 2452
f 2251 2278 2277
f 2357 2306 2338
f 2181 2186 2185
f 2326 2281 2276
f 2432 2457 2446
f 2198 2201 2193
f 2198 2232 2201
f 2201 2232 2247
f 2389 2452 2374
f 1630 955 2452
f 1749 1444 1403
f 1561 1555 1996
f 2427 2385 2357
f 2428 230 2385
f 2415 2424 2409
f 2304 2331 2299
f 2193 2200 2180
f 2445 2452 2389
f 1759 1927 1565
f 1544 2370 2380
f 2427 2357 2338
f 2428 2385 2427
f 253 230 222
f 2202 2198 2193
f 2209 2198 2202
f 2209 2241 2198
f 2241 2232 2198
f 2259 2266 2275
f 1340 1128 2365
f 518 2424 2415
f 170 2427 2338
f 2428 2427 170
f 2177 2181 2185
f 2196 2195 2189
f 2183 2193 2180
f 2453 1630 2452
f 2197 2214 2189
f 2401 2409 2414
f 822 2220 2197
f 2388 1210 2361
f 2187 2193 2183
f 2187 2202 2193
f 2266 2279 2275
f 2300 2292 2279
f 2375 2347 2362
f 2375 2390 2347
f 2390 2389 2347
f 2453 2452 2445
f 1347 1630 2453
f 1347 522 1630
f 2197 2220 2206
f 2286 2262 2350
f 254 2428 170
f 2457 1973 2446
f 1973 1365 2446
f 2183 2180 2174
f 2194 2202 2187
f 2222 2241 2209
f 2241 2222 2260
f 2247 2266 2259
f 2390 2445 2389
f 825 2264 2251
f 2368 2351 2363
f 2326 2393 2341
f 1851 1855 1850
f 2210 2209 2202
f 2210 2222 2209
f 2222 2261 2260
f 2280 2279 2266
f 2280 2300 2279
f 251 263 2359
f 2277 2304 2299
f 2206 2220 2230
f 2194 2210 2202
f 2234 2213 2243
f 2327 2322 2328
f 2310 2293 2294
f 2214 2196 2189
f 2219 2196 2213
f 2224 2222 2210
f 2421 2390 2375
f 2206 2230 2214
f 2203 2210 2194
f 2222 2224 2261
f 2421 2445 2390
f 2327 2318 2322
f 2393 2408 2341
f 1973 510 1365
f 2216 2210 2203
f 2216 2224 2210
f 2308 2280 2266
f 2300 2280 2308
f 2407 2421 2375
f 2175 2183 2174
f 2203 2194 2190
f 2421 2454 2445
f 523 522 1347
f 2456 2455 2423
f 2178 823 2197
f 2287 2281 2333
f 2188 2187 2183
f 2190 2194 2188
f 2188 2194 2187
f 2315 2300 2308
f 2407 2375 2362
f 2443 2420 2503
f 2420 2411 2503
f 2411 1493 2503
f 2503 1493 1487
f 1318 2503 1487
f 1320 2503 1318
f 2443 2503 1320
f 2271 2298 2297
f 2250 2271 2297
f 2236 2250 2297
f 2296 2288 2255
f 2249 2244 2255
f 2244 2296 2255
f 2235 2236 2297
f 2244 2235 2296
f 2235 2297 2296
f 2292 2300 2314
f 2300 2315 2314
f 2315 2340 2314
f 2314 2362 2321
f 2348 2362 2314
f 2340 2348 2314
f 2368 2363 2364
f 2393 2368 2364
f 2393 2364 2414
f 2408 2393 2414
f 2413 2408 2414
f 2412 2413 2414
f 2412 2414 2423
f 2392 2412 2423
f 2391 2392 2400
f 2392 2423 2400
f 2317 2316 2309
f 2317 2309 2301
f 2286 2317 2301
f 2286 2301 2266
f 2261 2286 2266
f 2247 2261 2266
f 2241 2260 2232
f 2260 2261 2247
f 2232 2260 2247

(TODO: use PGA to implement cylindrical slicing so that 3D objects can be constructed by cutting and rolling paper.)

Example 3.3 polygon intersection testing

3D version of Separating Axis Theorem (SAT). The moving cube turns green only when a separating plane is detected between the two cubes, meaning the two cubes are not intersecting.

The inner product, fundamental to the implementation of the Separating Axis Theorem (SAT), was demonstrated in this essay’s REPL session D.

julia> include("ripga2d.jl"); # REPL D

julia> theta = pi/3
1.0471975511965976

julia> P = point(Float32.(
[1/4 5/4 1/4+cos(theta);
0 0 sin(theta)]));

julia> L = [P[:,1]&P[:,2] P[:,1]&P[:,3]];

julia> result = [basis P L L[:,1]|L[:,2]]
8×7 Matrix{Any}:
"1" 0.0 0.0 0.0 0.0 0.0 0.5
"e0" 0.0 0.0 0.0 0.0 -0.216506 0.0
"e1" 0.0 0.0 0.0 0.0 0.866025 0.0
"e2" 0.0 0.0 0.0 -1.0 -0.5 0.0
"e01" 0.0 0.0 0.866025 0.0 0.0 0.0
"e20" 0.25 1.25 0.75 0.0 0.0 0.0
"e12" 1.0 1.0 1.0 0.0 0.0 0.0
"e012" 0.0 0.0 0.0 0.0 0.0 0.0
# polyx3.jl
# polygon 3D intersection testing
# The Separating Axis Theorem (SAT) applies to only
# convex polygons. However, any non-convex polygon
# can be partitioned into convex polygons.
#
# NOTE:
# In this example, the vertices of each face should
# be indexed in the counter clockwise direction (i.e.,
# according to the right hand rule).
#
# NOTE2:
# This implementation of sat() is efficient in that it
# does not require the intermediate storage of all the
# projected distances and then a comparison of the
# minimum and maximum values of those projected distances.
# Instead, because of the choice of the origin and the
# ordering of the polygon vertices, the projected
# distances of the vertices within the polygon containing
# the origin are guaranteed to be less than or equal to
# zero and therefore don't need to be calculated at all.
# A separating axis is detected only when all the sides
# of the other polygon (not including the chosen origin)
# have projection distances greater than zero.
#
# The tetrahedron has three faces (the base face is
# empty), all edges of length 2.0, and a peak that
# is directly above (i.e., z offset) the origin. All
# the tetrahedron's vertices and faces are defined,
# according to the wavefront 3D object file format,
# by seven lines of text (within the following
# multiline comment):
#=
v 1.0 -0.5773502692 0.0
v 0.0 1.15470053838 0.0
v -1.0 -0.5773502692 0.0
v 0.0 0.0 1.632993162
f 1 2 4
f 2 3 4
f 3 1 4
f 1 3 2
=#
#
# The unit cube has four sides (the bottom and top
# sides are empty). All the unit cube's vertices and
# faces are defined, according to the wavefront 3D
# object file format, by 16 lines of text (within the following
# multiline comment):
#=
v 0.5 0.5 0
v -0.5 0.5 0
v -0.5 -0.5 0
v 0.5 -0.5 0
v 0.5 0.5 1
v -0.5 0.5 1
v -0.5 -0.5 1
v 0.5 -0.5 1
f 1 2 6
f 6 5 1
f 2 3 7
f 7 6 2
f 3 4 8
f 8 7 3
f 4 1 5
f 5 8 4
f 1 3 2
f 3 1 4
f 5 6 7
f 7 8 5
=#
using GLMakie, GLMakie.FileIO
using GeometryBasics # for normal_mesh()
using Images # for RGBA
include("ripga3d.jl") # needed for satpga() not sat()

function point(A::Point{3, Float32})
return A[1]*e032 + A[2]*e013 + A[3]*e021 + e123
end

# separating axis test
# arguments:
# vertices, faces, face normals of 2 3D objects
function sat(
AV::Vector{Point{3, Float32}},
AF::Vector{NgonFace{3, OffsetInteger{-1, UInt32}}},
AN::Matrix{Float32},
BV::Vector{Point{3, Float32}},
BF::Vector{NgonFace{3, OffsetInteger{-1, UInt32}}},
BN::Matrix{Float32})

# determine number of vertices and faces in 3D objects
nAV = length(AV)
nAF = length(AF)
nBV = length(BV)
nBF = length(BF)

# for each face of first 3D object
for iAF = 1:nAF
P0 = AV[AF[iAF]][1]
P1 = P0 + AN[:,iAF]
R01 = (P1 - P0)'
count = 0
for iBV = 1:nBV
P2 = BV[iBV]
d = R01 * (P2 - P0)
count = (d > 0) ? count + 1 : break
end
if (count == nBV)
return iAF;
end
end

# for each face of second 3D object
for iBF = 1:nBF
P0 = BV[BF[iBF]][1]
P1 = P0 + BN[:,iBF]
R01 = (P1 - P0)'
count = 0
for iAV = 1:nAV
d = R01 * (AV[iAV] - P0)
count = (d > 0) ? count + 1 : break
end
if (count == nAV)
return nAF + iBF;
end
end
return 0 # no gaps detected -> an intersection
end

# separating axis test using PGA
# arguments:
# vertices, faces, face normals of 2 3D objects
function satpga(
AV::Vector{Point{3, Float32}},
AF::Vector{NgonFace{3, OffsetInteger{-1, UInt32}}},
AN::Matrix{Float32},
BV::Vector{Point{3, Float32}},
BF::Vector{NgonFace{3, OffsetInteger{-1, UInt32}}},
BN::Matrix{Float32})

# determine number of vertices and faces in 3D objects
nAV = length(AV)
nAF = length(AF)
nBV = length(BV)
nBF = length(BF)

# for each face of first 3D object
for iAF = 1:nAF
P0 = point(AV[AF[iAF]][1])
P1 = point(AV[AF[iAF]][1] + AN[:,iAF])
count = 0
for iBV = 1:nBV
P2 = point(BV[iBV])
# Plane = ga"P0 · P1 · P2"
# proj = ga"P0 ∨ P1 · Plane"
# d = ga"(proj · (P0 ∨ P2 · Plane))[1]"
Plane = P0 & P1 & P2
proj = P0 & P1 | Plane
d = (proj | (P0 & P2 | Plane))[1]
count = (d > 0) ? count + 1 : break
end
if (count == nBV)
return iAF;
end
end

# for each face of second 3D object
for iBF = 1:nBF
P0 = point(BV[BF[iBF]][1])
P1 = point(BV[BF[iBF]][1] + BN[:,iBF])
count = 0
for iAV = 1:nAV
P2 = point(AV[iAV])
# Plane = ga"P0 · P1 · P2"
# proj = ga"P0 ∨ P1 · Plane"
# d = ga"(proj · (P0 ∨ P2 · Plane))[1]"
Plane = P0 & P1 & P2
proj = P0 & P1 | Plane
d = (proj | (P0 & P2 | Plane))[1]
count = (d > 0) ? count + 1 : break
end
if (count == nAV)
return nAF + iBF;
end
end
return 0 # no gaps detected -> an intersection
end

# calculate unit vectors normal to 3D object's faces
function calc_normals(X)
C,F = coordinates(X),faces(X)
nF = length(F)
res = Matrix{Float32}(undef,3,nF)
for iF = 1:nF
P = C[F[iF]]
V1 = P[2] - P[1]
V2 = P[3] - P[1]
N = [
V1[2]*V2[3] - V1[3]*V2[2];
-(V1[1]*V2[3] - V1[3]*V2[1]);
V1[1]*V2[2] - V1[2]*V2[1]]
mag = sqrt(N[1]^2 + N[2]^2 + N[3]^2)
res[:,iF] = N ./ mag
end
return res
end

# polygon 3D intersection testing
function polyx3()

# define path
nFrame = 360
nPath = nFrame + 1 # +1 to avoid duplicate frame
# in repeating animated gif
rMajor = 1.2
rMinor = 0.6
zMax = 1.1
THETAP = LinRange(0, 2*pi, nPath)'
PATH = Float32.([
rMajor.*cos.(THETAP);
rMinor.*sin.(THETAP);
zMax.*sin.(THETAP)])

# define spin of object and elevation of camera
THETAS = Float32.(LinRange(0, 6*pi, nPath)')
nFrame4 = div(nFrame,4,RoundDown)
THETAEL = Vector{Float32}(undef, nPath)
THETAEL[1:nFrame4] =
LinRange(pi/8, 0.95*pi/2, nFrame4)
THETAEL[nFrame4+1:3*nFrame4] =
LinRange(0.95*pi/2,0.05,2*nFrame4)
THETAEL[3*nFrame4+1:end] =
LinRange(0.05, pi/8, nFrame4+1)

# initialize figure
fig = Figure(resolution = (800, 800))
ax3d = GLMakie.Axis3(fig[1,1],
elevation = pi/8,
azimuth = -pi/4,
viewmode = :fit,
aspect = (1,1,1),
limits = (-2.,2., -2.,2., -2.,2.),
title = "polygon 3D intersection testing")

# load meshes of two convex 3D objects
C = load("ucube.obj") # Cube (stationary)
CV,CF,CN = coordinates(C),faces(C),calc_normals(C)
# T = load("tetrahedron.obj") # Tetrahedron (on path)
T = load("ucube.obj")
# T = deepcopy(C)
TV,TF = coordinates(T),faces(T)

# initialize plot containing observable data
iFrame = 45
# str = @sprintf("iFrame %d> ROT=%f; PATH=[%f %f %f]",
# iFrame, THETAS[iFrame],
# PATH[1,iFrame], PATH[2,iFrame], PATH[3,iFrame])
# println(str)
ROT = Float32.([
cos(THETAS[iFrame]) -sin(THETAS[iFrame]) 0;
sin(THETAS[iFrame]) cos(THETAS[iFrame]) 0;
0 0 1])
TV2 = Vector{Point{3, Float32}}(
map(c2 -> PATH[:,iFrame] + c2, # translate second
map(c -> ROT * c, TV))) # rotate first
T2 = GeometryBasics.Mesh(TV2, TF)
T_obs = Observable(T2)
TN2 = calc_normals(T2)
# iSepFace = sat(CV,CF,CN, TV2,TF,TN2)
iSepFace = satpga(CV,CF,CN, TV2,TF,TN2)
T_color_obs = iSepFace==0 ?
Observable(RGBA(1, 0, 0, 0.5)) :
Observable(RGBA(0, 1, 0, 0.5))
lines!(ax3d, PATH, color = :gold)
mesh!(ax3d, C,
color = RGBA(1, 0, 0, 0.5))
mesh!(ax3d, T_obs,
color = T_color_obs)
fig

# generate video
record(fig, "polyx3.mp4", 1:nFrame) do iFrame
ax3d.elevation[] = THETAEL[iFrame]
ROT = Float32.([
cos(THETAS[iFrame]) -sin(THETAS[iFrame]) 0;
sin(THETAS[iFrame]) cos(THETAS[iFrame]) 0;
0 0 1])
TV2 = Vector{Point{3, Float32}}(
map(c2 -> PATH[:,iFrame] + c2, # translate second
map(c -> ROT * c, TV))) # rotate first
T2 = GeometryBasics.Mesh(TV2, TF)
T_obs[] = T2
TN2 = calc_normals(T2)
# T_color_obs[] = sat(CV,CF,CN, TV2,TF,TN2)==0 ?
T_color_obs[] = satpga(CV,CF,CN, TV2,TF,TN2)==0 ?
RGBA(1, 0, 0, 0.5) :
RGBA(0, 1, 0, 0.5)
end # for each video frame
end # polyx()

# quick and dirty ffmpeg conversion from polyx3.mp4 to polyx3.gif:
# ffmpeg -i polyx3.mp4 -r 24 -s 480x480 -loop 0 polyx3.gif

The 3D inner product used in the above application is a little more complicated than the 2D inner product demonstrated in REPL session D because in nD, where n>2, the plane of the two lines must be specified in order for the orientation of the inner product to be correct. This (n>2)D inner product is demonstrated in the following REPL session.

julia> include("ripga2d.jl"); # REPL Example 3.3

julia> P = point(Float32.([
1/2 1/2 0.123;
1/2 3/2 0.456]));

julia> L = [normalize(P[:,1]&P[:,2]) normalize(P[:,1]&P[:,3])];

julia> result = [basis P L L[:,1]|L[:,2]]
8×7 Matrix{Any}:
"1" 0.0 0.0 0.0 0.0 0.0 -0.115924
"e0" 0.0 0.0 0.0 -0.5 -0.438667 0.0
"e1" 0.0 0.0 0.0 1.0 -0.115924 0.0
"e2" 0.0 0.0 0.0 0.0 0.993258 0.0
"e01" 0.5 1.5 0.456 0.0 0.0 0.0
"e20" 0.5 0.5 0.123 0.0 0.0 0.0
"e12" 1.0 1.0 1.0 0.0 0.0 0.0
"e012" 0.0 0.0 0.0 0.0 0.0 0.0

julia>

julia> include("ripga3d.jl");

julia> P = point(Float32.([
1/2 1/2 0.123;
1/2 3/2 0.456;
0 0 0]));

julia> L = [normalize(P[:,1]&P[:,2]) normalize(P[:,1]&P[:,3])];

julia> result2 = [basis P L L[:,1]|L[:,2]]
16×7 Matrix{Any}:
"1" 0.0 0.0 0.0 0.0 0.0 0.115924
"e0" 0.0 0.0 0.0 0.0 0.0 0.0
"e1" 0.0 0.0 0.0 0.0 0.0 0.0
"e2" 0.0 0.0 0.0 0.0 0.0 0.0
"e3" 0.0 0.0 0.0 0.0 0.0 0.0
"e01" 0.0 0.0 0.0 0.0 0.0 0.0
"e02" 0.0 0.0 0.0 0.0 0.0 0.0
"e03" 0.0 0.0 0.0 -0.5 -0.438667 0.0
"e12" 0.0 0.0 0.0 0.0 0.0 0.0
"e31" 0.0 0.0 0.0 -1.0 0.115924 0.0
"e23" 0.0 0.0 0.0 0.0 0.993258 0.0
"e021" 0.0 0.0 0.0 0.0 0.0 0.0
"e013" 0.5 1.5 0.456 0.0 0.0 0.0
"e032" 0.5 0.5 0.123 0.0 0.0 0.0
"e123" 1.0 1.0 1.0 0.0 0.0 0.0
"e0123" 0.0 0.0 0.0 0.0 0.0 0.0

julia>

julia> Plane = P[:,1] & P[:,2] & P[:,3];

julia> L = [normalize(P[:,1]&P[:,2]|Plane) normalize(P[:,1]&P[:,3]|Plane)];

julia> result3 = [basis P Plane L L[:,1]|L[:,2]]
16×8 Matrix{Any}:
"1" 0.0 0.0 0.0 0.0 0.0 0.0 -0.115924
"e0" 0.0 0.0 0.0 0.0 0.5 0.438667 0.0
"e1" 0.0 0.0 0.0 0.0 -1.0 0.115924 0.0
"e2" 0.0 0.0 0.0 0.0 0.0 -0.993258 0.0
"e3" 0.0 0.0 0.0 -0.377 0.0 0.0 0.0
"e01" 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"e02" 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"e03" 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"e12" 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"e31" 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"e23" 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"e021" 0.0 0.0 0.0 0.0 0.0 0.0 0.0
"e013" 0.5 1.5 0.456 0.0 0.0 0.0 0.0
"e032" 0.5 0.5 0.123 0.0 0.0 0.0 0.0
"e123" 1.0 1.0 1.0 0.0 0.0 0.0 0.0
"e0123" 0.0 0.0 0.0 0.0 0.0 0.0 0.0

2D version of Separating Axis Theorem (SAT). The Separating Axis Theorem applies only to convex polygons. However, any arbitrary polygon can be partitioned into convex polygons and SAT can be applied to each partition.

The Separating Axis Theorem detects separated polygons only if the polygons are complex. However, all non-convex polygons can be partitioned into convex polygons, as shown above where the rotating concave polygon is partitioned into two rotating convex polygons. The implementation of the separating axis test is based upon a lot of 2D dot products and implementing 2D dot products in PGA is overkill but the following listing includes both sat() (i.e., a non-PGA implementation of the separating axis test) and satpga() (i.e., a PGA implementation of the separating axis test).

# polyx.jl
# polygon intersection testing
# The Separating Axis Theorem (SAT) applies to only
# convex polygons. However, any non-convex polygon
# can be partitioned into convex polygons, as done
# in this example.
#
# NOTE:
# In this example, the vertices of each polygon should
# be indexed in the counter clockwise direction (i.e.,
# according to the right hand rule).
#
# NOTE2:
# This implementation of sat() is efficient in that it
# does not require the intermediate storage of all the
# projected distances and then a comparison of the
# minimum and maximum values of those projected distances.
# Instead, because of the choice of the origin and the
# ordering of the polygon vertices, the projected
# distances of the vertices within the polygon containing
# the origin are guaranteed to be less than or equal to
# zero and therefore don't need to be calculated at all.
# A separating axis is detected only when all the sides
# of the other polygon (not including the chosen origin)
# have projection distances greater than zero.
#
using GLMakie
using Images # for RGBA
include("ripga2d.jl") # needed for satpga() not sat()

# separating axis test
function sat(A::Matrix{Float32}, B::Matrix{Float32})
nA = size(A, 2)
nB = size(B, 2)
TOPERP = [0 1; -1 0] # rotate to outward perpendicular
for iA = 1:nA-1
P0 = A[:, iA]
DA = (TOPERP * (A[:, iA+1] - P0))'
count = 0
for iB = 1:nB-1
d = DA * (B[:, iB] - P0)
count = (d > 0) ? count + 1 : break
end
if (count == nB-1) return iA; end
end
for iB = 1:nB-1
P0 = B[:, iB]
DB = (TOPERP * (B[:, iB+1] - P0))'
count = 0
for iA = 1:nA-1
d = DB * (A[:, iA] - P0)
count = (d > 0) ? count + 1 : break
end
if (count == nA-1) return nA + iB; end
end
return 0
end

# separating axis test ... PGA version
#
# NOTE:
# The separating axis test is fundamentally a bunch
# of 2D dot products. Employing PGA to implement 2D
# dot products is overkill but it can be done and is
# only a little slower. For example, my laptop computer
# takes 3.1 seconds to run the PGA version to generate
# the 15 second video and 3.0 seconds to run the nonPGA
# version to generate the same 15 second video.
#
function satpga(A::Matrix{Float32}, B::Matrix{Float32})
nA = size(A, 2)
nB = size(B, 2)
AX = point(A)
BX = point(B)
for iA = 1:nA-1
# lineProj = Float32.(rotor(pi/2, AX[:,iA])) >>>
# ga"(AX[:,iA]∨AX[:,iA+1])"
lineProj = Float32.(rotor(pi/2, AX[:,iA])) >>>
(AX[:,iA] & AX[:,iA+1])
count = 0
for iB = 1:nB-1
# d = ga"((AX[:,iA]∨BX[:,iB])·lineProj)[1]"
d = ((AX[:,iA] & BX[:,iB]) | lineProj)[1]
count = (d > 0) ? count + 1 : break
end
if (count == nB-1) return iA; end
end
for iB = 1:nB-1
# lineProj = Float32.(rotor(pi/2, BX[:,iB])) >>>
# ga"(BX[:,iB]∨BX[:,iB+1])"
lineProj = Float32.(rotor(pi/2, BX[:,iB])) >>>
(BX[:,iB] & BX[:,iB+1])
count = 0
for iA = 1:nA-1
# d = ga"((BX[:,iB]∨AX[:,iA])·lineProj)[1]"
d = ((BX[:,iB] & AX[:,iA]) | lineProj)[1]
count = (d > 0) ? count + 1 : break
end
if (count == nA-1) return nA + iB; end
end
return 0
end

# polygon intersection testing
function polyx()

# initialize figure
fig = Figure(resolution = (800, 800))
LIM = (-8,8, -8,8)
ax2d = GLMakie.Axis(fig[1,1], limits=LIM, aspect=1,
title = "polygon intersection testing")

# define shapes and paths
iFrame = 1
nFrame = 360
nPath = nFrame + 1 # +1 to avoid duplicate frame
# in repeating animated gif
rMajor = 5
rMinor = 3
THETAP = LinRange(0, 2*pi, nPath)'
PATH = [rMinor.*cos.(THETAP); rMajor.*sin.(THETAP)]
THETAS = LinRange(0, 6*pi, nPath)'

nA = 3
rA = 2
xA = 0
yA = 0
THETAA = LinRange(0, 2*pi, nA+1)'
A = Float32.(
[xA .+ rA.*cos.(THETAA); # polygon A
yA .+ rA.*sin.(THETAA)])

nB = 6
rB = 4
xB = rMinor
yB = 0
THETAB = LinRange(0, 2*pi, nB+1)'
BV = [rB.*cos.(THETAB); # polygon B
rB.*sin.(THETAB)]
BV[:,div(nB,2,RoundDown)+1] .= 0
B = Matrix{Float32}[]
push!(B, hcat(BV[:,1:4], BV[:,1]))
push!(B, hcat(BV[:,1], BV[:,4], BV[:,5:end]))

# initialize plot containing observable data
MB = Matrix{Float32}[] # mobile convex polygon partitions
push!(MB, Float32.(B[1] .+ PATH[:,iFrame]))
push!(MB, Float32.(B[2] .+ PATH[:,iFrame]))
OBS_MB = Observable[]
push!(OBS_MB, Observable(MB[1]))
push!(OBS_MB, Observable(MB[2]))
OBS_C = Observable[]
push!(OBS_C, Observable(RGBA(0, 1, 0, 0.5)))
push!(OBS_C, Observable(RGBA(0, 1, 0, 0.5)))
poly!(ax2d, A, color = RGBA(1, 0, 0, 0.5))
poly!(ax2d, OBS_MB[1], color = OBS_C[1])
poly!(ax2d, OBS_MB[2], color = OBS_C[2])
lines!(ax2d, PATH, linestyle = :dot, color = :gray)
iSep = sat(A,MB[1])
fig

# record spinning polynomial following path
record(fig, "polyx.mp4", 1:nFrame) do iFrame
ROT = [cos(THETAS[iFrame]) -sin(THETAS[iFrame]);
sin(THETAS[iFrame]) cos(THETAS[iFrame])]
nPartition = length(B)
for iPartition = 1:nPartition
MB[iPartition] = Float32.((ROT * B[iPartition])
.+ PATH[:,iFrame])
OBS_C[iPartition][] = (satpga(A, MB[iPartition]) == 0) ?
RGBA(1, 0, 0, 0.5) : RGBA(0, 1, 0, 0.5)
OBS_MB[iPartition][] = MB[iPartition]
end # for each partition
end # for each video frame
end # polyx()

# quick and dirty ffmpeg conversion from polyx.mp4 to polyx.gif:
# ffmpeg -i polyx.mp4 -r 24 -s 480x480 -loop 0 polyx.gif

Example 3.4 origami

Rotation is the fundamental operation in the origami application. The PGA sandwich operator (>>>) rotates a point about a pivot line. As shown in the following REPL session, the orientation of the pivot line affects the direction of the rotation.

julia> include("ripga3d.jl"); # REPL 1 origami

julia> P = point(Float32.([ # PGA points
0 0 1;
0 0 0;
0 1 0]));

julia> PL = P[:,1] & P[:,2]; # Pivot Line

julia> R = rotor(pi/2, PL);

julia> result = toCoord(R >>> P[:,3])
3-element Vector{Float32}:
0.0
0.99999994
0.0

julia> PL2 = P[:,2] & P[:,1]; # Pivot Line 2

julia> R2 = rotor(pi/2, PL2); # Rotor 2

julia> result2 = toCoord(R2 >>> P[:,3])
3-element Vector{Float32}:
0.0
-0.99999994
0.0

A common argument against using PGA is that it inefficiently takes 16 floats to store a 3D point instead of just three. However in many applications, only a small portion of the 3D points are operated upon at any time and only those would need to be converted to PGA points. For example in this origami application, all the points are stored in a Makie mesh and only the points in that mesh getting rotated during a fold are temporarily converted to PGA points. The following REPL session demonstrates how to construct a Makie mesh directly from 3D points (e.g., after they have been rotated using PGA).

julia> include("ripga3d.jl"); # REPL 2 origami

julia> using GLMakie;

julia> using GeometryBasics;

julia> PC = Float32.([ # Point Coordinates
0 0 1;
0 0 0;
0 1 0]);

julia> V = Point{3, Float32}[]; # mesh Vertices

julia> push!(V,PC[:,1]); push!(V,PC[:,2]); push!(V,PC[:,3]);

julia> F = TriangleFace{Int64}[]; # mesh Faces

julia> push!(F,[1,2,3]);

julia> result = GeometryBasics.Mesh(V,F)
Mesh{3, Float32, Triangle}:
Triangle(Float32[0.0, 0.0, 0.0], Float32[0.0, 0.0, 1.0], Float32[1.0, 0.0, 0.0])
# origami.jl : animate folding of kabuto (Samurai helmet)
using GLMakie
using GeometryBasics
using Images # for RGBA
include("ripga3d.jl")

# convert coordinate Point{3, Float32} to/from PGA point
function point(A::Point{3, Float32})::Vector{Float32}
return A[1]*e032 + A[2]*e013 + A[3]*e021 + e123
end
function point(A::Vector{Point{3, Float32}})
nCol = length(A)
res = Matrix{Float32}(undef, (16,nCol))
for iCol = 1:nCol
res[:,iCol] = A[iCol][1]*e032 + A[iCol][2]*e013 +
A[iCol][3]*e021 + e123
end
return res
end
function toPoint(A::Vector{Float32})
return Point{3, Float32}(A[14],A[13],A[12])
end
function toPoint(A::Matrix{Float32})
res = Point{3, Float32}[]
nCol = size(A,2)
for iCol = 1:nCol
push!(res, Point{3, Float32}(A[14,iCol],
A[13,iCol], A[12,iCol]))
end
return res
end

# fold
#
# NOTE:
# The mesh's vertices (SV) do not change, but the
# mesh's faces (SF) do change: new folds modify some
# previous faces and generate new face(s). During a
# fold, some faces don't rotate and some do. To help
# with the bookkeeping, the faces that rotate during
# the fold are typically put at the end of the face
# vector.
#
function fold(iFold::Int64,
SV::Vector{Point{3, Float32}}, # Sheet Vertices (i/o)
SF::Vector{TriangleFace{Int64}}, # Sheet Faces (i/o)
PL::Vector{Float32}, # Pivot Line (output)
MVI::Vector{Int64}) # Moving Vertex Indices (output)

# initialize data
nSweep = 1

# fold 1: diagonal fold that puts
# vertex 1 on top of vertex 3
if iFold == 1
# calculate Pivot Line
PL[:] = point(SV[2]) & point(SV[4])

# generate new faces in mesh
push!(SF, [2,3,4]) # 1.
push!(SF, [1,2,4]) # 2. rotating

# identify the rotating vertices
empty!(MVI)
push!(MVI, 1)
push!(MVI, 7)
push!(MVI, 8)
push!(MVI, 10)
push!(MVI, 11)
push!(MVI, 16)
push!(MVI, 17)
push!(MVI, 20)
push!(MVI, 21)
push!(MVI, 23)
push!(MVI, 24)
push!(MVI, 25)
push!(MVI, 26)
push!(MVI, 27)
push!(MVI, 28)

# fold 2: horizontal fold that puts
# vertex 4 on top of vertex 3
elseif iFold == 2
# calculate Pivot Line
PL[:] = point(SV[5]) & point(SV[6])

# modify existing faces in mesh
deleteat!(SF, 1); insert!(SF, 1, [2,3,5]) # 1.
deleteat!(SF, 2); insert!(SF, 2, [1,2,5]) # 2.

# generate new faces in mesh
push!(SF, [3,6,5]) # 3.
push!(SF, [1,5,8]) # 4.
push!(SF, [4,5,6]) # 5. rotating
push!(SF, [4,8,5]) # 6. rotating

# identify the rotating vertices
empty!(MVI)
push!(MVI, 4)
push!(MVI, 14)
push!(MVI, 15)
push!(MVI, 16)
push!(MVI, 19)
push!(MVI, 20)

# fold 3: vertical fold that puts
# vertex 2 on top of vertex 3
elseif iFold == 3
# calculate Pivot Line
PL[:] = point(SV[9]) & point(SV[5])

# modify existing faces in mesh
deleteat!(SF, 1); insert!(SF, 1, [9,3,5]) # 1.
deleteat!(SF, 2); insert!(SF, 2, [1,5,7]) # 2.

# generate new faces in mesh
push!(SF, [2,9,5]) # 7. rotating
push!(SF, [5,7,2]) # 8. rotating

# identify the rotating vertices
empty!(MVI)
push!(MVI, 2)
push!(MVI, 12)
push!(MVI, 17)
push!(MVI, 18)
push!(MVI, 21)
push!(MVI, 22)

# fold 4: diagonal fold that puts
# vertex 4 on top of vertex 5
elseif iFold == 4
# calculate Pivot Line
PL[:] = point(SV[6]) & point(SV[9])

# modify existing faces in mesh
deleteat!(SF, 5); insert!(SF, 5, [5,6,14]) # 5.
deleteat!(SF, 6); insert!(SF, 6, [5,8,14]) # 6.

# generate new faces in mesh
push!(SF, [4,14,6]) # 9. rotating
push!(SF, [4,14,8]) #10. rotating

# identify the rotating vertices
empty!(MVI)
push!(MVI, 4)
push!(MVI, 15)
push!(MVI, 16)
push!(MVI, 19)
push!(MVI, 20)

# fold 5: diagonal fold that puts
# vertex 2 on top of vertex 5
elseif iFold == 5
# calculate Pivot Line
PL[:] = point(SV[6]) & point(SV[9])

# modify existing faces in mesh
deleteat!(SF, 7); insert!(SF, 7, [5,9,12]) # 7.
deleteat!(SF, 8); insert!(SF, 8, [5,7,12]) # 8.

# generate new faces in mesh
push!(SF, [9,12,2]) #11. rotating
push!(SF, [7,12,2]) #12. rotating

# identify the rotating vertices
empty!(MVI)
push!(MVI, 2)
push!(MVI, 17)
push!(MVI, 18)
push!(MVI, 21)
push!(MVI, 22)

# fold 6: vertical crease
elseif iFold == 6
# calculate Pivot Line
PL[:] = point(SV[14]) & point(SV[16])

# modify existing faces in mesh
deleteat!(SF, 9); insert!(SF, 9, [6,14,15]) # 9.
deleteat!(SF, 10); insert!(SF, 10, [8,14,16]) # 10.

# generate new faces in mesh
push!(SF, [4,14,16]) #13. rotating
push!(SF, [4,14,15]) #14. rotating

# identify the rotating vertices
empty!(MVI)
push!(MVI, 4)
push!(MVI, 19)
push!(MVI, 20)

# sweep back and forth
nSweep = 2

# fold 7: horizontal crease
elseif iFold == 7
# calculate Pivot Line
PL[:] = point(SV[18]) & point(SV[12])

# modify existing faces in mesh
deleteat!(SF, 11); insert!(SF, 11, [9,18,12]) # 11.
deleteat!(SF, 12); insert!(SF, 12, [7,17,12]) # 12.

# generate new faces in mesh
push!(SF, [12,2,18]) #15. rotating
push!(SF, [12,2,17]) #16. rotating

# identify the rotating vertices
empty!(MVI)
push!(MVI, 2)
push!(MVI, 21)
push!(MVI, 22)

# sweep back and forth
nSweep = 2

# fold 8: near diagonal fold that aligns
# vertex 4 with vertices 14 and 16
elseif iFold == 8
# calculate Pivot Line
PL[:] = point(SV[14]) & point(SV[20])

# modify existing faces in mesh
deleteat!(SF, 13); insert!(SF, 13, [20,14,16]) # 13.
deleteat!(SF, 14); insert!(SF, 14, [19,14,15]) # 14.

# generate new faces in mesh
push!(SF, [4,14,20]) #17. rotating
push!(SF, [4,14,19]) #18. rotating

# identify the rotating vertices
empty!(MVI)
push!(MVI, 4)

# fold 9: near diagonal fold that aligns
# vertex 2 with vertices 12 and 17
elseif iFold == 9
# calculate Pivot Line
PL[:] = point(SV[22]) & point(SV[12])

# modify existing faces in mesh
deleteat!(SF, 15); insert!(SF, 15, [22,12,18]) # 15.
deleteat!(SF, 16); insert!(SF, 16, [21,12,17]) # 16.

# generate new faces in mesh
push!(SF, [2,12,22]) #19. rotating
push!(SF, [2,12,21]) #20. rotating

# identify the rotating vertices
empty!(MVI)
push!(MVI, 2)

# fold 10: diagonal fold that puts
# vertex 1 on top of vertex 10
elseif iFold == 10
# calculate Pivot Line
PL[:] = point(SV[24]) & point(SV[23])

# modify existing faces in mesh
deleteat!(SF, 1); insert!(SF, 1, [9,13,5]) # 1.
deleteat!(SF, 2); insert!(SF, 2, [7,11,5]) # 2.
deleteat!(SF, 3); insert!(SF, 3, [13,6,5]) # 3.
deleteat!(SF, 4); insert!(SF, 4, [11,5,8]) # 4.

# generate new faces in mesh
push!(SF, [9,3,13]) # 21.
push!(SF, [3,6,13]) # 22.
push!(SF, [7,23,11]) # 23.
push!(SF, [23,25,11]) # 24.
push!(SF, [23,1,25]) # 25.
push!(SF, [8,24,11]) # 26.
push!(SF, [24,25,11]) # 27.
push!(SF, [24,1,25]) # 28.

# identify the rotating vertices
empty!(MVI)
push!(MVI, 1)
push!(MVI, 26)
push!(MVI, 27)
push!(MVI, 28)

# fold 11: diagonal fold to finish front brim
elseif iFold == 11
# calculate Pivot Line
PL[:] = point(SV[8]) & point(SV[7])

# modify existing faces in mesh
deleteat!(SF, 25); insert!(SF, 25, [26,1,28]) # 25.
deleteat!(SF, 28); insert!(SF, 28, [27,1,28]) # 28.

# generate new faces in mesh
push!(SF, [23,26,28]) # 29.
push!(SF, [28,25,23]) # 30.
push!(SF, [24,27,28]) # 31.
push!(SF, [28,25,24]) # 32.

# identify the rotating vertices
empty!(MVI)
push!(MVI, 23)
push!(MVI, 24)
push!(MVI, 25)

# fold 12: diagonal fold to finish back brim
elseif iFold == 12
# calculate Pivot Line
PL[:] = point(SV[7]) & point(SV[8])

# identify the rotating vertices
empty!(MVI)
push!(MVI, 3)
end
return nSweep
end

function origami()
# initialize data about sheet
nFold = 12
x = 1 + tan(pi/8)
SV = Point{3, Float32}[ # Sheet Vertices
[-2, 0, 2], # 1. upper left corner of unfolded sheet
[-2, 0, -2], # 2. lower left corner of unfolded sheet
[2, 0, -2], # 3. lower right corner of unfolded sheet
[2, 0, 2], # 4. upper right corner of unfolded sheet
[0, 0, 0], # 5. center of unfolded sheet
[2, 0, 0], # 6. center of right edge of unfolded sheet
[-2, 0, 0], # 7. center of left edge of unfolded sheet
[0, 0, 2], # 8. center of top edge of unfolded sheet
[0, 0, -2], # 9. center of bottom edge of unfolded sheet
[-1/2, 0, 1/2], #10.
[-1, 0, 1], #11. upper left center of folded square
[-1, 0, -1], #12. lower left center of folded square
[1, 0, -1], #13. lower right center of folded square
[1, 0, 1], #14. upper right center of folded square
[2, 0, 1], #15. right edge quarter length
[1, 0, 2], #16. top edge quarter length
[-2, 0, -1], #17. left edge quarter length
[-1, 0, -2], #18. bottom edge quarter length
[2, 0, x], #19. right edge eighth length
[x, 0, 2], #20. top edge eighth length
[-2, 0, -x], #21. left edge eighth length
[-x, 0, -2], #22. bottom edge eighth length
[-2, 0, 1/2], #23.
[-1/2, 0, 2], #24.
[-5/4, 0, 5/4], #25.
[-2, 0, 1], #26.
[-1, 0, 2], #27.
[-3/2, 0, 3/2]] #28.
SF = NgonFace{3, Int64}[] # Sheet Faces (no folds -> no faces)
S = GeometryBasics.Mesh(SV,SF) # mesh of Sheet
S_obs = Observable(S)

# precalculate some rotation angles
nFrameFold = 100 # number of video frames per fold
nFrameShow = 400 # number of video frames to show kabuto
THETAFOLD = LinRange(0, pi, nFrameFold)
THETAFOLD2 = pi .* (1 .-
abs.(2 .* ((1:nFrameFold) .- 1) ./ nFrameFold .- 1))
phi0 = -pi/4
PHISHOW = LinRange(phi0, phi0+2*pi, nFrameShow)

# initialize figure
strTitle = @sprintf("fold %d of %d", 0, nFold)
str_obs = Observable(strTitle)
fig = Figure(resolution = (600, 650))
ax3d = Axis3(fig[1,1],
elevation = pi/16,
azimuth = phi0,
viewmode = :fit,
limits = (-2,2, -2,2, -2,2),
aspect = (1,1,1),
title = str_obs)
poly!(ax3d, S_obs,
color = RGBA(1, 1, 0.9, 0.8),
strokewidth = 1)
fig

# generate video of folding
iFold = 0
nFrame = nFold * nFrameFold + nFrameShow
nSweep = 0
SP = Point{3, Float32}[]# Starting Point(s)
PL = zeros(Float32, 16) # Pivot Line PGA expression
MVI = zeros(Int64, 1) # Moving Vertex Indices
record(fig, "origami.mp4", 1:nFrame) do iFrame
iFrameMod = mod(iFrame-1, nFrameFold)

# if time for a new fold
if iFrameMod == 0
iFold += 1
nSweep = fold(iFold, SV, SF, PL, MVI)
SP = point(SV[MVI]) # Starting Point
str_obs[] = (iFold <= nFold) ?
@sprintf("fold %d of %d", iFold, nFold) :
@sprintf("kabuto (Samurai helmet)")
end

# if folding origami
if iFold <= nFold
angle = nSweep == 1 ?
THETAFOLD[iFrameMod+1] :
THETAFOLD2[iFrameMod+1]
R = rotor(angle, PL)
MP = R >>> SP # Moving Point
SV[MVI] = toPoint(MP)
S_obs[] = GeometryBasics.Mesh(SV, SF)

# else showing finished origami
else
ax3d.azimuth[] = PHISHOW[iFrame - nFold*nFrameFold]
end
end # for each video frame
end # origami()

# quick and dirty ffmpeg conversion from origami.mp4 to origami.gif:
# ffmpeg -i origami.mp4 -r 24 -s 460x480 -loop 0 origami.gif
# ffmpeg -i origami.mp4 -r 24 -s 600x650 -loop 0 origami.gif

4. Conclusion

(TODO)

Although I’m not an expert at Julia or projective geometric algebra, they have both been very helpful in designing a new type of tactile display for the blind. There are many other urgent problems that are also fundamentally geometry problems, each offering a guided path to learning Julia and PGA.

Appendix — reference implementation of PGA based upon bivector.net’s C++ reference implementation of PGA

This reference implementation of Projective Geometric Algebra is split into four files:

  • ripga2d.jl for 2D geometry,
  • ripga3d.jl for 3D geometry,
  • ripga4d.jl for 4D geometry and
  • ripgand.jl for functions common to any dimension geometry.
# ripga2d.jl : reference implementation of 
# Projective Geometric Algebra for 2D
#
# This is a Julia port of bivector.net's C++ reference
# implementation of projective geometric algebra
# available at https://bivector.net/tools.html
#
using Printf

# define multivector basis names
# 0 denotes projective dimension (e.g., e0 * e0 = 0)
basis = [ # iField
"1" # 1 scalar
"e0" # 2 grade 1 vectors
"e1" # 3
"e2" # 4
"e01" # 5 grade 2 vectors (bivectors)
"e20" # 6
"e12" # 7
"e012"] # 8 pseudoscalar

# define the basis elements
nField = 2^3 # 3 = 2D + 1 dimensions
e0 = zeros(Float32, nField); e0[2] = 1
e1 = zeros(Float32, nField); e1[3] = 1
e2 = zeros(Float32, nField); e2[4] = 1
e01 = zeros(Float32, nField); e01[5] = 1
e20 = zeros(Float32, nField); e20[6] = 1
e12 = zeros(Float32, nField); e12[7] = 1
e012 = zeros(Float32, nField); e012[8] = 1

function Base.:*(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
res = similar(a)
res[1]=a[1]*b[1]+a[3]*b[3]+a[4]*b[4]-a[7]*b[7]
res[2]=a[1]*b[2]+a[2]*b[1]+a[5]*b[3]-a[3]*b[5]+a[4]*b[6]-a[6]*b[4]-a[7]*b[8]-a[8]*b[7]
res[3]=a[1]*b[3]+a[3]*b[1]-a[4]*b[7]+a[7]*b[4]
res[4]=a[1]*b[4]+a[4]*b[1]+a[3]*b[7]-a[7]*b[3]
res[5]=a[1]*b[5]+a[5]*b[1]+a[2]*b[3]-a[3]*b[2]+a[4]*b[8]+a[8]*b[4]+a[6]*b[7]-a[7]*b[6]
res[6]=a[1]*b[6]+a[6]*b[1]-a[2]*b[4]+a[4]*b[2]+a[3]*b[8]+a[8]*b[3]-a[5]*b[7]+a[7]*b[5]
res[7]=a[1]*b[7]+a[7]*b[1]+a[3]*b[4]-a[4]*b[3]
res[8]=a[1]*b[8]+a[8]*b[1]+a[2]*b[7]+a[7]*b[2]+a[3]*b[6]+a[6]*b[3]+a[4]*b[5]+a[5]*b[4]
return res
end

function Base.:&(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
res = similar(a)
res[1]=a[1]*b[8]+a[2]*b[7]+a[3]*b[6]+a[4]*b[5]+a[5]*b[4]+a[6]*b[3]+a[7]*b[2]+a[8]*b[1]
res[2]=a[2]*b[8]+a[8]*b[2]+a[5]*b[6]-a[6]*b[5]
res[3]=a[3]*b[8]+a[8]*b[3]-a[5]*b[7]+a[7]*b[5]
res[4]=a[4]*b[8]+a[8]*b[4]+a[6]*b[7]-a[7]*b[6]
res[5]=a[5]*b[8]+a[8]*b[5]
res[6]=a[6]*b[8]+a[8]*b[6]
res[7]=a[7]*b[8]+a[8]*b[7]
res[8]=a[8]*b[8]
return res
end

function Base.:|(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
res = similar(a)
res[1]=a[1]*b[1]+a[3]*b[3]+a[4]*b[4]-a[7]*b[7]
res[2]=a[1]*b[2]+a[2]*b[1]+a[5]*b[3]-a[6]*b[4]-a[3]*b[5]+a[4]*b[6]-a[7]*b[8]-a[8]*b[7]
res[3]=a[1]*b[3]+a[3]*b[1]-a[4]*b[7]+a[7]*b[4]
res[4]=a[1]*b[4]+a[4]*b[1]+a[3]*b[7]-a[7]*b[3]
res[5]=a[1]*b[5]+a[5]*b[1]+a[4]*b[8]+a[8]*b[4]
res[6]=a[1]*b[6]+a[6]*b[1]+a[3]*b[8]+a[8]*b[3]
res[7]=a[1]*b[7]+a[7]*b[1]
res[8]=a[1]*b[8]+a[8]*b[1]
return res
end

function Base.:^(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
res = similar(a)
res[1]=a[1]*b[1]
res[2]=a[1]*b[2]+a[2]*b[1]
res[3]=a[1]*b[3]+a[3]*b[1]
res[4]=a[1]*b[4]+a[4]*b[1]
res[5]=a[1]*b[5]+a[5]*b[1]+a[2]*b[3]-a[3]*b[2]
res[6]=a[1]*b[6]+a[6]*b[1]-a[2]*b[4]+a[4]*b[2]
res[7]=a[1]*b[7]+a[7]*b[1]+a[3]*b[4]-a[4]*b[3]
res[8]=a[1]*b[8]+a[2]*b[7]+a[3]*b[6]+a[4]*b[5]+a[5]*b[4]+a[6]*b[3]+a[7]*b[2]+a[8]*b[1]
return res
end

function Base.:~(a::Vector{Float32}) # reverse operator
res = copy(a)
res[5:8] .*= -1
return res
end

function conjugate(a::Vector{Float32})::Vector{Float32}
res = copy(a)
res[2:7] .*= -1
return res
end

function normIdeal(a::Vector{Float32})
return sqrt(a[2]^2)
end

# unit test
# arguments:
# - nLoop repeats a section of the PGA calculations for benchmarking
# usage notes:
# - @time utest(1) checks on whether the unit test output of ripga.jl
# exactly matches the unit test output of pga3d.cpp. The comparison
# ends with the printing of the number of tests in the unit test that
# don't match. 0 indicates success.
# - @time utest(1,true) outputs a slightly simplified version of the
# unit test output that does not match the unit test output by pga3d.cpp.
# - @btime utest() is a test for execution speed of ripga.jl.
# (NOTE: requires using BenchmarkTools)
function utest(nLoop=100, flgSimplify::Bool=false)
nField = length(basis)
P0 = Vector{Float32}(undef,nField)
P1 = Vector{Float32}(undef,nField)
P2 = Vector{Float32}(undef,nField)
P3 = Vector{Float32}(undef,nField)
line0 = Vector{Float32}(undef,nField)
line1 = Vector{Float32}(undef,nField)
x = Vector{Float32}(undef,nField)
tst1 = Vector{Float32}(undef,nField)
tst2 = Vector{Float32}(undef,nField)

for iLoop = 1:nLoop
# define some points
P0 = point(0,0)
P1 = point(1,0)
P2 = point(0,1)
P3 = point(1,1)

# calculate intersection of parallel lines
line0 = P0 & P1
line1 = P2 & P3
x = line0 ^ line1

tst1 = e0 - 1f0
tst2 = 1f0 - e0

# # geometric algebra equations in math syntax
# ga"axis_z = e1 ∧ e2"
# ga"origin = axis_z ∧ e3"
#
# px = point(1f0, 0f0, 0f0)
# ga"line = origin ∨ px"
# p = plane(2f0,0f0,1f0,-3f0)
# ga"rot = rotor(Float32(pi/2), e1 e2)"
# ga"rot_point = rot px ~rot"
# ga"rot_line = rot line ~rot"
# ga"rot_plane = rot p ~rot"
#
# tst1 = e0 - 1f0
# tst2 = 1f0 - e0
end

# if (slow) output of unit test results wanted
if nLoop == 1
nError = 0

S = Matrix{String}(undef,9,3) # 3 columns:
S[1,1] = " P0 : " # 1) label
S[1,2] = toStr(P0) # 2) toStr()
S[1,3] = "e12" # 3) expected string

S[2,1] = " P1 : "
S[2,2] = toStr(P1)
S[2,3] = "e20 + e12"

S[3,1] = " P2 : "
S[3,2] = toStr(P2)
S[3,3] = "e01 + e12"

S[4,1] = " P3 : "
S[4,2] = toStr(P3)
S[4,3] = "e01 + e20 + e12"

S[5,1] = " line0 : "
S[5,2] = toStr(line0)
S[5,3] = "-e2"

S[6,1] = " line1 : "
S[6,2] = toStr(line1)
S[6,3] = "e0 - e2"

S[7,1] = " intersection : "
S[7,2] = toStr(x)
S[7,3] = "-e20"

S[8,1] = " toStr test 1 : "
S[8,2] = toStr(tst1)
S[8,3] = "-1 + e0"

S[9,1] = " toStr test 2 : "
S[9,2] = toStr(tst2)
S[9,3] = "1 - e0"

# print unit test results;
nTest = size(S,1)
for iTest = 1:nTest
if S[iTest,2] == S[iTest,3]
mark = " "
else
mark = "x"
nError += 1
end
println("$mark" * S[iTest,1] * S[iTest,2])
end

return nError # return unit test results
end
end # utest()

The following is the 3D version of the reference implementation of PGA.

# ripga3d.jl : reference implementation of 
# Projective Geometric Algebra for 3D
#
# This is a Julia port of pga3d.cpp, bivector.net's C++
# reference implementation of projective geometric algebra
# available at https://bivector.net/tools.html
#
using Printf

# define multivector basis names
# 0 denotes projective dimension (e.g., e0 * e0 = 0)
basis = [ # iField
"1" # 1 scalar
"e0" # 2 grade 1 vectors
"e1" # 3
"e2" # 4
"e3" # 5
"e01" # 6 grade 2 vectors (bivectors)
"e02" # 7
"e03" # 8
"e12" # 9
"e31" # 10
"e23" # 11
"e021" # 12 grade 3 vectors (trivectors)
"e013" # 13
"e032" # 14
"e123" # 15
"e0123"]# 16 pseudoscalar

# define basis multivectors
nField = 2^4 # 4 = 3D + 1 dimensions
e0 = zeros(Float32, nField); e0[2] = 1
e1 = zeros(Float32, nField); e1[3] = 1
e2 = zeros(Float32, nField); e2[4] = 1
e3 = zeros(Float32, nField); e3[5] = 1
e01 = zeros(Float32, nField); e01[6] = 1
e02 = zeros(Float32, nField); e02[7] = 1
e03 = zeros(Float32, nField); e03[8] = 1
e12 = zeros(Float32, nField); e12[9] = 1
e31 = zeros(Float32, nField); e31[10] = 1
e23 = zeros(Float32, nField); e23[11] = 1
e021 = zeros(Float32, nField); e021[12] = 1
e013 = zeros(Float32, nField); e013[13] = 1
e032 = zeros(Float32, nField); e032[14] = 1
e123 = zeros(Float32, nField); e123[15] = 1
e0123 = zeros(Float32, nField); e0123[16] = 1

# geometric product
function Base.:*(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
res = similar(a)
res[1]=b[1]*a[1]+b[3]*a[3]+b[4]*a[4]+b[5]*a[5]-b[9]*a[9]-b[10]*a[10]-b[11]*a[11]-b[15]*a[15]
res[2]=b[2]*a[1]+b[1]*a[2]-b[6]*a[3]-b[7]*a[4]-b[8]*a[5]+b[3]*a[6]+b[4]*a[7]+b[5]*a[8]+b[12]*a[9]+b[13]*a[10]+b[14]*a[11]+b[9]*a[12]+b[10]*a[13]+b[11]*a[14]+b[16]*a[15]-b[15]*a[16]
res[3]=b[3]*a[1]+b[1]*a[3]-b[9]*a[4]+b[10]*a[5]+b[4]*a[9]-b[5]*a[10]-b[15]*a[11]-b[11]*a[15]
res[4]=b[4]*a[1]+b[9]*a[3]+b[1]*a[4]-b[11]*a[5]-b[3]*a[9]-b[15]*a[10]+b[5]*a[11]-b[10]*a[15]
res[5]=b[5]*a[1]-b[10]*a[3]+b[11]*a[4]+b[1]*a[5]-b[15]*a[9]+b[3]*a[10]-b[4]*a[11]-b[9]*a[15]
res[6]=b[6]*a[1]+b[3]*a[2]-b[2]*a[3]-b[12]*a[4]+b[13]*a[5]+b[1]*a[6]-b[9]*a[7]+b[10]*a[8]+b[7]*a[9]-b[8]*a[10]-b[16]*a[11]-b[4]*a[12]+b[5]*a[13]+b[15]*a[14]-b[14]*a[15]-b[11]*a[16]
res[7]=b[7]*a[1]+b[4]*a[2]+b[12]*a[3]-b[2]*a[4]-b[14]*a[5]+b[9]*a[6]+b[1]*a[7]-b[11]*a[8]-b[6]*a[9]-b[16]*a[10]+b[8]*a[11]+b[3]*a[12]+b[15]*a[13]-b[5]*a[14]-b[13]*a[15]-b[10]*a[16]
res[8]=b[8]*a[1]+b[5]*a[2]-b[13]*a[3]+b[14]*a[4]-b[2]*a[5]-b[10]*a[6]+b[11]*a[7]+b[1]*a[8]-b[16]*a[9]+b[6]*a[10]-b[7]*a[11]+b[15]*a[12]-b[3]*a[13]+b[4]*a[14]-b[12]*a[15]-b[9]*a[16]
res[9]=b[9]*a[1]+b[4]*a[3]-b[3]*a[4]+b[15]*a[5]+b[1]*a[9]+b[11]*a[10]-b[10]*a[11]+b[5]*a[15]
res[10]=b[10]*a[1]-b[5]*a[3]+b[15]*a[4]+b[3]*a[5]-b[11]*a[9]+b[1]*a[10]+b[9]*a[11]+b[4]*a[15]
res[11]=b[11]*a[1]+b[15]*a[3]+b[5]*a[4]-b[4]*a[5]+b[10]*a[9]-b[9]*a[10]+b[1]*a[11]+b[3]*a[15]
res[12]=b[12]*a[1]-b[9]*a[2]+b[7]*a[3]-b[6]*a[4]+b[16]*a[5]-b[4]*a[6]+b[3]*a[7]-b[15]*a[8]-b[2]*a[9]+b[14]*a[10]-b[13]*a[11]+b[1]*a[12]+b[11]*a[13]-b[10]*a[14]+b[8]*a[15]-b[5]*a[16]
res[13]=b[13]*a[1]-b[10]*a[2]-b[8]*a[3]+b[16]*a[4]+b[6]*a[5]+b[5]*a[6]-b[15]*a[7]-b[3]*a[8]-b[14]*a[9]-b[2]*a[10]+b[12]*a[11]-b[11]*a[12]+b[1]*a[13]+b[9]*a[14]+b[7]*a[15]-b[4]*a[16]
res[14]=b[14]*a[1]-b[11]*a[2]+b[16]*a[3]+b[8]*a[4]-b[7]*a[5]-b[15]*a[6]-b[5]*a[7]+b[4]*a[8]+b[13]*a[9]-b[12]*a[10]-b[2]*a[11]+b[10]*a[12]-b[9]*a[13]+b[1]*a[14]+b[6]*a[15]-b[3]*a[16]
res[15]=b[15]*a[1]+b[11]*a[3]+b[10]*a[4]+b[9]*a[5]+b[5]*a[9]+b[4]*a[10]+b[3]*a[11]+b[1]*a[15]
res[16]=b[16]*a[1]+b[15]*a[2]+b[14]*a[3]+b[13]*a[4]+b[12]*a[5]+b[11]*a[6]+b[10]*a[7]+b[9]*a[8]+b[8]*a[9]+b[7]*a[10]+b[6]*a[11]-b[5]*a[12]-b[4]*a[13]-b[3]*a[14]-b[2]*a[15]+b[1]*a[16]
return res
end # geometric product (*)

# regressive product: vee operator (&, \vee)
function Base.:&(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
res = similar(a)
res[16]=a[16]*b[16]
res[15]=a[15]*b[16]+a[16]*b[15]
res[14]=a[14]*b[16]+a[16]*b[14]
res[13]=a[13]*b[16]+a[16]*b[13]
res[12]=a[12]*b[16]+a[16]*b[12]
res[11]=a[11]*b[16]+a[14]*b[15]-a[15]*b[14]+a[16]*b[11]
res[10]=a[10]*b[16]+a[13]*b[15]-a[15]*b[13]+a[16]*b[10]
res[9]=a[9]*b[16]+a[12]*b[15]-a[15]*b[12]+a[16]*b[9]
res[8]=a[8]*b[16]+a[13]*b[14]-a[14]*b[13]+a[16]*b[8]
res[7]=a[7]*b[16]-a[12]*b[14]+a[14]*b[12]+a[16]*b[7]
res[6]=a[6]*b[16]+a[12]*b[13]-a[13]*b[12]+a[16]*b[6]
res[5]=a[5]*b[16]+a[8]*b[15]-a[10]*b[14]+a[11]*b[13]+a[13]*b[11]-a[14]*b[10]+a[15]*b[8]+a[16]*b[5]
res[4]=a[4]*b[16]+a[7]*b[15]+a[9]*b[14] -a[11]*b[12]-a[12]*b[11]+a[14]*b[9] +a[15]*b[7]+a[16]*b[4]
res[3]=a[3]*b[16]+a[6]*b[15]-a[9]*b[13]+a[10]*b[12]+a[12]*b[10]-a[13]*b[9]+a[15]*b[6]+a[16]*b[3]
res[2]=a[2]*b[16]-a[6]*b[14]-a[7]*b[13]-a[8]*b[12] -a[12]*b[8] -a[13]*b[7]-a[14]*b[6]+a[16]*b[2]
res[1]=a[1]*b[16]-a[2]*b[15]-a[3]*b[14]-a[4]*b[13] -a[5]*b[12] +a[6]*b[11]+a[7]*b[10]+a[8]*b[9]+a[9]*b[8]+a[10]*b[7]+a[11]*b[6]+a[12]*b[5]+a[13]*b[4]+a[14]*b[3]+a[15]*b[2]+a[16]*b[1]
return res
end # regressive product; vee operator (&, \vee)

# inner product (|)
function Base.:|(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
res = similar(a)
res[1]=b[1]*a[1]+b[3]*a[3]+b[4]*a[4]+b[5]*a[5]-b[9]*a[9]-b[10]*a[10]-b[11]*a[11]-b[15]*a[15]
res[2]=b[2]*a[1]+b[1]*a[2]-b[6]*a[3]-b[7]*a[4]-b[8]*a[5]+b[3]*a[6]+b[4]*a[7]+b[5]*a[8]+b[12]*a[9]+b[13]*a[10]+b[14]*a[11]+b[9]*a[12]+b[10]*a[13]+b[11]*a[14]+b[16]*a[15]-b[15]*a[16]
res[3]=b[3]*a[1]+b[1]*a[3]-b[9]*a[4]+b[10]*a[5]+b[4]*a[9]-b[5]*a[10]-b[15]*a[11]-b[11]*a[15]
res[4]=b[4]*a[1]+b[9]*a[3]+b[1]*a[4]-b[11]*a[5]-b[3]*a[9]-b[15]*a[10]+b[5]*a[11]-b[10]*a[15]
res[5]=b[5]*a[1]-b[10]*a[3]+b[11]*a[4]+b[1]*a[5]-b[15]*a[9]+b[3]*a[10]-b[4]*a[11]-b[9]*a[15]
res[6]=b[6]*a[1]-b[12]*a[4]+b[13]*a[5]+b[1]*a[6]-b[16]*a[11]-b[4]*a[12]+b[5]*a[13]-b[11]*a[16]
res[7]=b[7]*a[1]+b[12]*a[3]-b[14]*a[5]+b[1]*a[7]-b[16]*a[10]+b[3]*a[12]-b[5]*a[14]-b[10]*a[16]
res[8]=b[8]*a[1]-b[13]*a[3]+b[14]*a[4]+b[1]*a[8]-b[16]*a[9]-b[3]*a[13]+b[4]*a[14]-b[9]*a[16]
res[9]=b[9]*a[1]+b[15]*a[5]+b[1]*a[9]+b[5]*a[15]
res[10]=b[10]*a[1]+b[15]*a[4]+b[1]*a[10]+b[4]*a[15]
res[11]=b[11]*a[1]+b[15]*a[3]+b[1]*a[11]+b[3]*a[15]
res[12]=b[12]*a[1]+b[16]*a[5]+b[1]*a[12]-b[5]*a[16]
res[13]=b[13]*a[1]+b[16]*a[4]+b[1]*a[13]-b[4]*a[16]
res[14]=b[14]*a[1]+b[16]*a[3]+b[1]*a[14]-b[3]*a[16]
res[15]=b[15]*a[1]+b[1]*a[15]
res[16]=b[16]*a[1]+b[1]*a[16]
return res
end # inner product (|)

# outer product; wedge operator (^)
function Base.:^(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
res = similar(a)
res[1]=b[1]*a[1]
res[2]=b[2]*a[1]+b[1]*a[2]
res[3]=b[3]*a[1]+b[1]*a[3]
res[4]=b[4]*a[1]+b[1]*a[4]
res[5]=b[5]*a[1]+b[1]*a[5]
res[6]=b[6]*a[1]+b[3]*a[2]-b[2]*a[3]+b[1]*a[6]
res[7]=b[7]*a[1]+b[4]*a[2]-b[2]*a[4]+b[1]*a[7]
res[8]=b[8]*a[1]+b[5]*a[2]-b[2]*a[5]+b[1]*a[8]
res[9]=b[9]*a[1]+b[4]*a[3]-b[3]*a[4]+b[1]*a[9]
res[10]=b[10]*a[1]-b[5]*a[3]+b[3]*a[5]+b[1]*a[10]
res[11]=b[11]*a[1]+b[5]*a[4]-b[4]*a[5]+b[1]*a[11]
res[12]=b[12]*a[1]-b[9]*a[2]+b[7]*a[3]-b[6]*a[4]-b[4]*a[6]+b[3]*a[7]-b[2]*a[9]+b[1]*a[12]
res[13]=b[13]*a[1]-b[10]*a[2]-b[8]*a[3]+b[6]*a[5]+b[5]*a[6]-b[3]*a[8]-b[2]*a[10]+b[1]*a[13]
res[14]=b[14]*a[1]-b[11]*a[2]+b[8]*a[4]-b[7]*a[5]-b[5]*a[7]+b[4]*a[8]-b[2]*a[11]+b[1]*a[14]
res[15]=b[15]*a[1]+b[11]*a[3]+b[10]*a[4]+b[9]*a[5]+b[5]*a[9]+b[4]*a[10]+b[3]*a[11]+b[1]*a[15]
res[16]=b[16]*a[1]+b[15]*a[2]+b[14]*a[3]+b[13]*a[4]+b[12]*a[5]+b[11]*a[6]+b[10]*a[7]+b[9]*a[8]+b[8]*a[9]+b[7]*a[10]+b[6]*a[11]-b[5]*a[12]-b[4]*a[13]-b[3]*a[14]-b[2]*a[15]+b[1]*a[16]
return res
end # outer product; wedge operator (^)

# reverse operator (~)
function Base.:~(a::Vector{Float32})
res = copy(a)
res[6:15] .*= -1
return res
end

# conjugate
function conjugate(a::Vector{Float32})::Vector{Float32}
res = copy(a)
res[2:11] .*= -1
return res
end

function plane(a::Number, b::Number, c::Number, d::Number)::Vector{Float32}
return a*e1 + b*e2 + c*e3 + d*e0
end

function circle(t::Number, radius::Number, line::Vector{Float32})
return rotor(t*2*pi,line) * translator(radius,e1*e0)
end

function torus(s::Number, t::Number,
r1::Number, l1::Vector{Float32},
r2::Number, l2::Vector{Float32})
return circle(s,r2,l2) * circle(t,r1,l1)
end

function normIdeal(a::Vector{Float32},nd::Int64=2)
if nd == 2 # for area
return sqrt(a[6]^2 + a[7]^2 + a[8]^2)
else # for volume
return sqrt(a[2]^2)
end
end

# unit test
# arguments:
# - nLoop repeats a section of the PGA calculations for benchmarking
# - flgSimplify set to true diverges from the text output by pga3d.cpp
# usage notes:
# - @time utest(1) checks on whether the unit test output of ripga.jl
# exactly matches the unit test output of pga3d.cpp. The comparison
# ends with the printing of the number of tests in the unit test that
# don't match. 0 indicates success.
# - @time utest(1,true) outputs a slightly simplified version of the
# unit test output that does not match the unit test output by pga3d.cpp.
# - @btime utest() is a test for execution speed of ripga3d.jl.
# (NOTE: requires using BenchmarkTools)
function utest(nLoop=100, flgSimplify::Bool=false)
nField = length(basis)
axis_z = Vector{Float32}(undef,nField)
origin = Vector{Float32}(undef,nField)
px = Vector{Float32}(undef,nField)
line = Vector{Float32}(undef,nField)
p = Vector{Float32}(undef,nField)
rot = Vector{Float32}(undef,nField)
rot_point = Vector{Float32}(undef,nField)
rot_line = Vector{Float32}(undef,nField)
rot_plane = Vector{Float32}(undef,nField)
point_on_plane = Vector{Float32}(undef,nField)
to = Vector{Float32}(undef,nField)
point_on_torus = Vector{Float32}(undef,nField)
tst1 = Vector{Float32}(undef,nField)
tst2 = Vector{Float32}(undef,nField)

for iLoop = 1:nLoop
# geometric algebra equations in programming syntax
axis_z = e1 ^ e2
origin = axis_z ^ e3

px = point(1, 0, 0)
line = origin & px
p = plane(2,0,1,-3)
rot = rotor(pi/2, e1*e2)
rot_point = rot * px * ~rot
rot_line = rot * line * ~rot
rot_plane = rot * p * ~rot
point_on_plane = (p | px) * p
to = torus(0,0, 0.25,e1*e2, 0.6,e1*e3)
point_on_torus = to * e123 * ~to

tst1 = e0 - 1
tst2 = 1 - e0

# # geometric algebra equations in math syntax
# ga"axis_z = e1 ∧ e2"
# ga"origin = axis_z ∧ e3"
#
# px = point(1, 0, 0)
# ga"line = origin ∨ px"
# p = plane(2, 0, 1,-3)
# ga"rot = rotor(pi/2, e1 e2)"
# ga"rot_point = rot px ~rot"
# ga"rot_line = rot line ~rot"
# ga"rot_plane = rot p ~rot"
# ga"point_on_plane = (p·px) p"
# ga"to = torus(0,0, 0.25,e1 e2, 0.6,e1 e3)"
# ga"point_on_torus = to e123 ~to"
#
# tst1 = e0 - 1
# tst2 = 1 - e0
end

# if (slow) output of unit test results wanted
if nLoop == 1
nError = 0
if flgSimplify == false

S = Matrix{String}(undef,11,3) # 3 columns:
S[1,1] = " point : " # 1) label
S[1,2] = toStr1(px) # 2) toStr1()
S[1,3] = "1e032 + 1e123" # 3) expected string

S[2,1] = " line : "
S[2,2] = toStr1(line)
S[2,3] = "-1e23"

S[3,1] = " plane : "
S[3,2] = toStr1(p)
S[3,3] = "-3e0 + 2e1 + 1e3"

S[4,1] = " rotor : "
S[4,2] = toStr1(rot)
S[4,3] = "0.7071068 + 0.7071068e12"

S[5,1] = " rotated point : "
S[5,2] = toStr1(rot_point)
S[5,3] = "-0.9999999e013 + 0.9999999e123"

S[6,1] = " rotated line : "
S[6,2] = toStr1(rot_line)
S[6,3] = "0.9999999e31"

S[7,1] = " rotated plane : "
S[7,2] = toStr1(rot_plane)
S[7,3] = "-3e0 + -2e2 + 0.9999999e3"

S[8,1] = " point on plane : "
S[8,2] = toStr1(normalize(point_on_plane))
S[8,3] = "0.2e021 + 1.4e032 + 1e123"

S[9,1] = " point on torus : "
S[9,2] = toStr1(point_on_torus)
S[9,3] = "0.85e032 + 1e123"

S[10,1] = " toStr1 test 1 : "
S[10,2] = toStr1(tst1)
S[10,3] = "-1 + 1e0"

S[11,1] = " toStr1 test 2 : "
S[11,2] = toStr1(tst2)
S[11,3] = "1 + -1e0"

# print unit test results;
# 'x' in first column in tests with errors
nTest = size(S,1)
for iTest = 1:nTest
isError = S[iTest,2] != S[iTest,3]
xChar = isError ? 'x' : ' '
println(xChar * S[iTest,1] * S[iTest,2])
if (isError)
println(' ' * S[iTest,1] * S[iTest,3])
nError += 1
end
end

else # flgSimplify == true
S = Matrix{String}(undef,11,3) # 3 columns:
S[1,1] = " point : " # 1) label
S[1,2] = toStr(px) # 2) toStr()
S[1,3] = "1e032 + 1e123" # 3) expected string

S[2,1] = " line : "
S[2,2] = toStr(line)
S[2,3] = "-1e23"

S[3,1] = " plane : "
S[3,2] = toStr(p)
S[3,3] = "-3e0 + 2e1 + 1e3"

S[4,1] = " rotor : "
S[4,2] = toStr(rot)
S[4,3] = "0.7071068 + 0.7071068e12"

S[5,1] = " rotated point : "
S[5,2] = toStr(rot_point)
S[5,3] = "-0.9999999e013 + 0.9999999e123"

S[6,1] = " rotated line : "
S[6,2] = toStr(rot_line)
S[6,3] = "0.9999999e31"

S[7,1] = " rotated plane : "
S[7,2] = toStr(rot_plane)
S[7,3] = "-3e0 + -2e2 + 0.9999999e3"

S[8,1] = " point on plane : "
S[8,2] = toStr(normalize(point_on_plane))
S[8,3] = "0.2e021 + 1.4e032 + 1e123"

S[9,1] = " point on torus : "
S[9,2] = toStr(point_on_torus)
S[9,3] = "0.85e032 + 1e123"

S[10,1] = " toStr test 1 : "
S[10,2] = toStr(tst1)
S[10,3] = "-1 + 1e0"

S[11,1] = " toStr test 2 : "
S[11,2] = toStr(tst2)
S[11,3] = "1 + -1e0"

# print unit test results;
nTest = size(S,1)
for iTest = 1:nTest
println(S[iTest,1] * S[iTest,2])
end
end

return nError # return unit test results
end
end # utest()

The following is the 4D version of the reference implementation of PGA.

# ripga4d.jl : reference implementation of
# Projective Geometric Algebra for 4D
#
# This is an extension of pga3d.cpp, bivector.net's C++
# reference implementation of projective geometric algebra
# available at https://bivector.net/tools.html
#
using Printf

# define multivector basis names
# 0 denotes projective dimension (e.g., e0 * e0 = 0)
basis = [# iField
"1" # 1 scalar
"e0" # 2 grade 1 vectors
"e1" # 3
"e2" # 4
"e3" # 5
"e4" # 6
"e01" # 7 grade 2 vectors (bivectors)
"e02" # 8
"e03" # 9
"e04" # 10
"e12" # 11
"e13" # 12
"e14" # 13
"e23" # 14
"e24" # 15
"e34" # 16
"e012" # 17 grade 3 vectors (trivectors)
"e013" # 18
"e014" # 19
"e023" # 20
"e024" # 21
"e034" # 22
"e123" # 23
"e124" # 24
"e134" # 25
"e234" # 26
"e0123" # 27
"e0124" # 28
"e0134" # 29
"e0234" # 30
"e1234" # 31
"e01234"] # 32 pseudoscalar

# define basis multivectors
nField = 2^5
e0 = zeros(Float32, nField); e0[2] = 1
e1 = zeros(Float32, nField); e1[3] = 1
e2 = zeros(Float32, nField); e2[4] = 1
e3 = zeros(Float32, nField); e3[5] = 1
e4 = zeros(Float32, nField); e4[6] = 1
e01 = zeros(Float32, nField); e01[7] = 1
e02 = zeros(Float32, nField); e02[8] = 1
e03 = zeros(Float32, nField); e03[9] = 1
e04 = zeros(Float32, nField); e04[10] = 1
e12 = zeros(Float32, nField); e12[11] = 1
e13 = zeros(Float32, nField); e13[12] = 1
e14 = zeros(Float32, nField); e14[13] = 1
e23 = zeros(Float32, nField); e23[14] = 1
e24 = zeros(Float32, nField); e24[15] = 1
e34 = zeros(Float32, nField); e34[16] = 1
e012 = zeros(Float32, nField); e012[17] = 1
e013 = zeros(Float32, nField); e013[18] = 1
e014 = zeros(Float32, nField); e014[19] = 1
e023 = zeros(Float32, nField); e023[20] = 1
e024 = zeros(Float32, nField); e024[21] = 1
e034 = zeros(Float32, nField); e034[22] = 1
e123 = zeros(Float32, nField); e123[23] = 1
e124 = zeros(Float32, nField); e124[24] = 1
e134 = zeros(Float32, nField); e134[25] = 1
e234 = zeros(Float32, nField); e234[26] = 1
e0123 = zeros(Float32, nField); e0123[27] = 1
e0124 = zeros(Float32, nField); e0124[28] = 1
e0134 = zeros(Float32, nField); e0134[29] = 1
e0234 = zeros(Float32, nField); e0234[30] = 1
e1234 = zeros(Float32, nField); e1234[31] = 1
e01234 = zeros(Float32, nField); e01234[32] = 1

# geometric product
function Base.:*(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
res = similar(a)
res[1]=(a[1] *b[1] +a[3] *b[3] # 1
+a[4] *b[4] +a[5] *b[5]
+a[6] *b[6] -a[11]*b[11]
-a[12]*b[12]-a[13]*b[13]
-a[14]*b[14]-a[15]*b[15]
-a[16]*b[16]-a[23]*b[23]
-a[24]*b[24]-a[25]*b[25]
-a[26]*b[26]+a[31]*b[31])
res[2]=(a[2] *b[1] +a[1] *b[2] # e0
+a[7] *b[3] -a[3] *b[7]
+a[8] *b[4] -a[4] *b[8]
+a[9] *b[5] -a[5] *b[9]
+a[10]*b[6] -a[6] *b[10]
-a[17]*b[11]-a[11]*b[17]
-a[18]*b[12]-a[12]*b[18]
-a[19]*b[13]-a[13]*b[19]
-a[20]*b[14]-a[14]*b[20]
-a[21]*b[15]-a[15]*b[21]
-a[22]*b[16]-a[16]*b[22]
-a[27]*b[23]+a[23]*b[27]
-a[28]*b[24]+a[24]*b[28]
-a[29]*b[25]+a[25]*b[29]
-a[30]*b[26]+a[26]*b[30]
+a[32]*b[31]+a[31]*b[32])
res[3]=(a[3] *b[1] +a[1] *b[3] # e1
+a[11]*b[4] -a[4] *b[11]
+a[12]*b[5] -a[5] *b[12]
+a[13]*b[6] -a[6] *b[13]
-a[23]*b[14]-a[14]*b[23]
-a[24]*b[15]-a[15]*b[24]
-a[25]*b[16]-a[16]*b[25]
-a[31]*b[26]+a[26]*b[31])
res[4]=(a[4]*b[1] +a[1] *b[4] # e2
-a[11]*b[3] +a[3] *b[11]
+a[14]*b[5] -a[5] *b[14]
+a[15]*b[6] -a[6] *b[15]
+a[23]*b[12]+a[12]*b[23]
+a[24]*b[13]+a[13]*b[24]
-a[26]*b[16]-a[16]*b[26]
+a[31]*b[25]-a[25]*b[31])
res[5]=(a[5] *b[1] +a[1] *b[5] # e3
-a[12]*b[3] +a[3] *b[12]
-a[14]*b[4] +a[4] *b[14]
+a[16]*b[6] -a[6] *b[16]
-a[23]*b[11]-a[11]*b[23]
+a[25]*b[13]+a[13]*b[25]
+a[26]*b[15]+a[15]*b[26]
-a[31]*b[24]+a[24]*b[31])
res[6]=(a[6] *b[1] +a[1] *b[6] # e4
-a[13]*b[3] +a[3] *b[13]
-a[15]*b[4] +a[4] *b[15]
-a[16]*b[5] +a[5] *b[16]
-a[24]*b[11]-a[11]*b[24]
-a[25]*b[12]-a[12]*b[25]
-a[26]*b[14]-a[14]*b[26]
+a[31]*b[23]-a[23]*b[31])
res[7]=(a[7] *b[1] +a[1] *b[7] # e01
-a[3] *b[2] +a[2] *b[3]
+a[17]*b[4] +a[4] *b[17]
+a[18]*b[5] +a[5] *b[18]
+a[19]*b[6] +a[6] *b[19]
+a[11]*b[8] -a[8] *b[11]
+a[12]*b[9] -a[9] *b[12]
+a[13]*b[10]-a[10]*b[13]
-a[27]*b[14]-a[14]*b[27]
-a[28]*b[15]-a[15]*b[28]
-a[29]*b[16]-a[16]*b[29]
+a[23]*b[20]-a[20]*b[23]
+a[24]*b[21]-a[21]*b[24]
+a[25]*b[22]-a[22]*b[25]
-a[32]*b[26]-a[26]*b[32]
-a[31]*b[30]+a[30]*b[31])
res[8]=(a[8] *b[1] +a[1] *b[8] # e02
-a[4] *b[2] +a[2] *b[4]
-a[17]*b[3] -a[3] *b[17]
+a[20]*b[5] +a[5] *b[20]
+a[21]*b[6] +a[6] *b[21]
-a[11]*b[7] +a[7] *b[11]
+a[14]*b[9] -a[9] *b[14]
+a[15]*b[10]-a[10]*b[15]
+a[27]*b[12]+a[12]*b[27]
+a[28]*b[13]+a[13]*b[28]
-a[30]*b[16]-a[16]*b[30]
-a[23]*b[18]+a[18]*b[23]
-a[24]*b[19]+a[19]*b[24]
+a[26]*b[22]-a[22]*b[26]
+a[32]*b[25]+a[25]*b[32]
+a[31]*b[29]-a[29]*b[31])
res[9]=(a[9] *b[1] +a[1] *b[9] # e03
-a[5] *b[2] +a[2] *b[5]
-a[18]*b[3] -a[3] *b[18]
-a[20]*b[4] -a[4] *b[20]
+a[22]*b[6] +a[6] *b[22]
-a[12]*b[7] +a[7] *b[12]
-a[14]*b[8] +a[8] *b[14]
+a[16]*b[10]-a[10]*b[16]
-a[27]*b[11]-a[11]*b[27]
+a[29]*b[13]+a[13]*b[29]
+a[30]*b[15]+a[15]*b[30]
+a[23]*b[17]-a[17]*b[23]
-a[25]*b[19]+a[19]*b[25]
-a[26]*b[21]+a[21]*b[26]
-a[32]*b[24]-a[24]*b[32]
-a[31]*b[28]+a[28]*b[31])
res[10]=(a[10]*b[1] +a[1] *b[10] # e04
-a[6] *b[2] +a[2] *b[6]
-a[19]*b[3] -a[3] *b[19]
-a[21]*b[4] -a[4] *b[21]
-a[22]*b[5] -a[5] *b[22]
-a[13]*b[7] +a[7] *b[13]
-a[15]*b[8] +a[8] *b[15]
-a[16]*b[9] +a[9] *b[16]
-a[28]*b[11]-a[11]*b[28]
-a[29]*b[12]-a[12]*b[29]
-a[30]*b[14]-a[14]*b[30]
+a[24]*b[17]-a[17]*b[24]
+a[25]*b[18]-a[18]*b[25]
+a[26]*b[20]-a[20]*b[26]
+a[32]*b[23]+a[23]*b[32]
+a[31]*b[27]-a[27]*b[31])
res[11]=(a[11]*b[1] +a[1] *b[11] # e12
-a[4] *b[3] +a[3] *b[4]
+a[23]*b[5] +a[5] *b[23]
+a[24]*b[6] +a[6] *b[24]
+a[14]*b[12]-a[12]*b[14]
+a[15]*b[13]-a[13]*b[15]
-a[31]*b[16]-a[16]*b[31]
+a[26]*b[25]-a[25]*b[26])
res[12]=(a[12]*b[1] +a[1] *b[12] # e13
-a[5] *b[3] +a[3] *b[5]
-a[23]*b[4] -a[4] *b[23]
+a[25]*b[6] +a[6] *b[25]
-a[14]*b[11]+a[11]*b[14]
+a[16]*b[13]-a[13]*b[16]
+a[31]*b[15]+a[15]*b[31]
-a[26]*b[24]+a[24]*b[26])
res[13]=(a[13]*b[1] +a[1] *b[13] # e14
-a[6] *b[3] +a[3] *b[6]
-a[24]*b[4] -a[4] *b[24]
-a[25]*b[5] -a[5] *b[25]
-a[15]*b[11]+a[11]*b[15]
-a[16]*b[12]+a[12]*b[16]
-a[31]*b[14]-a[14]*b[31]
+a[26]*b[23]-a[23]*b[26])
res[14]=(a[14]*b[1] +a[1] *b[14] # e23
-a[5] *b[4] +a[4] *b[5]
+a[23]*b[3] +a[3] *b[23]
+a[26]*b[6] +a[6] *b[26]
+a[12]*b[11]-a[11]*b[12]
-a[31]*b[13]-a[13]*b[31]
+a[16]*b[15]-a[15]*b[16]
+a[25]*b[24]-a[24]*b[25])
res[15]=(a[15]*b[1] +a[1] *b[15] # e24
-a[6] *b[4] +a[4] *b[6]
+a[24]*b[3] +a[3] *b[24]
-a[26]*b[5] -a[5] *b[26]
+a[13]*b[11]-a[11]*b[13]
-a[16]*b[14]+a[14]*b[16]
+a[31]*b[12]+a[12]*b[31]
-a[25]*b[23]+a[23]*b[25])
res[16]=(a[16]*b[1] +a[1] *b[16] # e34
-a[6] *b[5] +a[5] *b[6]
+a[25]*b[3] +a[3] *b[25]
+a[26]*b[4] +a[4] *b[26]
+a[13]*b[12]-a[12]*b[13]
+a[15]*b[14]-a[14]*b[15]
-a[31]*b[11]-a[11]*b[31]
+a[24]*b[23]-a[23]*b[24])
res[17]=(a[17]*b[1] +a[1] *b[17] # e012
+a[11]*b[2] +a[2] *b[11]
-a[8] *b[3] -a[3] *b[8]
+a[7] *b[4] +a[4] *b[7]
+a[27]*b[5] -a[5] *b[27]
+a[28]*b[6] -a[6] *b[28]
-a[23]*b[9] +a[9] *b[23]
-a[24]*b[10]+a[10]*b[24]
+a[20]*b[12]-a[12]*b[20]
+a[21]*b[13]-a[13]*b[21]
-a[32]*b[16]-a[16]*b[32]
-a[18]*b[14]+a[14]*b[18]
-a[19]*b[15]+a[15]*b[19]
-a[31]*b[22]-a[22]*b[31]
+a[30]*b[25]+a[25]*b[30]
-a[29]*b[26]-a[26]*b[29])
res[18]=(a[18]*b[1] +a[1] *b[18] # e013
+a[12]*b[2] +a[2] *b[12]
-a[9] *b[3] -a[3] *b[9]
+a[7] *b[5] +a[5] *b[7]
-a[27]*b[4] +a[4] *b[27]
+a[29]*b[6] -a[6] *b[29]
+a[23]*b[8] -a[8] *b[23]
-a[25]*b[10]+a[10]*b[25]
-a[20]*b[11]+a[11]*b[20]
+a[22]*b[13]-a[13]*b[22]
+a[17]*b[14]-a[14]*b[17]
-a[19]*b[16]+a[16]*b[19]
+a[32]*b[15]+a[15]*b[32]
+a[31]*b[21]+a[21]*b[31]
-a[30]*b[24]-a[24]*b[30]
+a[28]*b[26]+a[26]*b[28])
res[19]=(a[19]*b[1] +a[1] *b[19] # e014
+a[13]*b[2] +a[2] *b[13]
-a[10]*b[3] -a[3] *b[10]
-a[28]*b[4] +a[4] *b[28]
-a[29]*b[5] +a[5] *b[29]
+a[7] *b[6] +a[6] *b[7]
+a[24]*b[8] -a[8] *b[24]
+a[25]*b[9] -a[9] *b[25]
-a[21]*b[11]+a[11]*b[21]
-a[22]*b[12]+a[12]*b[22]
+a[17]*b[15]-a[15]*b[17]
+a[18]*b[16]-a[16]*b[18]
-a[32]*b[14]-a[14]*b[32]
-a[31]*b[20]-a[20]*b[31]
+a[30]*b[23]+a[23]*b[30]
-a[27]*b[26]-a[26]*b[27])
res[20]=(a[20]*b[1] +a[1] *b[20] # e023
+a[14]*b[2] +a[2] *b[14]
+a[27]*b[3] -a[3] *b[27]
+a[30]*b[6] -a[6] *b[30]
-a[9] *b[4] -a[4] *b[9]
+a[8] *b[5] +a[5] *b[8]
-a[23]*b[7] +a[7] *b[23]
-a[26]*b[10]+a[10]*b[26]
+a[18]*b[11]-a[11]*b[18]
-a[17]*b[12]+a[12]*b[17]
+a[22]*b[15]-a[15]*b[22]
-a[21]*b[16]+a[16]*b[21]
-a[32]*b[13]-a[13]*b[32]
-a[31]*b[19]-a[19]*b[31]
+a[29]*b[24]+a[24]*b[29]
-a[28]*b[25]-a[25]*b[28])
res[21]=(a[21]*b[1] +a[1] *b[21] # e024
+a[15]*b[2] +a[2] *b[15]
+a[28]*b[3] -a[3] *b[28]
-a[30]*b[5] +a[5] *b[30]
-a[10]*b[4] -a[4] *b[10]
+a[8] *b[6] +a[6] *b[8]
-a[24]*b[7] +a[7] *b[24]
+a[26]*b[9] -a[9] *b[26]
+a[19]*b[11]-a[11]*b[19]
-a[17]*b[13]+a[13]*b[17]
-a[22]*b[14]+a[14]*b[22]
+a[20]*b[16]-a[16]*b[20]
+a[32]*b[12]+a[12]*b[32]
+a[31]*b[18]+a[18]*b[31]
-a[29]*b[23]-a[23]*b[29]
+a[27]*b[25]+a[25]*b[27])
res[22]=(a[22]*b[1] +a[1] *b[22] # e034
+a[16]*b[2] +a[2] *b[16]
+a[29]*b[3] -a[3] *b[29]
+a[30]*b[4] -a[4] *b[30]
-a[10]*b[5] -a[5] *b[10]
+a[9] *b[6] +a[6] *b[9]
-a[25]*b[7] +a[7] *b[25]
-a[26]*b[8] +a[8] *b[26]
+a[19]*b[12]-a[12]*b[19]
-a[18]*b[13]+a[13]*b[18]
+a[21]*b[14]-a[14]*b[21]
-a[20]*b[15]+a[15]*b[20]
-a[32]*b[11]-a[11]*b[32]
-a[31]*b[17]-a[17]*b[31]
+a[28]*b[23]+a[23]*b[28]
-a[27]*b[24]-a[24]*b[27])
res[23]=(a[23]*b[1] +a[1] *b[23] # e123
+a[14]*b[3] +a[3] *b[14]
+a[31]*b[6] -a[6] *b[31]
-a[12]*b[4] -a[4] *b[12]
+a[11]*b[5] +a[5] *b[11]
-a[26]*b[13]+a[13]*b[26]
+a[25]*b[15]-a[15]*b[25]
-a[24]*b[16]+a[16]*b[24])
res[24]=(a[24]*b[1] +a[1] *b[24] # e124
+a[15]*b[3] +a[3] *b[15]
-a[31]*b[5] +a[5] *b[31]
-a[13]*b[4] -a[4] *b[13]
+a[11]*b[6] +a[6] *b[11]
+a[26]*b[12]-a[12]*b[26]
-a[25]*b[14]+a[14]*b[25]
+a[23]*b[16]-a[16]*b[23])
res[25]=(a[25]*b[1] +a[1] *b[25] # e134
+a[16]*b[3] +a[3] *b[16]
+a[31]*b[4] -a[4] *b[31]
-a[13]*b[5] -a[5] *b[13]
+a[12]*b[6] +a[6] *b[12]
-a[26]*b[11]+a[11]*b[26]
+a[24]*b[14]-a[14]*b[24]
-a[23]*b[15]+a[15]*b[23])
res[26]=(a[26]*b[1] +a[1] *b[26] # e234
-a[31]*b[3] +a[3] *b[31]
+a[16]*b[4] +a[4] *b[16]
-a[15]*b[5] -a[5] *b[15]
+a[14]*b[6] +a[6] *b[14]
+a[25]*b[11]-a[11]*b[25]
-a[24]*b[12]+a[12]*b[24]
+a[23]*b[13]-a[13]*b[23])
res[27]=(a[27]*b[1] +a[1] *b[27] # e0123
-a[23]*b[2] +a[2] *b[23]
+a[20]*b[3] -a[3] *b[20]
-a[18]*b[4] +a[4] *b[18]
+a[17]*b[5] -a[5] *b[17]
+a[32]*b[6] +a[6] *b[32]
+a[14]*b[7] +a[7] *b[14]
-a[12]*b[8] -a[8] *b[12]
+a[11]*b[9] +a[9] *b[11]
+a[31]*b[10]-a[10]*b[31]
-a[30]*b[13]+a[13]*b[30]
+a[29]*b[15]-a[15]*b[29]
-a[28]*b[16]+a[16]*b[28]
+a[26]*b[19]+a[19]*b[26]
-a[25]*b[21]-a[21]*b[25]
+a[24]*b[22]+a[22]*b[24])
res[28]=(a[28]*b[1] +a[1] *b[28] # e0124
-a[24]*b[2] +a[2] *b[24]
+a[21]*b[3] -a[3] *b[21]
-a[19]*b[4] +a[4] *b[19]
-a[32]*b[5] -a[5] *b[32]
+a[17]*b[6] -a[6] *b[17]
+a[15]*b[7] +a[7] *b[15]
-a[13]*b[8] -a[8] *b[13]
-a[31]*b[9] +a[9] *b[31]
+a[11]*b[10]+a[10]*b[11]
+a[30]*b[12]-a[12]*b[30]
-a[29]*b[14]+a[14]*b[29]
+a[27]*b[16]-a[16]*b[27]
-a[26]*b[18]-a[18]*b[26]
+a[25]*b[20]+a[20]*b[25]
-a[23]*b[22]-a[22]*b[23])
res[29]=(a[29]*b[1] +a[1] *b[29] # e0134
-a[25]*b[2] +a[2] *b[25]
+a[22]*b[3] -a[3] *b[22]
+a[32]*b[4] +a[4] *b[32]
-a[19]*b[5] +a[5] *b[19]
+a[18]*b[6] -a[6] *b[18]
+a[16]*b[7] +a[7] *b[16]
+a[31]*b[8] -a[8] *b[31]
-a[13]*b[9] -a[9] *b[13]
+a[12]*b[10]+a[10]*b[12]
-a[30]*b[11]+a[11]*b[30]
+a[28]*b[14]-a[14]*b[28]
-a[27]*b[15]+a[15]*b[27]
+a[26]*b[17]+a[17]*b[26]
-a[24]*b[20]-a[20]*b[24]
+a[23]*b[21]+a[21]*b[23])
res[30]=(a[30]*b[1] +a[1] *b[30] # e0234
-a[26]*b[2] +a[2] *b[26]
-a[32]*b[3] -a[3] *b[32]
+a[22]*b[4] -a[4] *b[22]
-a[21]*b[5] +a[5] *b[21]
+a[20]*b[6] -a[6] *b[20]
-a[31]*b[7] +a[7] *b[31]
+a[16]*b[8] +a[8] *b[16]
-a[15]*b[9] -a[9] *b[15]
+a[14]*b[10]+a[10]*b[14]
+a[29]*b[11]-a[11]*b[29]
-a[28]*b[12]+a[12]*b[28]
+a[27]*b[13]-a[13]*b[27]
-a[25]*b[17]-a[17]*b[25]
+a[24]*b[18]+a[18]*b[24]
-a[23]*b[19]-a[19]*b[23])
res[31]=(a[31]*b[1] +a[1] *b[31] # e1234
-a[26]*b[3] +a[3]*b[26]
+a[25]*b[4] -a[4]*b[25]
-a[24]*b[5] +a[5]*b[24]
+a[23]*b[6] -a[6]*b[23]
+a[16]*b[11]+a[11]*b[16]
-a[15]*b[12]-a[12]*b[15]
+a[14]*b[13]+a[13]*b[14])
res[32]=(a[32]*b[1] +a[1] *b[32] # e01234
+a[31]*b[2] +a[2] *b[31]
-a[30]*b[3] -a[3] *b[30]
+a[29]*b[4] +a[4] *b[29]
-a[28]*b[5] -a[5] *b[28]
+a[27]*b[6] +a[6] *b[27]
+a[26]*b[7] +a[7] *b[26]
-a[25]*b[8] -a[8] *b[25]
+a[24]*b[9] +a[9] *b[24]
-a[23]*b[10]-a[10]*b[23]
+a[22]*b[11]+a[11]*b[22]
-a[21]*b[12]-a[12]*b[21]
+a[20]*b[13]+a[13]*b[20]
+a[19]*b[14]+a[14]*b[19]
-a[18]*b[15]-a[15]*b[18]
+a[17]*b[16]+a[16]*b[17])
return res
end # geometric product (*)

# regressive product: vee operator (&, \vee)
function Base.:&(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
res = similar(a)

res[1] =(a[32]*b[1] +a[1] *b[32] # 1
+a[31]*b[2] +a[2] *b[31]
-a[30]*b[3] -a[3] *b[30]
+a[29]*b[4] +a[4] *b[29]
-a[28]*b[5] -a[5] *b[28]
+a[27]*b[6] +a[6] *b[27]
+a[26]*b[7] +a[7] *b[26]
-a[25]*b[8] -a[8] *b[25]
+a[24]*b[9] +a[9] *b[24]
-a[23]*b[10]-a[10]*b[23]
+a[22]*b[11]+a[11]*b[22]
-a[21]*b[12]-a[12]*b[21]
+a[20]*b[13]+a[13]*b[20]
+a[19]*b[14]+a[14]*b[19]
-a[18]*b[15]-a[15]*b[18]
+a[17]*b[16]+a[16]*b[17])

res[2] =(a[32]*b[2] +a[2] *b[32] # e0
+a[30]*b[7] -a[7] *b[30]
-a[29]*b[8] +a[8] *b[29]
+a[28]*b[9] -a[9] *b[28]
-a[27]*b[10]+a[10]*b[27]
+a[22]*b[17]+a[17]*b[22]
-a[21]*b[18]-a[18]*b[21]
+a[20]*b[19]+a[19]*b[20])
res[3] =(a[32]*b[3] +a[3] *b[32] # e1
+a[31]*b[7] -a[7] *b[31]
-a[29]*b[11]+a[11]*b[29]
+a[28]*b[12]-a[12]*b[28]
-a[27]*b[13]+a[13]*b[27]
+a[25]*b[17]+a[17]*b[25]
-a[24]*b[18]-a[18]*b[24]
+a[23]*b[19]+a[19]*b[23])
res[4] =(a[32]*b[4] +a[4] *b[32] # e2
+a[31]*b[8] -a[8] *b[31]
-a[30]*b[11]+a[11]*b[30]
+a[28]*b[14]-a[14]*b[28]
-a[27]*b[15]+a[15]*b[27]
+a[26]*b[17]+a[17]*b[26]
-a[24]*b[20]-a[20]*b[24]
+a[23]*b[21]+a[21]*b[23])
res[5] =(a[32]*b[5] +a[5] *b[32] # e3
+a[31]*b[9] -a[9] *b[31]
-a[30]*b[12]+a[12]*b[30]
+a[29]*b[14]-a[14]*b[29]
-a[27]*b[16]+a[16]*b[27]
+a[26]*b[18]+a[18]*b[26]
-a[25]*b[20]-a[20]*b[25]
+a[23]*b[22]+a[22]*b[23])
res[6] =(a[32]*b[6] +a[6] *b[32] # e4
+a[31]*b[10]-a[10]*b[31]
-a[30]*b[13]+a[13]*b[30]
+a[29]*b[15]-a[15]*b[29]
-a[28]*b[16]+a[16]*b[28]
+a[26]*b[19]+a[19]*b[26]
-a[25]*b[21]-a[21]*b[25]
+a[24]*b[22]+a[22]*b[24])

res[7] =(a[32]*b[7] +a[7] *b[32] # e01
+a[29]*b[17]+a[17]*b[29]
-a[28]*b[18]-a[18]*b[28]
+a[27]*b[19]+a[19]*b[27])
res[8] =(a[32]*b[8] +a[8] *b[32] # e02
+a[30]*b[17]+a[17]*b[30]
-a[28]*b[20]-a[20]*b[28]
+a[27]*b[21]+a[21]*b[27])
res[9] =(a[32]*b[9] +a[9] *b[32] # e03
+a[30]*b[18]+a[18]*b[30]
-a[29]*b[20]-a[20]*b[29]
+a[27]*b[22]+a[22]*b[27])
res[10]=(a[32]*b[10]+a[10]*b[32] # e04
+a[30]*b[19]+a[19]*b[30]
-a[29]*b[21]-a[21]*b[29]
+a[28]*b[22]+a[22]*b[28])
res[11]=(a[32]*b[11]+a[11]*b[32] # e12
+a[31]*b[17]+a[17]*b[31]
-a[28]*b[23]-a[23]*b[28]
+a[27]*b[24]+a[24]*b[27])
res[12]=(a[32]*b[12]+a[12]*b[32] # e13
+a[31]*b[18]+a[18]*b[31]
-a[29]*b[23]-a[23]*b[29]
+a[27]*b[25]+a[25]*b[27])
res[13]=(a[32]*b[13]+a[13]*b[32] # e14
+a[31]*b[19]+a[19]*b[31]
-a[29]*b[24]-a[24]*b[29]
+a[28]*b[25]+a[25]*b[28])
res[14]=(a[32]*b[14]+a[14]*b[32] # e23
+a[31]*b[20]+a[20]*b[31]
-a[30]*b[23]-a[23]*b[30]
+a[27]*b[26]+a[26]*b[27])
res[15]=(a[32]*b[15]+a[15]*b[32] # e24
+a[31]*b[21]+a[21]*b[31]
-a[30]*b[24]-a[24]*b[30]
+a[28]*b[26]+a[26]*b[28])
res[16]=(a[32]*b[16]+a[16]*b[32] # e34
+a[31]*b[22]+a[22]*b[31]
-a[30]*b[25]-a[25]*b[30]
+a[29]*b[26]+a[26]*b[29])

res[17]=(a[32]*b[17]+a[17]*b[32] # e012
+a[28]*b[27]-a[27]*b[28])
res[18]=(a[32]*b[18]+a[18]*b[32] # e013
+a[29]*b[27]-a[27]*b[29])
res[19]=(a[32]*b[19]+a[19]*b[32] # e014
+a[29]*b[28]-a[28]*b[29])
res[20]=(a[32]*b[20]+a[20]*b[32] # e023
+a[30]*b[27]-a[27]*b[30])
res[21]=(a[32]*b[21]+a[21]*b[32] # e024
+a[30]*b[28]-a[28]*b[30])
res[22]=(a[32]*b[22]+a[22]*b[32] # e034
+a[30]*b[29]-a[29]*b[30])
res[23]=(a[32]*b[23]+a[23]*b[32] # e123
+a[31]*b[27]-a[27]*b[31])
res[24]=(a[32]*b[24]+a[24]*b[32] # e124`
+a[31]*b[28]-a[28]*b[31])
res[25]=(a[32]*b[25]+a[25]*b[32] # e134
+a[31]*b[29]-a[29]*b[31])
res[26]=(a[32]*b[26]+a[26]*b[32] # e234
+a[31]*b[30]-a[30]*b[31])

res[27]=a[32]*b[27]+a[27]*b[32] # e0123
res[28]=a[32]*b[28]+a[28]*b[32] # e0124
res[29]=a[32]*b[29]+a[29]*b[32] # e0134
res[30]=a[32]*b[30]+a[30]*b[32] # e0234
res[31]=a[32]*b[31]+a[31]*b[32] # e1234

res[32]=a[32]*b[32] # e01234
return res
end # regressive product; vee operator (&, \vee)

# inner product (|)
function Base.:|(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
res = similar(a)
res[1] =(a[1] *b[1] +a[3] *b[3] # 1
+a[4] *b[4] +a[5] *b[5]
+a[6] *b[6] -a[11]*b[11]
-a[12]*b[12]-a[13]*b[13]
-a[14]*b[14]-a[15]*b[15]
-a[16]*b[16]-a[23]*b[23]
-a[24]*b[24]-a[25]*b[25]
-a[26]*b[26]+a[31]*b[31])

res[2] =(a[2] *b[1] +a[1] *b[2] # e0
+a[7] *b[3] -a[3] *b[7]
+a[8] *b[4] -a[4] *b[8]
+a[9] *b[5] -a[5] *b[9]
+a[10]*b[6] -a[6] *b[10]
-a[17]*b[11]-a[11]*b[17]
-a[18]*b[12]-a[12]*b[18]
-a[19]*b[13]-a[13]*b[19]
-a[20]*b[14]-a[14]*b[20]
-a[21]*b[15]-a[15]*b[21]
-a[22]*b[16]-a[16]*b[22]
-a[27]*b[23]+a[23]*b[27]
-a[28]*b[24]+a[24]*b[28]
-a[29]*b[25]+a[25]*b[29]
-a[30]*b[26]+a[26]*b[30]
+a[32]*b[31]+a[31]*b[32])

res[3] =(a[3] *b[1] +a[1] *b[3] # e1
+a[11]*b[4] -a[4] *b[11]
+a[12]*b[5] -a[5] *b[12]
+a[13]*b[6] -a[6] *b[13]
-a[23]*b[14]-a[14]*b[23]
-a[24]*b[15]-a[15]*b[24]
-a[25]*b[16]-a[16]*b[25]
-a[31]*b[26]+a[26]*b[31])
res[4] =(a[4] *b[1] +a[1] *b[4] # e2
-a[11]*b[3] +a[3] *b[11]
+a[14]*b[5] -a[5] *b[14]
+a[15]*b[6] -a[6] *b[15]
+a[23]*b[12]+a[12]*b[23]
+a[24]*b[13]+a[13]*b[24]
-a[26]*b[16]-a[16]*b[26]
+a[31]*b[25]-a[25]*b[31])
res[5] =(a[5] *b[1] +a[1] *b[5] # e3
-a[12]*b[3] +a[3] *b[12]
-a[14]*b[4] +a[4] *b[14]
+a[16]*b[6] -a[6] *b[16]
-a[23]*b[11]-a[11]*b[23]
+a[25]*b[13]+a[13]*b[25]
+a[26]*b[15]+a[15]*b[26]
-a[31]*b[24]+a[24]*b[31])
res[6] =(a[6] *b[1] +a[1] *b[6] # e4
-a[13]*b[3] +a[3] *b[13]
-a[15]*b[4] +a[4] *b[15]
-a[16]*b[5] +a[5] *b[16]
-a[24]*b[11]-a[11]*b[24]
-a[25]*b[12]-a[12]*b[25]
-a[26]*b[14]-a[14]*b[26]
+a[31]*b[23]-a[23]*b[31])
res[7] =(a[7] *b[1] +a[1] *b[7] # e01
+a[17]*b[4] +a[4] *b[17]
+a[18]*b[5] +a[5] *b[18]
+a[19]*b[6] +a[6] *b[19]
-a[27]*b[14]-a[14]*b[27]
-a[28]*b[15]-a[15]*b[28]
-a[29]*b[16]-a[16]*b[29]
-a[32]*b[26]-a[26]*b[32])
res[8] =(a[8] *b[1] +a[1] *b[8] # e02
-a[17]*b[3] -a[3] *b[17]
+a[20]*b[5] +a[5] *b[20]
+a[21]*b[6] +a[6] *b[21]
+a[27]*b[12]+a[12]*b[27]
+a[28]*b[13]+a[13]*b[28]
-a[30]*b[16]-a[16]*b[30]
+a[32]*b[25]+a[25]*b[32])
res[9] =(a[9] *b[1] +a[1] *b[9] # e03
-a[18]*b[3] -a[3] *b[18]
-a[20]*b[4] -a[4] *b[20]
+a[22]*b[6] +a[6] *b[22]
-a[27]*b[11]-a[11]*b[27]
+a[29]*b[13]+a[13]*b[29]
+a[30]*b[15]+a[15]*b[30]
-a[32]*b[24]-a[24]*b[32])
res[10]=(a[10]*b[1] +a[1] *b[10] # e04
-a[19]*b[3] -a[3] *b[19]
-a[21]*b[4] -a[4] *b[21]
-a[22]*b[5] -a[5] *b[22]
-a[28]*b[11]-a[11]*b[28]
-a[29]*b[12]-a[12]*b[29]
-a[30]*b[14]-a[14]*b[30]
+a[32]*b[23]+a[23]*b[32])

res[11]=(a[11]*b[1] +a[1] *b[11] # e12
+a[23]*b[5] +a[5] *b[23]
+a[24]*b[6] +a[6] *b[24]
-a[31]*b[16]-a[16]*b[31])
res[12]=(a[12]*b[1] +a[1] *b[12] # e13
-a[23]*b[4] -a[4] *b[23]
+a[25]*b[6] +a[6] *b[25]
+a[31]*b[15]+a[15]*b[31])
res[13]=(a[13]*b[1] +a[1] *b[13] # e14
-a[24]*b[4] -a[4] *b[24]
-a[25]*b[5] -a[5] *b[25]
-a[31]*b[14]-a[14]*b[31])
res[14]=(a[14]*b[1] +a[1] *b[14] # e23
+a[23]*b[3] +a[3] *b[23]
+a[26]*b[6] +a[6] *b[26]
-a[31]*b[13]-a[13]*b[31])
res[15]=(a[15]*b[1] +a[1] *b[15] # e24
+a[24]*b[3] +a[3] *b[24]
-a[26]*b[5] -a[5] *b[26]
+a[31]*b[12]+a[12]*b[31])
res[16]=(a[16]*b[1] +a[1] *b[16] # e34
+a[25]*b[3] +a[3] *b[25]
+a[26]*b[4] +a[4] *b[26]
-a[31]*b[11]-a[11]*b[31])
res[17]=(a[17]*b[1] +a[1] *b[17] # e012
+a[27]*b[5] -a[5] *b[27]
+a[28]*b[6] -a[6] *b[28]
-a[32]*b[16]-a[16]*b[32])
res[18]=(a[18]*b[1] +a[1] *b[18] # e013
-a[27]*b[4] +a[4] *b[27]
+a[29]*b[6] -a[6] *b[29]
+a[32]*b[15]+a[15]*b[32])
res[19]=(a[19]*b[1] +a[1] *b[19] # e014
-a[28]*b[4] +a[4] *b[28]
-a[29]*b[5] +a[5] *b[29]
-a[32]*b[14]-a[14]*b[32])
res[20]=(a[20]*b[1] +a[1] *b[20] # e023
+a[27]*b[3] -a[3] *b[27]
+a[30]*b[6] -a[6] *b[30]
-a[32]*b[13]-a[13]*b[32])
res[21]=(a[21]*b[1] +a[1] *b[21] # e024
+a[28]*b[3] -a[3] *b[28]
-a[30]*b[5] +a[5] *b[30]
+a[32]*b[12]+a[12]*b[32])
res[22]=(a[22]*b[1] +a[1] *b[22] # e034
+a[29]*b[3] -a[3] *b[29]
+a[30]*b[4] -a[4] *b[30]
-a[32]*b[11]-a[11]*b[32])

res[23]=(a[23]*b[1] +a[1] *b[23] # e123
+a[31]*b[6] -a[6] *b[31])
res[24]=(a[24]*b[1] +a[1] *b[24] # e124
-a[31]*b[5] +a[5] *b[31])
res[25]=(a[25]*b[1] +a[1] *b[25] # e134
+a[31]*b[4] -a[4] *b[31])
res[26]=(a[26]*b[1] +a[1] *b[26] # e234
-a[31]*b[3] +a[3] *b[31])
res[27]=(a[27]*b[1] +a[1] *b[27] # e0123
+a[32]*b[6] +a[6] *b[32])
res[28]=(a[28]*b[1] +a[1] *b[28] # e0124
-a[32]*b[5] -a[5] *b[32])
res[29]=(a[29]*b[1] +a[1] *b[29] # e0134
+a[32]*b[4] +a[4] *b[32])
res[30]=(a[30]*b[1] +a[1] *b[30] # e0234
-a[32]*b[3] -a[3] *b[32])

res[31] =a[31]*b[1] +a[1] *b[31] # e1234
res[32] =a[32]*b[1] +a[1] *b[32] # e01234
return res
end # inner product (|)

# outer product; wedge operator (^)
function Base.:^(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
res = similar(a)

res[1]= a[1]*b[1] # 1

res[2]= a[2]*b[1] +a[1]*b[2] # e0
res[3]= a[3]*b[1] +a[1]*b[3] # e1
res[4]= a[4]*b[1] +a[1]*b[4] # e2
res[5]= a[5]*b[1] +a[1]*b[5] # e3
res[6]= a[6]*b[1] +a[1]*b[6] # e4

res[7]= (a[7] *b[1] +a[1]*b[7] # e01
-a[3] *b[2] +a[2]*b[3])
res[8]= (a[8] *b[1] +a[1]*b[8] # e02
-a[4] *b[2] +a[2]*b[4])
res[9]= (a[9] *b[1] +a[1]*b[9] # e03
-a[5] *b[2] +a[2]*b[5])
res[10]=(a[10]*b[1] +a[1]*b[10] # e04
-a[6] *b[2] +a[2]*b[6])
res[11]=(a[11]*b[1] +a[1]*b[11] # e12
-a[4] *b[3] +a[3]*b[4])
res[12]=(a[12]*b[1] +a[1]*b[12] # e13
-a[5] *b[3] +a[3]*b[5])
res[13]=(a[13]*b[1] +a[1]*b[13] # e14
-a[6] *b[3] +a[3]*b[6])
res[14]=(a[14]*b[1] +a[1]*b[14] # e23
-a[5] *b[4] +a[4]*b[5])
res[15]=(a[15]*b[1] +a[1]*b[15] # e24
-a[6] *b[4] +a[4]*b[6])
res[16]=(a[16]*b[1] +a[1]*b[16] # e34
-a[6] *b[5] +a[5]*b[6])

res[17]=(a[17]*b[1] +a[1]*b[17] # e012
+a[11]*b[2] +a[2]*b[11]
-a[8] *b[3] -a[3]*b[8]
+a[7] *b[4] +a[4]*b[7])
res[18]=(a[18]*b[1] +a[1]*b[18] # e013
+a[12]*b[2] +a[2]*b[12]
-a[9] *b[3] -a[3]*b[9]
+a[7] *b[5] +a[5]*b[7])
res[19]=(a[19]*b[1] +a[1]*b[19] # e014
+a[13]*b[2] +a[2]*b[13]
-a[10]*b[3] -a[3]*b[10]
+a[7] *b[6] +a[6]*b[7])
res[20]=(a[20]*b[1] +a[1]*b[20] # e023
+a[14]*b[2] +a[2]*b[14]
-a[9] *b[4] -a[4]*b[9]
+a[8] *b[5] +a[5]*b[8])
res[21]=(a[21]*b[1] +a[1]*b[21] # e024
+a[15]*b[2] +a[2]*b[15]
-a[10]*b[4] -a[4]*b[10]
+a[8] *b[6] +a[6]*b[8])
res[22]=(a[22]*b[1] +a[1]*b[22] # e034
+a[16]*b[2] +a[2]*b[16]
-a[10]*b[5] -a[5]*b[10]
+a[9] *b[6] +a[6]*b[9])
res[23]=(a[23]*b[1] +a[1]*b[23] # e123
+a[14]*b[3] +a[3]*b[14]
-a[12]*b[4] -a[4]*b[12]
+a[11]*b[5] +a[5]*b[11])
res[24]=(a[24]*b[1] +a[1]*b[24] # e124
+a[15]*b[3] +a[3]*b[15]
-a[13]*b[4] -a[4]*b[13]
+a[11]*b[6] +a[6]*b[11])
res[25]=(a[25]*b[1] +a[1]*b[25] # e134
+a[16]*b[3] +a[3]*b[16]
-a[13]*b[5] -a[5]*b[13]
+a[12]*b[6] +a[6]*b[12])
res[26]=(a[26]*b[1] +a[1]*b[26] # e234
+a[16]*b[4] +a[4]*b[16]
-a[15]*b[5] -a[5]*b[15]
+a[14]*b[6] +a[6]*b[14])

res[27]=(a[27]*b[1] +a[1]*b[27] # e0123
-a[23]*b[2] +a[2]*b[23]
+a[20]*b[3] -a[3]*b[20]
-a[18]*b[4] +a[4]*b[18]
+a[17]*b[5] -a[5]*b[17]
+a[14]*b[7] +a[7]*b[14]
-a[12]*b[8] -a[8]*b[12]
+a[11]*b[9] +a[9]*b[11])
res[28]=(a[28]*b[1] +a[1] *b[28] # e0124
-a[24]*b[2] +a[2] *b[24]
+a[21]*b[3] -a[3] *b[21]
-a[19]*b[4] +a[4] *b[19]
+a[17]*b[6] -a[6] *b[17]
+a[15]*b[7] +a[7] *b[15]
-a[13]*b[8] -a[8] *b[13]
+a[11]*b[10]+a[10]*b[11])
res[29]=(a[29]*b[1] +a[1] *b[29] # e0134
-a[25]*b[2] +a[2] *b[25]
+a[22]*b[3] -a[3] *b[22]
-a[19]*b[5] +a[5] *b[19]
+a[18]*b[6] -a[6] *b[18]
+a[16]*b[7] +a[7] *b[16]
-a[13]*b[9] -a[9] *b[13]
+a[12]*b[10]+a[10]*b[12])
res[30]=(a[30]*b[1] +a[1] *b[30] # e0234
-a[26]*b[2] +a[2] *b[26]
+a[22]*b[4] -a[4] *b[22]
-a[21]*b[5] +a[5] *b[21]
+a[20]*b[6] -a[6] *b[20]
+a[16]*b[8] +a[8] *b[16]
-a[15]*b[9] -a[9] *b[15]
+a[14]*b[10]+a[10]*b[14])
res[31]=(a[31]*b[1] +a[1] *b[31] # e1234
-a[26]*b[3] +a[3] *b[26]
+a[25]*b[4] -a[4] *b[25]
-a[24]*b[5] +a[5] *b[24]
+a[23]*b[6] -a[6] *b[23]
+a[16]*b[11]+a[11]*b[16]
-a[15]*b[12]-a[12]*b[15]
+a[14]*b[13]+a[13]*b[14])

res[32]=(a[32]*b[1] +a[1] *b[32] # e01234
+a[31]*b[2] +a[2] *b[31]
-a[30]*b[3] -a[3] *b[30]
+a[29]*b[4] +a[4] *b[29]
-a[28]*b[5] -a[5] *b[28]
+a[27]*b[6] +a[6] *b[27]
+a[26]*b[7] +a[7] *b[26]
-a[25]*b[8] -a[8] *b[25]
+a[24]*b[9] +a[9] *b[24]
-a[23]*b[10]-a[10]*b[23]
+a[22]*b[11]+a[11]*b[22]
-a[21]*b[12]-a[12]*b[21]
+a[20]*b[13]+a[13]*b[20]
+a[19]*b[14]+a[14]*b[19]
-a[18]*b[15]-a[15]*b[18]
+a[17]*b[16]+a[16]*b[17])
return res
end # outer product; wedge operator (^)

# reverse operator (~)
function Base.:~(a::Vector{Float32})
res = copy(a)
res[7:26] .*= -1
return res
end # reverse operator

# conjugate
function conjugate(a::Vector{Float32})::Vector{Float32}
res = copy(a)
res[2:16] .*= -1
res[32] *= -1
return res
end # conjugate()

# unit test
# arguments:
# - nLoop repeats a section of the PGA calculations for benchmarking
# usage notes:
# - @time utest(1) checks on whether the unit test output of ripga.jl
# exactly matches the unit test output of pga3d.cpp. The comparison
# ends with the printing of the number of tests in the unit test that
# don't match. 0 indicates success.
# - @time utest(1,true) outputs a slightly simplified version of the
# unit test output that does not match the unit test output by pga3d.cpp.
# - @btime utest() is a test for execution speed of ripga.jl.
# (NOTE: requires using BenchmarkTools)
function utest(nLoop=100, flgSimplify::Bool=false)
nField = length(basis)
P0 = Vector{Float32}(undef,nField)
P1 = Vector{Float32}(undef,nField)
P2 = Vector{Float32}(undef,nField)
P3 = Vector{Float32}(undef,nField)
line0 = Vector{Float32}(undef,nField)
line1 = Vector{Float32}(undef,nField)
x = Vector{Float32}(undef,nField)
tst1 = Vector{Float32}(undef,nField)
tst2 = Vector{Float32}(undef,nField)

for iLoop = 1:nLoop
# define some points
P0 = point(0,0,0,0)
P1 = point(1,0,0,0)
P2 = point(0,1,0,0)
P3 = point(1,1,0,0)

# calculate intersection of parallel lines
line0 = P0 & P1
line1 = P2 & P3
x = line0 ^ line1

tst1 = e0 - 1f0
tst2 = 1f0 - e0

# # geometric algebra equations in math syntax
# ga"axis_z = e1 ∧ e2"
# ga"origin = axis_z ∧ e3"
#
# px = point(1f0, 0f0, 0f0)
# ga"line = origin ∨ px"
# p = plane(2f0,0f0,1f0,-3f0)
# ga"rot = rotor(Float32(pi/2), e1 e2)"
# ga"rot_point = rot px ~rot"
# ga"rot_line = rot line ~rot"
# ga"rot_plane = rot p ~rot"
#
# tst1 = e0 - 1f0
# tst2 = 1f0 - e0
end

# if (slow) output of unit test results wanted
if nLoop == 1
nError = 0

S = Matrix{String}(undef,9,3) # 3 columns:
S[1,1] = " P0 : " # 1) label
S[1,2] = toStr(P0) # 2) toStr()
S[1,3] = "e1234" # 3) expected string

S[2,1] = " P1 : "
S[2,2] = toStr(P1)
S[2,3] = "e0234 + e1234"

S[3,1] = " P2 : "
S[3,2] = toStr(P2)
S[3,3] = "e0134 + e1234"

S[4,1] = " P3 : "
S[4,2] = toStr(P3)
S[4,3] = "e0134 + e0234 + e1234"

S[5,1] = " line0 : "
S[5,2] = toStr(line0)
S[5,3] = "-e234"

S[6,1] = " line1 : "
S[6,2] = toStr(line1)
S[6,3] = "e034 - e234"

S[7,1] = " intersection : "
S[7,2] = toStr(x)
S[7,3] = "-e20"

S[8,1] = " toStr test 1 : "
S[8,2] = toStr(tst1)
S[8,3] = "-1 + e0"

S[9,1] = " toStr test 2 : "
S[9,2] = toStr(tst2)
S[9,3] = "1 - e0"

# print unit test results;
nTest = size(S,1)
for iTest = 1:nTest
if S[iTest,2] == S[iTest,3]
mark = " "
else
mark = "x"
nError += 1
end
println("$mark" * S[iTest,1] * S[iTest,2])
end

return nError # return unit test results
end
end # utest()

The following is PGA code for any dimension.

# ripgand.jl : reference implementation of
# Projective Geometric Algebra common for nD
#
# This is a Julia port of pga3d.cpp, bivector.net's C++
# reference implementation of projective geometric algebra
# available at https://bivector.net/tools.html
#
using Printf

function Base.:*(a::Number, b::Vector{Float32})::Vector{Float32}
res = copy(b)
res .*= Float32(a)
return res
end

function cumgprod(a::Matrix{Float32})::Matrix{Float32}
res = similar(a)
res[:,1] = a[:,1]
nCol = size(a,2)
for iCol = 2:nCol
res[:,iCol] = res[:,iCol-1] * a[:,iCol]
end
return res # cumulative geometric product
end # similar to cumulative product cumprod()

function Base.:&(a::Matrix{Float32},b::Matrix{Float32})::Matrix{Float32}
nCol = size(a,2)
if nCol >= size(b,2)
res = similar(a)
else
res = similar(b)
nCol = size(b,2)
end
for iCol = 1:nCol
res[:,iCol] = a[:,iCol] & b[:,iCol]
end
return res
end

function Base.:^(a::Matrix{Float32},b::Vector{Float32})::Matrix{Float32}
res = similar(a)
nCol = size(res,2)
for iCol = 1:nCol
res[:,iCol] = a[:,iCol] ^ b
end
return res
end

function Base.:^(a::Vector{Vector{Float32}},b::Vector{Float32})::Matrix{Float32}
nCol = length(a)
res = Matrix{Float32}(undef,16,nCol)
for iCol = 1:nCol
res[:,iCol] = a[iCol] ^ b
end
return res
end

function Base.:^(a::Vector{Float32},b::Matrix{Float32})::Matrix{Float32}
res = similar(b)
nCol = size(res,2)
for iCol = 1:nCol
res[:,iCol] = a ^ b[:,iCol]
end
return res
end

function Base.:^(a::Vector{Float32},b::Vector{Vector{Float32}})::Matrix{Float32}
nCol = length(b)
res = Matrix{Float32}(undef,16,nCol)
for iCol = 1:nCol
res[:,iCol] = a ^ b[iCol]
end
return res
end

function Base.:+(a::Vector{Float32},b::Number)::Vector{Float32}
res = copy(a)
res[1] += Float32(b)
return res
end

function Base.:+(a::Number,b::Vector{Float32})::Vector{Float32}
res = copy(b)
res[1] += Float32(a)
return res
end

function Base.:-(a::Vector{Float32},b::Number)::Vector{Float32}
res = copy(a)
res[1] -= Float32(b)
return res
end

function Base.:-(a::Number,b::Vector{Float32})::Vector{Float32}
res = copy(.-b)
res[1] += Float32(a)
return res
end

function Base.:>>>(a::Vector{Float32},b::Vector{Float32})::Vector{Float32}
return a * b * ~a
end

function Base.:>>>(a::Vector{Float32},b::Matrix{Float32})
res = similar(b)
nCol = size(b,2)
for iCol = 1:nCol
res[:,iCol] = a * b[:,iCol] * ~a
end
return res
end

function Base.:!(a::Vector{Float32})::Vector{Float32}
return reverse(a)
end

function Base.:!(a::Matrix{Float32})
return mapslices(!, a, dims=1)
end

# mask off all vectors except those with specified grade (k)
function grade(a::Vector{Float32},k::Int64)
# if specified grade out of range
nBasis = length(a)
nD = Int(log2(nBasis)) - 1
nGrade = nD + 2
res = zeros(Float32,nBasis)
if (k < 0) || (k >= nGrade)
return res
end

# generate CN: ending index of each grade
N = zeros(Int32,nGrade) # basis vectors with grade
for iGrade = 1:nGrade
N[iGrade] = binomial(nD+1,iGrade-1)
end
CN = cumsum(N)

# copy vectors with specified grade
i2 = CN[k+1]
i0 = i2 - N[k+1] + 1
res[i0:i2] = a[i0:i2]
return res
end

# convert Euclidean coordinates to PGA expression
function point(x::Number, y::Number)::Vector{Float32}
return x*e20 + y*e01 + e12
end
function point(x::Number, y::Number, z::Number)::Vector{Float32}
return x*e032 + y*e013 + z*e021 + e123
end
function point(
x::Number,
y::Number,
z::Number,
w::Number)::Vector{Float32}
return x*e0234 +
y*e0134 +
z*e0124 +
w*e0123 +
e1234
end
function point(V::Vector{Float32},
nBasis::Int64=0)::Vector{Float32}

if nBasis == 0
nBasis = length(basis)
end
if nBasis == 8 # 2D
return V[1]*e20 +
V[2]*e01 +
e12
elseif nBasis == 16 # 3D
return V[1]*e032 +
V[2]*e013 +
V[3]*e021 +
e123
elseif nBasis == 32 # 4D
return V[1]*e0234 +
V[2]*e0134 +
V[3]*e0124 +
V[4]*e0123 +
e1234
end
end
function point(M::Matrix{Float32})::Matrix{Float32}
nCol = size(M,2)
res = Matrix{Float32}(undef, length(basis), nCol)
for iCol = 1:nCol
res[:,iCol] = point(M[:,iCol])
end
return res
end

# convert PGA expressions to Euclidean coordinates
function toCoord(M::Matrix{Float32},
keepIdeal::Bool=false,
nBasis::Int64=0)

if nBasis == 0
nBasis = length(basis)
end

nB1 = nBasis - 1
nD = Int(log2(nBasis)) - 1
MC = Matrix{Float32}(undef, (nD,size(M,2)))
B = M[nB1,:] .!= 0
S = sign.(M[nB1,:])
S[.!B] .= 1.0
for iRow = 1:nD
MC[iRow,:] = M[nB1-iRow,:] .* S # account for orientation
end
return keepIdeal ? MC : MC[:,B]
end
function toCoord(V::Vector{Float32})
nBasis = size(V,1)
nB1 = nBasis - 1
nD = Int(log2(nBasis)) - 1
res = Vector{Float32}(undef, nD)
for iRow = 1:nD
res[iRow] = V[nB1-iRow]
end
return res
end

function rotor(angle::Number, line::Vector{Float32})::Vector{Float32}
return Float32(cos(angle/2)) +
Float32(sin(angle/2))*normalize(line)
end

function translator(dist::Number, line::Vector{Float32})::Vector{Float32}
return 1 + Float32(dist/2) * line
end

function norm(a::Vector{Float32})
return sqrt(abs((a * conjugate(a))[1]))
end

function norm(a::Matrix{Float32})
return mapslices(norm, a, dims=1)
end

function normalize(a::Vector{Float32})::Vector{Float32}
return a ./ norm(a)
end

# exponential, restricted to case B ^ B = 0
function E(alpha::Number, B::Vector{Float32})
s = (B * B)[1]
if s == 0
return 1 + alpha*B
elseif s < 0
return cos(alpha) + sin(alpha)*B
else
return cosh(alpha) + sinh(alpha)*B
end
end

# convert multivector fields to string
# usage notes:
# - Use toStr1 to generate text that exactly
# matches the text generated by pga3d.cpp
# for example to unit test ripga.jl.
# - Use toStr to generate text that simplifies
# signs (e.g., a + -b simplifies to a - b)
# and avoids overloading the parsing of
# constants with exponentials to represent
# basis vectors (e.g., 1e1 with overloading
# simplifies to e1 without overloading).
function toStr1(V::Vector{Float32})
nField = size(V,1)
nNZField = 0
S = String[]
for iField = 1:nField
if V[iField] != 0
if nNZField != 0
push!(S, @sprintf(" + %0.7g%s",
V[iField], basis[iField]))
else
push!(S, @sprintf("%0.7g%s",
V[iField],
(iField==1) ? "" : basis[iField]))
end
nNZField += 1
end
end
return string(S...)
end

function toStr(V::Vector{Float32})
nField = size(V,1)
nNZField = 0
S = String[]
for iField = 1:nField
if V[iField] != 0
if nNZField != 0
if V[iField] < 0
if V[iField] == -1
push!(S, @sprintf(" - %s",
basis[iField]))
else
push!(S, @sprintf(" - %0.7g%s",
abs(V[iField]), basis[iField]))
end
else
if V[iField] == 1
push!(S, @sprintf(" + %s",
basis[iField]))
else
push!(S, @sprintf(" + %0.7g%s",
V[iField], basis[iField]))
end
end
else
if V[iField] == 1
push!(S, @sprintf("%s",
(iField==1) ? "1" : basis[iField]))
elseif V[iField] == -1
push!(S, @sprintf("-%s",
(iField==1) ? "1" : basis[iField]))
elseif iField == 1
push!(S, @sprintf("%0.7g",
V[iField]))
else
push!(S, @sprintf("%0.7g%s",
V[iField], basis[iField]))
end
end
nNZField += 1
end
end
if length(S) == 0
push!(S, "0")
end
return string(S...)
end # toStr()

# convert GA math syntax string to GA programming syntax expression
macro ga_str(str)
C = collect(str)
n = length(C)
for i = 1:n
if C[i] == ' ' # \thinspace for geometric product
C[i] = '*'
elseif C[i] == '∧' # \wedge for outer product
C[i] = '^'
elseif C[i] == '∨' # \vee for regressive product
C[i] = '&'
elseif C[i] == '·' # \cdotp for inner product
C[i] = '|'
elseif C[i] == '∗' # \ast for dual (suffix)
j = i-1
if C[j] == ')'
nDepth = 1
C[j+1] = C[j]
j -= 1
# shift from suffix to prefix of parentheses
while (j > 0) && (nDepth > 0)
if C[j] == ')'
nDepth += 1
elseif C[j] == '('
nDepth -= 1
end
C[j+1] = C[j]
j -= 1
end
else
# shift from suffix to prefix of variable
while (j > 0) && (isletter(C[j]) || isnumeric(C[j]) || C[j]=='_')
C[j+1] = C[j]
j -= 1
end
end
C[j+1] = '!' # prefix '!'
end
end
return esc(Meta.parse(String(C)))
end # ga_str()

# switch dimension of geometry workspace
function xdimension(nD::Int64, isVerbose::Bool=false)
# if specified dimension is current dimension
if 2^(nD+1) == length(basis)
if isVerbose
println("dimension is already $nD")
end

# else if specified dimension is implemented
elseif nD in [2, 3, 4]
strFile = @sprintf("ripga%dd.jl", nD)
include(strFile)

# else specified dimension not yet implemented
else
if isVerbose
println("xDimension error: dimension $nD not yet implemented")
end
end
end # xdimension()

# rtest: random test
function rtest()
nBasis = length(basis)
M = Float32.(rand(1:9,nBasis,3))
M[:,3] = M[:,1] * M[:,2]
# M[:,3] = normalize(M[:,1])
display([1:nBasis basis M])
println("expression to copy to bivector.net evaluator:")
println("(" * toStr(M[:,1]) * ") * (" *
toStr(M[:,2]) * ")")
end # rtest()

--

--

Greg Sepesi

software engineer pondering basic stuff like how to read