knitr::opts_chunk$set( warning = FALSE, message = FALSE, echo = TRUE, collapse = TRUE, fig.height = 5, fig.width = 5 ) options(digits=4) par(mar=c(4,4,1,1)+.1)
library(rgl) library(knitr) knit_hooks$set(webgl = hook_webgl)
library(matlib) # use the package library(rgl) # also using rgl
This vignette illustrates how linear transformations can be represented in 3D using
the rgl
package.
It is explained more fully in this StackExchange discussion
3D geometric interpretations of matrix inverse.
It also illustrates the determinant, $\det(\mathbf{A})$, as the volume of the transformed
cube, and the relationship between $\mathbf{A}$ and $\mathbf{A}^{-1}$.
Start with a unit cube, which represents the identity matrix $\mathbf{I}_{3 \times 3}$ = diag(3)
in 3 dimensions.
In rgl
this is given by cube3d()
which returns a "mesh3d"
object containing the vertices from -1 to 1
on each axis. I translate and scale this to make each vertex go from 0 to 1.
These are represented in homogeneous coordinates to handle perspective and transformations.
See help("identityMatrix")
for details.
library(rgl) library(matlib) # cube, with each face colored differently colors <- rep(2:7, each=4) c3d <- cube3d() # make it a unit cube at the origin c3d <- scale3d(translate3d(c3d, 1, 1, 1), .5, .5, .5) str(c3d)
The vertices are the 8 columns of the vb
component of the object.
c3d$vb
I want to show the transformation of $\mathbf{I}$ by a matrix $\mathbf{A}$ as the corresponding transformation of the cube.
# matrix A: A <- matrix(c( 1, 0, 1, 0, 2, 0, 1, 0, 2), 3, 3) |> print() det(A) # A can be produced by elementary row operations on the identity matrix I <- diag( 3 ) AA <- I |> rowadd(3, 1, 1) |> # add 1 x row 3 to row 1 rowadd(1, 3, 1) |> # add 1 x row 1 to row 3 rowmult(2, 2) # multiply row 2 by 2 all(AA==A)
I want to be able to display 3D mesh objects, with colored points, lines and faces.
This is a little tricky, because in rgl colors are applied to vertices, not faces. The faces are colored by interpolating the colors at the vertices. See the StackOverflow discussion
drawing a cube with colored faces, vertex points and lines.
The function draw3d()
handles this for "mesh3d"
objects.
Another function, vlabels()
allows for labeling vertices.
# draw a mesh3d object with vertex points and lines draw3d <- function(object, col=rep(rainbow(6), each=4), alpha=0.6, vertices=TRUE, lines=TRUE, reverse = FALSE, ...) { if(reverse) col <- rev(col) shade3d(object, col=col, alpha=alpha, ...) vert <- t(object$vb) indices <- object$ib if (vertices) points3d(vert, size=5) if (lines) { for (i in 1:ncol(indices)) lines3d(vert[indices[,i],]) } } # label vertex points vlabels <- function(object, vertices, labels=vertices, ...) { text3d( t(object$vb[1:3, vertices] * 1.05), texts=labels, ...) }
We can show the cube representing the identity matrix $\mathbf{I}$ using draw3d()
.
Transforming this by $\mathbf{A}$ is accomplished using transform3d()
.
open3d() draw3d(c3d) vlabels(c3d, c(1,2,3,5)) axes <- rbind( diag(3), -diag(3) ) rownames(axes) <- c("x", "y", "z", rep(" ", 3)) vectors3d(axes, frac.lab=1.2, headlength = 0.2, radius=1/20, lwd=3) c3t<- transform3d(c3d, A) draw3d(c3t) vlabels(c3t, c(1,2,3,5))
# NB: this scales each one separately, so can't see relative size open3d() mfrow3d(1,2, sharedMouse=TRUE) draw3d(c3d) vectors3d(axes, frac.lab=1.2, headlength = 0.2, radius=1/20, lwd=3) next3d() draw3d(c3t) vectors3d(axes, frac.lab=1.2, headlength = 0.2, radius=1/20, lwd=3)
The inverse of A
is found using `solve()
AI <- solve(A) |> print()
The determinant of the matrix A
here is 2. The determinant of $\mathbf{A}^{-1}$ is the reciprocal,
$1/ \det(\mathbf{A})$. Geometrically, this means that the larger $\mathbf{A}^{-1}$ is the smaller is
$\mathbf{A}^{-1}$.
det(A) det(AI)
You can see the relation between them by graphing both together in the same plot. It is clear that $\mathbf{A}^{-1}$ is small in the directions $\mathbf{A}$ is large, and vice versa.
open3d() draw3d(c3t) c3Inv <- transform3d(c3d, solve(A)) draw3d(c3Inv) vectors3d(axes, frac.lab=1.2, headlength = 0.2, radius=1/20, lwd=3) vlabels(c3t, 8, "A", cex=1.5) vlabels(c3t, c(2,3,5)) vlabels(c3Inv, 4, "Inv", cex=1.5) vlabels(c3Inv, c(2,3,5))
The rgl
graphic can be animated by spinning it around an axis using spin3d()
.
This can be played on screen using play3d()
or
saved to a .gif
file using movie3d()
.
play3d(spin3d(rpm=15), duration=4) #movie3d(spin3d(rpm=15), duration=4, movie="inv-demo", dir=".")
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.