Make a {brickr} soccer player

brickr
dataviz
r
sports
Author
Published

May 31, 2019

tl;dr

You can create 3D Lego models with {brickr}. I made some models of soccer players.

Virtual Lego

{brickr} is a fun package by Ryan Timpe that lets you build 2D mosaics and 3D models with Lego-like virtual bricks,1 with a little help from Tyler Morgan Wall’s {rayshader} package.

You can get started with the brickr toybox, which lets you arrange bricks in a spreadsheet that {brickr} can turn into a 3D model.

A mocked-up box cover for a Lego set called 'brickr starter kit'. Text reads 'Excel template for designing models in R with brickr' and 'lay out the models in the spreadsheet and import into R using brickr.' The side panel says '4 sets, 2 templates, 1 how-to, infinite bricks'.

From Ryan’s original tweet.

Kick-off

First, we install {brickr} from GitHub.2

remotes::install_github("ryantimpe/brickr")

And attach it along with some tidyverse packages.

suppressPackageStartupMessages({
  library(brickr)
  library(dplyr)
  library(tibble)
})

I’ve written a function called create_brickr_player() that lets you build a soccer player and select the brick colours for the shirt, socks, and much more. It lets you create the same model but change the brick colours with minimum fuss.

The function is simple. It helps you create a data frame that specifies the location and colour of individual bricks on successive 2D planes to build up a 3D model.

This data frame is a plan that can be interpreted by {brickr} and transformed into a special list that can be rendered in 3D space.

Click for the function definition.

Note that the arguments must all be numeric codes as per brickr::lego_colors().

create_brickr_player <- function(
  hair_col = 34,
  skin_col = 5,
  boot_col = 6,
  shirt_body_col = 3,
  shirt_sleeve_col = 1,
  shorts_col = 1,
  sock_col = 3,
  sock_trim_col = 1
){

  tibble::tribble(

    ~"Level", ~`1`, ~`2`, ~`3`, ~`4`, ~`5`,

    "A", 0, 0, 0, 0, 0,
    "A", 0, boot_col, 0, boot_col, 0,
    "A", 0, boot_col, 0, boot_col,0,

    "B", 0, 0, 0, 0, 0,
    "B", 0, sock_col, 0, sock_col, 0,
    "B", 0, 0, 0, 0, 0,

    "C", 0, 0, 0, 0, 0,
    "C", 0, sock_trim_col, 0, sock_trim_col, 0,
    "C", 0, 0, 0, 0, 0,

    "D", 0, 0, 0, 0, 0,
    "D", 0, skin_col, 0, skin_col, 0,
    "D", 0, 0, 0, 0, 0,

    "E", 0, shorts_col, 0, shorts_col, 0,
    "E", 0, shorts_col, 0, shorts_col, 0,
    "E", 0, shorts_col, 0, shorts_col, 0,

    "F", 0, shorts_col, shorts_col, shorts_col, 0,
    "F", 0, shorts_col, shorts_col, shorts_col, 0,
    "F", 0, shorts_col, shorts_col, shorts_col, 0,

    "G", 0, shirt_body_col, shirt_body_col, shirt_body_col, 0,
    "G", skin_col, shirt_body_col, shirt_body_col, shirt_body_col, skin_col,
    "G", 0, shirt_body_col, shirt_body_col, shirt_body_col, 0,

    "H", 0, shirt_body_col, shirt_body_col, shirt_body_col, 0,
    "H", skin_col, shirt_body_col, shirt_body_col, shirt_body_col, skin_col,
    "H", 0, shirt_body_col, shirt_body_col, shirt_body_col, 0,

    "I", 0, shirt_body_col, shirt_body_col, shirt_body_col, 0,
    "I", skin_col, shirt_body_col, shirt_body_col, shirt_body_col, skin_col,
    "I", 0, shirt_body_col, shirt_body_col, shirt_body_col, 0,

    "J", 0, shirt_body_col, shirt_body_col, shirt_body_col, 0,
    "J", shirt_sleeve_col, shirt_body_col, shirt_body_col, shirt_body_col, shirt_sleeve_col,
    "J", 0, shirt_body_col, shirt_body_col, shirt_body_col, 0,

    "K", 0, shirt_body_col, shirt_body_col, shirt_body_col, 0,
    "K", shirt_sleeve_col, shirt_sleeve_col, shirt_body_col, shirt_sleeve_col, shirt_sleeve_col,
    "K", 0, shirt_body_col, shirt_body_col, shirt_body_col, 0,

    "L", 0, 0, 0, 0, 0,
    "L", 0, 0, skin_col, 0, 0,
    "L", 0, 0, 0, 0, 0,

    "M", 0, skin_col, skin_col, skin_col, 0,
    "M", 0, skin_col, skin_col, skin_col, 0,
    "M", 0, skin_col, skin_col, skin_col, 0,

    "N", 0, hair_col, hair_col, hair_col, 0,
    "N", 0, hair_col, skin_col, hair_col, 0,
    "N", 0, skin_col, skin_col, skin_col, 0,

    "O", 0, hair_col, hair_col, hair_col, 0,
    "O", 0, hair_col, hair_col, hair_col, 0,
    "O", 0, hair_col, hair_col, hair_col, 0
  )

}

Here’s what happens when you use the function with default arguments.

player_plan <- create_brickr_player()
player_plan  # preview the object
# A tibble: 45 × 6
   Level   `1`   `2`   `3`   `4`   `5`
   <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
 1 A         0     0     0     0     0
 2 A         0     6     0     6     0
 3 A         0     6     0     6     0
 4 B         0     0     0     0     0
 5 B         0     3     0     3     0
 6 B         0     0     0     0     0
 7 C         0     0     0     0     0
 8 C         0     1     0     1     0
 9 C         0     0     0     0     0
10 D         0     0     0     0     0
# ℹ 35 more rows

Each layer of bricks gets a separate value in the Level column. The x-dimension is represented by the rows of the data frame and the y-dimension by the numbered columns.

Every non-zero number represents a brick and each value represents a different colour. For example, layer A has dimensions of 3 x 5 where 4 spots will be filled with a brick. Each of these has the value ‘2’, which encodes the colour black. Layer B, meanwhile, has a couple of bricks with value ‘7’, which is bright red.

How do you know which numbers encode which colours? You can access the codes from the lego_colors data frame in the {brickr} package.

select(lego_colors, brickrID, Color, hex)  # colour codes
# A tibble: 54 × 3
   brickrID Color             hex    
      <int> <chr>             <chr>  
 1        1 White             #F4F4F4
 2        2 Brick yellow      #CCB98D
 3        3 Bright red        #B40000
 4        4 Bright blue       #1E5AA8
 5        5 Bright yellow     #FAC80A
 6        6 Black             #1B2A34
 7        7 Dark green        #00852B
 8        8 Reddish brown     #5F3109
 9        9 Medium stone grey #969696
10       10 Dark stone grey   #646464
# ℹ 44 more rows
Click for full brick colour codes.
lego_colors %>% 
  mutate(
    hex = cell_spec(
      hex, 
      "html", 
      background = factor(brickrID, lego_colors$brickrID, lego_colors$hex)
    )
  ) %>% 
  select(`Colour ID` = brickrID, Colour = Color, `Hex code` = hex) %>% 
  kable(format = "html", escape = FALSE) %>%
  kable_styling("striped", full_width = TRUE)
Colour ID Colour Hex code
1 White #F4F4F4
2 Brick yellow #CCB98D
3 Bright red #B40000
4 Bright blue #1E5AA8
5 Bright yellow #FAC80A
6 Black #1B2A34
7 Dark green #00852B
8 Reddish brown #5F3109
9 Medium stone grey #969696
10 Dark stone grey #646464
11 Nougat #BB805A
12 Bright green #58AB41
13 Medium blue #7396C8
14 Bright orange #D67923
15 Br. yellowish green #A5CA18
16 Earth blue #19325A
17 Earth green #00451A
18 Dark red #720012
19 Bright purple #C8509B
20 Light purple #FF9ECD
21 Medium azur #68C3E2
22 Medium lavender #9A76AE
23 Dark orange #91501C
24 Bright bluish green #009894
25 Bright reddish violet #901F76
26 Sand blue #70819A
27 Sand yellow #897D62
28 Sand green #708E7C
29 Flame yellowish orange #FCAC00
30 Light royal blue #9DC3F7
31 Cool yellow #FFEC6C
32 Medium lilac #441A91
33 Light nougat #E1BEA1
34 Dark brown #352100
35 Medium nougat #AA7D55
36 Dark azur #469BC3
37 Aqua #D3F2EA
38 Lavender #CDA4DE
39 Spring yellowish green #E2F99A
40 Olive green #8B844F
41 Vibrant coral #F06D78
42 Transparent #EEEEEE
43 Tr. red #B80000
44 Tr. light blue #ADDDED
45 Tr. blue #0085B8
46 Tr. yellow #FFE622
47 Tr. green #73B464
48 Tr. fl green #FAF15B
49 Tr. brown #BBB29E
50 Tr. bright orange #E18D0A
51 Tr. fl red orange #CB4E29
52 Tr. medium violet #FD8ECF
53 Tr. bright violet #6F7AB8
54 Tr. bright green #AFD246

So ‘1’ is white, ‘2’ is black and so on. I think Timpe selected this set of colours to match the colours available from Lego sets.

Boring, boring Arsenal

To actually build the model, pass the data frame to a couple of {brickr} functions.

The first is bricks_from_table() that converts the data frame to a list containing several elements that define the required bricks and colours.

# Convert plan to list with brick types and colours
player_bricks <- player_plan %>%
  bricks_from_table()

names(player_bricks)  # see the element names
[1] "Img_lego"      "brickr_object" "Img_bricks"    "ID_bricks"    
[5] "pieces"        "use_bricks"   

As a side note, you can use display_pieces() to find out the set of pieces you’ll need to recreate the model in real life!

build_pieces(player_bricks)

Pass the list object to the display_bricks() function to get the plan rendered into 3D. This opens a new device window and the model will be built up layer by layer. When complete, you can use your mouse to click and drag the object to look at at from all directions.

build_bricks(player_bricks)  # opens separate window

A gif of a 3D Lego model of an Arsenal soccer player built with the create_bricker_player function.

So the default set builds up to make a player that has red socks with white trim, white shorts, and a red shirt with white sleeves. An Arsenal player, of course.3

Show your support

To change the colour of the player’s shirt you just need to change all the bricks associated with the shirt. This could be tedious by hand, so create_brickr_player() has an argument to do exactly this. Set shirt_body_col to ‘6’ to make it bright blue, for example.

You can change more than the shirt colour. Here’s the current set of arguments:

  • shirt_body_col and shirt_sleeve_col
  • shorts_col
  • sock_col and sock_trim_col
  • boot_col
  • hair_col and skin_col

So you could create a Manchester City player with the following:

# Build player plan with certain colours
man_city <- create_brickr_player(
  hair_col = 6,           # Black
  skin_col = 34,          # Dark brown
  boot_col = 3,           # Bright red
  shirt_body_col = 30,    # Light royal blue
  shirt_sleeve_col = 30,  # Light royal blue
  shorts_col = 1,         # White
  sock_col = 16,          # Earth blue
  sock_trim_col = 16      # Earth blue
)

# Convert plan to list and render it
man_city %>%
  bricks_from_table() %>%
  display_bricks()

A gif of a 3D Lego model of a Manchester City soccer player built with the create_bricker_player function.

In fact, this is a faithful rendering of the 2019 Premier League winner, FA Cup winner, League Cup winner, PFA Team of the Year inductee, PFA Young Player of the Year and FWA Footballer of the Year Raheem Sterling. Obviously.

I’ve added a couple more to a GitHub Gist. Feel free to add more.

Extra-time

Hopefully this is useful for anyone who wants to create the same {brickr} model in multiple colours. I realise that might be a niche audience.

The obvious next step would be to allow for features of the plan to change. For example, you could set an argument for player_height and add or remove layers from the plan to make the final model taller or shorter. Or maybe different shirt types could be specified, like horizontal_stripe = TRUE.

Pull requests always welcome!

Environment

Session info
Last rendered: 2023-08-01 18:53:57 BST
R version 4.3.1 (2023-06-16)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Ventura 13.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.11.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/London
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] tibble_3.2.1     dplyr_1.1.2      brickr_0.3.5     kableExtra_1.3.4
[5] knitr_1.43.1    

loaded via a namespace (and not attached):
 [1] gtable_0.3.3      jsonlite_1.8.7    compiler_4.3.1    webshot_0.5.5    
 [5] tidyselect_1.2.0  xml2_1.3.5        stringr_1.5.0     tidyr_1.3.0      
 [9] systemfonts_1.0.4 scales_1.2.1      yaml_2.3.7        fastmap_1.1.1    
[13] ggplot2_3.4.2     R6_2.5.1          labeling_0.4.2    generics_0.1.3   
[17] htmlwidgets_1.6.2 munsell_0.5.0     svglite_2.1.1     pillar_1.9.0     
[21] rlang_1.1.1       utf8_1.2.3        stringi_1.7.12    xfun_0.39        
[25] viridisLite_0.4.2 cli_3.6.1         withr_2.5.0       magrittr_2.0.3   
[29] grid_4.3.1        digest_0.6.33     rvest_1.0.3       rstudioapi_0.15.0
[33] lifecycle_1.0.3   vctrs_0.6.3       evaluate_0.21     glue_1.6.2       
[37] farver_2.1.1      fansi_1.0.4       colorspace_2.1-0  rmarkdown_2.23   
[41] purrr_1.0.1       httr_1.4.6        tools_4.3.1       pkgconfig_2.0.3  
[45] htmltools_0.5.5  

Footnotes

  1. Not an official product (yet).↩︎

  2. The package was on CRAN when this post was originally written, but it was later removed.↩︎

  3. Why Arsenal? Mostly to demonstrate that sleeves can be a different colour to the shirt body, but also because they just got binned 4-1 by Chelsea in the Europa League final and I feel sorry for them.↩︎

Reuse

CC BY-NC-SA 4.0