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