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

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

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

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

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

なぜやりたいのか

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

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

やってみる

始点・終点

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

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.0

途中の点

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

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

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

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

回転移動させる

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

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

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

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.0

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

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

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

考えた別の方法

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

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

今後やりたいこと

lineCap、lineJoin、miterLimit を実装する

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

【JS】Canvasで丸や四角で「消す」方法

こんにちはー。
題名のとおり、Canvas の「消す」機能で、丸などに形を変える方法を紹介します。

「消す」機能について

まずはじめに、消す方法としてのルールを書いておきます。

  • 白く塗る はダメ!(背景が白でない場合があるため)
  • 消した部分は透明になるようにする。

実装のポイント

context の globalCompositeOperation を、destination-out に変更すると、
fill や stroke で指定した領域を 切り取る ことができます。

また、このプロパティは、その他いろいろな値に変更できます。

サンプル:円形で消す

globalCompositeOperationを変更した状態で、arc メソッドを呼ぶことでできます。

var ctx = document.getElementById("xxx").getContext("2d");
function eraseArc() {
  ctx.globalCompositeOperation = "destination-out";
  ctx.beginPath();
  ctx.arc(100, 100, 50, 0, Math.PI*2, false);
  ctx.fill();
}

arc() の引数

arc(中心x, 中心y, 半径, 開始角度(ラジアン), 終了角度(ラジアン), どっち回りか(true→反時計回り))

応用例

See the Pen Canvas Erase By Arc by shundroid (@shundroid) on CodePen.dark

  • チェックボックス ON:描く、OFF:消す(globalCompositeOperation で切り替えています)
  • 背景色を変更で、消した部分が透明になっていることを確認できます

掲載したサンプルコードでは、ctx.arc で描画していますが、
このサンプルは mousemove で動いた時に描いているので、arc で描くと円が散らばってしまい、うまく描けません。
そのため、moveTo と、lineTo を使って描いています。

この方法については、次回ブログで書きたいと思います。

「消す」モードを戻したい

このままでは、常に stroke や fill した部分が切り取られてしまうため、
元の値に戻しておきましょう。
デフォルトの値は、source-over です。

var ctx = document.getElementById("xxx").getContext("2d");
ctx.globalCompositeOperation = "source-over";

js の正規表現で、「完全一致」でマッチする

こんにちはー。

機会があって、
正規表現で「完全一致」のテストの方法が知りたくなりました。
今日はその方法を紹介します。

どういうことがしたいのか

/abcde/.test("abcde"); // true
/abcde/.test("abcdef"); // これも true

このような場合だと、
1行目のようなテストする文字列がパターンと同じ場合、 true になりますが、
含んでいる場合(2行目)も true になってしまいます。
これを、2行目が false になって、1行目だけ true になるようにしたいんです。

完全一致したいとき

https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions

次のようにすればできました!

/^パターン$/

^が先頭を表し、$が末尾を表します。
だから、それにパターンを挟む形でやれば「完全一致」でマッチできます。

例:

/^下$/.test("下"); // true
/^下$/.test("下町"); // false
/^下$/.test("上下"); // false

メリットは・・?

なら、「if ("下" === "下") {}」でよくない?
と思うかもしれません。
たしかに上の例ではそうですが、
正規表現で比較すると、様々なメリットを得ることもできます。

「完全一致」の or 検索

例えば、次のように書いたとしましょう。

var reg = /^(下|上)$/;

こうすれば、or でのテストもできますよ~

使用例:

/^(下|上)$/.test("下"); // true
/^(下|上)$/.test("上"); // true
/^(下|上)$/.test("上下"); // false
/^(下|上)$/.test("上野"); // false

その他、いろいろな「特殊文字」が使える

完全一致と言っていいのかわかりませんが、
? や、. などの特殊文字も使えます。

どれか1文字にマッチする

. を使います。

/^A.C$/.test("ABC"); // true
/^A.C$/.test("AC"); // false

※ 改行文字にはマッチしません。
  改行文字にマッチするには、\n や、\s(スペースなども含む)を使用します。

直前の文字の1回以上の繰り返しにマッチする

+ を使います。

/^AA+B$/.test("AAB"); //true
/^AA+B$/.test("AAAAAAAAAAAAAAAAB"); // true
/^AA+B$/.test("AB"); //false

※繰り返さない場合(0回以上の繰り返し)にもマッチするときは、* を使います。

その他の特殊文字の意味と使い方は、MDN の記事をご覧ください。

感想

学んでいて、
正規表現って、実用性が高いんだな☆と気づきました。

voidを使えば、アロー関数 で{}が省ける。

こんにちはー。

今、void を使えばアロー関数で{}がいらないんじゃないか説が、
ふと頭のなかを通り過ぎて行きましたので紹介いたしますー。

「void 演算子で、アロー関数の {} がいらない気がする」ということです。

普通に {} はずせばいいのでは?

まず、通常のアロー関数の{}なしの使い方です。
普通の関数がどんどん短くなっていく過程をご覧ください。

// function 式
var a = function (a) { return a + 2; };

// アロー関数にそのまましてみた
var a = (a) => { return a + 2; };

// 引数一つだから () いらない。
var a = a => { return a + 2; };

// 中身 1行で return で返されているから {} いらない。
var a = a => a + 2;

こんな感じですかね。
では、次のような例だとどうでしょう。

var num = 0;

// function 式
var b = function () { num++; }

// アロー関数にそのまましてみる
var b = () => { num++; }

// () はずしたいけどはずせない。
// {} はずしたいけど、戻り値ないから・・

そうです。戻り値が void のとき(あ、言っちゃった☆)です。
もし、このまま {} をはずせば、戻り値として num が帰ってきてしまい、
意図した結果にはなりません。
 
 
 
 
 
さあ、どうしても {} が外したい・・(←なんで?)
どうしよう・・・
 
 
 
 
 
そんなときは、void の出番です。
void を使えば、{} を外すことができます。

// void があらわれた!!
var b = () => void num++;

それはなぜだ?

void ってなに?

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/void

void は、式を実行するけれど、undefined を返すという演算子なのです。
知っていたけれど、使い方はあまり良くわかっていませんでした。
今まで知っていた使い方は、こんな感じです。

if (a === void(0)) {} // undefined か判定する
if (a === void 0) {} // こちらでも良い
// でもこれは・・
if (typeof a === "undefined") {} // これで良かった。

// + 下の「ちなみに」(SyntaxErrorだな・・)

また、<a href="javascript:void(0)">hoge</a> として、
リンクは反応するけれどどこにも行かない、みたいな使い方もできるそうです。

まとめ

実用性があるかどうかはわかりませんが、
ちょっとした発見だったので、投稿しました。

まあ、void で 4 文字、{} で 2 文字で、ちょっと負けていますが、
使いたい人がいればどうぞ!!

ちなみに

void を知ったきっかけについて

最初 js 書いていた時、
C# の癖で、function のことずっと void って書いていたからです。

なぜこんなに {} が外したいのか

最近、できるだけ短くコードを書くのにハマっているからです。
「行末に ; つけると、1行で書けるよー」・・・はい。