//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);
}
沒有留言:
張貼留言