 # 3D Projections

piecepackr has limited support to project 3D board games (like the hundreds of piecepack games) onto a 2D diagram. It is hoped that in the future piecepackr will offer better 3D projection support with the help of OpenGL.

## Orthographic projections

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

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

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

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.

## 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 projections within one diagram

## Pyramids

Currently piecepackr doesn't attempt to properly do an orthographic projection of laid down pyramids nor oblique projections of laid down and standing pyramids:

```g.p <- function(...) { grid.piece(..., default.units="in") }
g.p("tile_back", x=2, y=2)
g.p("pyramid_top", x=2, y=2, suit=3, rank=3)
g.p("pyramid_face", x=2, y=3, suit=3, rank=3)
g.p("pyramid_left", x=1, y=2, suit=3, rank=3, angle=90)
g.p("pyramid_back", x=2, y=1, suit=3, rank=3, angle=180)
g.p("pyramid_right", x=3, y=2, suit=3, rank=3, angle=-90)
```

Pyramids aren't too confusing in the orthographic projection

```g.p <- function(..., z=1/4) { grid.piece(..., z=z, op_scale=0.5, default.units="in") }
g.p("tile_back", x=2, y=2, z=1/8)
g.p("pyramid_top", x=2, y=2, suit=3, rank=3)
g.p("pyramid_face", x=2, y=3, suit=3, rank=3)
g.p("pyramid_left", x=1, y=2, suit=3, rank=3, angle=90)
g.p("pyramid_back", x=2, y=1, suit=3, rank=3, angle=180)
g.p("pyramid_right", x=3, y=2, suit=3, rank=3, angle=-90)
```

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