 # 3D Projections

piecepackr has some basic support to project 3D board games (like the hundreds of piecepack games) onto a 2D diagram. It can make diagrams with an orthographic or oblique projection using R's grid graphics library. Beginning with piecepackr 1.3 it can also make diagrams using the rgl or rayrender 3D graphics packages.

## Orthographic projections (grid)

By default piecepackr's grid.piece function makes an approximate "top view" orthographic projection with pieces drawn later "placed on top" of (and potentially hiding) pieces drawn earlier:

```library("piecepackr")
g.p <- function(...) { grid.piece(..., default.units="in") }
g.p("tile_back", x=0.5+c(3,1,3,1), y=0.5+c(3,3,1,1))
g.p("tile_back", x=0.5+3, y=0.5+1)
g.p("tile_back", x=0.5+3, y=0.5+1)
g.p("die_face", suit=3, rank=5, x=1, y=1)
g.p("pawn_face", x=1, y=4, angle=90)
g.p("coin_back", x=3, y=4, angle=180)
g.p("coin_back", suit=4, x=3, y=4, angle=180)
g.p("coin_back", suit=2, x=3, y=1, angle=90)
```

'Top view' orthographic projection of a piecepack game

piecepackr makes an exact "top view" orthographic projection under the following conditions:

1. All pieces are placed "flat" parallel to the game table. Although most piecepack games do this there are exceptions like San Andreas where some tiles may shift from flat to "leaning" (and vice versa) during the course of a game.
2. The game doesn't use pyramids (although the "top" view of pyramids is an okay if not exact orthographic projection).
3. Any pawns used are (generalized) "meeple" pawns laid face down or face up.

## Oblique projections (grid)

With a little more effort by the piecepackr programmer one can also make oblique projections with grid.piece which makes it much easier to tell when pieces have been placed on top of other pieces:

```g.p <- function(...) { grid.piece(..., op_scale=0.5, op_angle=45, default.units="in") }

g.p("tile_back", x=0.5+c(3,1,3,1), y=0.5+c(3,3,1,1))
g.p("tile_back", x=0.5+3, y=0.5+1, z=1/4+1/8)
g.p("tile_back", x=0.5+3, y=0.5+1, z=2/4+1/8)
g.p("die_face", suit=3, rank=5, x=1, y=1, z=1/4+1/4)
g.p("pawn_face", x=1, y=4, z=1/4+1/8, angle=90)
g.p("coin_back", x=3, y=4, z=1/4+1/16, angle=180)
g.p("coin_back", suit=4, x=3, y=4, z=1/4+1/8+1/16, angle=180)
g.p("coin_back", suit=2, x=3, y=1, z=3/4+1/8, angle=90)
```

Oblique projection of a piecepack game

op_scale and op_angle are the arguments that control the appearance of the oblique projection. op_scale determines how much to scale the length of the piece's edge along op_angle. An op_scale of 0.5 is commonly used in the cabinet projection, an op_scale of 1.0 is used in the cavalier projection, and an op_scale of 0.0 gives you the default "top view" orthographic projection. op_angle controls what angle the edges the pieces "go up" - it defaults to 45 degrees.

Depending on your preferences you may want to change up your pawns look and/or the color of your piece edges:

```cfg <- pp_cfg(list(width.pawn=0.75, height.pawn=0.75, depth.pawn=1,
dm_text.pawn="", shape.pawn="convex6", invert_colors.pawn=TRUE,
edge_color.coin="tan", edge_color.tile="tan",
border_lex=2, border_color="black"))
g.p <- function(...) { grid.piece(..., op_scale=0.5, op_angle=45,
cfg=cfg, default.units="in") }
g.p("tile_back", x=0.5+c(3,1,3,1), y=0.5+c(3,3,1,1))
g.p("tile_back", x=0.5+3, y=0.5+1, z=1/4+1/8)
g.p("tile_back", x=0.5+3, y=0.5+1, z=2/4+1/8)
g.p("die_face", suit=3, rank=5, x=1, y=1, z=1/4+1/4)
g.p("pawn_face", x=1, y=4, z=1/4+1/2, angle=90)
g.p("coin_back", x=3, y=4, z=1/4+1/16, angle=180)
g.p("coin_back", suit=4, x=3, y=4, z=1/4+1/8+1/16, angle=180)
g.p("coin_back", suit=2, x=3, y=1, z=3/4+1/8, angle=90)
```

One can configure the appearance of the pawns and piece edges

piecepackr can make an exact oblique projection under the same conditions it needs to do an exact orthographic projection (i.e. pieces all laid flat, no pyramids, pawns must be (genalized) meeple pawns laid face up or down) although it won't attempt to properly draw any visible "side" die faces.

### 'op_transform' helper function

To directly make oblique projection graphics with grid.piece the programmer needs to figure out what height (the z argument) the various pieces are in relation to table and carefully arrange the grid.piece calls so the pieces on top and/or in front are drawn later. An easier way to make oblique projection graphics is to use pmap_piece with the helper function op_transform as pmap_piece's trans argument. op_transform uses heuristics to make educated guesses about the height the game pieces are at and what order they should be drawn in:

```df <- tibble::tibble(piece_side="tile_back",
x=c(2,2,2,4,6,6,4,2,5),
y=c(4,4,4,4,4,2,2,2,3))
pmap_piece(df, cfg=cfg, default.units="in",
op_angle=135, op_scale=0.5, trans=op_transform)
```

Using 'op_transform' with 'pmap_piece' to make educated oblique projection guesses

op_transform currently uses the following heuristic to guess which game pieces overlap:

1. We "bound" each piece's shape with either a circle or a convex polygon (if the piece's shape is exactly a circle or a convex polygon this bound is exact).
2. If two pieces' bounding circle/polygon overlap then we guess that the pieces overlap .
3. Piece "A"'s "z" value is estimated to be qual to the "z" value of the highest "overlapping" piece beneath "A" (let's call it "B") plus half of "B"'s depth and half of "A"'s depth. If there are no overlapping pieces beneath "A" its "z" value is just half of "A"'s depth.

op_transform currently uses the following heurist to sort the order of drawing game pieces:

1. Generalize each "bounding box" from the previous section into a "bounding cube" whose top is equal to the top of the game piece.
2. Piece's whose "bounding cube" top is higher (on "z" axis) are drawn later, for any tie those whose "bounding cube" top ("y" axis) is higher or lower (depending on the "op_angle") is drawn later, and finally for any remaining tie those whose "bounding cube" is more left or right (depending on the "op_angle") is drawn later .
  For circles and convex polygons any overlap can inferred via an application of the Separating Axis Theorem.
  In certain scenarios this may give the wrong ordering. An example may be a really tall pawn next to a stack of tiles that altogether are shorter than the pawn. For the common case of a single layer of tiles plus a single layer of pieces on top of the tiles this is very unlikely to have a false indentification. If there is a false identification one can sometimes change the dimensions of the pieces or the "op_angle" value to get a "correct" ordering. In an extreme case one can use tiles, coins, pawns, and dice all 1/2" tall which should always render the correct ordering (assuming their "z" values were correctly estimated).

### Mixed Projections

Technically since each grid.piece function can have its own op_scale and op_angle arguments one can mix and match projections in a single diagram:

```cfg <- pp_cfg(list(edge_color.tile="black", border_lex=3))
draw_3tiles <- function(x, y, op_angle) {
grid.piece("tile_back", x=x, y=y, z=(1:3)/4-1/8,
op_scale=0.5, op_angle=op_angle, cfg=cfg, default.units="in")
}
draw_3tiles(2, 5, 180)
draw_3tiles(8, 5, 0)
draw_3tiles(5, 2, -90)
draw_3tiles(5, 8, 90)
draw_3tiles(8, 8, 45)
draw_3tiles(2, 8, 135)
draw_3tiles(2, 2, 225)
draw_3tiles(8, 2, -45)
```

One can technically mix oblique projections within one diagram

## 3D graphics packages: rgl and rayrender

piecepackr 1.3 introduced basic support for the rgl and rayrender 3D graphics libraries.

### rgl (piece3d)

piece3d draws pieces using the rgl graphic system.

```library("ppgames", warn.conflicts = FALSE)
library("rgl")

invisible(rgl::open3d())
rgl::view3d(phi=-30, zoom = 0.8)

df <- ppgames::df_four_field_kono()
envir <- game_systems("dejavu3d")
pmap_piece(df, piece3d, trans=op_transform, envir = envir, scale = 0.98, res = 150)
``` 3D render with the rgl package

### rayrender (piece)

piece creates rayrender objects.

```library("rayrender")

df <- ppgames::df_four_field_kono()
envir <- piecepackr::game_systems("dejavu3d")
l <- pmap_piece(df, piece, trans=op_transform, envir = envir, scale = 0.98, res = 150)
render_scene(scene, lookat = c(2.5, 2.5, 0), lookfrom = c(0, -2, 13))
```

3D render with the rayrender package

## Pyramids

Currently piecepackr doesn't attempt to do a completely correct orthographic projection for laid down pyramids nor oblique projections of laid down and standing pyramids:

```library("tibble")
df <- tibble(piece_side = c("tile_back", "pyramid_top", "pyramid_face", "pyramid_left", "pyramid_back", "pyramid_right"),
x = c(2, 2, 2, 1, 2, 3),
y = c(2, 2, 3, 2, 1, 2),
suit = 3, rank = 3, angle = c(0, 0, 0, 90, 180, -90))
pmap_piece(df, grid.piece, default.units="in")
```

Pyramids aren't too confusing in the orthographic projection

```pmap_piece(df, grid.piece, default.units="in", op_scale = 0.5, trans = op_transform)
```

But pyramids sometimes don't look very good in the oblique projection

For best results one should render with a 3D graphics system like rgl or rayrender:

```library("rayrender")
l <- pmap_piece(df, piece, trans=op_transform, scale = 0.98, res = 150)
render_scene(scene, lookat = c(2.5, 2.5, 0), lookfrom = c(0, -2, 10))
```

Pyramids are best rendered with a 3D graphics system like rayrender