quad_sphere 0.9.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +18 -0
- data/README.md +153 -0
- data/examples/binning.rb +44 -0
- data/examples/distort_closure.rb +108 -0
- data/examples/gl1.rb +164 -0
- data/examples/gl2.rb +182 -0
- data/examples/grid.rb +69 -0
- data/lib/quad_sphere/csc.rb +1 -1
- data/lib/quad_sphere/version.rb +5 -0
- data/quad_sphere.gemspec +30 -0
- data/test/test_quad_sphere.rb +860 -0
- metadata +39 -40
data/examples/gl2.rb
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
# A shaded view of the CSC projection. You need OpenGL for this.
|
2
|
+
|
3
|
+
require 'quad_sphere/csc'
|
4
|
+
require 'opengl'
|
5
|
+
|
6
|
+
class CSCShadedApp
|
7
|
+
|
8
|
+
WINDOW_SIZE = 480
|
9
|
+
|
10
|
+
# How many subdivisions of each cube face. Can be raised for
|
11
|
+
# smoother shading, at the cost of model complexity.
|
12
|
+
FACE_SUBDIV = 10
|
13
|
+
|
14
|
+
def run
|
15
|
+
@ang1 = 0
|
16
|
+
@ang2 = 0
|
17
|
+
@ang3 = 0
|
18
|
+
|
19
|
+
glutInit
|
20
|
+
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
|
21
|
+
glutInitWindowSize(WINDOW_SIZE, WINDOW_SIZE)
|
22
|
+
glutCreateWindow('CSC')
|
23
|
+
|
24
|
+
glEnable(GL_LIGHTING)
|
25
|
+
glEnable(GL_LIGHT0)
|
26
|
+
glEnable(GL_DEPTH_TEST)
|
27
|
+
glEnable(GL_CULL_FACE)
|
28
|
+
glFrontFace(GL_CW)
|
29
|
+
glClearColor(0.5, 0.5, 0.5, 0.0)
|
30
|
+
setup_model
|
31
|
+
|
32
|
+
glutDisplayFunc(method :display)
|
33
|
+
glutReshapeFunc(method :reshape)
|
34
|
+
glutKeyboardFunc(method :keyboard)
|
35
|
+
|
36
|
+
# And run.
|
37
|
+
glutMainLoop()
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def setup_model
|
43
|
+
glGenLists(1)
|
44
|
+
glNewList(1, GL_COMPILE)
|
45
|
+
|
46
|
+
# The cube faces, and the colour we want each:
|
47
|
+
faces = [ [ QuadSphere::TOP_FACE, [ 0.9, 0.25, 0.25, 1.0 ] ], # red
|
48
|
+
[ QuadSphere::FRONT_FACE, [ 0.25, 0.25, 0.9, 1.0 ] ], # blue
|
49
|
+
[ QuadSphere::EAST_FACE, [ 0.25, 0.9, 0.25, 1.0 ] ], # green
|
50
|
+
[ QuadSphere::BACK_FACE, [ 0.9, 0.9, 0.25, 1.0 ] ], # yellow
|
51
|
+
[ QuadSphere::WEST_FACE, [ 0.25, 0.9, 0.9, 1.0 ] ], #cyan
|
52
|
+
[ QuadSphere::BOTTOM_FACE, [ 0.9, 0.25, 0.9, 1.0 ] ] ] #magenta
|
53
|
+
|
54
|
+
faces.each do |face, colour|
|
55
|
+
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, colour);
|
56
|
+
# Calculate all the vertices we'll use for this face...
|
57
|
+
vertices = grid(face, FACE_SUBDIV)
|
58
|
+
# ... and arrange them in triangle strips:
|
59
|
+
mesh2strips(FACE_SUBDIV, vertices)
|
60
|
+
end
|
61
|
+
|
62
|
+
glEndList
|
63
|
+
end
|
64
|
+
|
65
|
+
def reshape(w, h)
|
66
|
+
glViewport(0, 0, w, h)
|
67
|
+
glMatrixMode(GL_PROJECTION)
|
68
|
+
glLoadIdentity()
|
69
|
+
gluPerspective(45.0, w.to_f / h.to_f, 1.0, 20.0)
|
70
|
+
glMatrixMode(GL_MODELVIEW)
|
71
|
+
glLoadIdentity()
|
72
|
+
gluLookAt(3.2,0,0, 0,0,0, 0,0,1)
|
73
|
+
|
74
|
+
# Add a light.
|
75
|
+
glPushMatrix()
|
76
|
+
glTranslate(2.5,-0.8,0.8);
|
77
|
+
glLightfv(GL_LIGHT0, GL_POSITION, [0, 0, 0, 1])
|
78
|
+
glPopMatrix()
|
79
|
+
end
|
80
|
+
|
81
|
+
def display
|
82
|
+
# XXX - maybe it'd be more efficient to only clear the depth
|
83
|
+
# buffer if we moved the model or camera?
|
84
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
85
|
+
|
86
|
+
# Rotate and draw the model.
|
87
|
+
glPushMatrix()
|
88
|
+
glRotate(@ang1, 0, 0, 1)
|
89
|
+
glRotate(@ang2, 1, 0, 0)
|
90
|
+
glRotate(@ang3, 0, 1, 0)
|
91
|
+
glCallList(1)
|
92
|
+
glPopMatrix()
|
93
|
+
|
94
|
+
# Done.
|
95
|
+
glutSwapBuffers()
|
96
|
+
end
|
97
|
+
|
98
|
+
def keyboard(key, x, y)
|
99
|
+
case (key)
|
100
|
+
when ?s
|
101
|
+
@ang1 = (@ang1 + 5) % 360
|
102
|
+
glutPostRedisplay()
|
103
|
+
when ?a
|
104
|
+
@ang1 = (@ang1 - 5) % 360
|
105
|
+
glutPostRedisplay()
|
106
|
+
when ?w
|
107
|
+
@ang2 = (@ang2 + 5) % 360
|
108
|
+
glutPostRedisplay()
|
109
|
+
when ?q
|
110
|
+
@ang2 = (@ang2 - 5) % 360
|
111
|
+
glutPostRedisplay()
|
112
|
+
when ?x
|
113
|
+
@ang3 = (@ang3 + 5) % 360
|
114
|
+
glutPostRedisplay()
|
115
|
+
when ?z
|
116
|
+
@ang3 = (@ang3 - 5) % 360
|
117
|
+
glutPostRedisplay()
|
118
|
+
when ?r
|
119
|
+
@ang1 = @ang2 = @ang3 = 0
|
120
|
+
glutPostRedisplay()
|
121
|
+
when ?\e, ?Q
|
122
|
+
exit(0)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Create a NxN grid of points on a face of the cube. Note that this
|
127
|
+
# will generate (N+1)*(N+1) points.
|
128
|
+
#
|
129
|
+
# Each point is projected on the sphere and stored in an array. Note
|
130
|
+
# that all these are points on the unit sphere, and so their distance
|
131
|
+
# to the origin is 1, and so each point can be used as its own normal.
|
132
|
+
def grid(face, n)
|
133
|
+
dx = 2.0/n
|
134
|
+
dy = 2.0/n
|
135
|
+
a = Array.new
|
136
|
+
n += 1
|
137
|
+
|
138
|
+
n.times do |j|
|
139
|
+
y = -1.0 + j*dy
|
140
|
+
n.times do |i|
|
141
|
+
x = -1.0 + i*dx
|
142
|
+
lon, lat = QuadSphere::CSC.inverse(face, x, y)
|
143
|
+
sx = Math::cos(lat) * Math::cos(lon)
|
144
|
+
sy = Math::cos(lat) * Math::sin(lon)
|
145
|
+
sz = Math::sin(lat)
|
146
|
+
a << [sx,sy,sz]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
a
|
151
|
+
end
|
152
|
+
|
153
|
+
# p grid(0, 3)
|
154
|
+
#
|
155
|
+
# Create triangle strips to represent a NxN mesh. The given array
|
156
|
+
# should then contain (N+1)**2 points, arranged as N+1 rows of N+1
|
157
|
+
# points.
|
158
|
+
|
159
|
+
def mesh2strips(n,a)
|
160
|
+
dx = 2.0/n
|
161
|
+
dy = 2.0/n
|
162
|
+
row = n+1
|
163
|
+
|
164
|
+
n.times do |j|
|
165
|
+
glBegin(GL_TRIANGLE_STRIP)
|
166
|
+
rowi = j*row
|
167
|
+
row.times do |x|
|
168
|
+
add_vertex(a[rowi+x])
|
169
|
+
add_vertex(a[rowi+row+x])
|
170
|
+
end
|
171
|
+
glEnd
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def add_vertex(v)
|
176
|
+
glNormal3fv(v)
|
177
|
+
glVertex3fv(v)
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
CSCShadedApp.new.run
|
data/examples/grid.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'quad_sphere/csc'
|
4
|
+
require 'chunky_png'
|
5
|
+
|
6
|
+
# Not the best way to draw a grid — we repaint a lot of pixels. But
|
7
|
+
# it's compact and hopefully clear.
|
8
|
+
|
9
|
+
# The size of each cube face, and some colours:
|
10
|
+
size = 99
|
11
|
+
colour = ChunkyPNG::Color.html_color(:grey)
|
12
|
+
background = ChunkyPNG::Color::WHITE
|
13
|
+
face_border = ChunkyPNG::Color::BLACK
|
14
|
+
|
15
|
+
# This is where we'll place each face, the usual cross.
|
16
|
+
faces = {
|
17
|
+
QuadSphere::TOP_FACE => [ 2*size, 0 ],
|
18
|
+
QuadSphere::BACK_FACE => [ 0, size ],
|
19
|
+
QuadSphere::WEST_FACE => [ size, size ],
|
20
|
+
QuadSphere::FRONT_FACE => [ 2*size, size ],
|
21
|
+
QuadSphere::EAST_FACE => [ 3*size, size ],
|
22
|
+
QuadSphere::BOTTOM_FACE => [ 2*size, 2*size ]
|
23
|
+
}
|
24
|
+
|
25
|
+
image = ChunkyPNG::Image.new(4*size+1, 3*size+1, ChunkyPNG::Color::TRANSPARENT)
|
26
|
+
|
27
|
+
# The longest distance on a cube face is the diagonal, which is of
|
28
|
+
# length d=size*√2. This is an angle of π/2 on the sphere. We split
|
29
|
+
# π/2 over d to get a small angle Δ that, along the diagonal, would
|
30
|
+
# map to pixels at unit distance from each other, and we use Δ as
|
31
|
+
# "angular resolution" everywhere, because this will keep our lines
|
32
|
+
# continuous. This is wasteful: Δ could be larger everywhere else; we
|
33
|
+
# repaint a lot of pixels. But hey, tis just an example.
|
34
|
+
delta = Math::PI/(2*size*Math::sqrt(2))
|
35
|
+
|
36
|
+
# And this is a lambda to paint a point by latitude and longitude.
|
37
|
+
# We'll do this a lot.
|
38
|
+
plot = lambda do |lon,lat|
|
39
|
+
face, x, y = QuadSphere::CSC.forward(lon, lat)
|
40
|
+
x = (size*(x+1)/2).floor
|
41
|
+
y = (size*(y+1)/2).floor
|
42
|
+
offx, offy = faces[face]
|
43
|
+
image[x+offx,y+offy] = colour
|
44
|
+
end
|
45
|
+
|
46
|
+
# To work. Draw a rectangle around each face.
|
47
|
+
faces.each_pair do |face, offsets|
|
48
|
+
off_x, off_y = offsets
|
49
|
+
image.rect(off_x, off_y, off_x+size, off_y+size, face_border, background)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Draw the meridians.
|
53
|
+
36.times do |meridian|
|
54
|
+
lon = meridian*Math::PI/18
|
55
|
+
(-Math::PI/2).step(Math::PI/2, delta) do |lat|
|
56
|
+
plot.call(lon,lat)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Draw the parallels.
|
61
|
+
18.times do |parallel|
|
62
|
+
lat = -Math::PI/2 + parallel*Math::PI/18
|
63
|
+
(-Math::PI).step(Math::PI, delta) do |lon|
|
64
|
+
plot.call(lon,lat)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# There done, save our masterpiece.
|
69
|
+
image.save('grid.png', :best_compression)
|
data/lib/quad_sphere/csc.rb
CHANGED
@@ -165,7 +165,7 @@ module QuadSphere
|
|
165
165
|
# approximation there, don't quote me on those figures). So aye,
|
166
166
|
# we could use more precision.
|
167
167
|
#
|
168
|
-
# There is code in the +
|
168
|
+
# There is code in the +examples+ directory to compute the error
|
169
169
|
# distribution, and even generate a picture showing the areas of
|
170
170
|
# low and high error on the plane. Peek there if you're curious.
|
171
171
|
#
|
data/quad_sphere.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- coding: utf-8; mode: ruby -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'quad_sphere'
|
5
|
+
s.version = '1.0.0'
|
6
|
+
s.date = '2013-02-04'
|
7
|
+
s.summary = 'Quadrilateralised spherical cube projection'
|
8
|
+
s.description = <<END
|
9
|
+
An implementation of the quadrilateralised spherical cube, an
|
10
|
+
approximately equal-area projection of the sphere onto the faces of a
|
11
|
+
cube. It is useful for storing data collected on a spherical surface,
|
12
|
+
and for general mapmaking.
|
13
|
+
END
|
14
|
+
s.homepage = 'https://github.com/crinc/QuadSphere'
|
15
|
+
s.author = 'Cesar Rincon'
|
16
|
+
s.email = 'crincon@gmail.com'
|
17
|
+
s.files = [ 'README.md',
|
18
|
+
'COPYING',
|
19
|
+
'quad_sphere.gemspec',
|
20
|
+
'examples/binning.rb',
|
21
|
+
'examples/distort_closure.rb',
|
22
|
+
'examples/gl1.rb',
|
23
|
+
'examples/gl2.rb',
|
24
|
+
'examples/grid.rb',
|
25
|
+
'lib/quad_sphere.rb',
|
26
|
+
'lib/quad_sphere/tangential.rb',
|
27
|
+
'lib/quad_sphere/csc.rb',
|
28
|
+
'lib/quad_sphere/version.rb' ]
|
29
|
+
s.test_files = [ 'test/test_quad_sphere.rb' ]
|
30
|
+
end
|
@@ -0,0 +1,860 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'quad_sphere'
|
5
|
+
require 'quad_sphere/tangential'
|
6
|
+
require 'quad_sphere/csc'
|
7
|
+
|
8
|
+
module MappingTests
|
9
|
+
# Some constants to save typing (not that I typed all this heh)
|
10
|
+
PI = Math::PI
|
11
|
+
|
12
|
+
TOP_FACE = QuadSphere::TOP_FACE
|
13
|
+
FRONT_FACE = QuadSphere::FRONT_FACE
|
14
|
+
EAST_FACE = QuadSphere::EAST_FACE
|
15
|
+
BACK_FACE = QuadSphere::BACK_FACE
|
16
|
+
WEST_FACE = QuadSphere::WEST_FACE
|
17
|
+
BOTTOM_FACE = QuadSphere::BOTTOM_FACE
|
18
|
+
|
19
|
+
# This is the floating point round-off error expected to be
|
20
|
+
# introduced by our computations. Most of our tangential
|
21
|
+
# projections are exact to the unit roundoff given by Ruby,
|
22
|
+
# actually. It's only a couple that are above one epsilon. But in
|
23
|
+
# any case, just take from this the tangential projection is
|
24
|
+
# accurate to 15 digits...
|
25
|
+
#
|
26
|
+
# Float::EPSILON = 2.220446049250313e-16 on my MacBook, ruby
|
27
|
+
# 1.9.3p374 (2013-01-15 revision 38858) [x86_64-darwin11.4.2]
|
28
|
+
DELTA = 2*Float::EPSILON
|
29
|
+
|
30
|
+
# This is a small value we use to test rollover to the neighbouring
|
31
|
+
# face, in the forward projection.
|
32
|
+
NUDGE = Float::EPSILON
|
33
|
+
|
34
|
+
# Picture a sphere of unit radius inscribed in a cube, with the
|
35
|
+
# centre of the sphere at the origin of the cartesian space, and
|
36
|
+
# with the cube oriented so that each coordinate axis passes through
|
37
|
+
# the centres of two opposite cube faces.
|
38
|
+
#
|
39
|
+
# The sides of this cube are then of length 2 (twice the sphere's
|
40
|
+
# radius), thus the diagonal of the cube is of length 2*sqrt(3). It
|
41
|
+
# follows that, at each corner of the cube, the distance from the
|
42
|
+
# corner vertex to the horizontal plane is 1 (half the length of a
|
43
|
+
# cube side), and the distance from the vertex to the origin is
|
44
|
+
# sqrt(3) (half the length of the cube diagonal). It further
|
45
|
+
# follows that the sine of the elevation angle to the corners of the
|
46
|
+
# cube is 1/sqrt(3).
|
47
|
+
#
|
48
|
+
# So the four northern corners of the cube are at latitude
|
49
|
+
# θc = arcsin(1/sqrt(3)); and the southern corners at latitude -θc.
|
50
|
+
#
|
51
|
+
# I don't know is there is a way to write this angle in terms of π
|
52
|
+
# or roots or something. Would be cute.
|
53
|
+
THETA_C = Math::asin(1/Math::sqrt(3))
|
54
|
+
|
55
|
+
def test_forward_mapping
|
56
|
+
p = self.projection
|
57
|
+
|
58
|
+
# We'll test the mappings of 26 specific points around the sphere.
|
59
|
+
|
60
|
+
# First, the one north pole.
|
61
|
+
|
62
|
+
# 1. θ = π/2 (90°N) should map to the centre point of the top
|
63
|
+
# face. φ is irrelevant here.
|
64
|
+
face, x, y = p.forward(0.0, PI/2)
|
65
|
+
assert_equal(TOP_FACE, face)
|
66
|
+
assert_in_delta( 0.0, x, DELTA)
|
67
|
+
assert_in_delta( 0.0, y, DELTA)
|
68
|
+
|
69
|
+
# Then the four middle points of the edges of the top cube face.
|
70
|
+
# These are all at θ = π/4 (45°N).
|
71
|
+
|
72
|
+
# 2. φ = -π/2, θ = π/4 (45°N 90°W) should map to the left point of
|
73
|
+
# the top face, or the up point of the west face. When
|
74
|
+
# coordinates are given exactly like that, this implementation
|
75
|
+
# maps to the west face.
|
76
|
+
face, x, y = p.forward(-PI/2, PI/4)
|
77
|
+
assert_equal(WEST_FACE, face)
|
78
|
+
assert_in_delta( 0.0, x, DELTA)
|
79
|
+
assert_in_delta( 1.0, y, DELTA)
|
80
|
+
# A nudge northward, though, and we're on the top face.
|
81
|
+
face, x, y = p.forward(-PI/2, PI/4+NUDGE)
|
82
|
+
assert_equal(TOP_FACE, face)
|
83
|
+
assert_in_delta(-1.0+NUDGE, x, DELTA)
|
84
|
+
assert_in_delta( 0.0, y, DELTA)
|
85
|
+
|
86
|
+
# 3. φ = 0, θ = π/4 (45°N 0°) should map to the down point of the
|
87
|
+
# top face, or the up point of the front face. When coordinates
|
88
|
+
# are given exactly like that, this implementation maps to the
|
89
|
+
# front face.
|
90
|
+
face, x, y = p.forward(0.0, PI/4)
|
91
|
+
assert_equal(FRONT_FACE, face)
|
92
|
+
assert_in_delta( 0.0, x, DELTA)
|
93
|
+
assert_in_delta( 1.0, y, DELTA)
|
94
|
+
# A nudge northward, though, and we're on the top face.
|
95
|
+
face, x, y = p.forward(0.0, PI/4+NUDGE)
|
96
|
+
assert_equal(TOP_FACE, face)
|
97
|
+
assert_in_delta( 0.0, x, DELTA)
|
98
|
+
assert_in_delta(-1.0+NUDGE, y, DELTA)
|
99
|
+
|
100
|
+
# 4. φ = π/2, θ = π/4 (45°N 90°E) should map to the right point of
|
101
|
+
# the top face, or the up point of the east face. When
|
102
|
+
# coordinates are given exactly like that, this implementation
|
103
|
+
# maps to the east face.
|
104
|
+
face, x, y = p.forward(PI/2, PI/4)
|
105
|
+
assert_equal(EAST_FACE, face)
|
106
|
+
assert_in_delta( 0.0, x, DELTA)
|
107
|
+
assert_in_delta( 1.0, y, DELTA)
|
108
|
+
# A nudge northward, though, and we're on the top face.
|
109
|
+
face, x, y = p.forward(PI/2, PI/4+NUDGE)
|
110
|
+
assert_equal(TOP_FACE, face)
|
111
|
+
assert_in_delta( 1.0-NUDGE, x, DELTA)
|
112
|
+
assert_in_delta( 0.0, y, DELTA)
|
113
|
+
|
114
|
+
# 5. φ = π, θ = π/4 (45°N 180°) should map to the up point of the
|
115
|
+
# top face, or the up point of the back face. When coordinates
|
116
|
+
# are given exactly like that, this implementation maps to the
|
117
|
+
# back face.
|
118
|
+
face, x, y = p.forward(PI, PI/4)
|
119
|
+
assert_equal(BACK_FACE, face)
|
120
|
+
assert_in_delta( 0.0, x, DELTA)
|
121
|
+
assert_in_delta( 1.0, y, DELTA)
|
122
|
+
# A nudge northward, though, and we're on the top face.
|
123
|
+
face, x, y = p.forward(PI, PI/4+NUDGE)
|
124
|
+
assert_equal(TOP_FACE, face)
|
125
|
+
assert_in_delta( 0.0, x, DELTA)
|
126
|
+
assert_in_delta( 1.0-NUDGE, y, DELTA)
|
127
|
+
|
128
|
+
# Then the four corners of the top cube face. These are all at
|
129
|
+
# θ = θc = arcsin(1/√3) -- about 35.26°N.
|
130
|
+
|
131
|
+
# 6. φ = -3π/4, θ = θc (35.26°N 135°W) should map to the up-left
|
132
|
+
# point of the top face, or the up-right point of the back face,
|
133
|
+
# or the up-left point of the west face. When coordinates are
|
134
|
+
# given exactly like that, this implementation maps to the top
|
135
|
+
# face.
|
136
|
+
face, x, y = p.forward(-3*PI/4, THETA_C)
|
137
|
+
assert_equal(TOP_FACE, face)
|
138
|
+
assert_in_delta(-1.0, x, DELTA)
|
139
|
+
assert_in_delta( 1.0, y, DELTA)
|
140
|
+
# A nudge westward, though, and we're on the back face.
|
141
|
+
face, x, y = p.forward(-3*PI/4-2*NUDGE, THETA_C)
|
142
|
+
assert_equal(BACK_FACE, face)
|
143
|
+
assert_in_delta( 1.0-3*NUDGE, x, DELTA)
|
144
|
+
assert_in_delta( 1.0, y, DELTA)
|
145
|
+
# Or a nudge eastward, and we're on the west face.
|
146
|
+
face, x, y = p.forward(-3*PI/4+2*NUDGE, THETA_C)
|
147
|
+
assert_equal(WEST_FACE, face)
|
148
|
+
assert_in_delta(-1.0+3*NUDGE, x, DELTA)
|
149
|
+
assert_in_delta( 1.0, y, DELTA)
|
150
|
+
|
151
|
+
# 7. φ = -π/4, θ = θc (35.26°N 45°W) should map to the down-left
|
152
|
+
# point of the top face, or the up-left point of the front face,
|
153
|
+
# or the up-right point of the west face. When coordinates are
|
154
|
+
# given exactly like that, this implementation maps to the top
|
155
|
+
# face.
|
156
|
+
face, x, y = p.forward(-PI/4, THETA_C)
|
157
|
+
assert_equal(TOP_FACE, face)
|
158
|
+
assert_in_delta(-1.0, x, DELTA)
|
159
|
+
assert_in_delta(-1.0, y, DELTA)
|
160
|
+
# A nudge westward, though, and we're on the west face.
|
161
|
+
face, x, y = p.forward(-PI/4-2*NUDGE, THETA_C)
|
162
|
+
assert_equal(WEST_FACE, face)
|
163
|
+
assert_in_delta( 1.0-3*NUDGE, x, DELTA)
|
164
|
+
assert_in_delta( 1.0, y, DELTA)
|
165
|
+
# Or a nudge eastward, and we're on the front face.
|
166
|
+
face, x, y = p.forward(-PI/4+2*NUDGE, THETA_C)
|
167
|
+
assert_equal(FRONT_FACE, face)
|
168
|
+
assert_in_delta(-1.0+3*NUDGE, x, DELTA)
|
169
|
+
assert_in_delta( 1.0, y, DELTA)
|
170
|
+
|
171
|
+
# 8. φ = π/4, θ = θc (35.26°N 45°E) should map to the down-right
|
172
|
+
# point of the top face, or the up-right point of the front face,
|
173
|
+
# or the up-left point of the east face. When coordinates are
|
174
|
+
# given exactly like that, this implementation maps to the top
|
175
|
+
# face.
|
176
|
+
face, x, y = p.forward(PI/4, THETA_C)
|
177
|
+
assert_equal(TOP_FACE, face)
|
178
|
+
assert_in_delta( 1.0, x, DELTA)
|
179
|
+
assert_in_delta(-1.0, y, DELTA)
|
180
|
+
# A nudge westward, though, and we're on the front face.
|
181
|
+
face, x, y = p.forward(PI/4-2*NUDGE, THETA_C)
|
182
|
+
assert_equal(FRONT_FACE, face)
|
183
|
+
assert_in_delta( 1.0-3*NUDGE, x, DELTA)
|
184
|
+
assert_in_delta( 1.0, y, DELTA)
|
185
|
+
# Or a nudge eastward, and we're on the east face.
|
186
|
+
face, x, y = p.forward(PI/4+2*NUDGE, THETA_C)
|
187
|
+
assert_equal(EAST_FACE, face)
|
188
|
+
assert_in_delta(-1.0+3*NUDGE, x, DELTA)
|
189
|
+
assert_in_delta( 1.0, y, DELTA)
|
190
|
+
|
191
|
+
# 9. φ = 3π/4, θ = θc (35.26°N 135°E) should map to the up-right
|
192
|
+
# point of the top face, or the up-right point of the east face,
|
193
|
+
# or the up-left point of the back face. When coordinates are
|
194
|
+
# given exactly like that, this implementation maps to the top
|
195
|
+
# face.
|
196
|
+
face, x, y = p.forward(3*PI/4, THETA_C)
|
197
|
+
assert_equal(TOP_FACE, face)
|
198
|
+
assert_in_delta( 1.0, x, DELTA)
|
199
|
+
assert_in_delta( 1.0, y, DELTA)
|
200
|
+
# A nudge westward, though, and we're on the east face.
|
201
|
+
face, x, y = p.forward(3*PI/4-2*NUDGE, THETA_C)
|
202
|
+
assert_equal(EAST_FACE, face)
|
203
|
+
assert_in_delta( 1.0-3*NUDGE, x, DELTA)
|
204
|
+
assert_in_delta( 1.0, y, DELTA)
|
205
|
+
# Or a nudge eastward, and we're on the back face.
|
206
|
+
face, x, y = p.forward(3*PI/4+2*NUDGE, THETA_C)
|
207
|
+
assert_equal(BACK_FACE, face)
|
208
|
+
assert_in_delta(-1.0+3*NUDGE, x, DELTA)
|
209
|
+
assert_in_delta( 1.0, y, DELTA)
|
210
|
+
|
211
|
+
# Then the "equatorial belt", eight points, being the four centres
|
212
|
+
# of the front, east, back and west faces, and the the midpoints
|
213
|
+
# of the vertical edges of these faces. These eight points are at
|
214
|
+
# θ = zero, of course.
|
215
|
+
|
216
|
+
# 10. φ = -3π/4, θ = 0 (0° 135°W) should map to the right point of
|
217
|
+
# the back face, or the left point of the west face. When
|
218
|
+
# coordinates are given exactly like that, this implementation
|
219
|
+
# maps to the west face.
|
220
|
+
face, x, y = p.forward(-3*PI/4, 0.0)
|
221
|
+
assert_equal(WEST_FACE, face)
|
222
|
+
assert_in_delta(-1.0, x, DELTA)
|
223
|
+
assert_in_delta( 0.0, y, DELTA)
|
224
|
+
# A nudge westward, though, and we're on the back face.
|
225
|
+
face, x, y = p.forward(-3*PI/4-2*NUDGE, 0.0)
|
226
|
+
assert_equal(BACK_FACE, face)
|
227
|
+
assert_in_delta( 1.0-3*NUDGE, x, DELTA)
|
228
|
+
assert_in_delta( 0.0, y, DELTA)
|
229
|
+
|
230
|
+
# 11. φ = -π/2, θ = 0 (0° 90°W) should map to the centre point of
|
231
|
+
# the west face.
|
232
|
+
face, x, y = p.forward(-PI/2, 0.0)
|
233
|
+
assert_equal(WEST_FACE, face)
|
234
|
+
assert_in_delta( 0.0, x, DELTA)
|
235
|
+
assert_in_delta( 0.0, y, DELTA)
|
236
|
+
|
237
|
+
# 12. φ = -π/4, θ = 0 (0° 45°W) should map to the left point of
|
238
|
+
# the front face, or the right point of the west face. When
|
239
|
+
# coordinates are given exactly like that, this implementation
|
240
|
+
# maps to the front face.
|
241
|
+
face, x, y = p.forward(-PI/4, 0.0)
|
242
|
+
assert_equal(FRONT_FACE, face)
|
243
|
+
assert_in_delta(-1.0, x, DELTA)
|
244
|
+
assert_in_delta( 0.0, y, DELTA)
|
245
|
+
# A nudge westward, though, and we're on the XXX face.
|
246
|
+
face, x, y = p.forward(-PI/4-2*NUDGE, 0.0)
|
247
|
+
assert_equal(WEST_FACE, face)
|
248
|
+
assert_in_delta( 1.0-3*NUDGE, x, DELTA)
|
249
|
+
assert_in_delta( 0.0, y, DELTA)
|
250
|
+
|
251
|
+
# 13. φ = 0, θ = 0 (0° 0°) should map to the centre point of the
|
252
|
+
# front face.
|
253
|
+
face, x, y = p.forward(0.0, 0.0)
|
254
|
+
assert_equal(FRONT_FACE, face)
|
255
|
+
assert_in_delta( 0.0, x, DELTA)
|
256
|
+
assert_in_delta( 0.0, y, DELTA)
|
257
|
+
|
258
|
+
# 14. φ = π/4, θ = 0 (0° 45°E) should map to the right point of
|
259
|
+
# the front face, or the left point of the east face. When
|
260
|
+
# coordinates are given exactly like that, this implementation
|
261
|
+
# maps to the front face.
|
262
|
+
face, x, y = p.forward(PI/4, 0.0)
|
263
|
+
assert_equal(FRONT_FACE, face)
|
264
|
+
assert_in_delta( 1.0, x, DELTA)
|
265
|
+
assert_in_delta( 0.0, y, DELTA)
|
266
|
+
# A nudge eastward, though, and we're on the east face.
|
267
|
+
face, x, y = p.forward(PI/4+2*NUDGE, 0.0)
|
268
|
+
assert_equal(EAST_FACE, face)
|
269
|
+
assert_in_delta(-1.0+3*NUDGE, x, DELTA)
|
270
|
+
assert_in_delta( 0.0, y, DELTA)
|
271
|
+
|
272
|
+
# 15. φ = π/2, θ = 0 (0° 90°E) should map to the centre point of
|
273
|
+
# the east face.
|
274
|
+
face, x, y = p.forward(PI/2, 0.0)
|
275
|
+
assert_equal(EAST_FACE, face)
|
276
|
+
assert_in_delta( 0.0, x, DELTA)
|
277
|
+
assert_in_delta( 0.0, y, DELTA)
|
278
|
+
|
279
|
+
# 16. φ = 3π/4, θ = 0 (0° 135°E) should map to the right point of
|
280
|
+
# the east face, or the left point of the back face. When
|
281
|
+
# coordinates are given exactly like that, this implementation
|
282
|
+
# maps to the east face.
|
283
|
+
face, x, y = p.forward(3*PI/4, 0.0)
|
284
|
+
assert_equal(EAST_FACE, face)
|
285
|
+
assert_in_delta( 1.0, x, DELTA)
|
286
|
+
assert_in_delta( 0.0, y, DELTA)
|
287
|
+
# A nudge eastward, though, and we're on the back face.
|
288
|
+
face, x, y = p.forward(3*PI/4+2*NUDGE, 0.0)
|
289
|
+
assert_equal(BACK_FACE, face)
|
290
|
+
assert_in_delta(-1.0+3*NUDGE, x, DELTA)
|
291
|
+
assert_in_delta( 0.0, y, DELTA)
|
292
|
+
|
293
|
+
# 17. φ = π, θ = 0 (0° 180°) should map to the centre point of the
|
294
|
+
# back face.
|
295
|
+
face, x, y = p.forward(PI, 0.0)
|
296
|
+
assert_equal(BACK_FACE, face)
|
297
|
+
assert_in_delta( 0.0, x, DELTA)
|
298
|
+
assert_in_delta( 0.0, y, DELTA)
|
299
|
+
|
300
|
+
# Then the four corners of the bottom cube face. These are all at
|
301
|
+
# θ = -θc = -arcsin(1/√3) -- about 35.26°S.
|
302
|
+
|
303
|
+
# 18. φ = -3π/4, θ = -θc (35.26°S 135°W) should map to the
|
304
|
+
# down-right point of the back face, or the down-left point of the
|
305
|
+
# west face, or the down-left point of the bottom face. When
|
306
|
+
# coordinates are given exactly like that, this implementation
|
307
|
+
# maps to the bottom face.
|
308
|
+
face, x, y = p.forward(-3*PI/4, -THETA_C)
|
309
|
+
assert_equal(BOTTOM_FACE, face)
|
310
|
+
assert_in_delta(-1.0, x, DELTA)
|
311
|
+
assert_in_delta(-1.0, y, DELTA)
|
312
|
+
# A nudge westward, though, and we're on the back face.
|
313
|
+
face, x, y = p.forward(-3*PI/4-2*NUDGE, -THETA_C)
|
314
|
+
assert_equal(BACK_FACE, face)
|
315
|
+
assert_in_delta( 1.0-3*NUDGE, x, DELTA)
|
316
|
+
assert_in_delta(-1.0, y, DELTA)
|
317
|
+
# Or a nudge eastward, and we're on the west face.
|
318
|
+
face, x, y = p.forward(-3*PI/4+2*NUDGE, -THETA_C)
|
319
|
+
assert_equal(WEST_FACE, face)
|
320
|
+
assert_in_delta(-1.0+3*NUDGE, x, DELTA)
|
321
|
+
assert_in_delta(-1.0, y, DELTA)
|
322
|
+
|
323
|
+
# 19. φ = -π/4, θ = -θc (35.26°S 45°W) should map to the down-left
|
324
|
+
# point of the front face, or the down-right point of the west
|
325
|
+
# face, or the up-left point of the bottom face. When coordinates
|
326
|
+
# are given exactly like that, this implementation maps to the
|
327
|
+
# bottom face.
|
328
|
+
face, x, y = p.forward(-PI/4, -THETA_C)
|
329
|
+
assert_equal(BOTTOM_FACE, face)
|
330
|
+
assert_in_delta(-1.0, x, DELTA)
|
331
|
+
assert_in_delta( 1.0, y, DELTA)
|
332
|
+
# A nudge westward, though, and we're on the west face.
|
333
|
+
face, x, y = p.forward(-PI/4-2*NUDGE, -THETA_C)
|
334
|
+
assert_equal(WEST_FACE, face)
|
335
|
+
assert_in_delta( 1.0-3*NUDGE, x, DELTA)
|
336
|
+
assert_in_delta(-1.0, y, DELTA)
|
337
|
+
# Or a nudge eastward, and we're on the front face.
|
338
|
+
face, x, y = p.forward(-PI/4+2*NUDGE, -THETA_C)
|
339
|
+
assert_equal(FRONT_FACE, face)
|
340
|
+
assert_in_delta(-1.0+3*NUDGE, x, DELTA)
|
341
|
+
assert_in_delta(-1.0, y, DELTA)
|
342
|
+
|
343
|
+
# 20. φ = π/4, θ = -θc (35.26°S 45°E) should map to the down-right
|
344
|
+
# point of the front face, or the down-left point of the east
|
345
|
+
# face, or the up-right point of the bottom face. When
|
346
|
+
# coordinates are given exactly like that, this implementation
|
347
|
+
# maps to the bottom face.
|
348
|
+
face, x, y = p.forward(PI/4, -THETA_C)
|
349
|
+
assert_equal(BOTTOM_FACE, face)
|
350
|
+
assert_in_delta( 1.0, x, DELTA)
|
351
|
+
assert_in_delta( 1.0, y, DELTA)
|
352
|
+
# A nudge westward, though, and we're on the front face.
|
353
|
+
face, x, y = p.forward(PI/4-2*NUDGE, -THETA_C)
|
354
|
+
assert_equal(FRONT_FACE, face)
|
355
|
+
assert_in_delta( 1.0-3*NUDGE, x, DELTA)
|
356
|
+
assert_in_delta(-1.0, y, DELTA)
|
357
|
+
# Or a nudge eastward, and we're on the east face.
|
358
|
+
face, x, y = p.forward(PI/4+2*NUDGE, -THETA_C)
|
359
|
+
assert_equal(EAST_FACE, face)
|
360
|
+
assert_in_delta(-1.0+3*NUDGE, x, DELTA)
|
361
|
+
assert_in_delta(-1.0, y, DELTA)
|
362
|
+
|
363
|
+
# 21. φ = 3π/4, θ = -θc (35.26°S 135°E) should map to the
|
364
|
+
# down-right point of the east face, or the down-left point of the
|
365
|
+
# back face, or the down-right point of the bottom face. When
|
366
|
+
# coordinates are given exactly like that, this implementation
|
367
|
+
# maps to the bottom face.
|
368
|
+
face, x, y = p.forward(3*PI/4, -THETA_C)
|
369
|
+
assert_equal(BOTTOM_FACE, face)
|
370
|
+
assert_in_delta( 1.0, x, DELTA)
|
371
|
+
assert_in_delta(-1.0, y, DELTA)
|
372
|
+
# A nudge westward, though, and we're on the east face.
|
373
|
+
face, x, y = p.forward(3*PI/4-2*NUDGE, -THETA_C)
|
374
|
+
assert_equal(EAST_FACE, face)
|
375
|
+
assert_in_delta( 1.0-3*NUDGE, x, DELTA)
|
376
|
+
assert_in_delta(-1.0, y, DELTA)
|
377
|
+
# Or a nudge eastward, and we're on the back face.
|
378
|
+
face, x, y = p.forward(3*PI/4+2*NUDGE, -THETA_C)
|
379
|
+
assert_equal(BACK_FACE, face)
|
380
|
+
assert_in_delta(-1.0+3*NUDGE, x, DELTA)
|
381
|
+
assert_in_delta(-1.0, y, DELTA)
|
382
|
+
|
383
|
+
# Then the four middle points of the edges of the bottom cube
|
384
|
+
# face. These are all at θ = -π/4 (45°S).
|
385
|
+
|
386
|
+
# 22. φ = -π/2, θ = -π/4 (45°S 90°W) should map to the down point
|
387
|
+
# of the west face, or the left point of the bottom face. When
|
388
|
+
# coordinates are given exactly like that, this implementation
|
389
|
+
# maps to the west face.
|
390
|
+
face, x, y = p.forward(-PI/2, -PI/4)
|
391
|
+
assert_equal(WEST_FACE, face)
|
392
|
+
assert_in_delta( 0.0, x, DELTA)
|
393
|
+
assert_in_delta(-1.0, y, DELTA)
|
394
|
+
# A nudge southward, though, and we're on the bottom face.
|
395
|
+
face, x, y = p.forward(-PI/2, -PI/4-NUDGE)
|
396
|
+
assert_equal(BOTTOM_FACE, face)
|
397
|
+
assert_in_delta(-1.0+NUDGE, x, DELTA)
|
398
|
+
assert_in_delta( 0.0, y, DELTA)
|
399
|
+
|
400
|
+
# 23. φ = 0, θ = -π/4 (45°S 0°) should map to the down point of
|
401
|
+
# the front face, or the up point of the bottom face. When
|
402
|
+
# coordinates are given exactly like that, this implementation
|
403
|
+
# maps to the FRONT face.
|
404
|
+
face, x, y = p.forward(0.0, -PI/4)
|
405
|
+
assert_equal(FRONT_FACE, face)
|
406
|
+
assert_in_delta( 0.0, x, DELTA)
|
407
|
+
assert_in_delta(-1.0, y, DELTA)
|
408
|
+
# A nudge southward, though, and we're on the bottom face.
|
409
|
+
face, x, y = p.forward(0.0, -PI/4-NUDGE)
|
410
|
+
assert_equal(BOTTOM_FACE, face)
|
411
|
+
assert_in_delta( 0.0, x, DELTA)
|
412
|
+
assert_in_delta( 1.0-NUDGE, y, DELTA)
|
413
|
+
|
414
|
+
# 24. φ = π/2, θ = -π/4 (45°S 90°E) should map to the down point
|
415
|
+
# of the east face, or the right point of the bottom face. When
|
416
|
+
# coordinates are given exactly like that, this implementation
|
417
|
+
# maps to the east face.
|
418
|
+
face, x, y = p.forward(PI/2, -PI/4)
|
419
|
+
assert_equal(EAST_FACE, face)
|
420
|
+
assert_in_delta( 0.0, x, DELTA)
|
421
|
+
assert_in_delta(-1.0, y, DELTA)
|
422
|
+
# A nudge southward, though, and we're on the bottom face.
|
423
|
+
face, x, y = p.forward(PI/2, -PI/4-NUDGE)
|
424
|
+
assert_equal(BOTTOM_FACE, face)
|
425
|
+
assert_in_delta( 1.0-NUDGE, x, DELTA)
|
426
|
+
assert_in_delta( 0.0, y, DELTA)
|
427
|
+
|
428
|
+
# 25. φ = π, θ = -π/4 (45°S 180°) should map to the down point of
|
429
|
+
# the back face, or the down point of the bottom face. When
|
430
|
+
# coordinates are given exactly like that, this implementation
|
431
|
+
# maps to the back face.
|
432
|
+
face, x, y = p.forward(PI, -PI/4)
|
433
|
+
assert_equal(BACK_FACE, face)
|
434
|
+
assert_in_delta( 0.0, x, DELTA)
|
435
|
+
assert_in_delta(-1.0, y, DELTA)
|
436
|
+
# A nudge southward, though, and we're on the bottom face.
|
437
|
+
face, x, y = p.forward(PI, -PI/4-NUDGE)
|
438
|
+
assert_equal(BOTTOM_FACE, face)
|
439
|
+
assert_in_delta( 0.0, x, DELTA)
|
440
|
+
assert_in_delta(-1.0+NUDGE, y, DELTA)
|
441
|
+
|
442
|
+
# Finally, the one south pole.
|
443
|
+
|
444
|
+
# 26. θ = -π/2 (90°S ) should map to the centre point of the
|
445
|
+
# bottom face. φ is irrelevant here.
|
446
|
+
|
447
|
+
face, x, y = p.forward(0.0, -PI/2)
|
448
|
+
assert_equal(BOTTOM_FACE, face)
|
449
|
+
assert_in_delta( 0.0, x, DELTA)
|
450
|
+
assert_in_delta( 0.0, y, DELTA)
|
451
|
+
|
452
|
+
end # test_forward_mapping
|
453
|
+
|
454
|
+
|
455
|
+
def test_inverse_mapping
|
456
|
+
p = self.projection
|
457
|
+
|
458
|
+
# We'll test nine points on each cube face: the four corners, the
|
459
|
+
# four centres of each edge, and the centre of the face. Please
|
460
|
+
# note that, except for the face centres, the mappings are not
|
461
|
+
# necessarily reversible. This is because, for any point that is
|
462
|
+
# shared amongst two or three faces, the forward projection may
|
463
|
+
# choose to return a mapping to any of the shared faces. See all
|
464
|
+
# those nudges in the forward mapping tests above.
|
465
|
+
|
466
|
+
# 1. The top face.
|
467
|
+
|
468
|
+
# The up-left point of the top face should map to
|
469
|
+
# φ = -3π/4, θ = θc.
|
470
|
+
phi, theta = p.inverse(TOP_FACE, -1.0, 1.0)
|
471
|
+
assert_in_delta(-3*PI/4, phi, DELTA)
|
472
|
+
assert_in_delta(THETA_C, theta, DELTA)
|
473
|
+
|
474
|
+
# The up point of the top face should map to
|
475
|
+
# φ = π, θ = π/4.
|
476
|
+
phi, theta = p.inverse(TOP_FACE, 0.0, 1.0)
|
477
|
+
assert_in_delta(PI, phi, DELTA)
|
478
|
+
assert_in_delta(PI/4, theta, DELTA)
|
479
|
+
|
480
|
+
# The up-right point of the top face should map to
|
481
|
+
# φ = 3π/4, θ = θc.
|
482
|
+
phi, theta = p.inverse(TOP_FACE, 1.0, 1.0)
|
483
|
+
assert_in_delta(3*PI/4, phi, DELTA)
|
484
|
+
assert_in_delta(THETA_C, theta, DELTA)
|
485
|
+
|
486
|
+
# The left point of the top face should map to
|
487
|
+
# φ = -π/2, θ = π/4.
|
488
|
+
phi, theta = p.inverse(TOP_FACE, -1.0, 0.0)
|
489
|
+
assert_in_delta(-PI/2, phi, DELTA)
|
490
|
+
assert_in_delta(PI/4, theta, DELTA)
|
491
|
+
|
492
|
+
# The centre point of the top face should map to
|
493
|
+
# θ = π/2, with φ being irrelevant.
|
494
|
+
phi, theta = p.inverse(TOP_FACE, 0.0, 0.0)
|
495
|
+
assert_in_delta(PI/2, theta, DELTA)
|
496
|
+
# So here, any value is correct. This implementation returns
|
497
|
+
# PI, though, for some reason, and we'll watch for changes.
|
498
|
+
assert_in_delta(PI, phi, DELTA)
|
499
|
+
|
500
|
+
# The right point of the top face should map to
|
501
|
+
# φ = π/2, θ = π/4.
|
502
|
+
phi, theta = p.inverse(TOP_FACE, 1.0, 0.0)
|
503
|
+
assert_in_delta(PI/2, phi, DELTA)
|
504
|
+
assert_in_delta(PI/4, theta, DELTA)
|
505
|
+
|
506
|
+
# The down-left point of the top face should map to
|
507
|
+
# φ = -π/4, θ = θc.
|
508
|
+
phi, theta = p.inverse(TOP_FACE, -1.0, -1.0)
|
509
|
+
assert_in_delta(-PI/4, phi, DELTA)
|
510
|
+
assert_in_delta(THETA_C, theta, DELTA)
|
511
|
+
|
512
|
+
# The down point of the top face should map to
|
513
|
+
# φ = 0, θ = π/4.
|
514
|
+
phi, theta = p.inverse(TOP_FACE, 0.0, -1.0)
|
515
|
+
assert_in_delta(0.0, phi, DELTA)
|
516
|
+
assert_in_delta(PI/4, theta, DELTA)
|
517
|
+
|
518
|
+
# The down-right point of the top face should map to
|
519
|
+
# φ = π/4, θ = θc.
|
520
|
+
phi, theta = p.inverse(TOP_FACE, 1.0, -1.0)
|
521
|
+
assert_in_delta(PI/4, phi, DELTA)
|
522
|
+
assert_in_delta(THETA_C, theta, DELTA)
|
523
|
+
|
524
|
+
# 2. The front face.
|
525
|
+
|
526
|
+
# The up-left point of the front face should map to
|
527
|
+
# φ = -π/4, θ = θc.
|
528
|
+
phi, theta = p.inverse(FRONT_FACE, -1.0, 1.0)
|
529
|
+
assert_in_delta(-PI/4, phi, DELTA)
|
530
|
+
assert_in_delta(THETA_C, theta, DELTA)
|
531
|
+
|
532
|
+
# The up point of the front face should map to
|
533
|
+
# φ = 0, θ = π/4.
|
534
|
+
phi, theta = p.inverse(FRONT_FACE, 0.0, 1.0)
|
535
|
+
assert_in_delta(0.0, phi, DELTA)
|
536
|
+
assert_in_delta(PI/4, theta, DELTA)
|
537
|
+
|
538
|
+
# The up-right point of the front face should map to
|
539
|
+
# φ = π/4, θ = θc.
|
540
|
+
phi, theta = p.inverse(FRONT_FACE, 1.0, 1.0)
|
541
|
+
assert_in_delta(PI/4, phi, DELTA)
|
542
|
+
assert_in_delta(THETA_C, theta, DELTA)
|
543
|
+
|
544
|
+
# The left point of the front face should map to
|
545
|
+
# φ = -π/4, θ = 0.
|
546
|
+
phi, theta = p.inverse(FRONT_FACE, -1.0, 0.0)
|
547
|
+
assert_in_delta(-PI/4, phi, DELTA)
|
548
|
+
assert_in_delta(0.0, theta, DELTA)
|
549
|
+
|
550
|
+
# The centre point of the front face should map to
|
551
|
+
# φ = 0, θ = 0.
|
552
|
+
phi, theta = p.inverse(FRONT_FACE, 0.0, 0.0)
|
553
|
+
assert_in_delta(0.0, phi, DELTA)
|
554
|
+
assert_in_delta(0.0, theta, DELTA)
|
555
|
+
|
556
|
+
# The right point of the front face should map to
|
557
|
+
# φ = π/4, θ = 0.
|
558
|
+
phi, theta = p.inverse(FRONT_FACE, 1.0, 0.0)
|
559
|
+
assert_in_delta(PI/4, phi, DELTA)
|
560
|
+
assert_in_delta(0.0, theta, DELTA)
|
561
|
+
|
562
|
+
# The down-left point of the front face should map to
|
563
|
+
# φ = -π/4, θ = -θc.
|
564
|
+
phi, theta = p.inverse(FRONT_FACE, -1.0, -1.0)
|
565
|
+
assert_in_delta(-PI/4, phi, DELTA)
|
566
|
+
assert_in_delta(-THETA_C, theta, DELTA)
|
567
|
+
|
568
|
+
# The down point of the front face should map to
|
569
|
+
# φ = 0, θ = -π/4.
|
570
|
+
phi, theta = p.inverse(FRONT_FACE, 0.0, -1.0)
|
571
|
+
assert_in_delta(0.0, phi, DELTA)
|
572
|
+
assert_in_delta(-PI/4, theta, DELTA)
|
573
|
+
|
574
|
+
# The down-right point of the front face should map to
|
575
|
+
# φ = π/4, θ = -θc.
|
576
|
+
phi, theta = p.inverse(FRONT_FACE, 1.0, -1.0)
|
577
|
+
assert_in_delta(PI/4, phi, DELTA)
|
578
|
+
assert_in_delta(-THETA_C, theta, DELTA)
|
579
|
+
|
580
|
+
# 3. The east face.
|
581
|
+
|
582
|
+
# The up-left point of the east face should map to
|
583
|
+
# φ = π/4, θ = θc.
|
584
|
+
phi, theta = p.inverse(EAST_FACE, -1.0, 1.0)
|
585
|
+
assert_in_delta(PI/4, phi, DELTA)
|
586
|
+
assert_in_delta(THETA_C, theta, DELTA)
|
587
|
+
|
588
|
+
# The up point of the east face should map to
|
589
|
+
# φ = π/2, θ = π/4.
|
590
|
+
phi, theta = p.inverse(EAST_FACE, 0.0, 1.0)
|
591
|
+
assert_in_delta(PI/2, phi, DELTA)
|
592
|
+
assert_in_delta(PI/4, theta, DELTA)
|
593
|
+
|
594
|
+
# The up-right point of the east face should map to
|
595
|
+
# φ = 3π/4, θ = θc.
|
596
|
+
phi, theta = p.inverse(EAST_FACE, 1.0, 1.0)
|
597
|
+
assert_in_delta(3*PI/4, phi, DELTA)
|
598
|
+
assert_in_delta(THETA_C, theta, DELTA)
|
599
|
+
|
600
|
+
# The left point of the east face should map to
|
601
|
+
# φ = π/4, θ = 0.
|
602
|
+
phi, theta = p.inverse(EAST_FACE, -1.0, 0.0)
|
603
|
+
assert_in_delta(PI/4, phi, DELTA)
|
604
|
+
assert_in_delta(0.0, theta, DELTA)
|
605
|
+
|
606
|
+
# The centre point of the east face should map to
|
607
|
+
# φ = π/2, θ = 0.
|
608
|
+
phi, theta = p.inverse(EAST_FACE, 0.0, 0.0)
|
609
|
+
assert_in_delta(PI/2, phi, DELTA)
|
610
|
+
assert_in_delta(0.0, theta, DELTA)
|
611
|
+
|
612
|
+
# The right point of the east face should map to
|
613
|
+
# φ = 3π/4, θ = 0.
|
614
|
+
phi, theta = p.inverse(EAST_FACE, 1.0, 0.0)
|
615
|
+
assert_in_delta(3*PI/4, phi, DELTA)
|
616
|
+
assert_in_delta(0.0, theta, DELTA)
|
617
|
+
|
618
|
+
# The down-left point of the east face should map to
|
619
|
+
# φ = π/4, θ = -θc.
|
620
|
+
phi, theta = p.inverse(EAST_FACE, -1.0, -1.0)
|
621
|
+
assert_in_delta(PI/4, phi, DELTA)
|
622
|
+
assert_in_delta(-THETA_C, theta, DELTA)
|
623
|
+
|
624
|
+
# The down point of the east face should map to
|
625
|
+
# φ = π/2, θ = -π/4.
|
626
|
+
phi, theta = p.inverse(EAST_FACE, 0.0, -1.0)
|
627
|
+
assert_in_delta(PI/2, phi, DELTA)
|
628
|
+
assert_in_delta(-PI/4, theta, DELTA)
|
629
|
+
|
630
|
+
# The down-right point of the east face should map to
|
631
|
+
# φ = 3π/4, θ = -θc.
|
632
|
+
phi, theta = p.inverse(EAST_FACE, 1.0, -1.0)
|
633
|
+
assert_in_delta(3*PI/4, phi, DELTA)
|
634
|
+
assert_in_delta(-THETA_C, theta, DELTA)
|
635
|
+
|
636
|
+
# 4. The back face.
|
637
|
+
|
638
|
+
# The up-left point of the back face should map to
|
639
|
+
# φ = 3π/4, θ = θc.
|
640
|
+
phi, theta = p.inverse(BACK_FACE, -1.0, 1.0)
|
641
|
+
assert_in_delta(3*PI/4, phi, DELTA)
|
642
|
+
assert_in_delta(THETA_C, theta, DELTA)
|
643
|
+
|
644
|
+
# The up point of the back face should map to
|
645
|
+
# φ = π, θ = π/4.
|
646
|
+
# Note the implementation returns φ = -π, which is also correct.
|
647
|
+
phi, theta = p.inverse(BACK_FACE, 0.0, 1.0)
|
648
|
+
assert_in_delta(-PI, phi, DELTA)
|
649
|
+
assert_in_delta(PI/4, theta, DELTA)
|
650
|
+
|
651
|
+
# The up-right point of the back face should map to
|
652
|
+
# φ = -3π/4, θ = θc.
|
653
|
+
phi, theta = p.inverse(BACK_FACE, 1.0, 1.0)
|
654
|
+
assert_in_delta(-3*PI/4, phi, DELTA)
|
655
|
+
assert_in_delta(THETA_C, theta, DELTA)
|
656
|
+
|
657
|
+
# The left point of the back face should map to
|
658
|
+
# φ = 3π/4, θ = 0.
|
659
|
+
phi, theta = p.inverse(BACK_FACE, -1.0, 0.0)
|
660
|
+
assert_in_delta(3*PI/4, phi, DELTA)
|
661
|
+
assert_in_delta(0.0, theta, DELTA)
|
662
|
+
|
663
|
+
# The centre point of the back face should map to
|
664
|
+
# φ = π, θ = 0.
|
665
|
+
# Note the implementation returns φ = -π, which is also correct.
|
666
|
+
phi, theta = p.inverse(BACK_FACE, 0.0, 0.0)
|
667
|
+
assert_in_delta(-PI, phi, DELTA)
|
668
|
+
assert_in_delta(0.0, theta, DELTA)
|
669
|
+
|
670
|
+
# The right point of the back face should map to
|
671
|
+
# φ = -3π/4, θ = 0.
|
672
|
+
phi, theta = p.inverse(BACK_FACE, 1.0, 0.0)
|
673
|
+
assert_in_delta(-3*PI/4, phi, DELTA)
|
674
|
+
assert_in_delta(0.0, theta, DELTA)
|
675
|
+
|
676
|
+
# The down-left point of the back face should map to
|
677
|
+
# φ = 3π/4, θ = -θc.
|
678
|
+
phi, theta = p.inverse(BACK_FACE, -1.0, -1.0)
|
679
|
+
assert_in_delta(3*PI/4, phi, DELTA)
|
680
|
+
assert_in_delta(-THETA_C, theta, DELTA)
|
681
|
+
|
682
|
+
# The down point of the back face should map to
|
683
|
+
# φ = π, θ = -π/4.
|
684
|
+
# Note the implementation returns φ = -π, which is also correct.
|
685
|
+
phi, theta = p.inverse(BACK_FACE, 0.0, -1.0)
|
686
|
+
assert_in_delta(-PI, phi, DELTA)
|
687
|
+
assert_in_delta(-PI/4, theta, DELTA)
|
688
|
+
|
689
|
+
# The down-right point of the back face should map to
|
690
|
+
# φ = -3π/4, θ = -θc.
|
691
|
+
phi, theta = p.inverse(BACK_FACE, 1.0, -1.0)
|
692
|
+
assert_in_delta(-3*PI/4, phi, DELTA)
|
693
|
+
assert_in_delta(-THETA_C, theta, DELTA)
|
694
|
+
|
695
|
+
# 5. The west face.
|
696
|
+
|
697
|
+
# The up-left point of the west face should map to
|
698
|
+
# φ = -3π/4, θ = θc.
|
699
|
+
phi, theta = p.inverse(WEST_FACE, -1.0, 1.0)
|
700
|
+
assert_in_delta(-3*PI/4, phi, DELTA)
|
701
|
+
assert_in_delta(THETA_C, theta, DELTA)
|
702
|
+
|
703
|
+
# The up point of the west face should map to
|
704
|
+
# φ = -π/2, θ = π/4.
|
705
|
+
phi, theta = p.inverse(WEST_FACE, 0.0, 1.0)
|
706
|
+
assert_in_delta(-PI/2, phi, DELTA)
|
707
|
+
assert_in_delta(PI/4, theta, DELTA)
|
708
|
+
|
709
|
+
# The up-right point of the west face should map to
|
710
|
+
# φ = -π/4, θ = θc.
|
711
|
+
phi, theta = p.inverse(WEST_FACE, 1.0, 1.0)
|
712
|
+
assert_in_delta(-PI/4, phi, DELTA)
|
713
|
+
assert_in_delta(THETA_C, theta, DELTA)
|
714
|
+
|
715
|
+
# The left point of the west face should map to
|
716
|
+
# φ = -3π/4, θ = 0.
|
717
|
+
phi, theta = p.inverse(WEST_FACE, -1.0, 0.0)
|
718
|
+
assert_in_delta(-3*PI/4, phi, DELTA)
|
719
|
+
assert_in_delta(0.0, theta, DELTA)
|
720
|
+
|
721
|
+
# The centre point of the west face should map to
|
722
|
+
# φ = -π/2, θ = 0.
|
723
|
+
phi, theta = p.inverse(WEST_FACE, 0.0, 0.0)
|
724
|
+
assert_in_delta(-PI/2, phi, DELTA)
|
725
|
+
assert_in_delta(0.0, theta, DELTA)
|
726
|
+
|
727
|
+
# The right point of the west face should map to
|
728
|
+
# φ = -π/4, θ = 0.
|
729
|
+
phi, theta = p.inverse(WEST_FACE, 1.0, 0.0)
|
730
|
+
assert_in_delta(-PI/4, phi, DELTA)
|
731
|
+
assert_in_delta(0.0, theta, DELTA)
|
732
|
+
|
733
|
+
# The down-left point of the west face should map to
|
734
|
+
# φ = -3π/4, θ = -θc.
|
735
|
+
phi, theta = p.inverse(WEST_FACE, -1.0, -1.0)
|
736
|
+
assert_in_delta(-3*PI/4, phi, DELTA)
|
737
|
+
assert_in_delta(-THETA_C, theta, DELTA)
|
738
|
+
|
739
|
+
# The down point of the west face should map to
|
740
|
+
# φ = -π/2, θ = -π/4.
|
741
|
+
phi, theta = p.inverse(WEST_FACE, 0.0, -1.0)
|
742
|
+
assert_in_delta(-PI/2, phi, DELTA)
|
743
|
+
assert_in_delta(-PI/4, theta, DELTA)
|
744
|
+
|
745
|
+
# The down-right point of the west face should map to
|
746
|
+
# φ = -π/4, θ = -θc.
|
747
|
+
phi, theta = p.inverse(WEST_FACE, 1.0, -1.0)
|
748
|
+
assert_in_delta(-PI/4, phi, DELTA)
|
749
|
+
assert_in_delta(-THETA_C, theta, DELTA)
|
750
|
+
|
751
|
+
# 6. The bottom face.
|
752
|
+
|
753
|
+
# The up-left point of the bottom face should map to
|
754
|
+
# φ = -π/4, θ = -θc.
|
755
|
+
phi, theta = p.inverse(BOTTOM_FACE, -1.0, 1.0)
|
756
|
+
assert_in_delta(-PI/4, phi, DELTA)
|
757
|
+
assert_in_delta(-THETA_C, theta, DELTA)
|
758
|
+
|
759
|
+
# The up point of the bottom face should map to
|
760
|
+
# φ = 0, θ = -π/4.
|
761
|
+
phi, theta = p.inverse(BOTTOM_FACE, 0.0, 1.0)
|
762
|
+
assert_in_delta(0.0, phi, DELTA)
|
763
|
+
assert_in_delta(-PI/4, theta, DELTA)
|
764
|
+
|
765
|
+
# The up-right point of the bottom face should map to
|
766
|
+
# φ = π/4, θ = -θc.
|
767
|
+
phi, theta = p.inverse(BOTTOM_FACE, 1.0, 1.0)
|
768
|
+
assert_in_delta(PI/4, phi, DELTA)
|
769
|
+
assert_in_delta(-THETA_C, theta, DELTA)
|
770
|
+
|
771
|
+
# The left point of the bottom face should map to
|
772
|
+
# φ = -π/2, θ = -π/4.
|
773
|
+
phi, theta = p.inverse(BOTTOM_FACE, -1.0, 0.0)
|
774
|
+
assert_in_delta(-PI/2, phi, DELTA)
|
775
|
+
assert_in_delta(-PI/4, theta, DELTA)
|
776
|
+
|
777
|
+
# The centre point of the bottom face should map to
|
778
|
+
# θ = -π/2, with φ being irrelevant.
|
779
|
+
phi, theta = p.inverse(BOTTOM_FACE, 0.0, 0.0)
|
780
|
+
assert_in_delta(-PI/2, theta, DELTA)
|
781
|
+
# So here, any value is correct. This implementation returns
|
782
|
+
# zero, though, for some reason. And we'll watch for changes.
|
783
|
+
assert_in_delta(0.0, phi, DELTA)
|
784
|
+
|
785
|
+
# The right point of the bottom face should map to
|
786
|
+
# φ = π/2, θ = -π/4.
|
787
|
+
phi, theta = p.inverse(BOTTOM_FACE, 1.0, 0.0)
|
788
|
+
assert_in_delta(PI/2, phi, DELTA)
|
789
|
+
assert_in_delta(-PI/4, theta, DELTA)
|
790
|
+
|
791
|
+
# The down-left point of the bottom face should map to
|
792
|
+
# φ = -3π/4, θ = -θc.
|
793
|
+
phi, theta = p.inverse(BOTTOM_FACE, -1.0, -1.0)
|
794
|
+
assert_in_delta(-3*PI/4, phi, DELTA)
|
795
|
+
assert_in_delta(-THETA_C, theta, DELTA)
|
796
|
+
|
797
|
+
# The down point of the bottom face should map to
|
798
|
+
# φ = π, θ = -π/4.
|
799
|
+
phi, theta = p.inverse(BOTTOM_FACE, 0.0, -1.0)
|
800
|
+
assert_in_delta(PI, phi, DELTA)
|
801
|
+
assert_in_delta(-PI/4, theta, DELTA)
|
802
|
+
|
803
|
+
# The down-right point of the bottom face should map to
|
804
|
+
# φ = 3π/4, θ = -θc.
|
805
|
+
phi, theta = p.inverse(BOTTOM_FACE, 1.0, -1.0)
|
806
|
+
assert_in_delta(3*PI/4, phi, DELTA)
|
807
|
+
assert_in_delta(-THETA_C, theta, DELTA)
|
808
|
+
|
809
|
+
end # test_inverse_mapping
|
810
|
+
|
811
|
+
end # module MappingTests
|
812
|
+
|
813
|
+
class TangentialMappingTest < Test::Unit::TestCase
|
814
|
+
include MappingTests
|
815
|
+
|
816
|
+
def projection
|
817
|
+
QuadSphere::Tangential
|
818
|
+
end
|
819
|
+
end
|
820
|
+
|
821
|
+
class CSCMappingTest < Test::Unit::TestCase
|
822
|
+
include MappingTests
|
823
|
+
|
824
|
+
def projection
|
825
|
+
QuadSphere::CSC
|
826
|
+
end
|
827
|
+
|
828
|
+
def test_distortion_closure
|
829
|
+
expected_error = 2.4e-4
|
830
|
+
|
831
|
+
(-0.99).step(0.99, 0.01) do |psi|
|
832
|
+
(-0.99).step(0.99, 0.01) do |chi|
|
833
|
+
x = projection.forward_distort(chi, psi)
|
834
|
+
y = projection.forward_distort(psi, chi)
|
835
|
+
chi1 = projection.inverse_distort(x,y)
|
836
|
+
assert_in_delta(0.0, chi1-chi, expected_error)
|
837
|
+
psi1 = projection.inverse_distort(y,x)
|
838
|
+
error = Math::sqrt((chi1-chi)**2 + (psi1-psi)**2)
|
839
|
+
assert_in_delta(0.0, error, expected_error)
|
840
|
+
end
|
841
|
+
end
|
842
|
+
end
|
843
|
+
|
844
|
+
def test_closure
|
845
|
+
error = 3.0e-4
|
846
|
+
|
847
|
+
100.times do |row|
|
848
|
+
100.times do |col|
|
849
|
+
x = 0.005+col/100.0
|
850
|
+
y = 0.005+row/100.0
|
851
|
+
phi, theta = projection.inverse(WEST_FACE, x, y)
|
852
|
+
f1, x1, y1 = projection.forward(phi, theta)
|
853
|
+
assert_equal(WEST_FACE, f1)
|
854
|
+
assert_in_delta(x, x1, error)
|
855
|
+
assert_in_delta(y, y1, error)
|
856
|
+
end
|
857
|
+
end
|
858
|
+
end
|
859
|
+
end
|
860
|
+
|