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.
tdebindings/tqtruby/rubylib/examples/ruboids/ruboids/Graphics.rb

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