Search code examples
rplotrotational-matrices

Make right hand turns


I have a problem where I have a bunch of lengths and want to start at the origin (pretend I'm facing to the positive end of the y axis), I make a right and move positively along the x axis for the distance of length_i. At this time I make another right turn, walk the distance of length_i and repeat n times. I can do this but I think there's a more efficient way to do it and I lack a math background:

## Fake Data
set.seed(11)
dat <- data.frame(id = LETTERS[1:6], lens=sample(2:9, 6), 
    x1=NA, y1=NA, x2=NA, y2=NA)

##   id lens x1 y1 x2 y2
## 1  A    4 NA NA NA NA
## 2  B    2 NA NA NA NA
## 3  C    5 NA NA NA NA
## 4  D    8 NA NA NA NA
## 5  E    6 NA NA NA NA
## 6  F    9 NA NA NA NA

## Add a cycle of 4 column    
dat[, "cycle"] <- rep(1:4, ceiling(nrow(dat)/4))[1:nrow(dat)]

##For loop to use the information from cycle column
for(i in 1:nrow(dat)) {

    ## set x1, y1
    if (i == 1) {
       dat[1, c("x1", "y1")] <- 0
    } else {
       dat[i, c("x1", "y1")] <- dat[(i - 1), c("x2", "y2")]
    }

    col1 <- ifelse(dat[i, "cycle"] %% 2 == 0, "x1", "y1")
    col2 <- ifelse(dat[i, "cycle"] %% 2 == 0, "x2", "y2")
    dat[i, col2] <- dat[i, col1]

    col3 <- ifelse(dat[i, "cycle"] %% 2 != 0, "x2", "y2")
    col4 <- ifelse(dat[i, "cycle"] %% 2 != 0, "x1", "y1")
    mag <- ifelse(dat[i, "cycle"] %in% c(1, 4), 1, -1)
    dat[i, col3] <- dat[i, col4] + (dat[i, "lens"] * mag)

}

This gives the desired result:

> dat

  id lens x1 y1 x2 y2 cycle
1  A    4  0  0  4  0     1
2  B    2  4  0  4 -2     2
3  C    5  4 -2 -1 -2     3
4  D    8 -1 -2 -1  6     4
5  E    6 -1  6  5  6     1
6  F    9  5  6  5 -3     2

Here it is as a plot:

library(ggplot2); library(grid)
ggplot(dat, aes(x = x1, y = y1, xend = x2, yend = y2)) + 
    geom_segment(aes(color=id), size=3, arrow = arrow(length = unit(0.5, "cm"))) + 
    ylim(c(-10, 10)) + xlim(c(-10, 10))

This seems slow and clunky. I'm guessing there's a better way to do this than the items I do in the for loop. What's a more efficient way to keep making programatic rights?

enter image description here


Solution

  • (As suggested by @DWin) Here is a solution using complex numbers, which is flexible to any kind of turn, not just 90 degrees (-pi/2 radians) right angles. Everything is vectorized:

    set.seed(11)
    dat <- data.frame(id = LETTERS[1:6], lens = sample(2:9, 6),
                                         turn = -pi/2)
    
    dat <- within(dat, { facing   <- pi/2 + cumsum(turn)
                         move     <- lens * exp(1i * facing)
                         position <- cumsum(move)
                         x2       <- Re(position)
                         y2       <- Im(position)
                         x1       <- c(0, head(x2, -1))
                         y1       <- c(0, head(y2, -1))
                       })
    
    dat[c("id", "lens", "x1", "y1", "x2", "y2")]
    #   id lens x1 y1 x2 y2
    # 1  A    4  0  0  4  0
    # 2  B    2  4  0  4 -2
    # 3  C    5  4 -2 -1 -2
    # 4  D    8 -1 -2 -1  6
    # 5  E    6 -1  6  5  6
    # 6  F    9  5  6  5 -3
    

    The turn variable should really be considered as an input together with lens. Right now all turns are -pi/2 radians but you can set each one of them to whatever you want. All other variables are outputs.


    Now having a little fun with it:

    trace.path <- function(lens, turn) {
      facing   <- pi/2 + cumsum(turn)
      move     <- lens * exp(1i * facing)
      position <- cumsum(move)
      x        <- c(0, Re(position))
      y        <- c(0, Im(position))
    
      plot.new()
      plot.window(range(x), range(y))
      lines(x, y)
    }
    
    trace.path(lens = seq(0, 1,  length.out = 200),
               turn = rep(pi/2 * (-1 + 1/200), 200))
    

    enter image description here

    (My attempt at replicating the graph here: http://en.wikipedia.org/wiki/Turtle_graphics)

    I also let you try these:

    trace.path(lens = seq(1, 10, length.out = 1000),
               turn = rep(2 * pi / 10, 1000))
    
    trace.path(lens = seq(0, 1,  length.out = 500),
               turn = seq(0, pi, length.out = 500))
    
    trace.path(lens = seq(0, 1,  length.out = 600) * c(1, -1),
               turn = seq(0, 8*pi, length.out = 600) * seq(-1, 1, length.out = 200))
    

    Feel free to add yours!