You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
279 lines
7.5 KiB
279 lines
7.5 KiB
#
|
|
# Copyright (c) 2001 by Jim Menard <jimm@io.com>
|
|
#
|
|
# Released under the same license as Ruby. See
|
|
# http://www.ruby-lang.org/en/LICENSE.txt.
|
|
#
|
|
|
|
require 'Triangle'
|
|
|
|
class Graphics
|
|
|
|
DEFAULT_SPHERE_ITERATIONS = 3
|
|
|
|
XPLUS = Point.new(1, 0, 0) # X
|
|
XMINUS = Point.new(-1, 0, 0)# -X
|
|
YPLUS = Point.new(0, 1, 0) # Y
|
|
YMINUS = Point.new(0, -1, 0)# -Y
|
|
ZPLUS = Point.new(0, 0, 1) # Z
|
|
ZMINUS = Point.new(0, 0, -1)# -Z
|
|
|
|
# defined w/counter-clockwise triangles
|
|
OCTAHEDRON = [
|
|
Triangle.new(YPLUS, ZPLUS, XPLUS),
|
|
Triangle.new(XMINUS, ZPLUS, YPLUS),
|
|
Triangle.new(YMINUS, ZPLUS, XMINUS),
|
|
Triangle.new(XPLUS, ZPLUS, YMINUS),
|
|
Triangle.new(ZMINUS, YPLUS, XPLUS),
|
|
Triangle.new(ZMINUS, XMINUS , YPLUS),
|
|
Triangle.new(ZMINUS, YMINUS , XMINUS),
|
|
Triangle.new(ZMINUS, XPLUS, YMINUS)
|
|
]
|
|
# Defines counter-clockwise points used in OpenGL TRIANGLE_STRIP to
|
|
# create a circle on the X/Z plane. Don't include center point here;
|
|
# It is added when outputting the circle.
|
|
SQUARE = [
|
|
XPLUS, ZMINUS, XMINUS, ZPLUS, XPLUS
|
|
]
|
|
|
|
@@spheres = Hash.new()
|
|
@@circles = Hash.new()
|
|
|
|
def Graphics.radiansToDegrees(rad)
|
|
return rad * 180.0 / Math::PI
|
|
end
|
|
|
|
def Graphics.degreesToRadians(deg)
|
|
return deg * Math::PI / 180.0
|
|
end
|
|
|
|
# Given a vector, return a point containing x, y, z rotation angles.
|
|
#
|
|
# atan2(x, y) = the angle formed with the x axis by the ray from the
|
|
# origin to the point {x,y}
|
|
def Graphics.rotations(v)
|
|
return Point::ORIGIN.dup() if v.nil?
|
|
return v if v == Point::ORIGIN
|
|
|
|
x = Math.atan2(v.y, v.z)
|
|
y = Math.atan2(v.z, v.x)
|
|
z = Math.atan2(v.y, v.x)
|
|
|
|
rot = Point.new(z, x, y)
|
|
rot.add(Math::PI).multiplyBy(180.0).divideBy(Math::PI)
|
|
|
|
rot.x = rot.x.to_i
|
|
rot.y = rot.y.to_i
|
|
rot.z = rot.z.to_i
|
|
|
|
return rot
|
|
end
|
|
|
|
# Build box from corners. All faces are counter-clockwise.
|
|
def Graphics.boxFromCorners(p0, p1)
|
|
pa = p0.dup()
|
|
pb = p1.dup()
|
|
|
|
# Make sure all coords of pa are < all coords of pb
|
|
if pa.x > pb.x
|
|
tmp = pa.x; pa.x = pb.x; pb.x = tmp
|
|
end
|
|
if pa.y > pb.y
|
|
tmp = pa.y; pa.y = pb.y; pb.y = tmp
|
|
end
|
|
if pa.z > pb.z
|
|
tmp = pa.z; pa.z = pb.z; pb.z = tmp
|
|
end
|
|
|
|
Begin(TQUAD_STRIP)
|
|
|
|
# top
|
|
Vertex(pb.x, pb.y, pa.z)
|
|
Vertex(pa.x, pb.y, pa.z)
|
|
# top/front
|
|
Vertex(pb.x, pb.y, pb.z)
|
|
Vertex(pa.x, pb.y, pb.z)
|
|
# front/bottom
|
|
Vertex(pb.x, pa.y, pb.z)
|
|
Vertex(pa.x, pa.y, pb.z)
|
|
# bottom/back
|
|
Vertex(pb.x, pa.y, pa.z)
|
|
Vertex(pa.x, pa.y, pa.z)
|
|
# back/top
|
|
Vertex(pb.x, pb.y, pa.z)
|
|
Vertex(pa.x, pb.y, pa.z)
|
|
|
|
End()
|
|
|
|
Begin(TQUADS)
|
|
|
|
# left
|
|
Vertex(pa.x, pa.y, pb.z)
|
|
Vertex(pa.x, pa.y, pa.z)
|
|
Vertex(pa.x, pb.y, pa.z)
|
|
Vertex(pa.x, pb.y, pb.z)
|
|
|
|
# right
|
|
Vertex(pb.x, pa.y, pb.z)
|
|
Vertex(pb.x, pa.y, pa.z)
|
|
Vertex(pb.x, pb.y, pa.z)
|
|
Vertex(pb.x, pb.y, pb.z)
|
|
|
|
End()
|
|
end
|
|
|
|
# sphere() (and buildSphere()) - generate a triangle mesh approximating
|
|
# a sphere by recursive subdivision. First approximation is an
|
|
# octahedron; each level of refinement increases the number of
|
|
# triangles by a factor of 4.
|
|
#
|
|
# Level 3 (128 triangles) is a good tradeoff if gouraud shading is used
|
|
# to render the database.
|
|
#
|
|
# Usage: sphere [level] [counterClockwise]
|
|
#
|
|
# The value level is an integer >= 1 setting the recursion level
|
|
# (default = DEFAULT_SPHERE_ITERATIONS).
|
|
# The boolean counterClockwise causes triangles to be generated
|
|
# with vertices in counterclockwise order as viewed from
|
|
# the outside in a RHS coordinate system. The default is
|
|
# counter-clockwise.
|
|
#
|
|
# @author Jon Leech (leech@cs.unc.edu) 3/24/89 (C version)
|
|
# Ruby version by Jim Menard (jimm@io.com), May 2001.
|
|
def Graphics.sphere(iterations = DEFAULT_SPHERE_ITERATIONS,
|
|
counterClockwise = true)
|
|
if @@spheres[iterations].nil?
|
|
@@spheres[iterations] = buildSphere(iterations, OCTAHEDRON)
|
|
end
|
|
sphere = @@spheres[iterations]
|
|
|
|
Begin(TRIANGLES)
|
|
sphere.each { | triangle |
|
|
triangle.points.each { | p |
|
|
Vertex(p.x, p.y, p.z) if counterClockwise
|
|
Vertex(p.z, p.y, p.x) if !counterClockwise
|
|
}
|
|
}
|
|
End()
|
|
end
|
|
|
|
#
|
|
# Subdivide each triangle in the oldObj approximation and normalize
|
|
# the new points thus generated to lie on the surface of the unit
|
|
# sphere.
|
|
# Each input triangle with vertices labelled [0,1,2] as shown
|
|
# below will be turned into four new triangles:
|
|
#
|
|
# Make new points
|
|
# a = (0+2)/2
|
|
# b = (0+1)/2
|
|
# c = (1+2)/2
|
|
# 1
|
|
# /\ Normalize a, b, c
|
|
# / \
|
|
# b/____\ c Construct new counter-clockwise triangles
|
|
# /\ /\ [a,b,0]
|
|
# / \ / \ [c,1,b]
|
|
# /____\/____\ [c,b,a]
|
|
# 0 a 2 [2,c,a]
|
|
#
|
|
#
|
|
# The normalize step (which makes each point a, b, c unit distance
|
|
# from the origin) is where we can modify the sphere's shape.
|
|
#
|
|
def Graphics.buildSphere(iterations, sphere)
|
|
oldObj = sphere
|
|
# Subdivide each starting triangle (maxlevel - 1) times
|
|
iterations -= 1
|
|
iterations.times {
|
|
# Create a new object. Allocate 4 * the number of points in the
|
|
# the current approximation.
|
|
newObj = Array.new(oldObj.length * 4)
|
|
|
|
j = 0
|
|
oldObj.each { | oldt |
|
|
# New midpoints
|
|
a = Point.midpoint(oldt.points[0], oldt.points[2])
|
|
a.normalize!()
|
|
b = Point.midpoint(oldt.points[0], oldt.points[1])
|
|
b.normalize!()
|
|
c = Point.midpoint(oldt.points[1], oldt.points[2])
|
|
c.normalize!()
|
|
|
|
# New triangeles. Their vertices are counter-clockwise.
|
|
newObj[j] = Triangle.new(a, b, oldt.points[0])
|
|
j += 1
|
|
newObj[j] = Triangle.new(c, oldt.points[1], b)
|
|
j += 1
|
|
newObj[j] = Triangle.new(c, b, a)
|
|
j += 1
|
|
newObj[j] = Triangle.new(oldt.points[2], c, a)
|
|
j += 1
|
|
}
|
|
|
|
# Continue subdividing new triangles
|
|
oldObj = newObj
|
|
}
|
|
return oldObj
|
|
end
|
|
|
|
# Creates a circle in the X/Z plane. To have the circle's normal
|
|
# point down (-Y), specify clockwise instead of counter-clockwise.
|
|
# To create the circle in another plane, call OpenGL's Rotate() method
|
|
# before calling this.
|
|
def Graphics.circle(iterations = DEFAULT_SPHERE_ITERATIONS,
|
|
counterClockwise = true)
|
|
if @@circles[iterations].nil?
|
|
@@circles[iterations] = buildCircle(iterations, SQUARE)
|
|
end
|
|
circle = @@circles[iterations]
|
|
|
|
Begin(TRIANGLE_FAN)
|
|
Vertex(0, 0, 0)
|
|
if counterClockwise
|
|
circle.each { | p | Vertex(p.x, 0, p.z) }
|
|
else
|
|
circle.reverse.each { | p | Vertex(p.x, 0, p.z) }
|
|
end
|
|
End()
|
|
end
|
|
|
|
# Different than buildSphere because we are creating triangles to
|
|
# be used in an OpenGL TRIANGLE_FAN operation. Thus the first point
|
|
# (the center) is always inviolate. We create new points between
|
|
# the remaining points.
|
|
def Graphics.buildCircle(iterations, circle)
|
|
oldObj = circle
|
|
# Subdivide each starting line segment (maxlevel - 1) times
|
|
iterations -= 1
|
|
iterations.times {
|
|
# Create a new object. Allocate 2 * the number of points in the
|
|
# the current approximation. Subtract one because the last point
|
|
# (same as the first point) is simply copied.
|
|
newObj = Array.new(oldObj.length * 2 - 1)
|
|
|
|
prevP = nil
|
|
j = 0
|
|
oldObj.each { | p |
|
|
if !prevP.nil?
|
|
newObj[j] = prevP
|
|
j += 1
|
|
|
|
# New midpoint
|
|
a = Point.midpoint(prevP, p)
|
|
a.normalize!()
|
|
newObj[j] = a
|
|
j += 1
|
|
end
|
|
prevP = p
|
|
}
|
|
newObj[j] = prevP # Copy last point
|
|
|
|
# Continue subdividing new triangles
|
|
oldObj = newObj
|
|
}
|
|
return oldObj
|
|
end
|
|
end
|