0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

p5.jsで曲線の内側と外側を判定する

Last updated at Posted at 2025-03-28

はじめに

 線をつないでループする曲線を作る。それの内側と外側を判定する。

 isInside

function setup() {
  const points = [];
  let lastPoint = createVector();
  let isDrawing = false;
  let isFinished = false;
  const randomPoints = [];
  for(let i=0; i<200; i++){
    randomPoints.push(createVector(random(400), random(400)));
  }
  
  createCanvas(400, 400);
  mousePressed = () => {
    lastPoint.set(mouseX, mouseY);
    points.push(lastPoint.copy());
    isDrawing = true;
  }
  mouseReleased = () => {
    isDrawing = false;
    if(dist(lastPoint.x, lastPoint.y, points[0].x, points[0].y) > 8){
      points.length = 0;
    }else{
      points.push(points[0].copy());
      isFinished = true;
    }
  }
  
  draw = () => {
    background(0);
    if(isDrawing && mouseIsPressed){
      if(dist(mouseX, mouseY, lastPoint.x, lastPoint.y) > 8){
        lastPoint.set(mouseX, mouseY);
        points.push(lastPoint.copy());
      }
    }
    stroke(255);
    for(let i=0; i<points.length-1; i++){
      line(points[i].x, points[i].y, points[i+1].x, points[i+1].y);
    }
    noStroke();
    if(!isFinished){
      fill("blue");
      for(const p of randomPoints){ circle(p.x, p.y, 5); }
    }else{
      for(const p of randomPoints){
        if(isInside(p, points)){fill(255);}else{fill("blue");}
        circle(p.x, p.y, 5);
      }
      noLoop();
    }
  }
}

function isInside(p, points){
  let angleSum = 0;
  for(let k=0; k<points.length-1; k++){
    const q = points[k].copy().sub(p);
    const r = points[k+1].copy().sub(p);
    angleSum += q.angleBetween(r);
  }
  angleSum = Math.abs(angleSum);
  if(Math.round(angleSum/TAU) % 2 === 1){
    return true;
  }
  return false;
}

実行結果

wf3wr3r3r33333.png

wwegege.png

このように、マウスで曲線というかまあ折れ線、を作った時に、それらが囲む点だけ色が付く。曲線の内側と外側を判定している。内側の点だけ白くなるような仕組み。なお自己交叉する場合はevenoddルールに従って内外判定するものとする(2枚目)。

コードの概要

 あらかじめ200個の点がランダムに配置されている。マウスプレスで線を引き始める。マウスリリース時に最初の点と離れている場合はやり直し、近い場合、曲線が成立し、内側と判定された点の色が白に変わる。それでコードの再生も終わる。

メインコード

 内外判定しているのはここである。

function isInside(p, points){
  let angleSum = 0;
  for(let k=0; k<points.length-1; k++){
    const q = points[k].copy().sub(p);
    const r = points[k+1].copy().sub(p);
    angleSum += q.angleBetween(r);
  }
  angleSum = Math.abs(angleSum);
  if(Math.round(angleSum/TAU) % 2 === 1){
    return true;
  }
  return false;
}

pointsは点列で、頭とおしりは同じ点である。$p$が調べる点である。pointsの連続する点について、$p$からその点に向かうベクトルを用意する。その際の角度の変化をangleBetweenで計算する。これは向きを考慮して符号付きの値を返すので累積を計算すると点の周りを曲線がぐるっと回っているかがわかる。具体的にはTAUの奇数倍の時に内側である。偶数倍の時に外側となる(0の場合も含めて)。ただし先ほども述べたように自己交叉がある場合はevenoddに従う。

おわりに

 ここまでお読みいただいてありがとうございました。

追記:

 スマホだとmousePressedが機能しないようです。おかしいな...1.9.1以降ですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?