2025年5月1日 星期四

12750211-week11

1.郵差動畫關節控制與儲存重播系統

//week11-1-postman-saveString-loadString
//頭 身體 手臂 手轴 腳
//改自week10_6_postman_many_angle_ID_saveStrings_loadStrings

// 載入郵差角色的各個部位圖片
PImage postman, head, body, hand1, uparm1, hand2, uparm2;
float [] angle = new float[20];// 準備 20 個關節角度變數
int ID = 0;// 現在正在操作的關節編號(用來配合 angle 陣列)
void mouseDragged() {// 滑鼠拖曳時,根據 X 軸移動距離來調整當前選取的關節角度
  angle[ID] += mouseX - pmouseX;
}

void keyPressed() {
  // 指定當前操作的關節 ID
  if(key == '1') ID = 1; // 左邊上臂
  if(key == '2') ID = 2; // 左邊手
  if(key == '3') ID = 3; // 右邊上臂
  if(key == '4') ID = 4; // 右邊手
  if(key == '5') ID = 5; // 保留用
  if(key == '6') ID = 6; // 保留用
  if(key == '0') ID = 0; // 頭部

  // 按下 s 鍵:儲存當前所有角度為一組資料,寫入 angles.txt
  if(key == 's') {
    String now = ""; // 儲存目前 20 個角度的字串
    for(int i = 0; i < 20; i++) {
      now += angle[i] + " "; // 把每個角度串接起來
    }
    lines.add(now); // 加入到 lines 陣列中
    String[] arr = new String[lines.size()];
    lines.toArray(arr); // 轉為字串陣列
    saveStrings("angles.txt", arr); // 寫入檔案
  }

  // 按下 r 鍵:重播從檔案讀入的角度資料
  if(key == 'r') {
    if(R == 0) {
      // 第一次重播時,先從檔案讀取所有儲存的角度資料
      String[] file = loadStrings("angles.txt");
      if(file != null) {
        for(int i = 0; i < file.length; i++) {
          lines.add(file[i]);
        }
      }
    }
    // 依序將角度值套用到 angle 陣列中,進行動畫重播
    if(R < lines.size()) {
      float[] now = float(split(lines.get(R), ' ')); 
      for(int i = 0; i < 20; i++) {
        angle[i] = now[i];
      }
      R = (R + 1) % lines.size(); // 播完回到開頭(循環播放)
    }
  }
}
// 重播控制用的指標
int R = 0;
// 儲存所有角度動作的序列(每筆為一幀)
ArrayList<String> lines = new ArrayList<String>();

void setup() {
  size(560, 560); 
  postman = loadImage("postman.png");
  head = loadImage("head.png");
  body = loadImage("body.png");
  hand1 = loadImage("hand1.png");
  uparm1 = loadImage("uparm1.png");
  hand2 = loadImage("hand2.png");
  uparm2 = loadImage("uparm2.png");
}

// draw():每幀重畫畫面
void draw() {
  background(#FFFFF2); // 背景色

  image(postman, 0, 0); // 畫出初始角色背景圖

  fill(255, 0, 255, 128); // 半透明紫色濾鏡
  rect(0, 0, 560, 560); // 蓋上透明層,用於對比動畫部件

  // 畫出左邊手臂(上臂 + 手)
  pushMatrix();
    translate(184, 263); // 設定左上臂旋轉中心
    rotate(radians(angle[1]));
    translate(-184, -263);
    image(uparm1, 0, 0); // 左上臂

    pushMatrix();
      translate(116, 265); // 設定左手旋轉中心
      rotate(radians(angle[2]));
      translate(-116, -265);
      image(hand1, 0, 0); // 左手
    popMatrix();
  popMatrix();

  // 畫出右邊手臂(上臂 + 手)
  pushMatrix();
    translate(290, 262); // 右上臂旋轉中心
    rotate(radians(angle[3]));
    translate(-290, -262);
    image(uparm2, 0, 0); // 右上臂

    pushMatrix();
      translate(357, 259); // 右手旋轉中心
      rotate(radians(angle[4]));
      translate(-357, -259);
      image(hand2, 0, 0); // 右手
    popMatrix();
  popMatrix();

  // 畫出頭部(可旋轉)
  pushMatrix();
    translate(236, 231); // 頭部旋轉中心
    rotate(radians(angle[0]));
    translate(-236, -231);
    image(head, 0, 0); // 頭部圖像
  popMatrix();

  // 畫出身體(固定不轉動)
  image(body, 0, 0);
}
按r讀出剛剛存檔的座標

angle.txt:
222.0 100.0 -74.0 72.0 -106.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 64.0 15.0 102.0 72.0 25.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -102.0 112.0 -30.0 72.0 244.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -7.0 112.0 -30.0 -28.0 244.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0



2. 郵差動畫關節控制與內插播放系統

// week11-2_postman_alpha_interpolation

// 頭 身體 手臂 手軸 腳
// 加入了內插動畫功能,使用 p 鍵執行當前幀與下一幀的平滑過渡

// 載入郵差角色的各個部位圖片
PImage postman, head, body, hand1, uparm1, hand2, uparm2;

float[] angle = new float[20];// 準備 20 個關節角度變數
int ID = 0;// 現在正在操作的關節編號(用來配合 angle 陣列)
void mouseDragged() {// 滑鼠拖曳時,根據 X 軸移動距離來調整目前選取的關節角度
  angle[ID] += mouseX - pmouseX;
}

void keyPressed() {
  // 指定當前操作的關節 ID(透過鍵盤按鍵)
  if (key == '1') ID = 1; // 左上臂
  if (key == '2') ID = 2; // 左手
  if (key == '3') ID = 3; // 右上臂
  if (key == '4') ID = 4; // 右手
  if (key == '5') ID = 5; // 保留
  if (key == '6') ID = 6; // 保留
  if (key == '0') ID = 0; // 頭部

  // 按下 s 鍵:將目前所有關節角度儲存為一筆資料,存入文字檔
  if (key == 's') {
    String now = "";
    for (int i = 0; i < 20; i++) {
      now += angle[i] + " "; // 用空格分隔每個角度
    }
    lines.add(now); // 加入角度序列
    String[] arr = new String[lines.size()];
    lines.toArray(arr);
    saveStrings("angles.txt", arr); // 寫入 angles.txt
  }

  // 按下 r 鍵:讀取 angles.txt 並逐幀重播關節角度
  if (key == 'r') {
    if (R == 0) {
      String[] file = loadStrings("angles.txt"); // 載入所有儲存的資料
      if (file != null) {
        for (int i = 0; i < file.length; i++) {
          lines.add(file[i]);
        }
      }
    }
    if (R < lines.size()) {
      float[] now = float(split(lines.get(R), ' ')); // 取得第 R 幀的角度
      for (int i = 0; i < 20; i++) {
        angle[i] = now[i]; // 套用角度
      }
      R = (R + 1) % lines.size(); // 下一幀(循環)
    }
  }

  // 按下 p 鍵:執行兩幀之間的線性內插,產生平滑動畫過渡效果
  if (key == 'p') {
    // 取得當前幀與下一幀的角度陣列
    float[] oldAngle = float(split(lines.get(R), ' '));
    float[] newAngle = float(split(lines.get((R + 1) % lines.size()), ' '));
    
    // 根據 frameCount 動態產生內插比例 alpha,範圍為 0 ~ 1
    float alpha = (frameCount % 30) / 30.0;

    // 執行線性內插公式:angle = A*(1-alpha) + B*alpha
    for (int i = 0; i < 20; i++) {
      angle[i] = oldAngle[i] * (1 - alpha) + newAngle[i] * alpha;
    }
  }
}

// 用來記錄重播當前播放到第幾幀
int R = 0;

// 儲存所有角度的動畫序列(每行一幀)
ArrayList<String> lines = new ArrayList<String>();

// 初始化畫面與載入圖像
void setup() {
  size(560, 560);
  postman = loadImage("postman.png");
  head = loadImage("head.png");
  body = loadImage("body.png");
  hand1 = loadImage("hand1.png");
  uparm1 = loadImage("uparm1.png");
  hand2 = loadImage("hand2.png");
  uparm2 = loadImage("uparm2.png");
}

// 每一幀畫面重新繪製角色組合(依照 angle[] 角度變化)
void draw() {
  background(#FFFFF2); // 背景顏色

  image(postman, 0, 0); // 畫基礎郵差圖像(非關節部分)

  fill(255, 0, 255, 128); // 紫色半透明濾鏡
  rect(0, 0, 560, 560); // 畫上濾鏡

  // 左手臂(上臂 + 手)
  pushMatrix();
    translate(184, 263); // 左上臂旋轉中心
    rotate(radians(angle[1]));
    translate(-184, -263);
    image(uparm1, 0, 0); // 畫左上臂

    pushMatrix();
      translate(116, 265); // 左手旋轉中心
      rotate(radians(angle[2]));
      translate(-116, -265);
      image(hand1, 0, 0); // 畫左手
    popMatrix();
  popMatrix();

  // 右手臂(上臂 + 手)
  pushMatrix();
    translate(290, 262); // 右上臂旋轉中心
    rotate(radians(angle[3]));
    translate(-290, -262);
    image(uparm2, 0, 0); // 畫右上臂

    pushMatrix();
      translate(357, 259); // 右手旋轉中心
      rotate(radians(angle[4]));
      translate(-357, -259);
      image(hand2, 0, 0); // 畫右手
    popMatrix();
  popMatrix();

  // 頭部(可旋轉)
  pushMatrix();
    translate(236, 231); // 頭部旋轉中心
    rotate(radians(angle[0]));
    translate(-236, -231);
    image(head, 0, 0); // 畫頭部
  popMatrix();

  // 身體(固定不轉動)
  image(body, 0, 0);
}

持續按著P







3. 郵差動畫關節控制與內插自動播放系統(自動插補版)

// week11-2_postman_alpha_interpolation_better
// 頭、身體、手臂、手軸、腳的動畫控制與儲存系統(加上自動內插)

PImage postman, head, body, hand1, uparm1, hand2, uparm2;
float[] angle = new float[20];// 儲存每個關節角度的陣列,最多 20 個關節
int ID = 0;// 目前正在控制的關節 ID(用鍵盤切換)

void mouseDragged() {// 滑鼠拖曳時改變當前關節的角度,透過 X 軸滑動來調整
  angle[ID] += mouseX - pmouseX;
}

void keyPressed() {
  // 使用鍵盤 0~4 來選擇控制哪個關節
  if (key == '1') ID = 1; // 左上臂
  if (key == '2') ID = 2; // 左手
  if (key == '3') ID = 3; // 右上臂
  if (key == '4') ID = 4; // 右手
  if (key == '5') ID = 5; // 保留用
  if (key == '6') ID = 6; // 保留用
  if (key == '0') ID = 0; // 頭部

  // 按下 's' 鍵時,將目前所有關節角度存入文字檔 angles.txt
  if (key == 's') {
    String now = ""; // 存儲一行角度的字串
    for (int i = 0; i < 20; i++) {
      now += angle[i] + " "; // 將所有角度接成一行(空格分隔)
    }
    lines.add(now); // 加入目前角度到 lines 陣列
    String[] arr = new String[lines.size()];
    lines.toArray(arr); // 轉為 String 陣列以便儲存
    saveStrings("angles.txt", arr); // 儲存到文字檔
  }

  // 按下 'r' 鍵時,從文字檔讀入儲存的角度並開始播放
  if (key == 'r') {
    if (R == 0) {
      String[] file = loadStrings("angles.txt"); // 載入檔案內容
      if (file != null) {
        for (int i = 0; i < file.length; i++) {
          lines.add(file[i]); // 把每一行加入 lines
        }
      }
    }

    // 每次按下 r 會播放一幀
    if (R < lines.size()) {
      float[] now = float(split(lines.get(R), ' ')); // 解析該行角度
      for (int i = 0; i < 20; i++) {
        angle[i] = now[i]; // 套用該幀角度
      }
      R = (R + 1) % lines.size(); // 前進到下一幀(循環播放)
    }
  }

  // 註:原本的 p 鍵內插功能被改成 myInterpolate(),自動播放
}

// 控制目前播放的幀位置(R = Replay Index)
int R = 0;

// 儲存所有角度資料的陣列,每一筆為一幀動畫
ArrayList<String> lines = new ArrayList<String>();

// 當前自動播放使用的動畫內插函式
void myInterpolate() {
  if (lines.size() > 0) {
    // 取得插值用比例 alpha(隨 frameCount 漸變)
    float alpha = (frameCount % 30) / 30.0;

    // 每 30 幀換一次幀
    if (alpha == 0.0) R = (R + 1) % lines.size();

    int R2 = (R + 1) % lines.size(); // 下一幀編號

    // 取出目前幀與下一幀的角度
    float[] oldAngle = float(split(lines.get(R), ' '));
    float[] newAngle = float(split(lines.get(R2), ' '));

    // 執行線性插值公式:old*(1-alpha) + new*alpha
    for (int i = 0; i < 20; i++) {
      angle[i] = oldAngle[i] * (1 - alpha) + newAngle[i] * alpha;
    }
  }
}

// 設定畫面大小與載入圖片
void setup() {
  size(560, 560);
  postman = loadImage("postman.png");
  head = loadImage("head.png");
  body = loadImage("body.png");
  hand1 = loadImage("hand1.png");
  uparm1 = loadImage("uparm1.png");
  hand2 = loadImage("hand2.png");
  uparm2 = loadImage("uparm2.png");
}

// 主繪圖函式,每一幀都會執行一次
void draw() {
  myInterpolate(); // 每一幀執行自動內插動畫

  background(#FFFFF2); // 淺黃色背景
  image(postman, 0, 0); // 畫出整體輪廓
  fill(255, 0, 255, 128); // 半透明紫色濾鏡
  rect(0, 0, 560, 560); // 疊加濾鏡

  // 左手臂(上臂 + 手)
  pushMatrix();
    translate(184, 263); // 左上臂旋轉中心
    rotate(radians(angle[1]));
    translate(-184, -263);
    image(uparm1, 0, 0); // 畫左上臂

    pushMatrix();
      translate(116, 265); // 左手旋轉中心
      rotate(radians(angle[2]));
      translate(-116, -265);
      image(hand1, 0, 0); // 畫左手
    popMatrix();
  popMatrix();

  // 右手臂(上臂 + 手)
  pushMatrix();
    translate(290, 262); // 右上臂旋轉中心
    rotate(radians(angle[3]));
    translate(-290, -262);
    image(uparm2, 0, 0); // 畫右上臂

    pushMatrix();
      translate(357, 259); // 右手旋轉中心
      rotate(radians(angle[4]));
      translate(-357, -259);
      image(hand2, 0, 0); // 畫右手
    popMatrix();
  popMatrix();

  // 頭部(可旋轉)
  pushMatrix();
    translate(236, 231); // 頭部旋轉中心
    rotate(radians(angle[0]));
    translate(-236, -231);
    image(head, 0, 0); // 畫頭
  popMatrix();

  // 畫出身體(不旋轉)
  image(body, 0, 0);
}
按下r鍵 如果有兩組座標以上就會自己動

4. 郵差動畫角色控制系統:3D 關節旋轉與動畫重播

// week11-3_postman-again
// 重構程式架構:支援多關節控制(頭、手、腳),加入X/Y旋轉與動畫儲存播放功能

// ===== 1. 全域變數宣告區 =====

PImage postman, head, body, hand1, uparm1, hand2, uparm2, foot1, foot2;

float[] angleX = new float[10]; // 水平旋轉角度(X軸)
float[] angleY = new float[10]; // 垂直旋轉角度(Y軸,目前未應用)

int ID = 0; // 當前控制的關節編號
int R = 0; // 動畫播放的當前幀索引
boolean playing = false; // 是否播放動畫

ArrayList<String> lines = new ArrayList<String>(); // 所有儲存的動畫幀

// ===== 2. 初始化與圖像載入 =====

void setup() {
  size(560, 560); // 畫面尺寸

  // 載入圖檔
  postman = loadImage("postman.png");
  head = loadImage("head.png");
  body = loadImage("body.png");
  hand1 = loadImage("hand1.png");
  uparm1 = loadImage("uparm1.png");
  hand2 = loadImage("hand2.png");
  uparm2 = loadImage("uparm2.png");
  foot1 = loadImage("foot1.png"); // 左腳
  foot2 = loadImage("foot2.png"); // 右腳
}

// ===== 3. 主繪圖邏輯 =====

void draw() {
  background(#FFFFF2); // 背景

  if (playing) myInterpolate(); // 若啟動播放,執行內插動畫

  image(body, 0, 0); // 畫身體(固定)

  // 畫頭部(旋轉)
  pushMatrix();
    translate(236, 231);
    rotate(radians(angleX[0]));
    translate(-236, -231);
    image(head, 0, 0);
  popMatrix();

  // 畫左腳
  pushMatrix();
    translate(220, 375);
    rotate(radians(angleX[5]));
    translate(-220, -375);
    image(foot1, 0, 0);
  popMatrix();

  // 畫右腳
  pushMatrix();
    translate(260, 372);
    rotate(radians(angleX[6]));
    translate(-260, -372);
    image(foot2, 0, 0);
  popMatrix();

  // 畫左臂(上臂 + 手)
  pushMatrix();
    translate(184, 263);
    rotate(radians(angleX[1]));
    translate(-184, -263);
    image(uparm1, 0, 0);
    pushMatrix();
      translate(116, 265);
      rotate(radians(angleX[2]));
      translate(-116, -265);
      image(hand1, 0, 0);
    popMatrix();
  popMatrix();

  // 畫右臂(上臂 + 手)
  pushMatrix();
    translate(290, 262);
    rotate(radians(angleX[3]));
    translate(-290, -262);
    image(uparm2, 0, 0);
    pushMatrix();
      translate(357, 259);
      rotate(radians(angleX[4]));
      translate(-357, -259);
      image(hand2, 0, 0);
    popMatrix();
  popMatrix();
}

// ===== 4. 滑鼠控制角度 =====

void mouseDragged() {
  angleX[ID] += mouseX - pmouseX;
  angleY[ID] += mouseY - pmouseY; // 尚未應用,可擴充為 3D 使用
}

// ===== 5. 鍵盤互動控制 =====

void keyPressed() {
  //  儲存目前所有關節角度
  if (key == 's') {
    String now = "";
    for (int i = 0; i < 10; i++) {
      now += angleX[i] + " " + angleY[i] + " ";
    }
    lines.add(now);
    saveStrings("angles.txt", lines.toArray(new String[0]));
    println("現在存檔: " + now);
  }

  //  讀取儲存的角度資料
  if (key == 'r') {
    String[] file = loadStrings("angles.txt");
    if (file != null) {
      for (String line : file) {
        lines.add(line);
        println("現在讀檔: " + line);
      }
    }
  }

  //  開關動畫播放
  if (key == 'p') playing = !playing;

  //  選擇控制關節 ID(0~6)
  if (key == '0') ID = 0; // 頭
  if (key == '1') ID = 1; // 左臂
  if (key == '2') ID = 2; // 左手
  if (key == '3') ID = 3; // 右臂
  if (key == '4') ID = 4; // 右手
  if (key == '5') ID = 5; // 左腳
  if (key == '6') ID = 6; // 右腳
}

// ===== 6. 內插動畫函式(平滑播放) =====

void myInterpolate() {
  if (lines.size() >= 2) {
    float alpha = (frameCount % 30) / 30.0;
    if (alpha == 0.0) R = (R + 1) % lines.size();
    int R2 = (R + 1) % lines.size();

    float[] oldAngle = float(split(lines.get(R), ' '));
    float[] newAngle = float(split(lines.get(R2), ' '));

    for (int i = 0; i < 10; i++) {
      angleX[i] = oldAngle[i * 2] * (1 - alpha) + newAngle[i * 2] * alpha;
      angleY[i] = oldAngle[i * 2 + 1] * (1 - alpha) + newAngle[i * 2 + 1] * alpha;
    }
  }
}
讀檔

按下r鍵 如果有兩組座標以上就會自己動(更順暢)


沒有留言:

張貼留言