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/COPYING
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2012 Cesar Rincon <crincon@gmail.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
QuadSphere
|
2
|
+
==========
|
3
|
+
|
4
|
+
QuadSphere is a small Ruby gem that implements a projection of
|
5
|
+
spherical to planar coordinates, called the _quadrilateralised
|
6
|
+
spherical cube_. It is useful for handling geographic or astronomical
|
7
|
+
data, or for general mapmaking.
|
8
|
+
|
9
|
+
![Sphere picture][9] ![Grid picture][10]
|
10
|
+
|
11
|
+
Background
|
12
|
+
----------
|
13
|
+
|
14
|
+
The quadrilateralised spherical cube, or "quad sphere" for short, is a
|
15
|
+
projection of the sphere onto the sides of an inscribed cube, where
|
16
|
+
the distortion of the tangential (gnomonic) projection is compensated
|
17
|
+
by a further curvilinear transformation. This makes it approximately
|
18
|
+
equal-area, with no singularities at the poles, or anywhere;
|
19
|
+
distortion is moderate over the entire sphere. This makes it
|
20
|
+
well-suited for storing data collected on a spherical surface, like
|
21
|
+
the Earth or the celestial sphere, as rasters of pixels: each
|
22
|
+
equal-area pixel then corresponds to an equal-area region on the
|
23
|
+
sphere, so numerical analysis can be performed on the pixels rather
|
24
|
+
than the original surface.
|
25
|
+
|
26
|
+
This projection was proposed in 1975 in the report "Feasibility Study
|
27
|
+
of a Quadrilateralized Spherical Cube Earth Data Base", by F. K. Chan
|
28
|
+
and E. M. O'Neill ([citation entry][6]), and it was used to hold data
|
29
|
+
for the Cosmic Background Explorer project (COBE). The quad sphere,
|
30
|
+
along with a binning scheme for storing pixels along a Z-order curve,
|
31
|
+
became the [COBE Sky Cube][1] format.
|
32
|
+
|
33
|
+
This is not a Sky Cube reader, though — neither the binning scheme nor
|
34
|
+
the FITS format are implemented here. You should use a [FITS][5] library
|
35
|
+
if you need to read COBE data. And, for current astronomical work,
|
36
|
+
the quadrilateralised spherical cube projection has been superseded by
|
37
|
+
[HEALPix][3], so you should use that instead. This implementation was
|
38
|
+
only created because this author had a very specific need involving
|
39
|
+
storage and manipulation of spherical data — for a game, no less.
|
40
|
+
|
41
|
+
Note also that this is _not_ the projection by Laubscher and O'Neill,
|
42
|
+
1976, which is similar to this but introduces singularities, making it
|
43
|
+
non-differentiable along the diagonals.
|
44
|
+
|
45
|
+
As Chan's original report is not readily available, this
|
46
|
+
implementation is based on formulae found in [FITS WCS documents][2].
|
47
|
+
Specifically: "Representations of celestial coordinates in FITS (Paper
|
48
|
+
II)", Calabretta, M. R., and Greisen, E. W., _Astronomy &
|
49
|
+
Astrophysics_, 395, 1077-1122, 2002.
|
50
|
+
|
51
|
+
Finally, bear in mind that this is not an exact projection, it's
|
52
|
+
accuracy is limited — see discussion in the documentation of
|
53
|
+
[`QuadSphere::CSC.forward_distort`][8].
|
54
|
+
|
55
|
+
Examples
|
56
|
+
--------
|
57
|
+
|
58
|
+
The basic usage, for converting a tuple of spherical coordinates (φ,θ)
|
59
|
+
to cartesian (x,y) on a cube face, is:
|
60
|
+
|
61
|
+
require 'quad_sphere/csc'
|
62
|
+
face, x, y = QuadSphere::CSC.forward(phi, theta)
|
63
|
+
|
64
|
+
Parameters `phi` and `theta` should be given in radians. `phi` is the
|
65
|
+
azimuthal angle, or longitude; you'll want to make it something
|
66
|
+
between -π and π (or 0 and 2π, if you like). `theta` is the elevation
|
67
|
+
angle, or (geocentric) latitude, so it should be between -π/2 and π/2.
|
68
|
+
The values returned are: a face identifier (see constants in module
|
69
|
+
[QuadSphere][11]), and cartesian (x,y), with each coordinate between
|
70
|
+
-1 and 1.
|
71
|
+
|
72
|
+
The inverse transfomation looks, not very surprisingly, like this:
|
73
|
+
|
74
|
+
lon, lat = QuadSphere::CSC.inverse(face, x, y)
|
75
|
+
|
76
|
+
With all symbols meaning the same as before.
|
77
|
+
|
78
|
+
As a more practical example, suppose you're storing geographic data in
|
79
|
+
six bitmaps of 100x100 pixels each. The following function will give
|
80
|
+
you the bitmap and specific coordinates of the pixel where you should
|
81
|
+
store a given latitude and longitude.
|
82
|
+
|
83
|
+
def geographic_to_storage_bin(latitude, longitude)
|
84
|
+
# Convert both angles to radians.
|
85
|
+
latitude = latitude*Math::PI/180
|
86
|
+
longitude = longitude*Math::PI/180
|
87
|
+
|
88
|
+
# Geographic latitudes are normally geodetic; we convert this to
|
89
|
+
# geocentric because we want spherical coordinates. The magic
|
90
|
+
# number below is the Earth's eccentricity, squared, using the WGS84
|
91
|
+
# ellipsoid.
|
92
|
+
latitude = Math.atan((1 - 6.69437999014e-3) * Math.tan(latitude))
|
93
|
+
|
94
|
+
# Apply the forward transformation...
|
95
|
+
face, x, y = QuadSphere::CSC.forward(longitude, latitude)
|
96
|
+
|
97
|
+
# ... then adjust x and y so they become integer coordinates on a
|
98
|
+
# 100x100 grid, with 0,0 being top-left, as used in pictures.
|
99
|
+
x = (100*(1+x)/2).floor
|
100
|
+
y = 99 - (100*(1+y)/2).floor
|
101
|
+
|
102
|
+
# And return the computed values.
|
103
|
+
[ face, x, y ]
|
104
|
+
end
|
105
|
+
|
106
|
+
Trying the above on a few locations on Earth:
|
107
|
+
|
108
|
+
[ [ 'Accra', 5.5500, -0.2167 ],
|
109
|
+
[ 'Buenos Aires', -34.6036, -58.3817 ],
|
110
|
+
[ 'Cairo', 30.0566, -31.2262 ],
|
111
|
+
[ 'Honolulu', 21.3069, -157.8583 ],
|
112
|
+
[ 'Kuala Lumpur', 3.1597, 101.7000 ],
|
113
|
+
[ 'London', 51.5171, -0.1062 ],
|
114
|
+
[ 'Longyearbyen', 78.216667, 15.55 ],
|
115
|
+
[ 'New Delhi', 28.6667, 77.2167 ],
|
116
|
+
[ 'New York', 40.7142, -74.0064 ],
|
117
|
+
[ 'Quito', -0.2186, -78.5097 ],
|
118
|
+
[ 'Sydney', -33.8683, 151.2086 ],
|
119
|
+
[ 'Ushuaia', -54.8000, -68.3000 ] ].each do |city, lat, lon|
|
120
|
+
face, x, y = geographic_to_storage_bin(lat, lon)
|
121
|
+
puts '%-12s - bitmap %d, x=%2d, y=%2d' % [city, face, x, y]
|
122
|
+
end
|
123
|
+
|
124
|
+
Gives you:
|
125
|
+
|
126
|
+
Accra - bitmap 1, x=49, y=43
|
127
|
+
Buenos Aires - bitmap 4, x=85, y=93
|
128
|
+
Cairo - bitmap 1, x=14, y=11
|
129
|
+
Honolulu - bitmap 3, x=76, y=23
|
130
|
+
Kuala Lumpur - bitmap 2, x=63, y=46
|
131
|
+
London - bitmap 0, x=49, y=93
|
132
|
+
Longyearbyen - bitmap 0, x=53, y=63
|
133
|
+
New Delhi - bitmap 2, x=35, y=16
|
134
|
+
New York - bitmap 4, x=67, y= 3
|
135
|
+
Quito - bitmap 4, x=63, y=50
|
136
|
+
Sydney - bitmap 3, x=17, y=92
|
137
|
+
Ushuaia - bitmap 5, x=11, y=32
|
138
|
+
|
139
|
+
See more code in the `examples` directory, including the programs that
|
140
|
+
created the graphics above. And see the [API reference][7] for the
|
141
|
+
nitty gritty.
|
142
|
+
|
143
|
+
[1]: http://lambda.gsfc.nasa.gov/product/cobe/skymap_info_new.cfm
|
144
|
+
[2]: http://fits.gsfc.nasa.gov/fits_wcs.html
|
145
|
+
[3]: http://healpix.jpl.nasa.gov/
|
146
|
+
[4]: http://lambda.gsfc.nasa.gov/product/cobe/skymap_info_new.cfm
|
147
|
+
[5]: http://en.wikipedia.org/wiki/FITS
|
148
|
+
[6]: http://www.dtic.mil/docs/citations/ADA010232
|
149
|
+
[7]: http://rubydoc.info/github/crinc/QuadSphere/master/frames
|
150
|
+
[8]: http://rubydoc.info/github/crinc/QuadSphere/master/QuadSphere/CSC.forward_distort
|
151
|
+
[9]: https://raw.github.com/crinc/QuadSphere/master/examples/sphere.png
|
152
|
+
[10]: https://raw.github.com/crinc/QuadSphere/master/examples/grid.png
|
153
|
+
[11]: http://rubydoc.info/github/crinc/QuadSphere/master/QuadSphere
|
data/examples/binning.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# The example from the README.
|
4
|
+
|
5
|
+
require 'quad_sphere/csc'
|
6
|
+
|
7
|
+
def geographic_to_storage_bin(latitude, longitude)
|
8
|
+
# Convert both angles to radians.
|
9
|
+
latitude = latitude*Math::PI/180
|
10
|
+
longitude = longitude*Math::PI/180
|
11
|
+
|
12
|
+
# Geographic latitudes are normally geodetic; we convert this to
|
13
|
+
# geocentric because we want spherical coordinates. The magic
|
14
|
+
# number below is the Earth's eccentricity, squared, using the WGS84
|
15
|
+
# ellipsoid.
|
16
|
+
latitude = Math.atan((1 - 6.69437999014e-3) * Math.tan(latitude))
|
17
|
+
|
18
|
+
# Apply the forward transformation...
|
19
|
+
face, x, y = QuadSphere::CSC.forward(longitude, latitude)
|
20
|
+
|
21
|
+
# ... then adjust x and y so they become integer coordinates on a
|
22
|
+
# 100x100 grid, with 0,0 being top-left, as used in pictures.
|
23
|
+
x = (100*(1+x)/2).floor
|
24
|
+
y = 99 - (100*(1+y)/2).floor
|
25
|
+
|
26
|
+
# And return the computed values.
|
27
|
+
[ face, x, y ]
|
28
|
+
end
|
29
|
+
|
30
|
+
[ [ 'Accra', 5.5500, -0.2167 ],
|
31
|
+
[ 'Buenos Aires', -34.6036, -58.3817 ],
|
32
|
+
[ 'Cairo', 30.0566, -31.2262 ],
|
33
|
+
[ 'Honolulu', 21.3069, -157.8583 ],
|
34
|
+
[ 'Kuala Lumpur', 3.1597, 101.7000 ],
|
35
|
+
[ 'London', 51.5171, -0.1062 ],
|
36
|
+
[ 'Longyearbyen', 78.216667, 15.55 ],
|
37
|
+
[ 'New Delhi', 28.6667, 77.2167 ],
|
38
|
+
[ 'New York', 40.7142, -74.0064 ],
|
39
|
+
[ 'Quito', -0.2186, -78.5097 ],
|
40
|
+
[ 'Sydney', -33.8683, 151.2086 ],
|
41
|
+
[ 'Ushuaia', -54.8000, -68.3000 ] ].each do |city, lat, lon|
|
42
|
+
face, x, y = geographic_to_storage_bin(lat, lon)
|
43
|
+
puts '%-12s - bitmap %d, x=%2d, y=%2d' % [city, face, x, y]
|
44
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Compute the closure error introduced by the curvilinear distortion
|
3
|
+
# in QuadSphere::CSC. This requires two additional gems: Joseph
|
4
|
+
# Ruscio's Aggregate, and Willem van Bergen's ChunkyPNG.
|
5
|
+
|
6
|
+
require 'quad_sphere/csc'
|
7
|
+
require 'aggregate'
|
8
|
+
require 'chunky_png'
|
9
|
+
|
10
|
+
# Some routines to manipulate colours. We should really pack this
|
11
|
+
# stuff in a gem one day...
|
12
|
+
module Colour
|
13
|
+
|
14
|
+
# We expect an array of stops. A gradient stop is an array of 4
|
15
|
+
# elements: a min value, a max value, a start colour, and an end
|
16
|
+
# colour.
|
17
|
+
#
|
18
|
+
# We expect the min of a stop to be always less than the max, and
|
19
|
+
# we expect all stops to have a max equal or smaller than the min
|
20
|
+
# of the next stop. We expect all colours to be arrays of three
|
21
|
+
# floats.
|
22
|
+
#
|
23
|
+
# We don't validate any of this; just expect breakage if you don't
|
24
|
+
# conform.
|
25
|
+
def self.gradient_map(stops, value, rgbout)
|
26
|
+
stops.each do |min, max, start, finish|
|
27
|
+
if value >= min && value <= max
|
28
|
+
t = (value - min) / (max - min)
|
29
|
+
lerp(t, start, finish, rgbout)
|
30
|
+
return true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# If we're here, v is out of range. We'll just hardcode a
|
35
|
+
# value.
|
36
|
+
rgbout[0] = 1.0
|
37
|
+
rgbout[1] = 0.0
|
38
|
+
rgbout[2] = 0.0
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
# Performs a linear interpolation between two colours.
|
43
|
+
def self.lerp(t, rgb1, rgb2, rgbout)
|
44
|
+
rgbout[0] = rgb1[0] + t*(rgb2[0] - rgb1[0])
|
45
|
+
rgbout[1] = rgb1[1] + t*(rgb2[1] - rgb1[1])
|
46
|
+
rgbout[2] = rgb1[2] + t*(rgb2[2] - rgb1[2])
|
47
|
+
end
|
48
|
+
|
49
|
+
# Converts as expected by chunkypng
|
50
|
+
def self.rgb1_to_i(rgb)
|
51
|
+
r = (rgb[0]*256).floor
|
52
|
+
g = (rgb[1]*256).floor
|
53
|
+
b = (rgb[2]*256).floor
|
54
|
+
|
55
|
+
(r < 0 ? 0 : r > 255 ? 255 : r) << 24 |
|
56
|
+
(g < 0 ? 0 : g > 255 ? 255 : g) << 16 |
|
57
|
+
(b < 0 ? 0 : b > 255 ? 255 : b) << 8 | 0xff
|
58
|
+
end
|
59
|
+
end # module Colour
|
60
|
+
|
61
|
+
# Size of the pretty pic. Computation time depends on the square of
|
62
|
+
# this, so keep it reasonable.
|
63
|
+
grid = 200
|
64
|
+
|
65
|
+
image = ChunkyPNG::Image.new(grid,grid)
|
66
|
+
stats = Aggregate.new
|
67
|
+
|
68
|
+
# The expected maximum error is just below 2.4e-4, so we'll set a B&W
|
69
|
+
# gradient white to that.
|
70
|
+
stops = [ [ 0.0, 2.4e-4, [0.0,0.0,0.0], [1.0,1.0,1.0] ] ]
|
71
|
+
rgb = Array.new(3)
|
72
|
+
|
73
|
+
# We're mapping the range -1.0 to 1.0, inclusive, to 0 to grid-1.
|
74
|
+
# Which is to say, if grid is 100, we want -1.0 at grid 0, and 1.0 at
|
75
|
+
# grid 1...
|
76
|
+
d = 2.0/(grid-1)
|
77
|
+
|
78
|
+
# Perform grid² transformations of χ,ψ to x,y and back to χ',ψ', and
|
79
|
+
# measuring the closure error.
|
80
|
+
grid.times do |row|
|
81
|
+
grid.times do |col|
|
82
|
+
chi = col*d - 1.0
|
83
|
+
psi = row*d - 1.0
|
84
|
+
x = QuadSphere::CSC.forward_distort(chi,psi)
|
85
|
+
y = QuadSphere::CSC.forward_distort(psi,chi)
|
86
|
+
chi1 = QuadSphere::CSC.inverse_distort(x,y)
|
87
|
+
psi1 = QuadSphere::CSC.inverse_distort(y,x)
|
88
|
+
error = Math::sqrt((chi1-chi)**2 + (psi1-psi)**2)
|
89
|
+
|
90
|
+
# since we're dealing with very small errors, and Aggregate only
|
91
|
+
# works with integers, we scale the error for it.
|
92
|
+
stats << error*1e8
|
93
|
+
|
94
|
+
Colour.gradient_map(stops, error, rgb)
|
95
|
+
image[col,row] = Colour.rgb1_to_i(rgb)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Print statistics:
|
100
|
+
puts("samples: %d mean: %8g min: %8g max: %8g std_dev: %8g" %
|
101
|
+
[ stats.count,
|
102
|
+
stats.mean/1e8, stats.min/1e8, stats.max/1e8, stats.std_dev/1e8 ])
|
103
|
+
|
104
|
+
# Print histogram:
|
105
|
+
puts stats.to_s
|
106
|
+
|
107
|
+
# Write the pretty pic:
|
108
|
+
image.save('distort-error.png')
|
data/examples/gl1.rb
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
# A wireframe view of the CSC projection. You need OpenGL for this.
|
2
|
+
|
3
|
+
require 'quad_sphere/csc'
|
4
|
+
require 'opengl'
|
5
|
+
|
6
|
+
class CSCWireframeApp
|
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 = 20
|
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_DEPTH_TEST)
|
25
|
+
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
|
26
|
+
glClearColor(1, 1, 1, 0.0)
|
27
|
+
setup_model
|
28
|
+
|
29
|
+
glutDisplayFunc(method :display)
|
30
|
+
glutReshapeFunc(method :reshape)
|
31
|
+
glutKeyboardFunc(method :keyboard)
|
32
|
+
|
33
|
+
# And run.
|
34
|
+
glutMainLoop()
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def setup_model
|
40
|
+
glGenLists(1)
|
41
|
+
glNewList(1, GL_COMPILE)
|
42
|
+
|
43
|
+
# The cube faces, and the colour we want each:
|
44
|
+
faces = [ [ QuadSphere::TOP_FACE, [ 0.9, 0.25, 0.25, 1.0 ] ], # red
|
45
|
+
[ QuadSphere::FRONT_FACE, [ 0.25, 0.25, 0.9, 1.0 ] ], # blue
|
46
|
+
[ QuadSphere::EAST_FACE, [ 0.25, 0.8, 0.25, 1.0 ] ], # green
|
47
|
+
[ QuadSphere::BACK_FACE, [ 0.8, 0.75, 0.15, 1.0 ] ], # yellow
|
48
|
+
[ QuadSphere::WEST_FACE, [ 0.25, 0.9, 0.9, 1.0 ] ], #cyan
|
49
|
+
[ QuadSphere::BOTTOM_FACE, [ 0.9, 0.25, 0.9, 1.0 ] ] ] #magenta
|
50
|
+
|
51
|
+
faces.each do |face, colour|
|
52
|
+
glColor3fv(colour);
|
53
|
+
# Calculate all the vertices we'll use for this face...
|
54
|
+
vertices = grid(face, FACE_SUBDIV)
|
55
|
+
# ... and arrange them in triangle strips:
|
56
|
+
mesh2quads(FACE_SUBDIV, vertices)
|
57
|
+
end
|
58
|
+
|
59
|
+
glEndList
|
60
|
+
end
|
61
|
+
|
62
|
+
def reshape(w, h)
|
63
|
+
glViewport(0, 0, w, h)
|
64
|
+
glMatrixMode(GL_PROJECTION)
|
65
|
+
glLoadIdentity()
|
66
|
+
gluPerspective(60.0, w.to_f / h.to_f, 1.0, 20.0)
|
67
|
+
glMatrixMode(GL_MODELVIEW)
|
68
|
+
glLoadIdentity()
|
69
|
+
gluLookAt(2.2,0,0, 0,0,0, 0,0,1)
|
70
|
+
end
|
71
|
+
|
72
|
+
def display
|
73
|
+
# XXX - maybe it'd be more efficient to only clear the depth
|
74
|
+
# buffer if we moved the model or camera?
|
75
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
76
|
+
|
77
|
+
# Rotate and draw the model.
|
78
|
+
glPushMatrix()
|
79
|
+
glRotate(@ang1, 0, 0, 1)
|
80
|
+
glRotate(@ang2, 1, 0, 0)
|
81
|
+
glRotate(@ang3, 0, 1, 0)
|
82
|
+
glCallList(1)
|
83
|
+
glPopMatrix()
|
84
|
+
|
85
|
+
# Done.
|
86
|
+
glutSwapBuffers()
|
87
|
+
end
|
88
|
+
|
89
|
+
def keyboard(key, x, y)
|
90
|
+
case (key)
|
91
|
+
when ?s
|
92
|
+
@ang1 = (@ang1 + 5) % 360
|
93
|
+
glutPostRedisplay()
|
94
|
+
when ?a
|
95
|
+
@ang1 = (@ang1 - 5) % 360
|
96
|
+
glutPostRedisplay()
|
97
|
+
when ?w
|
98
|
+
@ang2 = (@ang2 + 5) % 360
|
99
|
+
glutPostRedisplay()
|
100
|
+
when ?q
|
101
|
+
@ang2 = (@ang2 - 5) % 360
|
102
|
+
glutPostRedisplay()
|
103
|
+
when ?x
|
104
|
+
@ang3 = (@ang3 + 5) % 360
|
105
|
+
glutPostRedisplay()
|
106
|
+
when ?z
|
107
|
+
@ang3 = (@ang3 - 5) % 360
|
108
|
+
glutPostRedisplay()
|
109
|
+
when ?r
|
110
|
+
@ang1 = @ang2 = @ang3 = 0
|
111
|
+
glutPostRedisplay()
|
112
|
+
when ?\e, ?Q
|
113
|
+
exit(0)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Create a NxN grid of points on a face of the cube. Note that this
|
118
|
+
# will generate (N+1)*(N+1) points.
|
119
|
+
def grid(face, n)
|
120
|
+
dx = 2.0/n
|
121
|
+
dy = 2.0/n
|
122
|
+
a = Array.new
|
123
|
+
n += 1
|
124
|
+
|
125
|
+
n.times do |j|
|
126
|
+
y = -1.0 + j*dy
|
127
|
+
n.times do |i|
|
128
|
+
x = -1.0 + i*dx
|
129
|
+
lon, lat = QuadSphere::CSC.inverse(face, x, y)
|
130
|
+
sx = Math::cos(lat) * Math::cos(lon)
|
131
|
+
sy = Math::cos(lat) * Math::sin(lon)
|
132
|
+
sz = Math::sin(lat)
|
133
|
+
a << [sx,sy,sz]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
a
|
138
|
+
end
|
139
|
+
|
140
|
+
# p grid(0, 3)
|
141
|
+
#
|
142
|
+
# Create quad strips to represent a NxN mesh. The given array
|
143
|
+
# should then contain (N+1)**2 points, arranged as N+1 rows of N+1
|
144
|
+
# points.
|
145
|
+
|
146
|
+
def mesh2quads(n,a)
|
147
|
+
dx = 2.0/n
|
148
|
+
dy = 2.0/n
|
149
|
+
row = n+1
|
150
|
+
|
151
|
+
n.times do |j|
|
152
|
+
glBegin(GL_QUAD_STRIP)
|
153
|
+
rowi = j*row
|
154
|
+
row.times do |x|
|
155
|
+
glVertex3fv(a[rowi+x])
|
156
|
+
glVertex3fv(a[rowi+row+x])
|
157
|
+
end
|
158
|
+
glEnd
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
CSCWireframeApp.new.run
|