feat(model): 添加模型姿态管理系统- 新增 ModelPose 类用于管理模型部件的姿态数据

- 在 Model2D 中实现姿态保存、应用和混合功能- 支持姿态的序列化和反序列化
- 添加日志记录替代原有的 System.out 和 System.err 输出-优化网格和模型部件的调试信息输出
- 引入 PartPoseData 和 PoseData用于姿态数据持久化- 实现姿态间的平滑过渡和插值计算
- 增加默认姿态初始化和管理机制
This commit is contained in:
tzdwindows 7
2025-10-12 08:41:34 +08:00
parent fb1db942ed
commit b501da0254
8 changed files with 825 additions and 22 deletions

View File

@@ -11,6 +11,8 @@ import org.joml.Vector2f;
import org.joml.Vector4f;
import org.lwjgl.opengl.*;
import org.lwjgl.system.MemoryUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
@@ -25,7 +27,7 @@ import static org.lwjgl.opengl.GL20.glGetUniformLocation;
* @author tzdwindows 7
*/
public final class ModelRender {
private static final Logger logger = LoggerFactory.getLogger(ModelRender.class);
private ModelRender() { /* no instances */ }
// ================== 全局状态 ==================
@@ -33,8 +35,8 @@ public final class ModelRender {
private static int viewportWidth = 800;
private static int viewportHeight = 600;
private static final Vector4f CLEAR_COLOR = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
private static boolean enableDepthTest = false;
private static boolean enableBlending = true;
private static final boolean enableDepthTest = false;
private static final boolean enableBlending = true;
// 着色器与资源
private static final Map<String, ShaderProgram> shaderMap = new HashMap<>();
@@ -80,7 +82,7 @@ public final class ModelRender {
int loc = glGetUniformLocation(programId, k);
if (loc == -1) {
// debug 时可以打开
// System.err.println("Warning: uniform not found: " + k);
logger.warn("Warning: uniform not found: {}", k);
}
return loc;
});
@@ -271,7 +273,7 @@ public final class ModelRender {
public static synchronized void initialize() {
if (initialized) return;
System.out.println("Initializing ModelRender...");
logger.info("Initializing ModelRender...");
// 需要在外部创建 OpenGL 上下文并调用 GL.createCapabilities()
logGLInfo();
@@ -283,7 +285,7 @@ public final class ModelRender {
try {
compileDefaultShader();
} catch (RuntimeException ex) {
System.err.println("Failed to compile default shader: " + ex.getMessage());
logger.error("Failed to compile default shader: {}", ex.getMessage());
throw ex;
}
@@ -294,14 +296,14 @@ public final class ModelRender {
GL11.glViewport(0, 0, viewportWidth, viewportHeight);
initialized = true;
System.out.println("ModelRender initialized successfully");
logger.info("ModelRender initialized successfully");
}
private static void logGLInfo() {
System.out.println("OpenGL Vendor: " + GL11.glGetString(GL11.GL_VENDOR));
System.out.println("OpenGL Renderer: " + GL11.glGetString(GL11.GL_RENDERER));
System.out.println("OpenGL Version: " + GL11.glGetString(GL11.GL_VERSION));
System.out.println("GLSL Version: " + GL20.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION));
logger.info("OpenGL Vendor: {}", GL11.glGetString(GL11.GL_VENDOR));
logger.info("OpenGL Renderer: {}", GL11.glGetString(GL11.GL_RENDERER));
logger.info("OpenGL Version: {}", GL11.glGetString(GL11.GL_VERSION));
logger.info("GLSL Version: {}", GL20.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION));
}
private static void uploadLightsToShader(ShaderProgram sp, Model2D model) {
@@ -438,7 +440,7 @@ public final class ModelRender {
public static synchronized void cleanup() {
if (!initialized) return;
System.out.println("Cleaning up ModelRender...");
logger.info("Cleaning up ModelRender...");
// mesh resources
for (MeshGLResources r : meshResources.values()) r.dispose();
@@ -456,7 +458,7 @@ public final class ModelRender {
}
initialized = false;
System.out.println("ModelRender cleaned up");
logger.info("ModelRender cleaned up");
}
// ================== 渲染流程 (已修改) ==================
@@ -790,7 +792,7 @@ public final class ModelRender {
private static void checkGLError(String op) {
int e = GL11.glGetError();
if (e != GL11.GL_NO_ERROR) {
System.err.println("OpenGL error during " + op + ": " + getGLErrorString(e));
logger.error("OpenGL error during {}: {}", op, getGLErrorString(e));
}
}

View File

@@ -35,10 +35,18 @@ public class Model2D {
private final PhysicsSystem physics;
// ==================== 渲染状态 ====================
private transient ModelPose currentPose;
private transient boolean needsUpdate = true;
private transient BoundingBox bounds;
// ==================== 姿态系统 ====================
private final Map<String, ModelPose> poses; // 存储所有预设姿态
private String currentPoseName = "default"; // 当前应用的姿态名称
private transient ModelPose currentPose; // 当前姿态实例
private transient ModelPose blendTargetPose; // 混合目标姿态
private float blendProgress = 1.0f; // 混合进度 (0-1)
private float blendSpeed = 1.0f; // 混合速度
// ==================== 光源系统 ====================
private final List<LightSource> lights;
@@ -55,6 +63,10 @@ public class Model2D {
this.currentPose = new ModelPose();
this.metadata = new ModelMetadata();
this.lights = new ArrayList<>();
this.poses = new HashMap<>();
this.currentPose = new ModelPose("default");
initializeDefaultPose();
}
public Model2D(String name) {
@@ -62,6 +74,69 @@ public class Model2D {
this.name = name;
}
// ==================== 姿态管理 ====================
/**
* 添加或更新姿态
*/
public void addPose(ModelPose pose) {
if (pose == null) {
throw new IllegalArgumentException("Pose cannot be null");
}
poses.put(pose.getName(), pose);
markNeedsUpdate();
}
/**
* 获取姿态
*/
public ModelPose getPose(String name) {
return poses.get(name);
}
/**
* 应用姿态(立即)
*/
public void applyPose(String poseName) {
ModelPose pose = poses.get(poseName);
if (pose != null) {
applyPoseInternal(pose);
this.currentPoseName = poseName;
this.currentPose = pose;
this.blendProgress = 1.0f;
this.blendTargetPose = null;
markNeedsUpdate();
}
}
/**
* 混合到目标姿态
*/
public void blendToPose(String targetPoseName, float blendTime) {
ModelPose targetPose = poses.get(targetPoseName);
if (targetPose != null) {
this.blendTargetPose = targetPose;
this.blendProgress = 0.0f;
this.blendSpeed = blendTime > 0 ? 1.0f / blendTime : 10.0f; // 默认0.1秒
markNeedsUpdate();
}
}
/**
* 保存当前状态为姿态
*/
public void saveCurrentPose(String poseName) {
ModelPose pose = new ModelPose(poseName);
captureCurrentPose(pose);
addPose(pose);
}
/**
* 获取当前姿态名称
*/
public String getCurrentPoseName() {
return currentPoseName;
}
// ==================== 光源管理 ====================
public List<LightSource> getLights() {
return Collections.unmodifiableList(lights);
@@ -204,6 +279,9 @@ public class Model2D {
// ==================== 更新系统 (已修改) ====================
public void update(float deltaTime) {
updatePoseBlending(deltaTime);
// 物理系统更新已被移至渲染器(ModelRender)中,以确保它在渲染前被调用。
// 这里的 hasActivePhysics() 检查可以保留,用于决定是否需要更新变换,以优化性能。
if (!needsUpdate && !physics.hasActivePhysics()) {
@@ -225,6 +303,79 @@ public class Model2D {
needsUpdate = false;
}
/**
* 更新姿态混合
*/
private void updatePoseBlending(float deltaTime) {
if (blendTargetPose != null && blendProgress < 1.0f) {
blendProgress += deltaTime * blendSpeed;
if (blendProgress >= 1.0f) {
blendProgress = 1.0f;
currentPose = blendTargetPose;
currentPoseName = blendTargetPose.getName();
blendTargetPose = null;
}
markNeedsUpdate();
}
}
/**
* 应用当前姿态到模型
*/
private void applyCurrentPoseToModel() {
if (blendTargetPose != null && blendProgress < 1.0f) {
// 混合姿态
ModelPose blendedPose = ModelPose.lerp(currentPose, blendTargetPose, blendProgress, "blended");
applyPoseInternal(blendedPose);
} else {
// 直接应用当前姿态
applyPoseInternal(currentPose);
}
}
/**
* 内部姿态应用方法
*/
private void applyPoseInternal(ModelPose pose) {
for (String partName : pose.getPartNames()) {
ModelPart part = partMap.get(partName);
if (part != null) {
ModelPose.PartPose partPose = pose.getPartPose(partName);
part.setPosition(partPose.getPosition());
part.setRotation(partPose.getRotation());
part.setScale(partPose.getScale());
part.setOpacity(partPose.getOpacity());
part.setVisible(partPose.isVisible());
}
}
}
/**
* 捕获当前模型状态到姿态
*/
private void captureCurrentPose(ModelPose pose) {
for (ModelPart part : parts) {
ModelPose.PartPose partPose = new ModelPose.PartPose(
part.getPosition(),
part.getRotation(),
part.getScale(),
part.getOpacity(),
part.isVisible(),
new org.joml.Vector3f(1, 1, 1) // 默认颜色,可根据需要修改
);
pose.setPartPose(part.getName(), partPose);
}
}
/**
* 初始化默认姿态
*/
private void initializeDefaultPose() {
ModelPose defaultPose = ModelPose.createDefaultPose();
captureCurrentPose(defaultPose);
poses.put("default", defaultPose);
}
private void updateParameterDeformations() {
for (AnimationParameter param : parameters.values()) {
if (param.hasChanged()) {
@@ -276,6 +427,44 @@ public class Model2D {
}
}
/**
* 检查是否存在指定姿态
*/
public boolean hasPose(String poseName) {
return poses.containsKey(poseName);
}
/**
* 移除姿态
*/
public void removePose(String poseName) {
if (!"default".equals(poseName)) { // 保护默认姿态
poses.remove(poseName);
if (currentPoseName.equals(poseName)) {
applyPose("default"); // 回退到默认姿态
}
}
}
/**
* 获取所有姿态名称
*/
public java.util.Set<String> getPoseNames() {
return Collections.unmodifiableSet(poses.keySet());
}
/**
* 立即混合到姿态(指定混合系数)
*/
public void setPoseBlend(String poseName, float blendFactor) {
ModelPose targetPose = poses.get(poseName);
if (targetPose != null) {
ModelPose blendedPose = ModelPose.lerp(currentPose, targetPose, blendFactor, "manual_blend");
applyPoseInternal(blendedPose);
markNeedsUpdate();
}
}
// ==================== 序列化支持 ====================
public ModelData serialize() {
return new ModelData(this);
@@ -368,7 +557,14 @@ public class Model2D {
}
public PhysicsSystem getPhysics() { return physics; }
public ModelPose getCurrentPose() { return currentPose; }
public ModelPose getCurrentPose() {
return new ModelPose(currentPose);
}
public float getBlendProgress() { return blendProgress; }
public boolean isBlending() { return blendProgress < 1.0f; }
public Map<String, ModelPose> getPoses() {
return Collections.unmodifiableMap(poses);
}
public BoundingBox getBounds() { return bounds; }
public String getVersion() { return version; }

View File

@@ -6,6 +6,8 @@ import com.chuangzhou.vivid2D.render.model.util.Matrix3fUtils;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import org.joml.Matrix3f;
import org.joml.Vector2f;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
@@ -18,6 +20,7 @@ import java.util.Objects;
* @author tzdwindows 7
*/
public class ModelPart {
private static final Logger logger = LoggerFactory.getLogger(ModelPart.class);
// ==================== 基础属性 ====================
private String name;
private ModelPart parent;
@@ -460,7 +463,7 @@ public class ModelPart {
public void printWorldPosition() {
float worldX = worldTransform.m02();
float worldY = worldTransform.m12();
System.out.println("World position: " + worldX + ", " + worldY);
logger.info("World position: {}, {}", worldX, worldY);
}
/**
@@ -590,7 +593,7 @@ public class ModelPart {
if (!pivotInitialized) {
// 确保第一次设置 pivot 的时候,必须是 (0,0) 因为这个为非00时后面如果想要热变换就会出问题
if (x != 0 || y != 0) {
System.out.println("The first time you set the pivot, it must be (0,0), which is automatically adjusted to (0,0).");
logger.warn("The first time you set the pivot, it must be (0,0), which is automatically adjusted to (0,0).");
x = 0;
y = 0;
}

View File

@@ -44,6 +44,8 @@ public class ModelData implements Serializable {
private List<ColliderData> physicsColliders;
private List<ConstraintData> physicsConstraints;
private List<LightSourceData> lights;
private List<PoseData> poses;
private String currentPoseName; // 当前应用的姿态名称
// 全局物理参数(便于序列化)
private float physicsGravityX;
@@ -93,6 +95,9 @@ public class ModelData implements Serializable {
this.physicsAirResistance = 0.1f;
this.physicsTimeScale = 1.0f;
this.physicsEnabled = true;
this.poses = new ArrayList<>();
this.currentPoseName = "default";
}
public ModelData(Model2D model) {
@@ -209,6 +214,40 @@ public class ModelData implements Serializable {
}
}
/**
* 序列化姿态数据
*/
private void serializePoses(Model2D model) {
poses.clear();
// 序列化所有预设姿态
for (ModelPose pose : model.getPoses().values()) {
poses.add(new PoseData(pose));
}
// 保存当前姿态名称
this.currentPoseName = model.getCurrentPoseName();
}
/**
* 反序列化姿态数据
*/
private void deserializePoses(Model2D model) {
if (poses != null) {
for (PoseData poseData : poses) {
ModelPose pose = poseData.toModelPose();
if (pose != null) {
model.addPose(pose);
}
}
}
// 恢复当前姿态
if (currentPoseName != null && model.hasPose(currentPoseName)) {
model.applyPose(currentPoseName);
}
}
private void serializeLights(Model2D model) {
lights.clear();
if (model.getLights() != null) {
@@ -271,6 +310,9 @@ public class ModelData implements Serializable {
// 序列化光源
serializeLights(model);
// 序列化姿态
serializePoses(model);
lastModifiedTime = System.currentTimeMillis();
}
@@ -371,6 +413,9 @@ public class ModelData implements Serializable {
// 反序列化光源
deserializeLights(model);
// 反序列化姿态
deserializePoses(model);
return model;
}
@@ -728,6 +773,8 @@ public class ModelData implements Serializable {
for (SpringData s : this.physicsSprings) copy.physicsSprings.add(s.copy());
for (ConstraintData c : this.physicsConstraints) copy.physicsConstraints.add(c.copy());
for (ColliderData c : this.physicsColliders) copy.physicsColliders.add(c.copy());
for (PoseData pose : this.poses) copy.poses.add(pose.copy());
copy.currentPoseName = this.currentPoseName;
copy.physicsGravityX = this.physicsGravityX;
copy.physicsGravityY = this.physicsGravityY;
@@ -783,9 +830,24 @@ public class ModelData implements Serializable {
this.textures.add(texture);
}
// 合并姿态
for (PoseData pose : other.poses) {
String originalName = pose.name;
int counter = 1;
while (poseExists(pose.name)) {
pose.name = originalName + "_" + counter++;
}
this.poses.add(pose);
}
lastModifiedTime = System.currentTimeMillis();
}
private boolean poseExists(String poseName) {
return poses.stream().anyMatch(pose -> pose.name.equals(poseName));
}
private boolean partExists(String partName) {
return parts.stream().anyMatch(part -> part.name.equals(partName));
}
@@ -1030,6 +1092,14 @@ public class ModelData implements Serializable {
this.animationLayers = animationLayers;
}
public List<PoseData> getPoses() { return poses; }
public void setPoses(List<PoseData> poses) { this.poses = poses; }
public String getCurrentPoseName() { return currentPoseName; }
public void setCurrentPoseName(String currentPoseName) {
this.currentPoseName = currentPoseName;
}
// ==================== Object方法 ====================
@Override

View File

@@ -0,0 +1,66 @@
package com.chuangzhou.vivid2D.render.model.data;
import com.chuangzhou.vivid2D.render.model.util.ModelPose;
import java.io.Serializable;
/**
* 部件姿态数据序列化类
* @author tzdwindows 7
*/
public class PartPoseData implements Serializable {
private static final long serialVersionUID = 1L;
public String partName;
public float posX, posY;
public float rotation;
public float scaleX, scaleY;
public float opacity;
public boolean visible;
public float colorR, colorG, colorB;
public PartPoseData() {}
public PartPoseData(String partName, ModelPose.PartPose partPose) {
this.partName = partName;
this.posX = partPose.getPosition().x;
this.posY = partPose.getPosition().y;
this.rotation = partPose.getRotation();
this.scaleX = partPose.getScale().x;
this.scaleY = partPose.getScale().y;
this.opacity = partPose.getOpacity();
this.visible = partPose.isVisible();
org.joml.Vector3f color = partPose.getColor();
this.colorR = color.x;
this.colorG = color.y;
this.colorB = color.z;
}
public ModelPose.PartPose toPartPose() {
return new ModelPose.PartPose(
new org.joml.Vector2f(posX, posY),
rotation,
new org.joml.Vector2f(scaleX, scaleY),
opacity,
visible,
new org.joml.Vector3f(colorR, colorG, colorB)
);
}
public PartPoseData copy() {
PartPoseData copy = new PartPoseData();
copy.partName = this.partName;
copy.posX = this.posX;
copy.posY = this.posY;
copy.rotation = this.rotation;
copy.scaleX = this.scaleX;
copy.scaleY = this.scaleY;
copy.opacity = this.opacity;
copy.visible = this.visible;
copy.colorR = this.colorR;
copy.colorG = this.colorG;
copy.colorB = this.colorB;
return copy;
}
}

View File

@@ -0,0 +1,62 @@
package com.chuangzhou.vivid2D.render.model.data;
import com.chuangzhou.vivid2D.render.model.util.ModelPose;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 姿态数据序列化类
* @author tzdwindows 7
*/
public class PoseData implements Serializable {
private static final long serialVersionUID = 1L;
public String name;
public float blendTime;
public boolean isDefaultPose;
public List<PartPoseData> partPoses;
public PoseData() {
this.partPoses = new ArrayList<>();
}
public PoseData(ModelPose pose) {
this.name = pose.getName();
this.blendTime = pose.getBlendTime();
this.isDefaultPose = pose.isDefaultPose();
this.partPoses = new ArrayList<>();
// 序列化所有部件姿态
for (String partName : pose.getPartNames()) {
ModelPose.PartPose partPose = pose.getPartPose(partName);
partPoses.add(new PartPoseData(partName, partPose));
}
}
public ModelPose toModelPose() {
ModelPose pose = new ModelPose(name);
pose.setBlendTime(blendTime);
pose.setDefaultPose(isDefaultPose);
// 反序列化部件姿态
for (PartPoseData partPoseData : partPoses) {
pose.setPartPose(partPoseData.partName, partPoseData.toPartPose());
}
return pose;
}
public PoseData copy() {
PoseData copy = new PoseData();
copy.name = this.name;
copy.blendTime = this.blendTime;
copy.isDefaultPose = this.isDefaultPose;
copy.partPoses = new ArrayList<>();
for (PartPoseData partPose : this.partPoses) {
copy.partPoses.add(partPose.copy());
}
return copy;
}
}

View File

@@ -1,5 +1,6 @@
package com.chuangzhou.vivid2D.render.model.util;
import com.chuangzhou.vivid2D.render.model.ModelPart;
import org.joml.Vector2f;
import java.nio.FloatBuffer;
@@ -10,6 +11,8 @@ import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 2D网格类用于存储和管理2D模型的几何数据
@@ -18,6 +21,7 @@ import org.lwjgl.system.MemoryUtil;
* @author tzdwindows 7
*/
public class Mesh2D {
private static final Logger logger = LoggerFactory.getLogger(Mesh2D.class);
// ==================== 网格数据 ====================
private String name;
private float[] vertices; // 顶点数据 [x0, y0, x1, y1, ...]
@@ -369,8 +373,6 @@ public class Mesh2D {
buffer.clear();
for (int i = 0; i < vertexCount; i++) {
// 明确使用定位方法,避免下标算错
//System.out.println("x:"+ getX(i) + "y:"+ getY(i));
buffer.put(getX(i)); // x
buffer.put(getY(i)); // y
buffer.put(uvs[i * 2]); // u
@@ -501,7 +503,7 @@ public class Mesh2D {
org.lwjgl.system.MemoryUtil.memFree(fb);
}
} else {
System.err.println("警告: 着色器中未找到 uModelMatrix 或 uModel uniform");
logger.warn("警告: 着色器中未找到 uModelMatrix 或 uModel uniform");
}
// 绘制

View File

@@ -1,4 +1,406 @@
package com.chuangzhou.vivid2D.render.model.util;
import org.joml.Vector2f;
import org.joml.Vector3f;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 模型姿态类 - 用于存储和管理2D模型的部件变换状态
* 支持动画系统、姿态保存/恢复、姿态混合等功能
*
* @author tzdwindows 7
*/
public class ModelPose {
}
// ================== 内部类:部件姿态 ==================
/**
* 单个部件的姿态数据
*/
public static class PartPose {
private Vector2f position;
private float rotation;
private Vector2f scale;
private float opacity;
private boolean visible;
private Vector3f color; // RGB颜色乘数
public PartPose() {
this(new Vector2f(0, 0), 0.0f, new Vector2f(1, 1), 1.0f, true, new Vector3f(1, 1, 1));
}
public PartPose(Vector2f position, float rotation, Vector2f scale,
float opacity, boolean visible, Vector3f color) {
this.position = new Vector2f(position);
this.rotation = rotation;
this.scale = new Vector2f(scale);
this.opacity = opacity;
this.visible = visible;
this.color = new Vector3f(color);
}
public PartPose(PartPose other) {
this.position = new Vector2f(other.position);
this.rotation = other.rotation;
this.scale = new Vector2f(other.scale);
this.opacity = other.opacity;
this.visible = other.visible;
this.color = new Vector3f(other.color);
}
// ================== 线性插值方法 ==================
/**
* 在两个部件姿态间进行线性插值
*/
public static PartPose lerp(PartPose a, PartPose b, float alpha) {
alpha = Math.max(0.0f, Math.min(1.0f, alpha)); // 钳制到[0,1]
Vector2f pos = new Vector2f(a.position).lerp(b.position, alpha);
float rot = a.rotation + (b.rotation - a.rotation) * alpha;
Vector2f scl = new Vector2f(a.scale).lerp(b.scale, alpha);
float opa = a.opacity + (b.opacity - a.opacity) * alpha;
Vector3f col = new Vector3f(a.color).lerp(b.color, alpha);
// 可见性当alpha>0.5时使用b的可见性
boolean vis = alpha < 0.5f ? a.visible : b.visible;
return new PartPose(pos, rot, scl, opa, vis, col);
}
/**
* 带旋转正确插值的线性插值处理360°边界
*/
public static PartPose lerpWithRotation(PartPose a, PartPose b, float alpha) {
alpha = Math.max(0.0f, Math.min(1.0f, alpha));
Vector2f pos = new Vector2f(a.position).lerp(b.position, alpha);
// 处理旋转插值的角度环绕问题
float shortestAngle = ((b.rotation - a.rotation) % 360 + 540) % 360 - 180;
float rot = a.rotation + shortestAngle * alpha;
Vector2f scl = new Vector2f(a.scale).lerp(b.scale, alpha);
float opa = a.opacity + (b.opacity - a.opacity) * alpha;
Vector3f col = new Vector3f(a.color).lerp(b.color, alpha);
boolean vis = alpha < 0.5f ? a.visible : b.visible;
return new PartPose(pos, rot, scl, opa, vis, col);
}
// ================== Getter和Setter ==================
public Vector2f getPosition() { return new Vector2f(position); }
public void setPosition(Vector2f position) { this.position.set(position); }
public float getRotation() { return rotation; }
public void setRotation(float rotation) { this.rotation = rotation; }
public Vector2f getScale() { return new Vector2f(scale); }
public void setScale(Vector2f scale) { this.scale.set(scale); }
public float getOpacity() { return opacity; }
public void setOpacity(float opacity) { this.opacity = opacity; }
public boolean isVisible() { return visible; }
public void setVisible(boolean visible) { this.visible = visible; }
public Vector3f getColor() { return new Vector3f(color); }
public void setColor(Vector3f color) { this.color.set(color); }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PartPose partPose = (PartPose) o;
return Float.compare(partPose.rotation, rotation) == 0 &&
Float.compare(partPose.opacity, opacity) == 0 &&
visible == partPose.visible &&
Objects.equals(position, partPose.position) &&
Objects.equals(scale, partPose.scale) &&
Objects.equals(color, partPose.color);
}
@Override
public int hashCode() {
return Objects.hash(position, rotation, scale, opacity, visible, color);
}
@Override
public String toString() {
return String.format("PartPose{pos=(%.2f,%.2f), rot=%.2f, scale=(%.2f,%.2f), opacity=%.2f, visible=%s, color=(%.2f,%.2f,%.2f)}",
position.x, position.y, rotation, scale.x, scale.y, opacity, visible, color.x, color.y, color.z);
}
}
// ================== ModelPose 主体 ==================
private String name;
private final Map<String, PartPose> partPoses;
private float blendTime = 0.3f; // 默认混合时间(秒)
private boolean isDefaultPose = false;
// ================== 构造函数 ==================
public ModelPose() {
this("Unnamed Pose");
}
public ModelPose(String name) {
this.name = name;
this.partPoses = new HashMap<>();
}
public ModelPose(ModelPose other) {
this.name = other.name + " (Copy)";
this.partPoses = new HashMap<>();
this.blendTime = other.blendTime;
this.isDefaultPose = other.isDefaultPose;
// 深拷贝所有部件姿态
for (Map.Entry<String, PartPose> entry : other.partPoses.entrySet()) {
this.partPoses.put(entry.getKey(), new PartPose(entry.getValue()));
}
}
// ================== 姿态管理方法 ==================
/**
* 设置指定部件的姿态
*/
public void setPartPose(String partName, PartPose pose) {
partPoses.put(partName, new PartPose(pose));
}
/**
* 获取指定部件的姿态(如果不存在则创建默认姿态)
*/
public PartPose getPartPose(String partName) {
return partPoses.computeIfAbsent(partName, k -> new PartPose());
}
/**
* 检查是否包含指定部件的姿态
*/
public boolean hasPartPose(String partName) {
return partPoses.containsKey(partName);
}
/**
* 移除指定部件的姿态
*/
public PartPose removePartPose(String partName) {
return partPoses.remove(partName);
}
/**
* 获取所有部件名称
*/
public java.util.Set<String> getPartNames() {
return partPoses.keySet();
}
/**
* 清空所有部件姿态
*/
public void clear() {
partPoses.clear();
}
/**
* 获取部件数量
*/
public int getPartCount() {
return partPoses.size();
}
// ================== 便捷方法 ==================
/**
* 设置部件位置
*/
public void setPartPosition(String partName, Vector2f position) {
getPartPose(partName).setPosition(position);
}
/**
* 设置部件旋转(角度)
*/
public void setPartRotation(String partName, float rotation) {
getPartPose(partName).setRotation(rotation);
}
/**
* 设置部件缩放
*/
public void setPartScale(String partName, Vector2f scale) {
getPartPose(partName).setScale(scale);
}
/**
* 设置部件不透明度
*/
public void setPartOpacity(String partName, float opacity) {
getPartPose(partName).setOpacity(opacity);
}
/**
* 设置部件可见性
*/
public void setPartVisible(String partName, boolean visible) {
getPartPose(partName).setVisible(visible);
}
/**
* 设置部件颜色
*/
public void setPartColor(String partName, Vector3f color) {
getPartPose(partName).setColor(color);
}
// ================== 姿态混合 ==================
/**
* 在两个姿态间进行线性插值
*/
public static ModelPose lerp(ModelPose a, ModelPose b, float alpha, String resultName) {
ModelPose result = new ModelPose(resultName);
result.setBlendTime(a.blendTime + (b.blendTime - a.blendTime) * alpha);
// 合并两个姿态的所有部件
java.util.Set<String> allParts = new java.util.HashSet<>();
allParts.addAll(a.getPartNames());
allParts.addAll(b.getPartNames());
for (String partName : allParts) {
PartPose poseA = a.partPoses.get(partName);
PartPose poseB = b.partPoses.get(partName);
if (poseA != null && poseB != null) {
// 两个姿态都有该部件:插值
result.setPartPose(partName, PartPose.lerpWithRotation(poseA, poseB, alpha));
} else if (poseA != null) {
// 只有姿态A有该部件根据alpha决定是否保留
if (alpha < 0.5f) {
result.setPartPose(partName, new PartPose(poseA));
}
} else if (poseB != null) {
// 只有姿态B有该部件根据alpha决定是否保留
if (alpha >= 0.5f) {
result.setPartPose(partName, new PartPose(poseB));
}
}
}
return result;
}
/**
* 将当前姿态与另一个姿态混合
*/
public void blendWith(ModelPose other, float alpha) {
Map<String, PartPose> newPoses = new HashMap<>();
java.util.Set<String> allParts = new java.util.HashSet<>();
allParts.addAll(this.getPartNames());
allParts.addAll(other.getPartNames());
for (String partName : allParts) {
PartPose thisPose = this.partPoses.get(partName);
PartPose otherPose = other.partPoses.get(partName);
if (thisPose != null && otherPose != null) {
newPoses.put(partName, PartPose.lerpWithRotation(thisPose, otherPose, alpha));
} else if (thisPose != null && alpha < 0.5f) {
newPoses.put(partName, new PartPose(thisPose));
} else if (otherPose != null && alpha >= 0.5f) {
newPoses.put(partName, new PartPose(otherPose));
}
}
this.partPoses.clear();
this.partPoses.putAll(newPoses);
}
// ================== 预设姿态工厂方法 ==================
/**
* 创建默认姿态(所有部件在原点,无旋转,正常缩放)
*/
public static ModelPose createDefaultPose() {
ModelPose pose = new ModelPose("Default Pose");
pose.isDefaultPose = true;
return pose;
}
/**
* 创建隐藏姿态(所有部件不可见)
*/
public static ModelPose createHiddenPose() {
ModelPose pose = new ModelPose("Hidden Pose");
pose.isDefaultPose = false;
// 不预先添加任何部件,使用时自动创建为隐藏状态
return pose;
}
/**
* 创建缩放姿态(统一缩放所有部件)
*/
public static ModelPose createScaledPose(float scaleX, float scaleY) {
ModelPose pose = new ModelPose("Scaled Pose");
pose.isDefaultPose = false;
// 不预先添加部件,使用时自动创建带缩放的姿态
return pose;
}
// ================== Getter和Setter ==================
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public float getBlendTime() { return blendTime; }
public void setBlendTime(float blendTime) { this.blendTime = Math.max(0, blendTime); }
public boolean isDefaultPose() { return isDefaultPose; }
public void setDefaultPose(boolean defaultPose) { isDefaultPose = defaultPose; }
// ================== 工具方法 ==================
/**
* 检查姿态是否为空(不包含任何部件姿态)
*/
public boolean isEmpty() {
return partPoses.isEmpty();
}
/**
* 获取姿态的简要描述
*/
public String getDescription() {
return String.format("ModelPose{name='%s', parts=%d, blendTime=%.2fs}",
name, partPoses.size(), blendTime);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ModelPose modelPose = (ModelPose) o;
return Float.compare(modelPose.blendTime, blendTime) == 0 &&
isDefaultPose == modelPose.isDefaultPose &&
Objects.equals(name, modelPose.name) &&
Objects.equals(partPoses, modelPose.partPoses);
}
@Override
public int hashCode() {
return Objects.hash(name, partPoses, blendTime, isDefaultPose);
}
@Override
public String toString() {
return getDescription();
}
}