1

I am working on a simple spaceship 2d game and i was thinking to create nice looking 3d illusion effect by utilising transform api. By experimenting i ended up adjusting a (Horizontal scaling) and d (Vertical scaling).

So by default (no transform) we can use ctx.transform(1, 0, 0, 1, 0, 0);

My current setting is this one ctx.transform(1.72, 0, 0, 0.65, 0, 0);

With this setting ship looks decent but only under some angles. I was thinking if there is a way to use proper math to apply formula for dynamically adjusting these values (probably based on angle ship is rotated under). Unfortunately my math knowledge is pretty poor so i am asking more experienced members for help figuring it out.

Below is the small prototype (by clicking radio buttons you can toggle between default and my hardcoded values):

var mode = "3d";
var toggle = function(ev, item) {
  mode = item.value;
};

var tools = new function() {
  this.rad = a => (Math.PI / 180) * a;
  this.deg = rad => (rad * 180) / Math.PI;
  this.distance = p =>
    Math.sqrt((p.x - p.dx) * (p.x - p.dx) + (p.y - p.dy) * (p.y - p.dy));
  this.rftv = p => Math.atan2(p.dy - p.y, p.dx - p.x);
  this.pfa = function(l, x, y, a) {
    return {
      x: Math.cos(this.rad(a)) * l + x,
      y: Math.sin(this.rad(a)) * l + y
    };
  };
}();

let design = {
  c: [
    { size: 0.7166666666666667, deg: -90 },
    { size: 0.5060742150229658, deg: -107.24145939893998 },
    { size: 0.42196629670573876, deg: -99.09027692082233 },
    { size: 0.08975274678557507, deg: -158.19859051364818 },
    { size: 0.08975274678557507, deg: -21.80140948635181 },
    { size: 0.42196629670573876, deg: -80.90972307917767 },
    { size: 0.5060742150229658, deg: -72.75854060106003 }
  ],
  l1: [
    { size: 0.4552166761249221, deg: -113.74949449286676 },
    { size: 0.3901566636906542, deg: -109.98310652189998 },
    { size: 0.18408935028645435, deg: -174.8055710922652 },
    { size: 0.6324555320336759, deg: 161.565051177078 }
  ],
  r1: [
    { size: 0.3901566636906542, deg: -70.01689347810003 },
    { size: 0.4552166761249221, deg: -66.25050550713325 },
    { size: 0.6324555320336759, deg: 18.43494882292201 },
    { size: 0.18408935028645435, deg: -5.194428907734806 }
  ],
  l2: [
    { size: 0.2608745973749754, deg: 153.434948822922 },
    { size: 0.6262764742685312, deg: 154.79887635452494 },
    { size: 0.6616477747093069, deg: 130.91438322002512 }
  ],
  r2: [
    { size: 0.2608745973749754, deg: 26.56505117707799 },
    { size: 0.6262764742685312, deg: 25.20112364547507 },
    { size: 0.6616477747093069, deg: 49.08561677997487 }
  ]
};

let circle = (x, y, r, fs, ss) => {
  ctx.save();
  ctx.beginPath();
  ctx.arc(x, y, r, 0, Math.PI * 2);
  if (fs !== false) {
    ctx.fillStyle = fs;
    ctx.fill();
  }
  if (ss !== false) {
    ctx.lineWidth = 1;
    ctx.strokeStyle = ss;
    ctx.stroke();
  }
  ctx.restore();
};

var transform = function(zAxis, tilt, scale, x, y) {
 var cs = Math.cos(zAxis), sn = Math.sin(zAxis);
 var h = Math.cos(tilt);
 var a = scale*cs, b = -scale*sn, c = x;
 var d = h*scale*sn, e = h*scale*cs, f = y;
  return { a, d, b, e, c, f };
};

let ship = (x, y, size, a, fs) => {
  ctx.save();
  ctx.beginPath();

  ctx.translate(x, y);
  if (mode === "2d") {
    ctx.transform(1, 0, 0, 1, 0, 0);
  }
  if (mode === "3d") {
    var { a, d, b, e, c, f } = transform(tools.rad(a), 45, 1, x, y);
    ctx.setTransform(a, d, b, e, c, f);
  }
  ctx.translate(-x, -y);

  for (let type in design) {
    for (let i = 0; i < design[type].length; i++) {
      let c = design[type][i],
        p = tools.pfa(size * c.size, x, y, c.deg + a + 90);
      if (i === 0) {
        ctx.moveTo(p.x, p.y);
      } else {
        ctx.lineTo(p.x, p.y);
      }
    }
    if (design[type].length > 0) {
      let c = design[type][0],
        p = tools.pfa(size * c.size, x, y, c.deg + a + 90);
      ctx.lineTo(p.x, p.y);
    }
  }

  ctx.fillStyle = fs;
  ctx.fill();
  ctx.restore();

  circle(x, y, size, false, "blue");
};

let cvs = document.createElement("canvas"),
  ctx = cvs.getContext("2d"),
  w = (cvs.width = 400),
  h = (cvs.height = 400),
  cx = w / 2,
  cy = h / 2;

let points = [
  { x: cx - 40, y: 3 },
  { x: cx + 40, y: h - 3 },
  { x: 3, y: cy + 40 },
  { x: w - 3, y: cy - 40 }
];

let shipData = {
  x: cx,
  y: cy,
  r: 40,
  a: 0,
  c: 0,
  dx: points[0].x,
  dy: points[0].y,
  run: function() {
    let d = tools.distance(this);
    if (d < 1) {
      this.c += 1;
      if (this.c > points.length - 1) {
        this.c = 0;
      }
      this.dx = points[this.c].x;
      this.dy = points[this.c].y;
    }
    let rad = tools.rftv(this);
    this.a = tools.deg(rad);
    this.x += Math.cos(rad);
    this.y += Math.sin(rad);
  }
};

let render = () => {
  ctx.clearRect(0, 0, w, h);
  ctx.fillStyle = "#ccc";
  ctx.fillRect(0, 0, w, h);

  /* debug */ circle(points[0].x, points[0].y, 3, "red", false);
  /* debug */ circle(points[1].x, points[1].y, 3, "red", false);
  /* debug */ circle(points[2].x, points[2].y, 3, "red", false);
  /* debug */ circle(points[3].x, points[3].y, 3, "red", false);

  ship(shipData.x, shipData.y, shipData.r, shipData.a, "blue");
  shipData.run();

  requestAnimationFrame(render);
};

document.body.appendChild(cvs);
render();
<div>
 2d <input type="radio" onclick="toggle(event, this);" name="display" value="2d">
 3d <input type="radio" onclick="toggle(event, this);" name="display" value="3d" checked>
</div>

---edit

I made an edit based on this topic where in the answer someone presented formulas for calculation matrix, but with my settings it doesnt seem to work.

I implemented it this way:

var transform = function(angle1, angle2, size1, size2) {
  var cs = Math.cos(angle1), sn = Math.sin(angle1);
  var h = Math.cos(angle2);
  var a = size1*cs, b = -size1*sn, c = size2;
  var d = h*size1*sn, e = h*size1*cs, f = size2;
  return { a, d, b, e, c, f };
};

if (mode === "3d") {
    var { a, d, b, e, c, f } = transform(a, a, size * 0.5, size);
    ctx.setTransform(a, d, b, e, c, f);
}

---edit 2

With the help of comments, i assumed following:

var transform = function(zAxis, tilt, scale, x, y) {
    var cs = Math.cos(zAxis), sn = Math.sin(zAxis);
    var h = Math.cos(tilt);
    var a = scale*cs, b = -scale*sn, c = x;
    var d = h*scale*sn, e = h*scale*cs, f = y;
    return { a, d, b, e, c, f };
};

if(mode === '3d') {
    var {a, d, b, e, c, f} = transform(tools.rad(a), 45, 1, x, y);
    ctx.setTransform(a, d, b, e, c, f);
}

So i imagine the scale 1 should be ok, and since my x, y are the center of ship so i can use them. Last piece of the puzzle would be the second parameter the global tilt, i did set it to be 45 deg and it looks pretty decent, but i dont know if its 100% correct, i updated the code in the snippet for someone to take a look.

Mevia
  • 1,517
  • 1
  • 16
  • 51

1 Answers1

1

You can easily render ismoetric (non-perspective) graphics using canvas and the same transformations can also be used with CSS. The matrix coefficients depend on the tilt angle (normally fixed in an isometric game) and the rotation around the Z (height) axis.

For full formulas you can see this answer: https://stackoverflow.com/a/5186153/320726

6502
  • 112,025
  • 15
  • 165
  • 265
  • hmmm i tried to apply formula found under that topic link (those that calculate `a, d, b, e, c, f` and apply `ctx.setTransform(a, d, b, e, c, f);` but it didnt work for my code. first of all, there are 2 angle variables `angle1, angle2` and 2 set of sizes `100, 200` i dont understand yet how it applies to my settings. I could use help with example of usage with my code. I edited my question code to include that formulas. – Mevia Mar 10 '19 at 09:31
  • 1
    @Mevia: 100 is the scale, 200 (`c`, `f`) is the position of the center and can be of course different for X and Y; `angle1` is the rotation on the Z axis, `angle2` is the global tilt angle (when `angle2` is 0 the view is 2d). – 6502 Mar 10 '19 at 09:53
  • i updated the question status with latest changes, could you take a look please? – Mevia Mar 10 '19 at 10:56
  • For the `tilt` parameter there is no "exact" value... you should use whatever you looks better for you. Anything between 30deg and 45deg should be ok. – 6502 Mar 10 '19 at 12:29