【Javascript】線の点・太さ(lineWidth)から、輪郭の座標を算出する

TOC

  1. なぜやりたいのか
  2. やってみる
    1. 始点・終点
    2. 途中の点
    3. 回転移動させる
  3. 考えた別の方法
    1. 線を直線の式に変えて、連立方程式で交点を出す
  4. 今後やりたいこと
    1. lineCap、lineJoin、miterLimit を実装する

こんにちはー。更新が遅れてしまいすみません。

あけましておめでとうございます。今年もよろしくお願いします。

今回は、線の点・太さから、輪郭の座標をとる方法を紹介します。
HTML5 Canvas の stroke() メソッドを、SVG などを使わず、自分で実装する感じです。

なお、自分で考えて手探りでやってみた方法なので、もっと効率のいい方法があるかもしれません。

なぜやりたいのか

matter.js というライブラリがあり、
それを使って、マウスで書いた線を Body に変換したかったのですが、
線を Body に直接することができなかったので、作ろうと思いました。

Constant とか使えばできそうですが、面白そうなのでやってみます。

やってみる

始点・終点

始点は1つ後の点から現在の点、終点は現在の点から1つ前の点を引いて、
逆三角関数でラジアンにし、それに垂直な方向になるように輪郭線を打ちます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var lineWidth = 5;
var points = [
{ x: 30, y: 50 },
{ x: 120, y: 150 }
];
var outline1 = [];
var outline2 = [];

points.forEach((point, index) => {
if (index === 0) {
// 始点
// Math.PI / 2 は degree で 90°
var rad = Math.atan2(points[index + 1].y - point.y, points[index + 1].x - point.x) - Math.PI / 2;
var sin = Math.sin(rad) * lineWidth;
var cos = Math.cos(rad) * lineWidth;
outline1.push({ x: point.x + cos, y: point.y + sin });
outline2.push({ x: point.x - cos, y: point.y - sin });
} else if (index === points.length - 1) {
// 終点
var rad = Math.atan2(point.y - points[index - 1].y, point.x - points[index - 1].x) - Math.PI / 2;
var sin = Math.sin(rad) * lineWidth;
var cos = Math.cos(rad) * lineWidth;
outline1.push({ x: point.x + cos, y: point.y + sin });
outline2.push({ x: point.x - cos, y: point.y - sin });
}
});

See the Pen Drawing outlines of a line by shundroid (@shundroid) on CodePen.

途中の点

いやー、これが難しかったです・・。
方法としては、輪郭線は角の二等分線上で交わるので、
そこで、sinθ = lineWidth となるときの cos をとってくる感じです。

求めた後、回転移動させるのですが、まずは移動させなくてもいいパターンでやってみます。

※省略部分は↑のコードと変わらない部分です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var lineWidth = 5;
var points = [
{ x: 200, y: 100 },
{ x: 100, y: 100 },
{ x: 150, y: 50 }
];

// 省略

points.forEach((point, index) => {
if (index === 0) {
// 省略
} else if (index === points.lenght - 1) {
// 省略
} else {
var rad1 = Math.atan2(points[index - 1].y - point.y, points[index - 1].x - point.x);
var rad2 = Math.atan2(points[index + 1].y - point.y, points[index + 1].x - point.x);
var rad = (rad2 - rad1) / 2;
var x = Math.cos(rad) * lineWidth / Math.sin(rad);
var y = lineWidth;
outline1.push({ x: point.x + x, y: point.y + y });
outline2.push({ x: point.x - x, y: point.y - y });
}
});

See the Pen Drawing outlines of a line 2 by shundroid (@shundroid) on CodePen.

回転移動させる

これでできたっぽいですが、これは入ってくる線の角度が 0° だったときのみ動きます。
それ以外で動かすようにするには、いったん入ってくる線を基準にして求め、それを回転移動させる必要があります。

回転移動については下のサイトが詳しいです。参考にしてみてください。
http://www.geisya.or.jp/~mwm48961/kou2/linear_image3.html

rad1 を基準にしたので、 rad1 の分だけ回転させればいいのです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var lineWidth = 5;
var points = [
{ x: 200, y: 100 },
{ x: 100, y: 100 },
{ x: 150, y: 50 }
];

// 省略

points.forEach((point, index) => {
if (index === 0) {
// 省略
} else if (index === points.lenght - 1) {
// 省略
} else {
var rad1 = Math.atan2(points[index - 1].y - point.y, points[index - 1].x - point.x);
var rad2 = Math.atan2(points[index + 1].y - point.y, points[index + 1].x - point.x);
var rad = (rad2 - rad1) / 2;
var x = Math.cos(rad) * lineWidth / Math.sin(rad);
var y = lineWidth;

var rx = x * Math.cos(rad1) - y * Math.sin(rad1);
var ry = x * Math.sin(rad1) + y * Math.cos(rad1);
outline1.push({ x: point.x + rx, y: point.y + ry });
outline2.push({ x: point.x - rx, y: point.y - ry });
}
});

See the Pen Drawing outlines of a line 3 by shundroid (@shundroid) on CodePen.

これでできました!間の点はいくつあってもできます。

See the Pen Drawing outlines of a line 4 by shundroid (@shundroid) on CodePen.

random にやっているので、急カーブになった場合、輪郭がちょうとんがります。
これは↓でもある、miterLimit を早く実装して直したいです。

考えた別の方法

線を直線の式に変えて、連立方程式で交点を出す

中学2年生で習うことでできそうですが、
どの線とどの線が交わるのかを見つけるのが超難しそうだったのでやめました。

今後やりたいこと

lineCap、lineJoin、miterLimit を実装する

Canvas にある、これらのプロパティも実装してみたいです。
miterLimit は今がんばっています。