2025年5月29日 星期四

12750211-week15

 1.建立多個獨立 Processing 視窗的結構範例

// week15_01_multiple_windows
// 多個獨立視窗的範例(主視窗 + 子視窗)

void setup(){
  size(300,200);
  background(255,0,0);
  WindowB child = new WindowB(); // 建立一個子視窗(會跑自己一套 setup/draw)

}

void draw(){ // 主視窗
  
}
// 以下會獨立執行
// 子視窗類別(會自己執行自己的 settings、setup、draw)
class WindowB extends PApplet{
  public WindowB(){ // 建構子:建立子視窗
    super(); // 呼叫 PApplet 父類別的初始化
    // 啟動新的 PApplet 實例,讓它獨立執行自己的程式
    PApplet.runSketch(new String[]{this.getClass().getName()},this);
  }
  
  public void settings(){
    size(300,200); // 子視窗大小
  }
  public void setup(){
    //size(300,200);
    background(0,255,0);
  }
  
  public void draw(){ // 子視窗
  
  }
}

2.使用 PGraphics 在主視窗中模擬子畫面並渲染 3D 方塊

// week15_02_multiple_window_PGraphics
// PGraphics 模擬第二個視窗(可獨立畫圖)

PGraphics pg;  // 宣告一個 PGraphics,用來當作獨立畫布

void setup() {
  size(400, 400, P3D);          // 主視窗尺寸與 3D 模式
  pg = createGraphics(200, 200, P3D); // 建立一塊 200x200 的 3D 畫布(子畫面)
}

void draw() {
  background(255, 0, 0);
  // 在 pg 畫布上開始繪圖(相當於另一個 draw())
  pg.beginDraw();
    pg.background(0, 255, 0);   // 子畫布背景設為綠色
    pg.translate(100, 100);    // 將原點移到畫布中心
    pg.rotateY(radians(frameCount)); // 每幀旋轉 Y 軸
    pg.box(100);               // 繪製一個 3D 方塊
  pg.endDraw();

  // 把 pg 畫布畫到主視窗左上角(x=0, y=0)
  image(pg, 0, 0);
}


3.不同顏色與角度的 3D 方塊動畫

// week15_03_multiple_windows_pg_pg2_pg3_pg4
// 修改 week15_02_multiple_window_PGraphics
// 建立四個子視窗,分別顯示不同顏色與旋轉方塊

PGraphics pg, pg2, pg3, pg4;  // 宣告一個 PGraphics,用來當作獨立畫布

void setup() {
  size(400, 400, P3D);          // 主視窗尺寸與 3D 模式
  pg = createGraphics(200, 200, P3D); // 建立一塊 200x200 的 3D 畫布(子畫面)
  pg2 = createGraphics(200, 200, P3D); // 建立一塊 200x200 的 3D 畫布(子畫面)
  pg3 = createGraphics(200, 200, P3D); // 建立一塊 200x200 的 3D 畫布(子畫面)
  pg4 = createGraphics(200, 200, P3D); // 建立一塊 200x200 的 3D 畫布(子畫面)
}

void draw() {
  background(255, 0, 0);
  // 在 pg 畫布上開始繪圖(相當於另一個 draw())
  pg.beginDraw();
    pg.background(0, 255, 0);   // 子畫布背景設為綠色
    pg.translate(100, 100);    // 將原點移到畫布中心
    pg.rotateY(radians(frameCount)); // 每幀旋轉 Y 軸
    pg.box(100);               // 繪製一個 3D 方塊
  pg.endDraw();

  pg2.beginDraw();
    pg2.background(255, 255, 0);   // 子畫布背景設為黃色
    pg2.translate(100, 100);    // 將原點移到畫布中心
    pg2.rotateY(radians(frameCount)); // 每幀旋轉 Y 軸
    pg2.fill(0, 100, 255); // 藍色
    pg2.box(100);               // 繪製一個 3D 方塊
  pg2.endDraw();
 
  pg3.beginDraw();
    pg3.background(255, 0, 0);   // 子畫布背景設為紅色
    pg3.translate(100, 100);    // 將原點移到畫布中心
    pg3.rotateY(radians(frameCount)); // 每幀旋轉 Y 軸
    pg3.fill(0); // 黑色
    pg3.box(100);               // 繪製一個 3D 方塊
  pg3.endDraw();

  pg4.beginDraw();
    pg4.background(255, 0, 255);   // 子畫布背景設為紫色
    pg4.translate(100, 100);    // 將原點移到畫布中心
    pg4.rotateY(radians(frameCount)); // 每幀旋轉 Y 軸
    pg4.fill(0, 255, 180); // 青綠色
    pg4.box(100);               // 繪製一個 3D 方塊
  pg4.endDraw();

  // 把 pg 畫布畫到主視窗左上角(x=0, y=0)
  image(pg, 0, 0);
  image(pg2, 200, 0);
  image(pg3, 0, 200);
  image(pg4, 200, 200);
}




4.使用 Arcball 控制其中一區 3D 方塊旋轉的多視窗 PGraphics 渲染範例

// week15_04_Arcball_rotation_PGraphics_pg
// 修改 week15_03_multiple_windows_pg_pg2_pg3_pg4
// 建立四個子視窗,分別顯示不同顏色與旋轉方塊

PGraphics pg, pg2, pg3, pg4;  // 宣告一個 PGraphics,用來當作獨立畫布
Arcball arcball; // 偷來的 Arcball 宣告小寫 arcball

void setup() {
  size(400, 400, P3D);          // 主視窗尺寸與 3D 模式
  arcball = new Arcball(this,200);
  pg = createGraphics(200, 200, P3D); // 建立一塊 200x200 的 3D 畫布(子畫面)
  pg2 = createGraphics(200, 200, P3D); // 建立一塊 200x200 的 3D 畫布(子畫面)
  pg3 = createGraphics(200, 200, P3D); // 建立一塊 200x200 的 3D 畫布(子畫面)
  pg4 = createGraphics(200, 200, P3D); // 建立一塊 200x200 的 3D 畫布(子畫面)
}

void mousePressed(){
  arcball.mousePressed();
}

void mouseDragged(){
  arcball.mouseDragged();
}

void draw() {
  background(255, 0, 0);
  // 在 pg 畫布上開始繪圖(相當於另一個 draw())
  pg.beginDraw();
    pg.background(0, 255, 0);   // 子畫布背景設為綠色
    arcball.run();
    // pg.translate(100, 100);    // 將原點移到畫布中心
    // pg.rotateY(radians(frameCount)); // 每幀旋轉 Y 軸
    pg.box(100);               // 繪製一個 3D 方塊
  pg.endDraw();

  pg2.beginDraw();
    pg2.background(255, 255, 0);   // 子畫布背景設為黃色
    pg2.translate(100, 100);    // 將原點移到畫布中心
    pg2.rotateY(radians(frameCount)); // 每幀旋轉 Y 軸
    pg2.fill(0, 100, 255); // 藍色
    pg2.box(100);               // 繪製一個 3D 方塊
  pg2.endDraw();
 
  pg3.beginDraw();
    pg3.background(255, 0, 0);   // 子畫布背景設為紅色
    pg3.translate(100, 100);    // 將原點移到畫布中心
    pg3.rotateY(radians(frameCount)); // 每幀旋轉 Y 軸
    pg3.fill(0); // 黑色
    pg3.box(100);               // 繪製一個 3D 方塊
  pg3.endDraw();

  pg4.beginDraw();
    pg4.background(255, 0, 255);   // 子畫布背景設為紫色
    pg4.translate(100, 100);    // 將原點移到畫布中心
    pg4.rotateY(radians(frameCount)); // 每幀旋轉 Y 軸
    pg4.fill(0, 255, 180); // 青綠色
    pg4.box(100);               // 繪製一個 3D 方塊
  pg4.endDraw();

  // 把 pg 畫布畫到主視窗左上角(x=0, y=0)
  image(pg, 0, 0);
  image(pg2, 200, 0);
  image(pg3, 0, 200);
  image(pg4, 200, 200);
}
更改地方
        
更改地方


5.滑鼠控制角色頭部旋轉角度的動畫儲存與播放系統(使用 atan2 自動對準方向)

// week15_05_postman_mouseDragged_head_angleX_0_atan2

// 修改 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() {
  // 要把原本 mosueX 的左右移動,改成像 IK 轉動
  // 從 void draw() 找到頭 掛的位置
  float dx = mouseX - 236; // 減掉座標
  float dy = mouseY - 231; // 減掉座標
  angleX[0] = degrees(atan2(dy, dx)) + 90; // 頭的角度
  // 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;
    }
  }
}





6.多關節角色動畫控制系統 v2 — 支援多點旋轉與滑鼠拖曳控制(基於 atan2)

// week15_06_postman_mouseDragged_posX_posY_ID_angleX_ID_atan2
// 修改 week15_05_postman_mouseDragged_head_angleX_0_atan2
// 重構程式架構:支援多關節控制(頭、手、腳),加入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. 滑鼠控制角度 =====
float [] posX = {236, 185, 116, 290, 357, 220, 260}; // void draw()
float [] posY = {231, 261, 265, 262, 259, 375, 372}; //全部的座標
float []posAngle = {90, 180, 180, 0, 0, -90, -90};
void mouseDragged() {
  // 要把原本 mosueX 的左右移動,改成像 IK 轉動
  // 從 void draw() 找到頭 掛的位置
  // float dx = mouseX - 236; // 減掉座標
  // float dy = mouseY - 231; // 減掉座標
  // angleX[0] = degrees(atan2(dy, dx)) + 90; // 頭的角度
  
  float dx = mouseX - posX[ID]; // 減掉座標
  float dy = mouseY - posY[ID]; // 減掉座標
  angleX[ID] = degrees(atan2(dy,dx)) + posAngle[ID]; // 減掉某個關節的角度
  
  // 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;
    }
  }
}

沒有留言:

張貼留言