2025年5月22日 星期四

12750211-week14

 1.字型練習:PFont + createFont + textSize + text

//week14_1_PFont_createFont_textSize_text
// 學習如何使用 PFont 設定文字字型與大小,順便列出所有可用字型

size(300, 300);

// 先印出一個預設字型的文字
textSize(50);  // 設定字體大小為 50
text("Ha Ha Ha", 10, 50);  // 預設字型

// 建立 Times New Roman 字型(50號)
PFont font = createFont("Times New Roman", 50); 
textFont(font);  // 換字型
text("原來只是印字", 10, 100);  // 再印一次文字,稍微往下移

// 列出所有電腦支援的字型(forEach 寫法)
for (String name : PFont.list()) {
  println(name);
}



2.多字型顯示:中英字型 + 注音字體 + 滑鼠位置追蹤

//week14_2_PFont_font1_font2_chinese_font_textFont_cursor
// 展示三種字型效果,包含 Times、微軟正黑體、精靈字體,配合滑鼠移動

PFont font1, font2, font3;
void setup() {
  size(350, 350);

  // 英文字型(有粗體+斜體)
  font1 = createFont("Times New Roman Bold Italic", 50);

  // 中文字型(微軟正黑體)
  font2 = createFont("微軟正黑體 Bold", 50);

  // 注音用的精靈字型,把字型檔拉進程式
  font3 = createFont("elffont-rock.otf", 50);
}

void draw() {
  background(0);  
  cursor(CROSS);  // 滑鼠游標改十字形
  fill(255);      // 白字

  textFont(font1);  // 英文 Times 字型
  text("NO Hello 中文", mouseX, mouseY - 20);  // 跟著滑鼠移動

  fill(#FF8E8E);    // 粉紅色
  textFont(font2);  // 微軟正黑體
  text("YES Hello 中文", mouseX, mouseY + 50);  // 底下再印一次

  textFont(font3);  // 精靈字體(支援注音)
  text("ㄓㄕㄌㄍ", mouseX + 20, mouseY + 100);  // 注音往右下印 正聖老哥
}




3.簡易 2D 機械臂關節控制:滑鼠拖曳改變角度

//week14_3_inverse_kinematics_part1
// 使用 pushMatrix/rotate 模擬關節轉動,未來可擴展為多節手臂

void setup() {
  size(400, 400);  // 使用 2D 模式
}

float[] angle = new float[5];  // 五個關節用的角度陣列(目前只用到 angle[0])

void draw() {
  background(255);            
  translate(200, 350);        // 把基底移到底部中央
  ellipse(0, 0, 12, 12);      // 畫個基底圓點當作固定端

  pushMatrix();               // 儲存目前座標狀態
    rotate(radians(angle[0]));  // 對 Z 軸旋轉 angle[0] 度(2D 模式下就是平面旋轉)
    rect(0, -5, 50, 10);        // 畫手臂(水平長條)
  popMatrix();                // 還原座標狀態
}

void mouseDragged() {
  // 滑鼠拖曳時,根據 X 軸移動改變第一節手臂角度
  angle[0] += mouseX - pmouseX;
}

4.機械手臂(二關節):滑鼠拖曳 + 鍵盤切換控制

//week14_4_inverse_kinematics_part2
// 使用 angle[] 陣列儲存各關節角度,pushMatrix 進行轉動與連接

float[] angle = new float[5];  // 預留五個關節角度(目前用到 0 和 1)
int ID = 0;  // 目前控制的關節編號

void setup() {
  size(400, 400);
}

void draw() {
  background(255);           
  translate(200, 350);       // 把起點移到底部中間
  ellipse(0, 0, 12, 12);     // 畫基座圓點

  pushMatrix();
    rotate(radians(angle[0]));   // 第一節轉動
    rect(0, -5, 50, 10);         // 畫第一節手臂

    pushMatrix();
      translate(50, 0);          // 移動到第一節末端
      rotate(radians(angle[1])); // 第二節轉動
      rect(0, -5, 50, 10);       // 畫第二節手臂
    popMatrix();
    
  popMatrix();
}

void mouseDragged() {
  // 拖曳滑鼠改變目前選擇的那一節角度(用滑鼠水平位移決定改多少)
  angle[ID] += mouseX - pmouseX;
}

void keyPressed() {
  // 按 0 或 1 切換控制第幾節
  if (key == '0') ID = 0;
  if (key == '1') ID = 1;
}

5.簡易頂點編號與滑鼠對應線段(進入 IK 反向運動學)

//week14_5_ik_inverse_kinematics_part3
// 使用 PVector 陣列儲存多個節點,畫線連接,並讓滑鼠連一條線出去

PVector[] p = new PVector[6];  // 最多 6 個頂點(目前用到前 2 個)

void setup() {
  size(400, 400);

  // 設定初始頂點座標:兩個點從下往上排,每隔 50 px
  for (int i = 0; i < 2; i++) {
    p[i] = new PVector(200, 350 - 50 * i);
  }
}

void draw() {
  background(255); 
  for (int i = 0; i < 2; i++) {
    // 如果不是第一個點,就畫線連前一個點
    if (i > 0) {
      line(p[i - 1].x, p[i - 1].y, p[i].x, p[i].y);
    }

    // 紅色小圓圈表示頂點位置
    fill(255, 0, 0);
    ellipse(p[i].x, p[i].y, 8, 8);

    // 黑字顯示編號
    fill(0);
    text("p:" + i, p[i].x + 10, p[i].y);
  }

  // 畫出滑鼠位置的小點(藍色)
  ellipse(mouseX, mouseY, 6, 6);
  
  // 從 p[0] 拉一條線到滑鼠(模擬手臂指向目標)
  line(p[0].x, p[0].y, mouseX, mouseY);
}




6.簡單反向運動學(IK)初步實作:單節向量靠近滑鼠,長度固定

//week14_6_ik_inverse_kinematics_part4
// 使用向量運算讓 p[1] 向滑鼠靠近,但手臂長度限制為 50(單一骨架)

PVector[] p = new PVector[6];  // 最多6個點,目前用 p[0], p[1]

void setup() {
  size(400, 400);

  // 初始兩個點往上排
  for (int i = 0; i < 2; i++) {
    p[i] = new PVector(200, 350 - 50 * i);
  }
}

void draw() {
  background(255);

  // 畫出骨架線與節點
  for (int i = 0; i < 2; i++) {
    if (i > 0) line(p[i - 1].x, p[i - 1].y, p[i].x, p[i].y);

    fill(255, 0, 0);  // 紅色節點
    ellipse(p[i].x, p[i].y, 8, 8);

    fill(0);
    text("p:" + i, p[i].x + 10, p[i].y);  // 顯示點的編號
  }

  // 以下是 IK 的運算邏輯:
  // 目標位置是滑鼠
  PVector now = new PVector(mouseX, mouseY);

  // 算出從 p[0] 指向滑鼠的單位向量,並放大到 50 的長度(手臂長度)
  PVector v = PVector.sub(now, p[0]).normalize().mult(50);

  // 設定 p[1] 的位置為從 p[0] 延伸出去 50 長度的點
  p[1].x = p[0].x + v.x;
  p[1].y = p[0].y + v.y;

  // 額外畫出滑鼠點與引導線
  ellipse(mouseX, mouseY, 6, 6);  // 滑鼠位置點
  line(p[0].x, p[0].y, mouseX, mouseY);  // 從 p[0] 指向滑鼠的虛線
}

7.多節手臂 (6 節) 練習:限制 p[5] 朝向滑鼠,固定長度

//week14_7_ik_inverse_kinematics_part5
// 模擬 IK:使用向量運算,讓最後一節(p[5])固定連到滑鼠方向但長度不變

PVector[] p = new PVector[6];  // 六個節點(p[0]~p[5])

void setup() {
  size(400, 400);

  // 初始化節點:每節往上堆,間距 50 像素
  for (int i = 0; i < 6; i++) {
    p[i] = new PVector(200, 350 - 50 * i);
  }
}

void draw() {
  background(255);

  // 畫節點 + 線段 + 座標文字
  for (int i = 0; i < 6; i++) {
    if (i > 0) line(p[i - 1].x, p[i - 1].y, p[i].x, p[i].y);  // 畫線
    fill(255, 0, 0);  // 紅色圓點
    ellipse(p[i].x, p[i].y, 8, 8);
    fill(0);
    text("p:" + i, p[i].x + 10, p[i].y);
  }

  // 滑鼠現在的位置(目標點)
  PVector now = new PVector(mouseX, mouseY);

  // 計算 p[5] 要指向滑鼠的方向,但距離固定為 50(與 p[4] 間距)
  PVector v = PVector.sub(now, p[4]).normalize().mult(50);

  // 設定 p[5] 的新位置:從 p[4] 出發,往滑鼠方向延伸 50
  p[5].x = p[4].x + v.x;
  p[5].y = p[4].y + v.y;

  // 額外視覺標記滑鼠
  ellipse(mouseX, mouseY, 6, 6);  // 小點:滑鼠位置
  line(p[4].x, p[4].y, mouseX, mouseY);  // 虛線:p[4] → 滑鼠
}

8.多節反向運動學(IK):滑鼠目標,多節向後反推

//week14_8_ik_inverse_kinematics_part6
// 從 p[5] 到 p[1],逐節反向計算位置,距離固定為 50,末端跟隨滑鼠

PVector[] p = new PVector[6];  // 六個節點(p[0]~p[5])

void setup() {
  size(400, 400);

  // 一開始每個點從底下往上排,間隔 50px
  for (int i = 0; i < 6; i++) {
    p[i] = new PVector(200, 350 - 50 * i);
  }
}

void draw() {
  background(255);

  // 畫每一節節點與連線
  for (int i = 0; i < 6; i++) {
    if (i > 0) line(p[i - 1].x, p[i - 1].y, p[i].x, p[i].y);  // 連線
    fill(255, 0, 0);  // 紅色節點
    ellipse(p[i].x, p[i].y, 8, 8);
    fill(0);  // 黑色文字
    text("p:" + i, p[i].x + 10, p[i].y);
  }

  // 設定滑鼠當作目標點
  PVector now = new PVector(mouseX, mouseY);

  // 反向迴圈:從末端 p[5] 開始,一節一節往前推回
  for (int i = 5; i > 0; i--) {
    PVector v = PVector.sub(now, p[i]);   // 現在點指向目標的向量
    v.normalize().mult(50);               // 單位向量拉長成手臂長度
    p[i].x = now.x - v.x;                 // 反推位置回來(尾往前)
    p[i].y = now.y - v.y;
    now = p[i];                           // 下一節以這節為目標
  }

  // 滑鼠目標點視覺提示
  ellipse(mouseX, mouseY, 6, 6);
}

9.多節 IK 完整實作

//week14_9_ik_inverse_kinematics_part7
// 用滑鼠當目標點,讓多節骨架實現逆向運動學的正反推理

PVector[] p = new PVector[6];  // 六個節點(p[0]~p[5])

void setup() {
  size(400, 400);
  // 初始化節點:從下往上排列
  for (int i = 0; i < 6; i++) {
    p[i] = new PVector(200, 350 - 50 * i);
  }
}

void draw() {
  background(255);

  // 畫骨架與節點
  for (int i = 0; i < 6; i++) {
    if (i > 0) line(p[i - 1].x, p[i - 1].y, p[i].x, p[i].y);  // 骨架線
    fill(255, 0, 0);
    ellipse(p[i].x, p[i].y, 8, 8);  // 節點
    fill(0);
    text("p:" + i, p[i].x + 10, p[i].y);  // 標記節點編號
  }

  // IK 運算
  
  // 1. 讓末端點跟隨滑鼠
  PVector now = new PVector(mouseX, mouseY);
  p[5].x = now.x;
  p[5].y = now.y;

  // 2. 從末端往回推(Backward)
  for (int i = 4; i >= 0; i--) {
    PVector v = PVector.sub(p[i + 1], p[i]).normalize().mult(50);  // 往回的單位向量 * 節距
    p[i].x = p[i + 1].x - v.x;
    p[i].y = p[i + 1].y - v.y;
  }

  // 3. 從基座往前推(Forward)
  p[0] = new PVector(200, 350);  // 固定根節點位置(p[0] 不動)
  for (int i = 1; i <= 5; i++) {
    PVector v = PVector.sub(p[i], p[i - 1]).normalize().mult(50);
    p[i].x = p[i - 1].x + v.x;
    p[i].y = p[i - 1].y + v.y;
  }

  // 滑鼠目標點視覺提示
  ellipse(mouseX, mouseY, 6, 6);
}

10.高節數 IK(20節骨架追滑鼠)

//week14_9b_ik_inverse_kinematics_part8
// 模擬多節柔性骨架,實作 IK 反向+前向調整,滑鼠控制末端

int N = 20; // 節點數
int L = 300 / N; // 每節長度(平均分配)
PVector[] p = new PVector[N];

void setup() {
  size(400, 400);

  // 初始化:每個節點往上疊
  for (int i = 0; i < N; i++) {
    p[i] = new PVector(200, 350 - L * i);
  }
}

void draw() {
  background(255);

  // 畫每個節點和連線
  for (int i = 0; i < N; i++) {
    if (i > 0) line(p[i - 1].x, p[i - 1].y, p[i].x, p[i].y);  // 骨架線
    fill(255, 0, 0);
    ellipse(p[i].x, p[i].y, 8, 8);  // 節點圓
    fill(0);
    text("p:" + i, p[i].x + 10, p[i].y);  // 編號
  }

  // 滑鼠位置變成 p[N-1](末端點)
  p[N - 1].x = mouseX;
  p[N - 1].y = mouseY;

  // 從末端往回推(Backward)
  for (int i = N - 2; i > 0; i--) {
    PVector v = PVector.sub(p[i + 1], p[i]).normalize().mult(L);
    p[i].x = p[i + 1].x - v.x;
    p[i].y = p[i + 1].y - v.y;
  }

  // 從根節往前推(Forward),固定 p[0]
  for (int i = 1; i < N; i++) {
    PVector v = PVector.sub(p[i], p[i - 1]).normalize().mult(L);
    p[i].x = p[i - 1].x + v.x;
    p[i].y = p[i - 1].y + v.y;
  }

  // 顯示滑鼠小點
  ellipse(mouseX, mouseY, 6, 6);
}


沒有留言:

張貼留言