[Processing]立体文字で誕生日を祝いたい

#立体文字で「Happy Birthday」
とある日、友人の誕生日を祝おうと思いました

 -  Processingでアニメーションしよ
 -  キラキラしたいわ
 - カラフルさも欲しげ


#実行結果

![HappyBirthday3DString.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/554244/43559803-d7b2-6a29-3373-8895355f3932.gif)


























#時計ほしいわ
誕生日を祝うには誕生日である証拠がほしい
とりあえず時間を表示しよう

```時刻.java
void setup(){
  size(1000, 800, P3D);
  frameRate(30);
  textSize(30);
  colorMode(HSB, 1);//HSBモードに変更して徐々に色が変わるようにしたい
  textAlign(CENTER);//時刻を中央揃え
}
void draw() {
  //中央に移動
  translate(width/2, height/2);
  //現在時刻
  fill(1, 0, 1, 1);
  text(String.format("%d/%02d/%02d\n%02d:%02d:%02d\n", year(), month(), day(), hour(), minute(), second()), 0, 0);
}
```


#キラキラプログラミング



##☆が欲しい
のでまずは星を描こう
星の各頂点を1個飛ばしで繋いでいく星が描きたい↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/554244/fd371260-29fa-3fa7-0b0d-c24946e89235.png)


















##星を描くソースコード
以下のリンクを参考に「StarObj.pde」ファイルを作る
[class別にファイルを分ける] 
(https://www.d-improvement.jp/learning/processing/2011-b/10.html)

で、StarObj.pdeで星を描く
よってプログラムはこうなる

```StarObj.java
class Star {
  public PVector position;//位置
  public PVector velocity;//移動ベクトル
  public final PVector g = new PVector(0, 2);//重力
  public final float size;//大きさ
  private float r = 0;//現在の回転角
  private float rotating;//回転量
  private final PVector[] points = new PVector[5];//頂点
  
  public Star(float Size) {
    size = Size;
    int rad = 126;//始まりが上になるようにするおまじないだったかな126
    float r;
    for (int i=0; i<5; i++) {
      r = radians((rad += 144)%360);//144 = (360/5)*2; = 頂点の角度を1個飛ばしでつなぐ
      points[i] = new PVector(size * cos(r), size * sin(r));
    }
    reset();
  }
  
  public void reset(){
    r = 0;
    //回転量設定
    rotating = random(-0.3f, 0.3f);
  }

  public void update() {
    position.add(velocity.add(g));//重力をかける

    pushMatrix();
    translate(position.x, position.y);
    rotate(r+=rotating);
    strokeWeight(size/5);
    beginShape();
    for (int i=0; i<points.length; i++) {
      vertex(points[i].x, points[i].y);
    }
    endShape(CLOSE);
    popMatrix();
  }
}
```
`update()`メソッドを呼ぶことで星が描かれて、重力落下するようになってる

#理想のリストが欲しいじゃん？

##前から思ってたんです。使いにくいって。
星を表示するにあたって、リスト使いたいけど
if(繋ぎ変えをしょっちゅう行う && 各要素にアクセスする負荷が低い)
っていう条件を満たすリストがない~~（知らないだけかもしれない）~~

##だからリスト作った

```List.java
public class TList<T> {  //必要最低限のリスト
  private Node head;//先頭
  private Node tale;//最後尾（追加するノード）
  private Node now;//現在リストが参照しているノード
  private int size;//リストサイズ

  private class Node {
    Node next, prev;
    T obj;
    Node(T obj) {
      this.obj = obj;
      next = prev = null;
    }
  }

  public int size() { 
    return size;
  }

  //要素の追加
  public void add(T t) {
    Node n = new Node(t);
    size++;
    if (head == null) {
      now = head = tale = new Node(t);
    } else {
      tale.next = n;
      n.prev = tale;
      tale = n;
    }
  }

  //先頭から取り出し
  public void remove() {
    if (head == null || size == 0) return;
    head = head.next;
    if (head != null)head.prev = null;
    size--;
  }
  
  //nowをリストから削除
  public void removeNow() {
    if (head == null || now == null) return;
    if (now == head) {
      head = now.next;
    }
    if (now == tale) {
      tale = now.prev;
    }
    if (now.prev != null) {
      now.prev.next = now.next;
    }
    if (now.next != null) now.next.prev = now.prev;
    now = now.next;
    size--;
  }

  //nowを先頭に参照
  public T head2Now() {
    if (head == null) return null;
    return (now = head).obj;
  }

  //連続して呼ぶと次のノードを取得できる
  public T getNext() {
    if (now == null || now.next == null) return null;
    return (now = now.next).obj;//nowに次のノードを代入しそののノードのobjを返してる
  }
}
```
どうせ`removeNow()`しか使わないので他のリムーブは実装してません
このリストの肝は`now`変数を作ったことにあり、このおかげでLinkedList並みに負荷なく繋ぎ変えができて、ArrayList並みに負荷なく要素に順番にアクセスできます
後で全体を見せますが、for文で以下のように書けるのが利点ですね

```for文.java
//リスト内の星を動かす
  for (Star s = stars.head2Now(); s!=null; s = stars.getNext()) {//こういうfor文が書ける連結リストが欲しかったから作った
    s.update();
    if (s.position.x > width/2 + s.size || s.position.x < -s.size - width/2 || s.position.y > height/2 + s.size) {
      s.reset();//星の数値をリセット
      stars.removeNow();//従来ならエラーするfor文内での削除も行える
      starPool.add(s);
    }
  }
```

#立体文字
星を負荷なくキラキラさせることができたので
あとは立体文字を表示するだけです
各クラスで同じことを書くのは面倒なので継承元のクラスを作りほんのちょっと楽します~~（きれいになるとは言ってない）~~

##継承元

```Stringer.java
class Font {
  public float size;//サイズ比率
  public float leng;//奥行サイズ（length）
  public float[] heiNow, hei, wid;//現在の高さ,理想高さ,横幅
  public PVector pos;//全体位置
  public PVector[] partsPos;//各パーツの位置
  public color c;//色
  
  public Font(float s,PVector p,color c){
    size = s;
    pos = p;
    leng = size/5f;
    this.c = c;
  }
  
  public Font(float s, PVector p) {
    this(s,p,color(1,1,1,1));
  }
  
  public void setColor(color c){
    this.c = c;
  }
  
  //メインからこのメソッドを呼ぶ
  public void update() {
    pushMatrix();
    translate(pos.x, pos.y-size/2f, pos.z);
    this.Draw();
    popMatrix();
  }
  
  //このメソッドをオーバーライドして文字を描く
  protected void Draw() {
    for (int i=0; i<hei.length; i++) {
      if (heiNow[i] < hei[i]) heiNow[i]++;
      pushMatrix();
      translate(partsPos[i].x, partsPos[i].y + heiNow[i]/2, 0);
      box(wid[i], heiNow[i], leng);
      popMatrix();
    }
  }

  //文字を描いている真偽
  public boolean Drawing() {
    return heiNow[heiNow.length-1] < hei[hei.length-1];
  }
}
```
各文字はこのクラスを継承して使います
##立体文字H
例えば大文字のHを描くならこんな感じ

```BigH.java
class H extends Font {

  H(float s, PVector p,color c) {
    super(s, p,c);
    heiNow = new float[3];
    hei = new float[3];
    wid = new float[3];

    wid[0] = wid[2] = leng;
    wid[1] = size - (leng * 3);

    hei[0] = hei[2] = size;
    hei[1] = leng;

    partsPos = new PVector[3];
    partsPos[0] = new PVector(-size/4, 0);
    partsPos[1] = new PVector(0, size/2 - leng/2);
    partsPos[2] = new PVector(size/4, 0);
  }
  public H(float s,PVector p){this(s,p,color(1,1,1,1));}
}
```

ではこの調子で次は「a」
#円が必要ですね
棒だけじゃ「a」は作れないっすわ
作ろ
バウムクーヘン型
この時の[[Processing]PrcessingでProcessingを描くProgram](https://qiita.com/NekoCan/items/6cb747d79976536a14b0)
バウムクーヘン型を描くメソッドを軽量化したものを使います

```Circle.java
class Donut extends Font { //バウムクーヘン型を作るクラス
  int radian, rad = 0;
  PVector[] outside, inside;
  float rotateZ = 0;
  int up = 0;//表示速度
  //大きさ,位置,幅,最大角度
  Donut(float s, PVector p, float w, int maxRadian, float r, int up) {
    super(s, p);
    radian = maxRadian;
    this.up = up;
    rotateZ = radians(r);

    leng = w;

    outside = new PVector[maxRadian];
    inside = new PVector[maxRadian];
    float x, y, z = leng/2;
    for (int deg=0; deg<maxRadian; deg++) {
      float radi = radians(deg);
      x = sin(radi);
      y = cos(radi);
      outside[deg] = new PVector(x * size, y * size, z);
      inside[deg] =  new PVector(x * (size - leng), y * (size - leng), z);
    }
  }
  Donut(float s, PVector p, float w, int maxRadian, float r) {
    this(s, p, w, maxRadian, r, 1);
  }
  Donut(float s, PVector p, float w, int maxRadian, int up) {
    this(s, p, w, maxRadian, 0, up);
  }
  protected void Draw() {
    rotateZ(rotateZ);
    //外側カーブ
    beginShape(QUAD_STRIP);
    for (int deg = 0; deg < rad; deg += 1) {
      vertex(outside[deg].x, outside[deg].y, outside[deg].z);
      vertex(outside[deg].x, outside[deg].y, -outside[deg].z);
    }
    endShape();

    //内側カーブ
    beginShape(QUAD_STRIP);
    for (int deg = 0; deg < rad; deg += 1) {
      vertex(inside[deg].x, inside[deg].y, inside[deg].z);
      vertex(inside[deg].x, inside[deg].y, -inside[deg].z);
    }
    endShape();

    //バウムクーヘンの層が見える部分(手前)(奥)
    for (int i = -1; i<=1; i+=2) {
      beginShape(TRIANGLE_STRIP);
      for (int deg = 0; deg < rad; deg += 1) {
        vertex(outside[deg].x, outside[deg].y, outside[deg].z * i);
        vertex(inside[deg].x, inside[deg].y, inside[deg].z * i);
      }
      endShape();
    }

    //蓋はじめ
    beginShape();
    vertex(outside[0].x, outside[0].y, outside[0].z);
    vertex(outside[0].x, outside[0].y, -outside[0].z);
    vertex(inside[0].x, inside[0].y, -inside[0].z);
    vertex(inside[0].x, inside[0].y, inside[0].z);
    endShape();
    int radi = rad;
    if (radi >= radian) radi = radian-1;
    //蓋動くほう
    beginShape();
    vertex(outside[radi].x, outside[radi].y, outside[radi].z);
    vertex(outside[radi].x, outside[radi].y, -outside[radi].z);
    vertex(inside[radi].x, inside[radi].y, -inside[radi].z);
    vertex(inside[radi].x, inside[radi].y, inside[radi].z);
    endShape();


    if (Drawing())rad += up;
  }

  public boolean Drawing() {
    return rad < radian;
  }
}

```

#小文字のaを描く
`Draw()`をオーバーライドして描き方を変更し、`Drawing()`をオーバーライドして描いている条件を変更します

```SmallA.java
class A extends Font {
  Donut donut;
  A(float s, PVector p,color c) {
    super(s, p,c);
    
    donut = new Donut(s/4f, new PVector(-size/25, size - size/7.5f), s/6f, 270, -125, (int)2);
    donut.setColor(c);

    wid = new float[1];
    wid[0] = leng;

    hei = new float[1];
    hei[0] = size/2f;
    heiNow = new float[1];
    partsPos = new PVector[1];
    partsPos[0] = new PVector(hei[0]/4, hei[0]);
  }

  public A(float s,PVector p){this(s,p,color(1,1,1,1));}
  
  protected void Draw() {
    donut.update();
    if (heiNow[0] < hei[0]) heiNow[0]++;
    translate(partsPos[0].x, partsPos[0].y + heiNow[0]/2, partsPos[0].z);
    box(wid[0], heiNow[0], leng);
  }

  public boolean Drawing() {
    return donut.Drawing();
  }
}
```

こんな調子で1文字ずつプログラムを書いていきます☆彡
~~フォントの読み込みなんて知らない~~

#メインのコード
とりあえずメインでどんな風に動いているかだけ載せて、あとはダウンロードして詳細を見てください


```HappyBirthday_3DString.java
//class別にファイルを分ける 
//https://www.d-improvement.jp/learning/processing/2011-b/10.html

int month = 1, day = 1;//祝う誕生日

TList<Star> stars = new TList<Star>();//表示する☆彡
TList<Star> starPool = new TList<Star>();//プールする☆彡
TList<Font> str = new TList<Font>();//立体文字リスト
float[] r = new float[360];//回転角度をキャッシュ
int rotateNum = 0;//回転角度
boolean enable = false;//立体文字の表示非表示

void setup() {
  size(1000, 800, P3D);
  //fullScreen(P3D);
  frameRate(30);
  textSize(30);
  colorMode(HSB, 1);//HSBモードに変更して徐々に色が変わるようにしたい
  textAlign(CENTER);//時刻を中央揃え
  background(0, 0, 0, 1);
  randomSeed(millis());

  //各文字を生成
  float y = -200;
  str.add( new H(100, new PVector(-400, y)));
  str.add( new A(100, new PVector(-325, y)));
  str.add( new P(100, new PVector(-275, y)));
  str.add( new P(100, new PVector(-220, y)));
  str.add( new Y(100, new PVector(-150, y)));
  str.add( new B(100, new PVector(-50, y)));
  str.add( new I(100, new PVector(10, y)));
  str.add( new R(100, new PVector(30, y)));
  str.add( new T(100, new PVector(120, y)));
  str.add( new Hs(100, new PVector(180, y)));//hだけ大文字と小文字かぶった
  str.add( new D(100, new PVector(250, y)));
  str.add( new A(100, new PVector(310, y)));
  str.add( new Y(100, new PVector(380, y)));

  float colorNum = 1;
  colorNum = 0;
  float up = 1f/(float)str.size();//文字数で虹色感
  for (Font f = str.head2Now(); f != null; f = str.getNext(), colorNum+=up) {
    f.setColor(color(colorNum, 1, 1, 1));
    println("color"+colorNum);
  }

  //回転角度のキャッシュ
  for (int i=0; i<360; i++)r[i] = radians(i);
}

void draw() {
  //中央に移動
  translate(width/2, height/2);
  //ウィンドウのタイトルにフレームレートと☆彡の合計サイズを表示
  surface.setTitle("fps : " + (int)frameRate+" stars : "+(stars.size()+starPool.size()));
  lights();//影ほしい

  //黒で残像のこりの塗りつぶし
  fill(0, 0, 0, 0.1f);
  noStroke();
  pushMatrix();
  translate(0, 0, -500);
  rect(-width, -height, width * 2, height * 2);
  popMatrix();

  stroke(0.16f, 0.9f, 1, 1);
  fill(0, 0, 0, 0);
  if (mousePressed) {
    //プールにあればプールから、なければ☆彡生成
    Star s = starPool.size() > 0 ? starPool.head2Now() : new Star(random(5, 25));
    s.position = new PVector(mouseX-width/2, mouseY-height/2);
    s.velocity = new PVector(random(-10, 10), random(-30, -10));
    stars.add(s);
    starPool.remove();
  }
  
  //リスト内の星を動かす
  for (Star s = stars.head2Now(); s!=null; s = stars.getNext()) {//こういうfor文が書ける連結リストが欲しかったから作った
    s.update();
    if (s.position.x > width/2 + s.size || s.position.x < -s.size - width/2 || s.position.y > height/2 + s.size) {
      s.reset();//星の数値をリセット
      stars.removeNow();//従来ならエラーするfor文内での削除も行える
      starPool.add(s);//プールのリストに追加
    }
  }

  //立体文字と時刻の回転
  rotateY(r[rotateNum]);

  //立体文字表示
  noStroke();
  if (enable || (month()==month && day() == day)) {//日付になったら絶対表示
    Font f = str.head2Now(); //先頭文字の取得
    Font prev = f; //前文字を用意
    fill(f.c);
    f.update();//先頭文字を描く
    //前の文字が書き終えるまで次の文字は表示しない
    for (f = str.getNext(); f != null && !prev.Drawing(); prev = f, f = str.getNext()) {
      fill(f.c);
      f.update();
    }

    //全部の文字を表示したら回転しない（中途半端な角度なら回転し続ける）
    if (prev.Drawing() || rotateNum > 0)rotateNum = (rotateNum+1)%360;
  }

  //現在時刻
  fill(1, 0, 1, 1);
  text(String.format("%d/%02d/%02d\n%02d:%02d:%02d\n", year(), month(), day(), hour(), minute(), second()), 0, 0);
}

void keyPressed() {

  if (key == 'a')rotateNum = (rotateNum+1)%360;//手動回転
  else if (key == 'z')rotateNum = (rotateNum-1) < 0 ? 359 : rotateNum-1; //手動回転
  else if (key == 's') enable = !enable;//表示非表示切り替え
}
```

#ソースコード
~~一度は言ってみたかったセリフ~~
[GitHubにアップロードしました](https://github.com/NekoCan2000/HappyBirthday3DString)