Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/coolbutuseless/threed
Three-Dimensional Object Transformations
https://github.com/coolbutuseless/threed
Last synced: 4 months ago
JSON representation
Three-Dimensional Object Transformations
- Host: GitHub
- URL: https://github.com/coolbutuseless/threed
- Owner: coolbutuseless
- License: mit
- Created: 2018-11-08T10:19:55.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2018-12-02T07:41:27.000Z (about 6 years ago)
- Last Synced: 2024-10-12T21:24:57.596Z (4 months ago)
- Language: R
- Size: 1.25 MB
- Stars: 43
- Watchers: 7
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.Rmd
- License: LICENSE
Awesome Lists containing this project
- awesome-r-dataviz - threed - Three-Dimensional Object Transformations. (Drawing & Rendering / Miscellaneous)
README
---
output: github_document
---```{r, echo = FALSE}
suppressPackageStartupMessages({
library(dplyr)
library(ggplot2)
library(threed)
})knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "figures/README-",
out.width = "100%"
)options(width = 90)
```# threed - 3d object transformation library
![](https://img.shields.io/badge/lifecycle-alpha-orange.svg)
[![Travis build status](https://travis-ci.org/coolbutuseless/threed.svg?branch=master)](https://travis-ci.org/coolbutuseless/threed)
[![Coverage status](https://codecov.io/gh/coolbutuseless/threed/branch/master/graph/badge.svg)](https://codecov.io/github/coolbutuseless/threed?branch=master)
[![AppVeyor build status](https://ci.appveyor.com/api/projects/status/github/coolbutuseless/threed?branch=master&svg=true)](https://ci.appveyor.com/project/coolbutuseless/threed)`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).#### Features:
* standard translate, scale and rotate transformations
* perspective + orthographic projection
* `as.data.frame.mesh3d`
- convert 3d objects into data.frames
- calculates a lot of meta data such as face normals, vertex normals and
whether a face is hidden from view.
* `fortify.mesh3d`
- this enables a `mesh3d` object to be given as the ggplot2 `data` argument.
* Includes some built-in `mesh3d` objects (see `threed::mesh3dobj`) e.g.
- `cube`, `icosahedron`, `teapot`, `cow`, `bunny`#### Vignettes
* `vignette('drawing-a-cube', package='threed')`
* All the ways of drawing a 3d cube using `threed` in `ggplot2`
* e.g. hidden line removal, fake light shading etc
* `vignette('mesh3d', package='threed')`
* Adaptations and extensions to the `mesh3d` format
* `vignette('animate-in-3d', package='threed')`
* Creating a simple 3d animated object in `ggplot2`#### Rendering objects
* `threed` is just a 3d object transformation package and does not include any facility for rendering of objects.
* Examples are included below showing how to convert 3d objects to a data.frame and then render with:
- `ggplot2` and `geom_polygon()`
- Base R plotting with `polygon()````{r gallery-cow, echo = FALSE, warning = FALSE, dev = 'jpeg'}
camera_to_world <- look_at_matrix(eye = c(1.5, 1.75, 4), at = c(0, 0, 0))obj <- threed::mesh3dobj$cow %>%
# scale_by(c(25, 25, 25)) %>%
# translate_by(c(10, 0, -10)) %>%
transform_by(invert_matrix(camera_to_world)) %>%
# perspective_projection(n = 10, f = 100) %>%
perspective_projection()set.seed(1)
N <- 100
dummy_df <- data_frame(
bin = sample(c(-0.1, 0, 0.1), N, replace = TRUE),
value = rnorm(N, mean=0, sd=0.25)
)ggplot() +
geom_boxplot(data = dummy_df, aes(x = bin, y = value, group = bin)) +
geom_polygon(data = obj, aes(x, y, group = zorder, fill = fnx + fny, colour = fnx + fny)) +
theme_minimal() +
theme(legend.position = 'none') +
coord_equal() +
scale_fill_viridis_c (option = 'D') +
scale_color_viridis_c(option = 'D') +
labs(
title = "3d Cow Model Rendered in 'ggplot2' with Viridis Shading",
subtitle = "Standing on a boxplot"
) +
# theme(axis.text = element_blank()) +
ylim(-0.2, 0.2) +
NULL```
## Installation
```{r, eval = FALSE}
# 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 element
* `element_type` indicating how many vertices in an element
* `vorder` the ordering of the vertices to define each element
* normal at each vertex - `vnx, vny, vnz`
* normal of each face - `fnx, fny, fnz`
* centroid of each face = `fcx, fcy, fcz`
* `vertex` global vertex identifier from the `mesh3d` object
* `zorder` the drawing order of the elements from back to front
* `hidden` whether or not the face is hidden. i.e. `fnz < 0````{r}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 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")
```Drawing a cube in `ggplot2`
------------------------------------------------------------------------------```{r cube}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 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()
```Drawing a cube with base plot
------------------------------------------------------------------------------```{r cube-baseplot}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 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'))
)
```# Gallery
### Bunny with light shading
```{r gallery-bunny, dev = 'jpeg'}
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()
```### Teapot with shading by z-order
```{r gallery-teapot, dev = 'jpeg'}
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')```
### Dashed hidden lines
```{r gallery-cube-dashed}
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()
```### Animated Icosahedron
See `vignette('animate-in-3d', package='threed')`
![](figures/icosahedron.gif)
### Hex logo
`threed` is used to generate its own hex logo by rendering an orthographic projection of a cube.
```{r gallery-logo, eval = FALSE}
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")
```![](figures/logo-with-pause.gif)