Tools

There are some useful tools that might help you explore the limited 3D world provided by Thebes.jl.

Getting your hands dirty

Suppose you want to remove the front-facing faces of an object, in order to see inside. That's possible, but a bit of code is needed.

eyepoint(200, 200, 200)
axes3D(300)
setlinejoin("bevel")

include(dirname(pathof(Thebes)) * "/../data/moreobjects.jl")

objectfull = make(cuboctahedron, "the full object")
objectcut  = make(cuboctahedron, "the cut-open object")

map(o -> scaleby!(o, 60, 60, 60), (objectfull, objectcut))

function cullfrontfaces!(m::Object, angle;
        eyepoint::Point3D=eyepoint())
    avgs = Float64[]
    for f in m.faces
        vs = m.vertices[f]
        s = 0.0
        for v in vs
            s += distance(v, eyepoint)
        end
        avg = s/length(unique(vs))

        θ = surfacenormal(vs)
        if anglebetweenvectors(θ, eyepoint) > angle
            push!(avgs, avg)
        end
    end
    neworder = reverse(sortperm(avgs))
    m.faces = m.faces[neworder]
    m.labels = m.labels[neworder]
    return m
end

sortfaces!.((objectcut, objectfull))
cullfrontfaces!(objectcut, π/3)

translate(-200, 0)
pin(objectcut)

translate(400, 0)
pin(objectfull)

@show length(objectcut.faces)
@show length(objectfull.faces)
length(objectcut.faces) = 10
length(objectfull.faces) = 14

The object on the left has had its four frontfacing faces removed. The one on the right is intact.

culling faces

Geometry

There are some basic geometry utility functions - some of them are analogous to their Luxor 2D counterparts.

General

Thebes.carpetFunction
carpet(n; kind=:circular)

Draw a circular carpet centered at the origin, using current Luxor parameters.

If kind is not :circular, the carpet will be a square.

Points that can't be rendered are not included in the final shape.

source
Thebes.drawcubeFunction
drawcube(n=10, action=:stroke)

Draw a cube. drawcube(1) draws a wireframe unit cube.

source

Distances

Luxor.betweenFunction
between(p1::Point3D, p2::Point3D, x=0.5)
between((p1::Point3D, p2::Point3D), x=0.5)

Find a point on a line between two 3D points. If x is 0.5, the returned point should be halfway between them.

source
Luxor.distanceFunction
distance(p1::Point3D, p2::Point3D)

Return the distance between two points.

source
Luxor.midpointFunction
midpoint(pt1::Point3D, pt2::Point3D)

Find the midpoint between two points. See also between().

source

Rotations

The task of rotating points in 3D space is given to Rotations.jl, a powerful and sophisticated package that offers many advanced functions for rotating things in 3D space. For Thebes, you'll probably only need the basics, but there's things like quaternions if you want to get fancy.

This code draws a cyan square lying in the XY plane with a corner at the 3D origin. The square is then rotated about the Z axis by 180° and drawn in purple. Then the square is rotated again, about the X axis, by 90° and drawn in orange.

eyepoint(Point3D(150, 250, 350))
perspective(520)

function drawsquare(ptlist)
    pin(ptlist, gfunction = (p3, p2) ->
        poly(p2, :fill, close=true))
end

square = [Point3D(0, 0, 0), Point3D(100, 0, 0), Point3D(100, 100, 0), Point3D(0, 100, 0)]

sethue("cyan")
drawsquare(square)

sethue("purple")
rotateby!(square, RotZ(π))
drawsquare(square)

sethue("orange")
rotateby!(square, RotX(-π/2))
drawsquare(square)

axes3D(160)

basic rotations

The most useful rotation functions are RotX(), RotY(), RotZ(), RotXY(), and RotXYZ(), which rotate around the axes. All the other permutations are available. A RotXYZ() rotation takes three angles. The right-most rotation is applied first, so RotXYZ() applies the Z rotation, followed by the Y, then followed by the X.

You can compose two or more rotations by multiplying them together, eg RotX(π/2) * RotZ(π/4).

The rotation functions without ! return new points or arrays of points.

There are also functions that accept a second 3D point, the about point. The rotation is applied around that point, rather than an axis. In the next example, the square is rotated in Z about the corner point at Point3D(100, 100, 0) and drawn in green.

sethue("grey40")
drawsquare(square)

sethue("green")
rotateby!(square, Point3D(100, 100, 0), RotZ(π))
drawsquare(square)

axes3D(160)

basic rotations about

  • rotateby(point::Point3D, r::Rotation)

  • rotateby(point::Point3D, about::Point3D, r::Rotation)

  • rotateby(ptlist::Array{Point3D, 1}, r::Rotation)

  • rotateby(ptlist::Array{Point3D, 1}, about::Point3D, r::Rotation)

  • rotateby(ptlist::Array{Point3D, 1}, angleX, angleY, angleZ)

  • rotateby(pt::Point3D, angleX, angleY, angleZ)

  • rotateby(point::Point3D, about::Point3D, angleX, angleY, angleZ)

  • rotateby(m::Object, angleX, angleY, angleZ)

  • rotateby(m::Object, pt::Point3D, angleX, angleY, angleZ)

Those with ! mutate the array of points in place. (You can't modify a single point.)

  • rotateby!(ptlist::Vector{Point3D}, r::RotXYZ{Float64})

  • rotateby!(ptlist::Array{Point3D, 1}, angleX, angleY, angleZ)

  • rotateby!(ptlist::Array{Point3D, 1}, existingpt::Point3D, angleX, angleY, angleZ)

  • rotateby!(ptlist::Array{Point3D, 1}, existingpt::Point3D, r::Rotation)

  • rotateby!(m::Object, angleX, angleY, angleZ)

  • rotateby!(m::Object, pt::Point3D, angleX, angleY, angleZ)

Thebes.rotateXFunction
rotateX(pt3D::Point3D, rad)

Return a new point resulting from rotating the point around the x axis by an angle in radians.

Rotations are anticlockwise when looking along axis from 0 to +axis.

source
Thebes.rotateYFunction
rotateY(pt3D::Point3D, rad)

Return a new point resulting from rotating the point around the y axis by an angle in radians.

Rotations are anticlockwise when looking along axis from 0 to +axis.

source
Thebes.rotateZFunction
rotateZ(pt3D::Point3D, rad)

Return a new point resulting from rotating the point around the z axis by an angle in radians.

source
Thebes.rotateby!Function
rotateby!(ptlist::Array{Point3D, 1}, angleX, angleY, angleZ)

Modify a list of points by rotating each one around the x, y, and z axes by angleX, angleY, angleZ.

source
rotateby!(ptlist::Array{Point3D, 1}, existingpt::Point3D, angleX, angleY, angleZ)
rotateby!(ptlist::Array{Point3D, 1}, existingpt::Point3D, r::Rotation)
rotateby!(ptlist::Array{Point3D, 1}, r::Rotation=RotXYZ{Float64})

Rotate each point in the list by rotation (or angleX, angleY, angleZ) around another point (or origin).

source
rotateby!(o::Object, r::Rotation)
rotateby!(o::Object, angleX, angleY, angleZ)

Rotate an object through rotation r, or around the x, y, and/or z axis by angleX, angleY, angleZ.

source
rotateby!(o::Object, pt::Point3D, angleX, angleY, angleZ)
rotateby!(o::Object, pt::Point3D, r::Rotation=RotXYZ(0, 0, 0))

Rotate an object around a point by rotation r, or angleX, angleY, angleZ.

source
Thebes.rotatebyFunction
rotateby(pt::Point3D, angleX, angleY, angleZ)
rotateby(ptlist::Array{Point3D, 1}, angleX, angleY, angleZ)
rotateby(point::Point3D, r::Rotation)
rotateby(ptlist::Array{Point3D, 1}, r::Rotation)

Return a new point/list of points resulting from rotating around the x, y, and z axes by angleX, angleY, angleZ.

The Z rotation is first, then the Y, then the X.

A 3×3 rotation matrix parameterized by the "Tait-Bryant" XYZ Euler angle convention, consisting of first a rotation about the Z axis by theta3, followed by a rotation about the Y axis by theta2, and finally a rotation about the X axis by theta1.

source
rotateby(point::Point3D, about::Point3D, angleX, angleY, angleZ)
rotateby(point::Point3D, about::Point3D, r::Rotation)
rotateby(ptlist::Array{Point3D, 1}, about::Point3D, r::Rotation)
source
rotateby(o::Object, angleX, angleY, angleZ)

Rotate a copy of the object by angleX, angleY, angleZ.

source
rotateby(o::Object, pt::Point3D, angleX, angleY, angleZ)
rotateby(o::Object, pt::Point3D, r::Rotation=RotXYZ(0, 0, 0))

Rotate a copy of the object around a point by rotation r, or angleX, angleY, angleZ.

source

Position and scale

You can change the position and scale of things. moveby() makes a copy, moveby!() moves the original.

In the next example, the square is first moved by -100/-100/0, then copies are moved upwards by the loop index i.

sethue("grey40")
drawsquare(square)

moveby!(square, Point3D(-100, -100, 0))

setopacity(.6)
for i in 10:10:200
    randomhue()
    drawsquare(moveby.(square, Point3D(0, 0, i)))
end

axes3D(160)

basic moves

scaleby!() changes the scale of a list of points.

axes3D(160)


sethue("red")
drawsquare(square)

sethue("blue")
scaleby!(square, .5, 2, 1)
moveby!(square, Point3D(0, 0, 30))
drawsquare(square)

basic scale

Thebes.moveby!Function
moveby!(ptlist::Point3D, x, y, z)
moveby!(ptlist::Point3D, pt::Point3D)

Move all points in the list by a vector.

source
moveby!(o::Object, x, y, z)
moveby!(o::Object, pt::Point3D)

Set the position of object to Point3D(x, y, z).

source
Thebes.movebyFunction
moveby(pt::Point3D, d::Point3D)

Return a new point that's the result of moving a point pt by a vector d.

source
moveby(o::Object, x, y, z)
moveby(o::Object, pt::Point3D)

Set the position of a copy of the object to Point3D(x, y, z).

source
Thebes.scaleby!Function
scaleby!(ptlist::Array{Point3D, 1}, x, y, z)

Scales a list of points by multiplying by x in X, y in Y, z in Z.

source
scaleby!(o::Object, x, y, z)

Scale object by x in x, y in y, and z in z.

source

Coordinates

Thebes.sphericaltocartesianFunction
sphericaltocartesian(ρ, θ, ϕ)

Return Point3D(x, y, z) corresponding to (ρ, θ, ϕ):

  • ρ is the distance from the origin (ie radius)

  • θ is the azimuthal angle (the longitude) 0 is +x, π is -x, 2π is +x

  • ϕ is the polar angle (the latitude) 0 is North Pole, π is South Pole

There are two major conventions for spherical coordinate notation.

In physics books:

(ρ, θ, φ) gives the radial distance, polar angle (latitude), and azimuthal angle (longitude)

In mathematics books:

(ρ, θ , φ ) gives the radial distance, azimuthal angle (longitude), and polar angle (latitude)

So we're using the mathematics one here.

source
sphericaltocartesian((ρ, θ, ϕ))

Return Point3D(x, y, z) corresponding to (ρ, θ, ϕ).

source
Thebes.cartesiantosphericalFunction
cartesiantospherical(x, y, z)

Return (ρ, θ, ϕ) (radius, longitude, latitude) of the Point3D(x, y, z).

source
cartesiantospherical(pt::Point3D)

Return (ρ, θ, ϕ) (radius, longitude, latitude) of pt.

source
Thebes.pointsperpendicularFunction
pointsperpendicular(p1::Point3D, p2::Point3D, radius, angles = [0, π])

Find points perpendicular to a line joining p1 and p2. Points are radius units away from the line.

source