p5.jsのwebglでp5.Geometryを使ってフレンチクルーラーのような形状を作る

# はじめに
　フレンチクルーラーとは次のようなドーナツです。いいことあるよ！ミスタードーナツ！
　[クルーラー](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AB%E3%83%BC%E3%83%A9%E3%83%BC)
ドーナツの生地をねじるように円を描きながら回して作るお菓子です。こういうのをwebglのp5.Geometryを使って構成します。

## コード全文
　ミスド最近行ってない（ポン・デ・ディグダも食べ損ねた...忙しかったからね。仕方ないね）。
　🍩🍩🍩
　[cruller](https://openprocessing.org/sketch/2247463)
```js
function setup() {
  createCanvas(600, 600, WEBGL);
  pixelDensity(1);
  createRotoid();
  noStroke();

  camera(400,400,300,0,0,0,0,0,-1);
  perspective(PI/3,width/height,40,4000);
}

function draw() {
  background(220);

  fill("gold");
  orbitControl();

  const front = this._renderer._curCamera.cameraMatrix.createSubMatrix3x3().row(2);

  ambientLight(128);
  directionalLight(255, 255, 255, -front.x, -front.y, -front.z);
  specularMaterial(10);

  this._renderer.drawBuffers("myGeom");
}

// XZ.
const starCircle = (t, phase) => createVector(
  200 + (60+14*cos(TAU*5*t + phase)) * sin(TAU*t), (60+14*cos(TAU*5*t + phase)) * cos(TAU*t)
);

function createRotoid(){
  const geom = new p5.Geometry();
  const detailTheta = 40;
  const detailPhi = 120;

  colorMode(HSB, 1,1,1);

  for(let k=0; k<detailPhi; k++){
    for(let t=0; t<=detailTheta; t++){
      const v = starCircle(t/detailTheta, k*16*TAU/detailPhi);
      const z = v.y;
      const x = v.x * cos(TAU*k/detailPhi);
      const y = v.x * sin(TAU*k/detailPhi);
      geom.vertices.push(createVector(x,y,z));
      const col = color(0.1 + 0.025*cos(TAU*k/detailPhi), 1, 0.75+0.25*cos(TAU*t/detailTheta));
      geom.vertexColors.push(
        red(col)/255, green(col)/255, blue(col)/255, 1
      );
    }
  }

  colorMode(RGB, 255);

  for(let k=0; k<detailPhi-1; k++){
    for(let t=0; t<detailTheta; t++){
      const lu = (k+1)*(detailTheta+1)+t;
      const ld = (k+1)*(detailTheta+1)+t+1;
      const ru = k*(detailTheta+1)+t;
      const rd = k*(detailTheta+1)+t+1;
      geom.faces.push([lu,ru,rd],[lu,rd,ld]);
    }
  }
  for(let t=0; t<detailTheta; t++){
    const lu = t;
    const ld = t+1;
    const ru = (detailPhi-1)*(detailTheta+1)+t;
    const rd = (detailPhi-1)*(detailTheta+1)+t+1;
    geom.faces.push([lu,ru,rd],[lu,rd,ld]);
  }
  geom.computeNormals();
  this._renderer.createBuffers("myGeom", geom);
}
```
実行結果：

![donut.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437349/16c081c3-99f9-78bb-5ee9-69a48f66dfb9.png)

わぁおいしそうですね！

## コードの説明
　頂点を用意しているのはここです。
```js
  const geom = new p5.Geometry();
  const detailTheta = 40;
  const detailPhi = 120;

  colorMode(HSB, 1,1,1);

  for(let k=0; k<detailPhi; k++){
    for(let t=0; t<=detailTheta; t++){
      const v = starCircle(t/detailTheta, k*16*TAU/detailPhi);
      const z = v.y;
      const x = v.x * cos(TAU*k/detailPhi);
      const y = v.x * sin(TAU*k/detailPhi);
      geom.vertices.push(createVector(x,y,z));
      const col = color(0.1 + 0.025*cos(TAU*k/detailPhi), 1, 0.75+0.25*cos(TAU*t/detailTheta));
      geom.vertexColors.push(
        red(col)/255, green(col)/255, blue(col)/255, 1
      );
    }
  }

  colorMode(RGB, 255);
```
補助関数のstarCircle:
```js:starCircle
// XZ.
const starCircle = (t, phase) => createVector(
  200 + (60+14*cos(TAU*5*t + phase)) * sin(TAU*t), (60+14*cos(TAU*5*t + phase)) * cos(TAU*t)
);
```
　まずちょっとした実験をします。定義式のここを1にすると通常のトーラスになります。

![fwww3fgg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437349/ea63602e-4be9-7bc9-9267-e7b0cffb5c79.png)

starCircleの「5」は断面の星形を作るためのものです。そしてphaseとは、この星形を回転させるためのものです。一周する間に何回転するかを定めるのが16です。そういうわけで、これらの5と16により、ホイップの先っちょが星形で、それをねじりながらぐるっと出したような感じになる、感じです。多分そんな感じだと思います。

　色についてはp5のcolorModeが便利なのでcolorMode(HSB)でMAXを1にしてそれを使っていますね。そうして得られた色からRGB関数で値を採取して0～1にしてぶちこんでいます。

　以上です。

# おわりに
　ずいぶん前に（めちゃくちゃ前に）書いたコードなのでどういう仕様なのか忘れてしまいました。暇があったら解読してみてください。

　次の記事も参考になると思います。
　[【p5.js】p5.Geometryであのドーナツをつくる](https://qiita.com/_sayo_y/items/78b7f4dbae267d562e4b)
アトリビュートについても簡単に説明してあります。このくらいの理解で充分かと思います。
　ここまでお読みいただいてありがとうございました。