threed
is a small, dependency-free R library for doing 3d object
transformations i.e. translation, scaling, rotation and perspective
projection.
The only 3d object format currently supported is the mesh3d
format
from rgl
(as well as some extensions to the mesh3d
format to support
point and line objects).
as.data.frame.mesh3d
fortify.mesh3d
mesh3d
object to be given as the ggplot2 data
argument.mesh3d
objects (see threed::mesh3dobj
)
e.g.cube
, icosahedron
, teapot
, cow
, bunny
vignette('drawing-a-cube', package='threed')
threed
in ggplot2
vignette('mesh3d', package='threed')
mesh3d
formatvignette('animate-in-3d', package='threed')
ggplot2
threed
is just a 3d object transformation package and does not
include any facility for rendering of objects.ggplot2
and geom_polygon()
polygon()
# install.packages("devtools")
devtools::install_github("coolbutuseless/threed")
as.data.frame.mesh3d()
A mesh3d
object can be converted to a data.frame representation using
threed::as.data.frame.mesh3d()
.
Besides the standard x,y,z
coordinates, the data.frame also includes:
element_id
identifier for each elementelement_type
indicating how many vertices in an elementvorder
the ordering of the vertices to define each elementvnx, vny, vnz
fnx, fny, fnz
fcx, fcy, fcz
vertex
global vertex identifier from the mesh3d
objectzorder
the drawing order of the elements from back to fronthidden
whether or not the face is hidden. i.e. fnz
< 0
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Convert the object from mesh3d to a data.frame
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
threed::mesh3dobj$cube %>%
as.data.frame() %>%
head() %>%
knitr::kable(caption = "First few rows of the mesh3d cube after conversion to a data.frame")
| element_id | element_type | vorder | x | y | z | vertex | vnx | vny | vnz | fnx | fny | fnz | fcx | fcy | fcz | zorder | zorder_var | hidden | | ----------: | ------------: | -----: | --: | --: | --: | -----: | ----------: | ----------: | ----------: | --: | --: | --: | --: | --: | --: | :----- | ----------: | :----- | | 1 | 4 | 1 | -1 | -1 | -1 | 1 | -0.5773503 | -0.5773503 | -0.5773503 | 0 | 0 | -1 | 0 | 0 | -1 | 1 | -1 | TRUE | | 1 | 4 | 2 | -1 | 1 | -1 | 3 | -0.5773503 | 0.5773503 | -0.5773503 | 0 | 0 | -1 | 0 | 0 | -1 | 1 | -1 | TRUE | | 1 | 4 | 3 | 1 | 1 | -1 | 4 | 0.5773503 | 0.5773503 | -0.5773503 | 0 | 0 | -1 | 0 | 0 | -1 | 1 | -1 | TRUE | | 1 | 4 | 4 | 1 | -1 | -1 | 2 | 0.5773503 | -0.5773503 | -0.5773503 | 0 | 0 | -1 | 0 | 0 | -1 | 1 | -1 | TRUE | | 2 | 4 | 1 | -1 | 1 | -1 | 3 | -0.5773503 | 0.5773503 | -0.5773503 | 0 | 1 | 0 | 0 | 1 | 0 | 2 | 0 | FALSE | | 2 | 4 | 2 | -1 | 1 | 1 | 7 | -0.5773503 | 0.5773503 | 0.5773503 | 0 | 1 | 0 | 0 | 1 | 0 | 2 | 0 | FALSE |
First few rows of the mesh3d cube after conversion to a data.frame
ggplot2
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define camera position and what it's looking at.
# Use the inverse of this to transform all objects in the world
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
camera_to_world <- threed::look_at_matrix(eye = c(3, 4, 5), at = c(0, 0, 0))
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# - take a cube object
# - position it in the camera view
# - perform perspective projection
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
obj <- threed::mesh3dobj$cube %>%
transform_by(invert_matrix(camera_to_world)) %>%
perspective_projection()
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Use ggplot to plot the obj
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ggplot(obj) +
geom_polygon(aes(x = x, y = y, group = zorder, fill = 0.5 * fnx + fny), colour = 'black', size = 0.2) +
theme_minimal() +
theme(
legend.position = 'none',
axis.text = element_blank()
) +
coord_equal()
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Explicitly convert to data.frame
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
obj_df <- as.data.frame(obj)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set up a palette - one entry for each face
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pal <- colorRampPalette(c('white', 'blue'))(6)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Initialise a plot of the correct size
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
with(obj_df, plot(x, y, asp = 1, type = 'p', pch = '.', ann = FALSE, axes = FALSE))
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# For each element_id, draw polygons
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
obj_df %>%
split(., .$zorder) %>%
purrr::walk(
~with(.x, polygon(x, y, col = pal[zorder], density = 300, border = 'black'))
)
camera_to_world <- look_at_matrix(eye = c(-1.5, 1.75, 4), at = c(0, 0, 0))
obj <- threed::mesh3dobj$bunny %>%
transform_by(invert_matrix(camera_to_world)) %>%
perspective_projection()
ggplot(obj, aes(x, y, group = element_id)) +
geom_polygon(aes(fill = fnx + fny, colour = fnx + fny, group = zorder)) +
theme_minimal() +
theme(
legend.position = 'none',
axis.text = element_blank()
) +
coord_equal()
camera_to_world <- look_at_matrix(eye = c(1.5, 1.75, 4), at = c(0, 0, 0))
obj <- threed::mesh3dobj$teapot %>%
transform_by(invert_matrix(camera_to_world)) %>%
perspective_projection()
ggplot(obj, aes(x, y, group = zorder)) +
geom_polygon(aes(fill = zorder, colour = zorder)) +
theme_minimal() +
theme(
legend.position = 'none',
axis.text = element_blank()
) +
coord_equal() +
scale_fill_viridis_d (option = 'A') +
scale_color_viridis_d(option = 'A')
camera_to_world <- look_at_matrix(eye = c(1.5, 1.75, 4), at = c(0, 0, 0))
obj <- threed::mesh3dobj$cube %>%
transform_by(invert_matrix(camera_to_world)) %>%
perspective_projection()
ggplot(obj, aes(x, y, group = element_id)) +
geom_polygon(fill = NA, colour='black', aes(linetype = hidden, size = hidden)) +
scale_linetype_manual(values = c('TRUE' = "FF", 'FALSE' = 'solid')) +
scale_size_manual(values = c('TRUE' = 0.2, 'FALSE' = 0.5)) +
theme_void() +
theme(legend.position = 'none') +
coord_equal()
See vignette('animate-in-3d', package='threed')
threed
is used to generate its own hex logo by rendering an
orthographic projection of a cube.
camera_to_world <- look_at_matrix(eye = c(4, 4, 4), at = c(0, 0, 0))
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Ensure the output directory is tidy
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
png_files = list.files("~/gganim", "logo.*png", full.names = TRUE)
unlink(png_files)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Render a cube at a range of angles. Use orthographic projection
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
angles <- tail(seq(0, pi/2, length.out = 15), -1)
for (i in seq_along(angles)) {
obj <- threed::mesh3dobj$cube %>%
rotate_by(angle = angles[i], v = c(0, 1, 0)) %>%
transform_by(invert_matrix(camera_to_world)) %>%
orthographic_projection()
p <- ggplot(obj, aes(x, y, group = zorder)) +
geom_polygon(aes(fill = fnx), colour='black') +
theme_void() +
theme(legend.position = 'none') +
coord_equal(xlim = c(-1.5, 1.5), ylim = c(-1.5, 1.5)) +
scale_fill_continuous(limits = c(-1, 1))
ggsave(sprintf("~/gganim/logo-%03i.png", i), plot = p, width = 2, height = 2)
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set background to be transparent, and create an animated gif
# Doing this manually in imagemagick to avoid aretfacts that gifski added,
# and to set a longer delay between loops
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
system("mogrify -transparent white ~/gganim/logo*.png")
system("convert -delay 0 -loop 0 -dispose previous -resize 200x200 ~/gganim/logo*.png figures/logo.gif")
system("convert figures/logo.gif \\( +clone -set delay 500 \\) +swap +delete figures/logo-with-pause.gif")
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.