1
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?

2D subdiv

Last updated at Posted at 2025-12-15

image.png

subdivision surface再分割曲面といえば、Cat-mull Clark

その2次元版

極限はB-Spline曲線

CatmullClarkSubdiv2D.pde
PVector[] pts0, pts1, pts2, pts3;

void setup() {
  size(600, 600);
  pixelDensity(1);

  // 入力:閉ループ
  pts0 = new PVector[] {
    new PVector(-1, -1),
    new PVector( 1, -1),
    new PVector( 3, -1),
    new PVector( 3,  1),
    new PVector( 3,  3),
    new PVector( 1,  3),
    new PVector( 1,  1),
    new PVector(-1,  1),
    new PVector(-1,  3),
    new PVector(-3,  3),
    new PVector(-3,  1),
    new PVector(-3, -1),
    new PVector(-5, -1),
    new PVector(-5, -3),
    new PVector(-3, -3),
    new PVector(-1, -3),
  };
  for (PVector p : pts0) p.mult(50);
  
  pts1 = ccBoundarySubdiv(pts0);//println(pts1);
  pts2 = ccBoundarySubdiv(pts1);
  pts3 = ccBoundarySubdiv(pts2);

  noLoop();
}

void draw() {
  background(255);
  translate(width/2, height/2);

  noFill();
  stroke(0);
  drawPoly(pts3);
  stroke(128);
  drawPoly(pts0);
}

void drawPoly(PVector[] pts) {
  beginShape();
  for (PVector p : pts) vertex(p.x, p.y);
  endShape(CLOSE);
}

// ---- Catmull–Clark Subdivision ----
PVector[] ccBoundarySubdiv(PVector[] pts) {
  int n = pts.length;
  PVector[] out = new PVector[n * 2];

  for (int i = 0; i < n; i++) {
    PVector prev = pts[(i - 1 + n) % n];
    PVector curr = pts[i];
    PVector next = pts[(i + 1) % n];

    // V' = (prev + 6*curr + next) / 8
    PVector vPrime = new PVector();
    vPrime.add(prev);
    vPrime.add(PVector.mult(curr, 6));
    vPrime.add(next);
    vPrime.mult(1.0f / 8.0f);

    // E = (curr + next) / 2
    PVector edgeMid = PVector.add(curr, next);
    edgeMid.mult(0.5f);

    out[2 * i]     = vPrime;
    out[2 * i + 1] = edgeMid;
  }
  return out;
}

image.png

image.png

CatmullClarkSubdiv2D_ex
// --- modeling polygon (base control polygon) ---
ArrayList<PVector> poly = new ArrayList<PVector>();
ArrayList<ArrayList<PVector>> undoStack = new ArrayList<ArrayList<PVector>>();

// --- selection ---
int hoverEdge = -1;
float pickTol = 12; // px

void setup() {
  size(800, 800, P2D);
  pixelDensity(1);
  resetPoly();
}

void draw() {
  background(255);
  translate(width/2, height/2);

  // hover edge detect (in local coords)
  hoverEdge = findNearestEdge(poly, mouseX - width/2, mouseY - height/2, pickTol);

  // subdiv 3 times for display
  PVector[] pts0 = poly.toArray(new PVector[0]);
  PVector[] pts1 = ccBoundarySubdiv(pts0);
  PVector[] pts2 = ccBoundarySubdiv(pts1);
  PVector[] pts3 = ccBoundarySubdiv(pts2);

  // draw smooth (subdiv) curve
  noFill();
  stroke(0);
  strokeWeight(2);
  drawPoly(pts3);

  // draw base polygon
  stroke(160);
  strokeWeight(2);
  drawPoly(pts0);

  // draw hover edge highlight
  if (hoverEdge != -1) {
    int i = hoverEdge;
    int j = (i + 1) % poly.size();
    PVector a = poly.get(i), b = poly.get(j);
    stroke(255, 0, 0);
    strokeWeight(5);
    line(a.x, a.y, b.x, b.y);
  }

  // UI text
  resetMatrix();
  fill(0);
  text("Click an edge to extrude outward by its length.  r: reset  z: undo", 14, 22);
}

// --- mouse / key controls ---
void mousePressed() {
  float mx = mouseX - width/2;
  float my = mouseY - height/2;
  int ei = findNearestEdge(poly, mx, my, pickTol);
  if (ei == -1) return;

  pushUndo();

  extrudeEdge(poly, ei);

}

void keyPressed() {
  if (key == 'r') resetPoly();
  if (key == 'z') undo();
}

// --- initialize ---
void resetPoly() {
  poly.clear();
  undoStack.clear();

  float s = 50;
  poly.add(new PVector(-s, -s));
  poly.add(new PVector( s, -s));
  poly.add(new PVector( s,  s));
  poly.add(new PVector(-s,  s));
}

// --- undo helpers ---
void pushUndo() {
  ArrayList<PVector> snap = new ArrayList<PVector>();
  for (PVector p : poly) snap.add(p.copy());
  undoStack.add(snap);
  if (undoStack.size() > 50) undoStack.remove(0);
}
void undo() {
  if (undoStack.size() == 0) return;
  poly = undoStack.remove(undoStack.size() - 1);
}

// =========================================================
//  Edge extrusion (2D polygon)
//  Replace edge (vi -> vj) by: vi, vi+off, vj+off, vj
//  off direction = outward normal * edgeLength
// =========================================================
void extrudeEdge(ArrayList<PVector> P, int i) {
  int n = P.size();
  int j = (i + 1) % n;

  PVector vi = P.get(i);
  PVector vj = P.get(j);

  PVector d = PVector.sub(vj, vi);
  float L = d.mag();
  if (L < 1e-6) return;

  // polygon orientation (CCW -> signed area > 0)
  float area = signedArea(P);
  boolean ccw = area > 0;

  // outward normal:
  // edge direction d = (dx,dy)
  // right normal = (dy, -dx), left normal = (-dy, dx)
  PVector nrm = ccw ? new PVector(d.y, -d.x) : new PVector(-d.y, d.x);
  nrm.normalize();

  // offset amount = edge length
  PVector off = PVector.mult(nrm, L);

  PVector vi2 = PVector.add(vi, off);
  PVector vj2 = PVector.add(vj, off);

  // insert between i and j:
  // P: ... vi, [vi2, vj2], vj ...
  // Note: j is i+1 mod n. In ArrayList insertion, handle wrap
  if (j == 0) {
    // edge is last->first, append before first => easiest: add at end
    P.add(vi2);
    P.add(vj2);
  } else {
    P.add(j, vj2);
    P.add(j, vi2);
  }
}

// =========================================================
//  Edge picking: nearest segment to mouse
// =========================================================
int findNearestEdge(ArrayList<PVector> P, float mx, float my, float tol) {
  int n = P.size();
  if (n < 2) return -1;

  float bestD = tol;
  int bestI = -1;

  PVector m = new PVector(mx, my);
  for (int i = 0; i < n; i++) {
    PVector a = P.get(i);
    PVector b = P.get((i + 1) % n);
    float d = distPointToSegment(m, a, b);
    if (d < bestD) {
      bestD = d;
      bestI = i;
    }
  }
  return bestI;
}

float distPointToSegment(PVector p, PVector a, PVector b) {
  PVector ab = PVector.sub(b, a);
  float ab2 = ab.magSq();
  if (ab2 < 1e-9) return PVector.dist(p, a);

  float t = PVector.sub(p, a).dot(ab) / ab2;
  t = constrain(t, 0, 1);
  PVector q = PVector.add(a, PVector.mult(ab, t));
  return PVector.dist(p, q);
}

float signedArea(ArrayList<PVector> P) {
  double s = 0;
  int n = P.size();
  for (int i = 0; i < n; i++) {
    PVector a = P.get(i);
    PVector b = P.get((i + 1) % n);
    s += (double)a.x * (double)b.y - (double)b.x * (double)a.y;
  }
  return (float)(0.5 * s);
}

// =========================================================
//  Drawing + boundary Catmull–Clark subdivision
// =========================================================
void drawPoly(PVector[] pts) {
  beginShape();
  for (PVector p : pts) vertex(p.x, p.y);
  endShape(CLOSE);
}

// boundary CC: out = [V0', E01, V1', E12, ...]
PVector[] ccBoundarySubdiv(PVector[] pts) {
  int n = pts.length;
  PVector[] out = new PVector[n * 2];

  for (int i = 0; i < n; i++) {
    PVector prev = pts[(i - 1 + n) % n];
    PVector curr = pts[i];
    PVector next = pts[(i + 1) % n];

    // V' = (prev + 6*curr + next) / 8
    PVector vPrime = new PVector();
    vPrime.add(prev);
    vPrime.add(PVector.mult(curr, 6));
    vPrime.add(next);
    vPrime.mult(1.0f / 8.0f);

    // E = (curr + next) / 2
    PVector edgeMid = PVector.add(curr, next);
    edgeMid.mult(0.5f);

    out[2 * i]     = vPrime;
    out[2 * i + 1] = edgeMid;
  }
  return out;
}
1
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
1
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?