feat(model): 添加模型姿态管理系统- 新增 ModelPose 类用于管理模型部件的姿态数据
- 在 Model2D 中实现姿态保存、应用和混合功能- 支持姿态的序列化和反序列化 - 添加日志记录替代原有的 System.out 和 System.err 输出-优化网格和模型部件的调试信息输出 - 引入 PartPoseData 和 PoseData用于姿态数据持久化- 实现姿态间的平滑过渡和插值计算 - 增加默认姿态初始化和管理机制
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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) 因为这个为非0,0时后面如果想要热变换就会出问题
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
// 绘制
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user