A moving demo

Order of matrix operations in code

  • Blue Star: rotate then translate
    • ctx.translate(w/4, h/4)
      ctx.rotate(blueStarTheta * Math.PI/180) 
  • Magenta Star: translate then rotate
    • ctx.rotate(blueStarTheta * Math.PI/180)
      ctx.translate(w/4, h/4)
  • Red Star: translate then rotate, but retstricted theta
    • ctx.rotate(redStarTheta * Math.PI/180)
      ctx.translate(w/4, 0)
  • Green Star: scale then translate
    • ctx.translate(-w/3, -h/3)
      ctx.scale(starScale,starScale)
  • Cyan Star: translate then scale
    • ctx.scale(starScale,starScale)
      ctx.translate(10, -10)
  • The black star and greeting
    • ctx.rotate(wordRotate * Math.PI/180) 

The HTML file:

<head>
<script type="text/javascript" src="move.js"></script>
<script type="text/javascript" src="moveUI.js"></script>
<script type="text/javascript" src="setup.js" defer></script>
</head>

<body>
<h1> A moving demo</h1>

<table>
<tr><td>
<canvas id="canvas" width="400" height="400" style="border:1px solid #000000;"></canvas>
</td><td>
<h3>Order of matrix operations in code</h3>
<ul>
    <li> Blue Star: rotate then translate
    <ul>
       <li> <pre class="prettyprint">
ctx.translate(w/4, h/4)
ctx.rotate(blueStarTheta * Math.PI/180) </pre>
    </ul>
    <li> Magenta Star:  translate then rotate
    <ul>
       <li> <pre class="prettyprint">
ctx.rotate(blueStarTheta * Math.PI/180)
ctx.translate(w/4, h/4)</pre>
    </ul>
    <li> Red Star: translate then rotate, but retstricted theta
    <ul>
       <li> <pre class="prettyprint">
ctx.rotate(redStarTheta * Math.PI/180)
ctx.translate(w/4, 0)</pre>
    </ul>
    <li> Green Star: scale then translate
    <ul>
       <li> <pre class="prettyprint">
ctx.translate(-w/3, -h/3)
ctx.scale(starScale,starScale)</pre>
    </ul>
    <li> Cyan Star: translate then scale
    <ul>
       <li> <pre class="prettyprint">
ctx.scale(starScale,starScale)
ctx.translate(10, -10)</pre>
    </ul>
    <li> The black star and greeting 
    <ul>
       <li> <pre class="prettyprint">
ctx.rotate(wordRotate * Math.PI/180) </pre>
    </ul>
</ul>
</td></tr>
</table>
<p>

<ul>
   <li> wasd or hjkl to move
   <li> m,n to rotate
   <li> +,- to scale
   <li> r to reset
   <li> x to toggle animation
</ul>


</body>
</html>

setup.js

'use strict'

let canvas = document.getElementById("canvas")
let ctx = canvas.getContext('2d') 
let width = canvas.width
let height = canvas.height

canvas.tabIndex = 0
canvas.addEventListener("keydown", MyHandler)

let blueStarTheta
let redStarTheta
let starScale
let worldTx
let worldTy
let worldScale
let worldRotate
let wordRotate

function Reset() {
      blueStarTheta = 20
      redStarTheta = 20
      starScale = 10
      worldTx = 0
      worldTy = 0
      worldScale = 1
      worldRotate = 0
      wordRotate = 0
}

Reset()
StartTicks()

moveUI.js

"use strict"
let timer=null
let redStarDelta = 1
let starDelta = 1

function MyHandler(evnt){
    let key = evnt.key
    let stopMove = true

    switch(key) {
       case 'x':
          if (timer == null) {
             StartTicks()
          } else {
             StopTicks()
          }
          stopMove = false
          break
       case 'd':
       case 'l':
          worldTx ++
          break
       case 'a':
       case 'h':
          worldTx --
          break
       case 'w':
       case 'k':
          worldTy++
          break
       case 's':
       case 'j':
          worldTy--
          break
       case '+':
          worldScale+= 0.1
          break
       case '-':
          worldScale-= 0.1
          break
       case 'n':
          worldRotate++
          break
       case 'm':
          worldRotate--
          break
       case 'r':
          Reset()
          break
    }
    if (stopMove && timer==null ) {
       DrawScene()
    }
}

function Tick() {
    blueStarTheta = (blueStarTheta + 1) %360
    wordRotate = (wordRotate - 1) %360

    if (redStarTheta >= 160) {
       redStarDelta = -1
        
    }  else if (redStarTheta <= 20) {
       redStarDelta = 1
    }
    redStarTheta += redStarDelta
    
    if (starScale > 10) {
       starDelta = -1
    } else if (starScale < 2) {
       starDelta = 1
    }

    starScale += starDelta

    DrawScene()
}

function StartTicks() {
    timer = setInterval(Tick, 200)    
}

function StopTicks() {
    if (timer != null) {
       clearInterval(timer)
       timer = null
    }
}

move.js

"use strict"

function DrawScene() {
    ctx.clearRect(0, 0, width, height)
    ctx.save()

    Axis(ctx,width,height)

    // filp the canvas
    ctx.setTransform(1, 0, 0, -1, 0, height)
    // center the origin
    ctx.translate(width/2, height/2)

    // user movement
    ctx.scale(worldScale, worldScale)
    ctx.rotate(worldRotate*Math.PI/180)
    ctx.translate(worldTx, worldTy)

    Scene(ctx, width, height)

    ctx.restore()
}


// This is drawn on the "raw" canvas
// outside of any transformations
function Axis(ctx, w, h) {

    ctx.strokeStyle = "black"
    ctx.beginPath()

    ctx.moveTo(0, h/2)
    ctx.lineTo(w,h/2)

    ctx.moveTo(w/2,0)
    ctx.lineTo(w/2,h)
    ctx.stroke()
}

function Greeting(ctx,w,h){
    ctx.save()

    ctx.scale(1,-1)

    ctx.beginPath()

    ctx.font = "60pt sans"
    ctx.strokeStyle = "black"

    // remember y will move the "wrong" way because we have things flipped.
    ctx.translate(-110,20)
    ctx.strokeText("Hello", 0 , 0)

    ctx.stroke()
    
    ctx.restore()
}

function Star(ctx, color = "black", r = 10) {
   let i, x, y, t
   
   let b = Math.PI/2
   let a = 2*Math.PI/5

   let sides=[0,2,4,1,3]

   t = 0

   ctx.beginPath()

   ctx.strokeStyle = color

   // compute the right place to start and move to it.
   let sx = r*Math.cos(b)
   let sy = r*Math.sin(b)

   ctx.moveTo(sx,sy);

   for(i = 1; i<=5;i++) {
      let s = sides[i]
      x = r*Math.cos(s*a + b)
      y = r*Math.sin(s*a + b)

      ctx.lineTo(x,y)
   }

   // go back to the start
   ctx.lineTo(sx,sy)

   ctx.stroke()

   return
}

function BlueStar(ctx, w,h){

    ctx.save()

    ctx.translate(w/4, h/4)
    ctx.rotate(blueStarTheta * Math.PI/180)
    Star(ctx, "blue", 20)

    ctx.restore()
}

function MagentaStar(ctx, w,h){

    ctx.save()

    ctx.rotate(blueStarTheta * Math.PI/180)
    ctx.translate(w/4, h/4)
    Star(ctx, "Magenta", 20)

    ctx.restore()
}

function RedStar(ctx, w,h){

    ctx.save()

    ctx.rotate(redStarTheta * Math.PI/180)
    ctx.translate(w/4, 0)
    Star(ctx, "red", 20)

    ctx.restore()
}

function GreenStar(ctx, w,h){

    ctx.save()

    ctx.translate(-w/3, -h/3)
    ctx.scale(starScale,starScale)
    ctx.lineWidth = 1/starScale
    Star(ctx, "green", 3)

    ctx.restore()
}

function CyanStar(ctx, w,h){

    ctx.save()

    ctx.scale(starScale,starScale)
    ctx.translate(10, -10)
    ctx.lineWidth = 1/starScale
    Star(ctx, "cyan", 3)

    ctx.restore()
}


function Scene(ctx, w, h) {

    BlueStar(ctx,w,h)
    MagentaStar(ctx,w,h)

    RedStar(ctx,w,h)

    GreenStar(ctx,w,h)
    CyanStar(ctx,w,h)

    ctx.save()
    ctx.rotate(wordRotate * Math.PI/180)
    Star(ctx)
    Greeting(ctx,w,h)
    ctx.restore()
}