quad_sphere 0.9.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|